• STL-vector


    一.vector的介绍及使用

    1. vector是表示可变大小数组的序列容器。可以把它理解为一个顺序表 。
    2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
    3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小,为了增加存储空间,其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
    4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
    5. 因此,vector占用了更多的存储空间,具有管理存储空间的能力,并且以一种有效的方式动态增长。
    6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。
    7. 使用STL的三个境界:能用,明理,能扩展 ,那么下面学习vector,我们也是按照这个方法去学习

    二.vector的常用接口介绍

    vector学习时一定要学会查看文档:vector的文档介绍,vector在实际中非常的重要,在实际中我们熟悉常见的接口就可以,下面会重点介绍一些易混淆的函数功能。

    (1)vector的成员函数

    在这里插入图片描述

    ①构造函数的使用

    在这里插入图片描述

    void test_vector1()
    {
    	vector<int> v1;
    	v1.push_back(1);
    	v1.push_back(2);
    	v1.push_back(3);
    	v1.push_back(4);
    
    	vector<double> v2;
    	v2.push_back(1.1);
    	v2.push_back(2.2);
    	v2.push_back(3.3);
    
    	vector<string> v3;
    	v3.push_back("李白");
    	v3.push_back("杜甫");
    	v3.push_back("苏轼");
    	v3.push_back("白居易");
    
    	vector<int> v4(10, 5);
    
    	vector<string> v5(++v3.begin(), --v3.end());
    	string s = "hello world";
    
    	//不一定是自己类型的迭代器,还可以是其他类型的迭代器
    	//但是迭代器所指向的内容解引用之后要匹配上
    	//我想把s中的每个字符弄到vector
    	vector<char> v6(s.begin(), s.end());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    ②vector iterator的使用

    在这里插入图片描述
    在这里插入图片描述

    如何遍历vector?

    void test_vector2()
    {
    	// 遍历
    	vector<int> v;
    	v.push_back(1);
    	v.push_back(2);
    	v.push_back(3);
    	v.push_back(4);
    
    	// 1、下表+[]
    	for (size_t i = 0; i < v.size(); ++i)
    	{
    		v[i] += 1;
    		cout << v[i] << " ";
    	}
    	cout << endl;
    
    	// 2.迭代器
    	//取vector类模板里面的iterator
    	vector<int>::iterator it = v.begin();
    	while (it != v.end())
    	{
    		*it -= 1;
    		cout << *it << " ";
    		++it;
    	}
    	cout << endl;
    
    	// 3.范围for
    	for (auto e : v)
    	{
    		cout << e << " ";
    	}
    	cout << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    ③vector空间增长问题

    在这里插入图片描述
    关于单次曾容曾多少的问题分析;
    单次增容越多,增容次数越少,效率越高,但是单次增容越多,可能浪费空间就越多。单次增容越少,会导致频繁增容,效率低下。

    capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

    // 测试vector的默认扩容机制
    void TestVectorExpand()
    {
    	size_t sz;
    	vector<int> v;
    	sz = v.capacity();
    	cout << "making v grow:\n";
    	for (int i = 0; i < 100; ++i)
    	{
    		v.push_back(i);
    		if (sz != v.capacity())
    		{
    			sz = v.capacity();
    			cout << "capacity changed: " << sz << '\n';
    		}
    	}
    }
    //vs:运行结果:vs下使用的STL基本是按照1.5倍方式扩容
    //making foo grow :
    //capacity changed : 1
    //capacity changed : 2
    //capacity changed : 3
    //capacity changed : 4
    //capacity changed : 6
    //capacity changed : 9
    //capacity changed : 13
    //capacity changed : 19
    //capacity changed : 28
    //capacity changed : 42
    //capacity changed : 63
    //capacity changed : 94
    //capacity changed : 141
    
    //g++运行结果:linux下使用的STL基本是按照2倍方式扩容
    //making foo grow :
    //capacity changed : 1
    //capacity changed : 2
    //capacity changed : 4
    //capacity changed : 8
    //capacity changed : 16
    //capacity changed : 32
    //capacity changed : 64
    //capacity changed : 128
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    为什么是2倍?
    也不一定是2倍,1.5倍,3倍,4倍都可以,只是说单次增容越多,增容次数越少,效率越高,但是单次增容越多,可能浪费空间就越多,而单次增容增少了,会导致频繁增容,效率低下,1.5倍和2倍是一个权衡后的中间值。

    vector开辟空间不需要考虑内存对齐,string需要考虑内存对齐,因为string的单个对象是char,太小了,它是单字节的,长度是3就是3字节,长度是5就是5字节。我们的程序向操作系统申请内存,操作系统一般不会按奇数字节给内存空间的,都是按偶数字节,比如4字节,8字节给出对齐的内存,比如我们malloc3字节或者malooc1字节,那么操作系统都会给出4字节,我们malloc5字节,操作系统会给你8字节。

    为什么呢?
    因为如果我要几字节的空间,操作系统就去搜索几字节的空间给我,这样太慢了,所以它会提前把1 ~ 4 字节的,5 ~ 8字节的,9~16字节的空间都给你开好,你要的时候,它就直接拿去给你用。

    reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。resize在开空间的同时还会进行初始化,影响size。

    // 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
    // 就可以避免边插入边扩容导致效率低下的问题了
    void TestVectorExpandOP()
    {
    	vector<int> v;
    	size_t sz = v.capacity();
    	v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
    	cout << "making bar grow:\n";
    	for (int i = 0; i < 100; ++i)
    	{
    		v.push_back(i);
    		if (sz != v.capacity())
    		{
    			sz = v.capacity();
    			cout << "capacity changed: " << sz << '\n';
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    shrink_to_fit:
    string vector等都有一个特点,删除数据,一般是不会主动缩容的。因为以后万一还要插入数据,又要扩容,所以很少使用。而且操作系统的内存不允许一部分一部分的还,缩容是指,比如我只要前10个有效字符了,那我先申请一片10个有效字符的空间,拷贝过去,然后再把原内存给释放掉,付出了性能的代价还掉了一些空间(以时间换空间),不划算,慎用,少用。

    能不能用vector来代替string呢?

    虽然它们底层都是char类型的数组,但是在细微的操作方面,还是有很多的区别的。

    vector不支持append和+=
    string依然是符合C语言形式的字符串,最后一个位置要有0。

    ④vector增删改查

    在这里插入图片描述
    vector和string一样不支持头插和头删,因为效率太低,如果我们想头插和头删,可以使用insert和erase函数。

    vector没有查找函数,当我们需要查找某个对象的时候,我们可以复用算法模块中的find函数。

    三.vector迭代器失效问题

    • 迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。这种就是野指针式失效。
    • 迭代器指向越界位置,可能会发生越界访问现象。

    对于vector可能会导致其迭代器失效的操作有:

    1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
    #include
    #include 
    using namespace std;
    #include 
    int main()
    {
    	vector<int> v{ 1,2,3,4,5,6 };
    	auto it = v.begin();
    	// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
    	// v.resize(100, 8);
    	// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
    	// v.reserve(100);
    	// 插入元素期间,可能会引起扩容,而导致原空间被释放
    	// v.insert(v.begin(), 0);
    	// v.push_back(8);
    	// 给vector重新赋值,可能会引起底层容量改变
    	v.assign(100, 8);
    	
    	while (it != v.end())
    	{
    		cout << *it << " ";
    		++it;
    	}
    	cout << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之前的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。

    //我们可以事先获取迭代器相对于起始处的位置
    size_t n = it - v.begin();
    //然后再扩容后,让迭代器指向新空间相应的位置
    it = v.begin() + n;
    //然后就可以继续利用迭代器对新空间进行操作了
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面这种属于野指针式的迭代器失效。下面再来简单总结一下“迭代器可能发生越界访问”这种迭代器失效。

    1. 指定位置元素的删除操作–erase

    由于删除操作没有扩容,因此删除操作引发的迭代器失效主要是因为迭代器可能会发生越界访问。

    #include 
    using namespace std;
    #include 
    int main()
    {
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));
    // 使用find查找3所在位置的iterator
    vector<int>::iterator pos = find(v.begin(), v.end(), 3);
    // 删除pos位置的数据,导致pos迭代器失效。
    v.erase(pos);
    cout << *pos << endl; // 此处会导致非法访问
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    为什么pos迭代器会失效?

    erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了(此时对pos的访问属于越界访问)。

    正如上面所说erase函数删除元素可能会导致迭代器失效,因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

    以下代码的功能是删除vector中所有的偶数,请问那个代码是正确的,为什么?

    #define _CRT_SECURE_NO_WARNINGS 1
    #include 
    using namespace std;
    #include 
    int main()
    {
    	vector<int> v{ 1, 2, 3, 4 };
    	auto it = v.begin();
    	while (it != v.end())
    	{
    		if (*it % 2 == 0)
    			v.erase(it);
    		++it;
    	}
    	return 0;
    }
    int main()
    {
    	vector<int> v{ 1, 2, 3, 4 };
    	auto it = v.begin();
    	while (it != v.end())
    	{
    		if (*it % 2 == 0)
    			it = v.erase(it);
    		else
    			++it;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    第一种方式,删除代码后迭代器就已经失效了,所以后面对迭代器的访问就会报错,第二种方式迭代器失效了,但是重置了迭代器,迭代器又活了过来,因此第二种方式才是正确的。

    注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端

    // 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
    int main()
    {
    	vector<int> v{ 1,2,3,4,5 };
    	for (size_t i = 0; i < v.size(); ++i)
    		cout << v[i] << " ";
    	cout << endl;
    	auto it = v.begin();
    	cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
    	// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效
    	v.reserve(100);
    	cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
    	// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
    	// 虽然可能运行,但是输出的结果是不对的
    	while (it != v.end())
    	{
    		cout << *it << " ";
    		++it;
    	}
    	cout << endl;
    	return 0;
    }
    /*程序输出:
    1 2 3 4 5
    扩容之前,vector的容量为: 5
    扩容之后,vector的容量为 : 100
    0 2 3 4 5 409 1 2 3 4 5 */
    
    // 2. erase删除任意位置代码后,linux下迭代器并没有失效
    // 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
    #include 
    #include 
    int main()
    {
    	vector<int> v{ 1,2,3,4,5 };
    	vector<int>::iterator it = find(v.begin(), v.end(), 3);
    	v.erase(it);
    	cout << *it << endl;
    	while (it != v.end())
    	{
    		cout << *it << " ";
    		++it;
    	}
    	cout << endl;
    	return 0;
    }
    /*程序可以正常运行,并打印:
    4
    4 5*/
    
    // 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
    // 此时迭代器是无效的,++it导致程序崩溃
    int main()
    {
    	vector<int> v{ 1,2,3,4,5 };
    	// vector v{1,2,3,4,5,6};
    	auto it = v.begin();
    	while (it != v.end())
    	{
    		if (*it % 2 == 0)
    			v.erase(it);
    		++it;
    	}
    	for (auto e : v)
    		cout << e << " ";
    	cout << endl;
    	return 0;
    }
    ========================================================
    // 使用第一组数据时,程序可以运行,但是这是一种侥幸。
    [sly@VM - 0 - 3 - centos 20220114]$ g++ testVector.cpp - std = c++11
    [sly@VM - 0 - 3 - centos 20220114]$ . / a.out
    1 3 5
    ======================================================== =
    // 使用第二组数据时,程序最终会崩溃
    [sly@VM - 0 - 3 - centos 20220114]$ vim testVector.cpp
    [sly@VM - 0 - 3 - centos 20220114]$ g++ testVector.cpp - std = c++11
    [sly@VM - 0 - 3 - centos 20220114]$ . / a.out
    Segmentation fault
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    从上述三个例子中可以看到:在Linux系统下迭代器失效后,代码并不一定会崩溃,但是运行结果可能不对,但是如果迭代器失效且不在begin和end范围内,那肯定会崩溃的。

    还要注意与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效(我们通常使用的是不含迭代器参数的string函数)

    #include 
    void TestString()
    {
    	string s("hello");
    	auto it = s.begin();
    	// 放开之后代码会崩溃,因为resize到20会string会进行扩容
    	// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
    	// 后序打印时,再访问it指向的空间程序就会崩溃
    	//s.resize(20, '!');
    	while (it != s.end())
    	{
    		cout << *it;
    		++it;
    	}
    	cout << endl;
    	
    	it = s.begin();
    	while (it != s.end())
    	{
    		it = s.erase(it);
    		// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后
    		// it位置的迭代器就失效了
    		// s.erase(it);
    		++it;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    迭代器失效解决办法:在使用前,对迭代器重新赋值即可。

    四.vector深度剖析及模拟实现

    vector的深度剖析

    在这里插入图片描述
    在这里插入图片描述

    vector的模拟实现

    #include 
    using namespace std;
    #include 
    
    namespace myVector
    {
    	template<class T>
    	class vector
    	{
    	public:
    		// Vector的迭代器是一个原生指针
    		typedef T* iterator;
    		typedef const T* const_iterator;
    
    		///
    		// 构造和销毁
    		vector()
    			: _start(nullptr)
    			, _finish(nullptr)
    			, _endOfStorage(nullptr)
    		{}
    
    		vector(size_t n, const T& value = T())
    			: _start(nullptr)
    			, _finish(nullptr)
    			, _endOfStorage(nullptr)
    		{
    			reserve(n);
    			while (n--)
    			{
    				push_back(value);
    			}
    		}
    
    		/*
    		* 理论上讲,提供了vector(size_t n, const T& value = T())之后
    		* vector(int n, const T& value = T())就不需要提供了,但是对于:
    		* vector v(10, 5);
    		* 编译器在编译时,认为T已经被实例化为int,而10和5编译器会默认其为int类型
    		* 就不会走vector(size_t n, const T& value = T())这个构造方法,
    		* 最终选择的是:vector(InputIterator first, InputIterator last)
    		* 因为编译器觉得区间构造两个参数类型一致,因此编译器就会将InputIterator实例化为int
    		* 但是10和5根本不是一个区间,编译时就报错了
    		* 故需要增加该构造方法
    		*/
    		vector(int n, const T& value = T())
    			: _start(new T[n])
    			, _finish(_start + n)
    			, _endOfStorage(_finish)
    		{
    			for (int i = 0; i < n; ++i)
    			{
    				_start[i] = value;
    			}
    		}
    
    		// 若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)只能是vector的迭代器
    		// 重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器
    		template<class InputIterator>
    		vector(InputIterator first, InputIterator last)
    		{
    			while (first != last)
    			{
    				push_back(*first);
    				++first;
    			}
    		}
    		
    		void swap(vector<T>& v)
    		{
    			std::swap(_start, v._start);
    			std::swap(_finish, v._finish);
    			std::swap(_endOfStorage, v._endOfStorage);
    		}
    		
    		vector(const vector<T>& v)
    			: _start(nullptr)
    			, _finish(nullptr)
    			, _endOfStorage(nullptr)
    		{
    			//现代写法,资本家写法
    			vector<T> temp(v.begin(),v.end());
    			swap(tmp);
    			
    		}
    
    		vector<T>& operator=(vector<T> v)
    		{
    			swap(v);
    			return *this;
    		}
    
    		~vector()
    		{
    			if (_start)
    			{
    				delete[] _start;
    				_start = _finish = _endOfStorage = nullptr;
    			}
    		}
    
    		/
    		// 迭代器相关
    		iterator begin()
    		{
    			return _start;
    		}
    
    		iterator end()
    		{
    			return _finish;
    		}
    
    		const_iterator cbegin() const
    		{
    			return _start;
    		}
    
    		const_iterator cend() const
    		{
    			return _finish;
    		}
    
    		//
    		// 容量相关
    		size_t size() const
    		{
    			return _finish - _start;
    		}
    
    		size_t capacity() const
    		{
    			return _endOfStorage - _start;
    		}
    
    		bool empty() const
    		{
    			return _start == _finish;
    		}
    
    		void reserve(size_t n)
    		{
    			if (n > capacity())
    			{
    				size_t oldSize = size();
    				// 1. 开辟新空间
    				T* tmp = new T[n];
    
    				// 2. 拷贝元素
    				// 这里直接使用memcpy会有问题吗?请思考下
    				//if (_start)
    				//	memcpy(tmp, _start, sizeof(T)*size);
    
    				if (_start)
    				{
    					for (size_t i = 0; i < oldSize; ++i)
    						tmp[i] = _start[i];
    
    					// 3. 释放旧空间
    					delete[] _start;
    				}
    
    				_start = tmp;
    				_finish = _start + oldSize;
    				_endOfStorage = _start + n;
    			}
    		}
    
    		void resize(size_t n, const T& value = T())
    		{
    			// 1.如果n小于当前的size,则数据个数缩小到n
    			if (n <= size())
    			{
    				_finish = _start + n;
    				return;
    			}
    
    			// 2.空间不够则增容
    			if (n > capacity())
    				reserve(n);
    
    			// 3.将size扩大到n
    			iterator it = _finish;
    			_finish = _start + n;
    			while (it != _finish)
    			{
    				*it = value;
    				++it;
    			}
    		}
    
    		///
    		// 元素访问
    		T& operator[](size_t pos)
    		{
    			assert(pos < size());
    			return _start[pos];
    		}
    
    		const T& operator[](size_t pos)const
    		{
    			assert(pos < size());
    			return _start[pos];
    		}
    
    		T& front()
    		{
    			return *_start;
    		}
    
    		const T& front()const
    		{
    			return *_start;
    		}
    
    		T& back()
    		{
    			return *(_finish - 1);
    		}
    
    		const T& back()const
    		{
    			return *(_finish - 1);
    		}
    		/
    		// vector的修改操作
    		void push_back(const T& x)//防止深拷贝,尽量用引用传参
    		{
    			insert(end(), x);
    		}
    
    		void pop_back()
    		{
    			erase(end() - 1);
    		}
    
    		iterator insert(iterator pos, const T& x)
    		{
    			assert(pos <= _finish);
    
    			// 空间不够先进行增容
    			if (_finish == _endOfStorage)
    			{
    				size_t n = pos - _start;
    				size_t newCapacity = (0 == capacity()) ? 1 : capacity() * 2;
    				reserve(newCapacity);
    
    				// 如果发生了增容,reserve会更新start和finish,但是不会更新pos,原空间被释放掉,迭代器失效了,所以需要重置pos
    				pos = _start + n;
    			}
    
    			iterator end = _finish - 1;
    			while (end >= pos)
    			{
    				*(end + 1) = *end;
    				--end;
    			}
    
    			*pos = x;
    			++_finish;
    			return pos;
    		}
    
    		// 返回删除数据的下一个数据
    		// 方便解决:一边遍历一边删除的迭代器失效问题
    		iterator erase(iterator pos)
    		{
    			// 挪动数据进行删除
    			iterator begin = pos + 1;
    			while (begin != _finish) {
    				*(begin - 1) = *begin;
    				++begin;
    			}
    
    			--_finish;
    			return pos;
    		}
    	private:
    		iterator _start;		// 指向数据块的开始
    		iterator _finish;		// 指向有效数据的下一个位置
    		iterator _endOfStorage;  // 指向存储容量的尾
    	};
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284

    使用memcpy拷贝问题

    假设模拟实现的vector中的reserve接口中,使用memcpy进行的拷贝,以下代码会发生什么问题?

    int main()
    {
    	bite::vector<bite::string> v;
    	v.push_back("1111");
    	v.push_back("2222");
    	v.push_back("3333");
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    问题分析:

    1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
    2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且
      自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

    总结:vector中,当T是涉及深浅拷贝的类型时如:string/vector等等,我们扩容使用memcpy拷贝数据是存在浅拷贝问题的

    所以我们要让这走深拷贝,我们一个个元素的拷贝,使用赋值运算符来完成,如果是内置类型没问题,如果是自定义类型,那赋值运算符中已经实现了深拷贝。

  • 相关阅读:
    form组件的封装(element ui ) 简单版本
    阿里云混合云管理平台多Region架构
    企业级node.js开发框架 【egg.js】 实用教程
    RCNN 目标检测网络学习笔记 (附代码)
    [附源码]Python计算机毕业设计Django教育企业网站
    Python爬虫教程12:从b站获取神仙姐姐的视频弹幕内容
    15.关注模块——peewee创建模型、tornado-peewee-async查询增加删除接口
    android 圆形进度条 横向进度条 不确定转圈进度条
    最短路径算法之一:单源无权图,python实现
    电脑可以模拟人脑吗
  • 原文地址:https://blog.csdn.net/weixin_44049823/article/details/126321501