关于标准库中的vector - (涉及迭代器失效,深浅拷贝,构造函数,内置类型构造函数,匿名对象)

目录

关于vector

vector中的常见接口

vector常见接口的实现

         迭代器失效

         关于深浅拷贝


关于vector

关于vector的文档介绍

  •  1. vector是表示可变大小数组的序列容器。

  • 2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。

  • 3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。

  • 4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

  • 5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

  • 6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起listforward_list统一的迭代器和引用更好。 

vector中的常见接口

 vector 的定义
void test_vector1()
{
	//构造函数 - 赋值构造
	vector<int> v1(10, 1);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	//构造函数 - 迭代器区间构造
	vector<int> v2(v1.begin(), v1.end());
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	//构造函数 - 可以传任意类型的迭代器区间,因为是模板
	string s1("hello world");
	vector<int> v3(s1.begin(), s1.end());
	for (auto e : v3)
	{
		cout << e << " ";
	}
	cout << endl;

	//可以++,或者--,类型需要匹配 - 这里char也属于int类型,因为存储的是ascii码
	vector<int> v4(++s1.begin(), --s1.end());
	for (auto e : v4)
	{
		cout << e << " ";
	}
	cout << endl;

	vector<char> v5(++s1.begin(), --s1.end());
	for (auto e : v5)
	{
		cout << e << " ";
	}
	cout << endl;
}

vector iterator 的使用

void test_vector2()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//迭代器访问
	vector<int> ::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//vector<int> ::reverse_iterator rit = v.rbegin();
	auto rit = v.rbegin();
	while (rit != v.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

vector 空间增长问题

//  vector的resize 和 reserve
// reisze(size_t n, const T& data = T())
// 将有效元素个数设置为n个,如果时增多时,增多的元素使用data进行填充
// 注意:resize在增多元素个数时可能会扩容
void Test_Vector3()
{
	vector<int> v;

	for (int i = 1; i < 10; i++)
		v.push_back(i);

	v.resize(5);
	v.resize(8, 100);
	v.resize(12);

	for (size_t i = 0; i < v.size(); i++)
		cout << ' ' << v[i];
	cout << '\n';
}

// 测试vector的默认扩容机制
// vs:按照1.5倍方式扩容
// linux:按照2倍方式扩容
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';
		}
	}
}

// 往vecotr中插入元素时,如果大概已经知道要存放多少个元素
// 可以通过reserve方法提前将容量设置好,避免边插入边扩容效率低
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.capacity 的代码在 vs g++ 下分别运行会发现, vs capacity 是按 1.5 倍增长的, g++ 是按 2 倍增长的
         2. 这个问题经常会考察,不要固化的认为, vector 增容都是 2 倍,具体增长多少是根据具体的需求定义的。vs PJ 版本 STL g++ SGI 版本 STL
         3. reserve 只负责开辟空间,如果确定知道需要用多少空间, reserve 可以缓解 vector 增容的代价缺陷问题。
        4. resize 在开空间的同时还会进行初始化,影响 size

vector 增删查改

// 尾插和尾删:push_back/pop_back
void Test_Vector4()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
    v.push_back(5);

    //vector<int>::iterator it = v.begin();
	auto it = v.begin();
	while (it != v.end()) 
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	v.pop_back();
	v.pop_back();

	it = v.begin();
	while (it != v.end()) 
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}


// 任意位置插入:insert和erase,以及查找find
// 注意find不是vector自身提供的方法,是STL提供的算法
void Test_Vector5()
{
	// 使用列表方式初始化,C++11新语法
	vector<int> v{ 1, 2, 3, 4 };

	// 在指定位置前插入值为val的元素,比如:3之前插入30,如果没有则不插入
	// 1. 先使用find查找3所在位置
	// 注意:vector没有提供find方法,如果要查找只能使用STL提供的全局find
	auto pos = find(v.begin(), v.end(), 3);
	if (pos != v.end())
	{
		// 2. 在pos位置之前插入30
		v.insert(pos, 30);
	}

	vector<int>::iterator it = v.begin();
	while (it != v.end()) 
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	pos = find(v.begin(), v.end(), 3);
	// 删除pos位置的数据
	v.erase(pos);

	it = v.begin();
	while (it != v.end()) 
    {
		cout << *it << " ";
		++it;
	}
	cout << endl;
}


vector常见接口的实现

这里接口的实现参考了一些stl底层源码

template<class T>
class vector
{
public:
	typedef T* iterator;

	//接口实现

private:
	iterator _start;
	iterator _finish;
	iterator _end_of_storage;
};

1.构造函数

无参构造

		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

带参构造

​
		vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

​

这个地方涉及到一个知识点,那就是:匿名对象调用默认构造函数

会有人好奇,匿名对象的生命周期不是只存在于这一行吗?

例:

class A
{
public:
	A(){
		cout << "A()" << endl;
	}

	~A(){
		cout << "~A()" << endl;
	}

};

vs2013有点bug 地方在于可以支持下列写法,但是vs2019之后就修复了。

迭代器构造

		//[first,last)
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

       这个地方涉及到一个知识点,那就是:允许类的成员函数再是函数模板 - 也就意味着,可以再添加函数模板参数

为什么不使用 iterator ,而使用模板?

 

使用模板不会显得多此一举吗?

        回答是不会,如果使用 iterator 就把这里的构造函数写死了,以后使用必须要传vector的迭代器来运行,迭代器区间的构造不一定要使用vector,一个容器使用迭代器区间去初始化,只要存储的类型符合,就可以使用任意类型的迭代器。


这里可以简单优化一下,因为c++11支持给缺省值,所以就可以省略掉初始化列表

        vector()
		{}

		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		//[first,last)
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

上述代码运行会有一个隐形的小问题,运行后发现:

test_vector()
{
    vector<int> (10, 5);

    return 0;
}

原因是因为:

这里有两种解决办法:


2.iterator

		typedef const T* const_iterator;


		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

3.size()

		size_t size() const
		{
			return _finish - _start;
		}

4.capacity()

		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

5.operator[ ]

		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}

6.reserve()

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];

				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}

				_start = tmp;
				_finish = _start + size(); 
				_end_of_storage = _start + n;
			}
		}

     这段代码存在一个问题,那就是size() 已经被修改,所以赋值给 _finish失败。 

修改后:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];

				size_t sz = size();
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz; 
				_end_of_storage = _start + n;
			}
		}

7.push_back()

		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			
			*_finish = x;
			_finish++;
		}

8.empty()

		bool empty()
		{
			return _start == _finish;
		}

9.pop_back

		void pop_back()
		{
			assert(!empty());
			--_finish;
		}

10.resize()

		void resize(size_t n , T val = T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}

				while (_finish != _start + n)
				{
					//注:这里如果实现逻辑是使用内存池,需要使用定位new来对已有空间进行初始化
					*_finish = val;
					++_finish;
				}

			}
		}
 

初始化的值是val,但是val缺省值为什么没给0?而是 T val = T()

因为写的是泛型编程 给0 ,char double都可以使用,但是像string一类就不行

这里是匿名对象调用默认构造 ,由此产生一个新问题 ,如果 T 是int类型,有没有默认构造?

template<class T>
	void f()
	{
		T x = T();
		cout << x << endl;
	}

	void test2()
	{
		f<int>();
		f<int*>();
		f<double>();
	}

	void test()
	{
		int i = int(); //支持
		int j = int(); //支持

		//int* pi = int* (); //这里也支出构造函数,但是不支持这样玩
	}

 //内置类型有没有构造函数?

 //默认是没有构造函数的这个说法的

 //默认构造函数是针对自定义类型的 

//我们不写,编译器会自动生成,我们写了,编译器就不会默认生成

 //默认生成的构造函数,内置类型不会处理,自定义类型会去调用它的构造函数

 //但是有了模板之后,内置类型需要有构造函数


11.insert() / 需要优化 ,优化后代码在下面

		void insert(iterator pos, const T& val)
		{
			assert(pos >= _start && pos <= _finish);

			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			_finish++;
		}

12.erase() / 需要优化 ,优化后代码在下面

		void erase(iterator pos)
		{
			assert(pos >= _start && pos <= _finish);

			iterator remove = pos + 1;
			while (remove != _finish)
			{
				*(remove - 1) = remove;
				remove++;
			}

			--_finish;
		}

       

 迭代器失效

  •         迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装

  •        比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(如果继续使用已经失效的迭代器,程序可能会崩溃)
对于 vector 可能会导致其迭代器失效的操作有:
  • 1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resizereserveinsertassign、push_back、erase等。
	void test_vector1()
	{
		vector<int> v;

		vector<int>::iterator it = v.begin();

		// 将有效元素个数增加到100个,多出的位置使用0填充,操作期间底层会扩容
			v.resize(100, 0);

		// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
		// v.reserve(100);

		// 插入元素期间,可能会引起扩容,而导致原空间被释放
		// v.insert(v.begin(), 0);
		// v.push_back(8);

		/*
		出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
		而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
		空间,而引起代码运行时崩溃。
		解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
		赋值即可。
		*/
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
  • 指定位置元素的插入操作   -   insert()

 测试:

        因为这里进行了扩容,_start  ,  _finish   ,   _end_of_storage 都发生了改变,但是pos指向的地址没有改变

解决办法:更新 pos  确定在新开空间里的相对位置  -  计算pos与_start的位置

但是运行后依旧发现问题没有解决,程序依旧报错,原因是什么呢?

        因为形参的改变不影响实参,虽然函数内修改了pos指向的位置,但是出了函数作用域pos依旧指向之前的位置,所以迭代器依旧会失效

那如果使用引用传参,是否可以解决?

那么就会出现下列问题:

当我们去查看文档时,会发现,其实 insert() 是有返回值的,返回的是

        所以具体实现如下:当然我们认为 insert 以后,pos位置就失效了,不能继续使用 ,所以并不建议继续使用

insert()

		iterator insert(iterator& pos, const T& val)
		{
			assert(pos >= _start && pos <= _finish);

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);

				//扩容后 更新pos
				pos = pos + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			_finish++;

			return pos;
		}

  • 指定位置元素的删除操作     erase()

      同理:erase() 我们认为也具有 insert() 的特性,所以 erase() 之后 ,也认为指针失效了,并且vs下,对此有严格的检查

例1:

vs怎么进行的检查?

因为(*pos)++; 这里不是指针解引用,而是是函数调用,vs重载了运算符 * 

         erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

例2:要求删除所有的偶数
    void Test_vector()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		//要求删除所有的偶数
		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			if (*it % 2 == 0)
			{
		
			     v1.erase(it);
			}
		
			++it;
			
		}

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

	}

上述代码运行后会崩溃,原因就在于迭代器失效,所以需要有返回值进行接收

所以, erase() 具体实现也会有返回值
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos <= _finish);

			iterator remove = pos + 1;
			while (remove != _finish)
			{
				*(remove - 1) = remove;
				remove++;
			}

			--_finish;

			return pos;
		}


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

例1:
int main()
{
 	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(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:

// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的

#include <vector>
#include <algorithm>

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:

// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃

int main()
{
 vector<int> v{1,2,3,4,5};

 // vector<int> 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;
}
// 使用第一组数据时,程序可以运行
[ @VM - 0 - 3 - centos ] $ g ++ testVector . cpp - std = c ++ 11
[ @VM - 0 - 3 - centos ] $ . / a . out
1 3 5
=========================================================
// 使用第二组数据时,程序最终会崩溃
[ @VM - 0 - 3 - centos ] $ vim testVector . cpp
[ @VM - 0 - 3 - centos ] $ g ++ testVector . cpp - std = c ++ 11
[ @VM - 0 - 3 - centos ] $ . / a . out
Segmentation fault
        从上述三个例子中可以看到: SGI STL 中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it 不在 begin end 范围内,肯定会崩溃的。

与vector类似,string在插入+扩容操作+erase之后,
#include <string>

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;
 }
}

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


关于深浅拷贝

      上面差不多就是 vector 的所有常用接口,当然也还差几个。比如,当运行下面这个测试用例,程会报错

	void test_vector1()
	{
		vector<int> v1(10, 2);

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2(v1);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

	}

        究其原因是在于这里我们并没有实现 拷贝构造 ,我们没有实现,编译器会自己实现,但是编译器实现的拷贝构造是值拷贝,也就是浅拷贝,会造成内存的重复释放,也就导致程序报错

所以 vector实现 这里我们还需实现一个拷贝构造

此时会发现上面的测试用例已经跑过了,那么我们多测几个:

	void test_vector1()
	{
		vector<std::string> v3(3, "********************");
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<std::string> v4(v3);
		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;
	}

        这个时候程序就挂掉了,why? 因为我们实现的拷贝构造是使用 memcpy函数

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

这里也会造成二次析构,导致程序崩溃。可以参考下图:

具体可以了解一下string类 以及 memcpy函数 里面都有些底层的实现:

         以上所述都表明这里的拷贝构造需要修改,但是怎么去修改呢?这里引用了模板,所以我们不能自己去实现深拷贝。

         不过,我们可以调用一个深拷贝函数,也就是 赋值 。这里不仅仅是拷贝构造需要修改,所有涉及memcpy的都需要修改,也就是说 reserve() 也需要修改

拷贝构造

		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			//memcpy(_start, v._start, sizeof(T) * v.size());
			for (size_t i = 0; i < v.size(); ++i)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}

reserve()

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];

				size_t sz = size();
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz; 
				_end_of_storage = _start + n;
			}
		}

是不是感觉到这vector接口总算没问题了?不,仍旧有问题,请看测试用例:以杨辉三角为例:

	class Solution 
	{
	public:
		vector<vector<int>> generate(int numRows) 
		{
			vector<vector<int>> vv;

			vv.resize(numRows, vector<int>());

			for (size_t i = 0; i < vv.size(); ++i)
			{
				vv[i].resize(i + 1, 0);
				vv[i][0] = vv[i][vv[i].size() - 1] = 1;
			}

			for (size_t i = 0; i < vv.size(); ++i)
			{
				for (size_t j = 0; j < vv[i].size(); ++j)
				{
					if (vv[i][j] == 0)
					{
						vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
					}
				}
			}

			return vv;
		}
	};

	void test_vector1()
	{
		vector<vector<int>> ret = Solution().generate(5);

		for (size_t i = 0; i < ret.size(); ++i)
		{
			for (size_t j = 0; j < ret[i].size(); ++j)
			{
				cout << ret[i][j] << " ";
			}
			cout << endl;
		}
		cout << endl;

	}

        通过调试我们可以看到,这两层  vector<vector<int>>  ,外面的 vector 实现的是深拷贝,而里面的vector 实现的是依旧浅拷贝,为什么?我们不是明明已经完善了拷贝构造嘛?

        说明什么,说明拷贝构造还不够完善呗,原因其实出在赋值上面,因为 vector 我们并没有写赋值 ,所以使用的是编译器默认生成的赋值,默认生成的赋值来进行拷贝,也就是浅拷贝。

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

此时,程序正常运行

另外,拷贝构造其实也可以有多种写法:

比如利用类模板



		vector(const vector<T>& v)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

或者范围for+push_back

		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto e : v)
			{
				push_back(e);
			}
		}

       值得吐槽的是,现在的vs做了很多的优化

       第一点是代码出错了也不一定挂掉,依旧拿上面的杨辉三角代码举例,如果不写赋值

vs2019代码会直接挂掉

vs2022代码正常运行

你说这扯不扯,由此也就牵扯了下面这个吐槽

      第二点是就算是正常,它也表现的不是很正常,此时已经写了赋值

vs2022运行正常,但是,看调试

这里不应该是深拷贝吗?不是已经重载了赋值了吗?为什么看起来还是浅拷贝?

        在vs19上我们其实可以看到,地址是不同的

         但是由于vs22的优化,导致我们看起来好像还是浅拷贝,其实不是,怎么样,看起来不正常,实际上是正常。

       这里有个猜测是,return vv 返回的时候,并没有把 vv 给析构掉,而是直接把 vv 的地址赋给了ret,并且程序结束的时候并没有析构两次。当然,这只是猜测,具体可以自己下去尝试以下。


最后,附上所有模拟底层接口实现代码以及测试用例:

                                                                              test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

#include "vector.h"

int main()
{
	dw::test_vector1();

	return 0;
}

                                                                              vector.h

#pragma once
#include <assert.h>

namespace dw
{
	template <class T>
	class vector
	{
	public:
		typedef T* iterator;

		typedef const T* const_iterator;


		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		vector()
		{}

		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		//[first,last)
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];

				size_t sz = size();
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz; 
				_end_of_storage = _start + n;
			}
		}

		void resize(size_t n , T val = T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}

				while (_finish != _start + n)
				{
					//注:这里如果实现逻辑是使用内存池,需要使用定位new来对已有空间进行初始化
					*_finish = val;
					++_finish;
				}

			}
		}

		iterator insert(iterator& pos, const T& val)
		{
			assert(pos >= _start && pos <= _finish);

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);

				//扩容后 更新pos
				pos = pos + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			_finish++;

			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos <= _finish);

			iterator remove = pos + 1;
			while (remove != _finish)
			{
				*(remove - 1) = remove;
				remove++;
			}

			--_finish;

			return pos;
		}

		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			
			*_finish = x;
			_finish++;
		}

		void pop_back()
		{
			assert(!empty());
			--_finish;
		}

		bool empty()
		{
			return _start == _finish;
		}


		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}

		size_t size() const
		{
			return _finish - _start;
		}

		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			//memcpy(_start, v._start, sizeof(T) * v.size());
			for (size_t i = 0; i < v.size(); ++i)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}

		//vector(const vector<T>& v)
		//{
		//	reserve(v.size());
		//	for (auto e : v)
		//	{
		//		push_back(e);
		//	}
		//}

		//vector(const vector<T>& v)
		//{
		//	vector<T> tmp(v.begin(), v.end());
		//	swap(tmp);
		//}

		~vector()
		{
			//cout << _start << endl;
			delete[] _start;
			_start = _finish = _end_of_storage;
		}

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
class Solution 
	{
	public:
		vector<vector<int>> generate(int numRows) 
		{
			vector<vector<int>> vv;

			vv.resize(numRows, vector<int>());

			for (size_t i = 0; i < vv.size(); ++i)
			{
				vv[i].resize(i + 1, 0);
				vv[i][0] = vv[i][vv[i].size() - 1] = 1;
			}

			for (size_t i = 0; i < vv.size(); ++i)
			{
				for (size_t j = 0; j < vv[i].size(); ++j)
				{
					if (vv[i][j] == 0)
					{
						vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
					}
				}
			}

			return vv;
		}
	};

	void test_vector1()
	{
		vector<vector<int>> ret = Solution().generate(5);

		for (size_t i = 0; i < ret.size(); ++i)
		{
			for (size_t j = 0; j < ret[i].size(); ++j)
			{
				cout << ret[i][j] << " ";
			}
			cout << endl;
		}
		cout << endl;

	}


	void test_vector5()
	{
		vector<std::string> v1(3, "*********************");
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<std::string> v2(v1);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void test_vector4()
	{
		vector<int> v1(10, 2);

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2(v1);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

	}


	void test_vector3()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		vector<int>::iterator pos = find(v.begin(), v.end(), 3);
		if (pos != v.end())
		{
			v.insert(pos, 30);

		}

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		(*pos)++;

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void test_vector()
	{
		vector<int> v;

		vector<int>::iterator it = v.begin();

		// 将有效元素个数增加到100个,多出的位置使用0填充,操作期间底层会扩容
			v.resize(100, 0);

		// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
		// v.reserve(100);

		// 插入元素期间,可能会引起扩容,而导致原空间被释放
		// v.insert(v.begin(), 0);
		// v.push_back(8);

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


	void test_vector2()
	{
		vector<int> v;

		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		for (size_t i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it <<" ";
			++it;
		}
		cout << endl;

		v.pop_back();
		v.pop_back();
		v.pop_back();
		v.pop_back();

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

	}

}

以上仅代表个人看法,欢迎讨论交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/211136.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

零售数字化“逆熵”的6项原则和8种能力建设|ShopeX徐礼昭

作者&#xff1a;徐礼昭 来源&#xff1a;《三体零售逆熵法则》节选 旧的规则与秩序被打破&#xff0c;无序成为常态 新时代洪流裹挟冲击着传统零售 无序带来的“熵增”侵蚀企业生命 所有人都在不确定性中寻找确定 数字化如何助力企业铸就「反熵增」神器&#xff1f; 如何…

【交换排序 简单选择排序 堆排序 归并排序】

文章目录 交换排序简单选择排序堆排序归并排序 交换排序 冒泡排序的算法分析&#xff1a; 冒泡排序最好的时间复杂度是O&#xff08;n&#xff09;冒泡排序最好的时间复杂度是O&#xff08;n平方&#xff09;冒泡排序平均时间复杂度为O&#xff08;n的平方&#xff09;冒泡排…

spring boot定时器实现定时同步数据

文章目录 目录 文章目录 前言 一、依赖和目录结构 二、使用步骤 2.1 两个数据源的不同引用配置 2.2 对应的mapper 2.3 定时任务处理 总结 前言 一、依赖和目录结构 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifa…

无线物理层安全学习

文章目录 3.17到3.203.85到3.88 3.17到3.20 3.85到3.88

#计算机毕业设计#微信小程序#社区团购#小猪优选

小猪优选 简介 类似于美团优选&#xff0c;多多买菜等线上平台。 是一套社区团购的项目&#xff0c;是依托真实社区的一种区域化、小众化、本地化、网络化的团购形式&#xff0c;同事也是一种生鲜商品流通的新零售模式。 背景&#xff1a; 社区团购是真实具名团体的一种互…

电脑提示mfc100u.dll缺失如何解决?分享有效的5个解决方法

由于各种原因&#xff0c;电脑可能会出现一些问题&#xff0c;其中之一就是电脑提示mfc100u.dll的错误。这个问题可能会导致电脑无法正常运行某些程序或功能。为了解决这个问题&#xff0c;我将分享验证有效的五个修复方法&#xff0c;帮助大家恢复电脑的正常运行。 首先&#…

SALib敏感性分析入门实践笔记

1. 敏感性分析 敏感性分析是指从定量分析的角度研究有关因素发生某种变化对某一个或一组关键指标影响程度的一种不确定分析技术。 其实质是通过逐一改变相关变量数值的方法来解释关键指标受这些因素变动影响大小的规律。 敏感性因素一般可选择主要参数&#xff08;如销售收入、…

Redis数据结构之跳表

跳表是一种有序的数据结构&#xff0c;它通过在每个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。其核心思想就是通过建立多级索引来实现空间换时间。 在Redis中&#xff0c;使用跳表作为Zset的一种底层实现之一&#xff0c;这也是跳表在Redis中的…

IO延迟引起的虚拟机故障排查

vmware 虚拟机连上之后总感觉非常卡&#xff0c;查看CPU 内存资源使用率是正常的。 message 日志有cpu卡住的报错 NMI watchdog: BUG: soft lockup - CPU#8 stuck for 23s! [container-31451:45878]下面分析是什么导致的服务器cpu卡住。 1、打开prometheus&#xff0c;观察服务…

CSS新手入门笔记整理:CSS图片样式

图片大小 语法 width:像素值; height:像素值; 图片边框&#xff1a;border 语法 边框&#xff1a;宽度值 样式值 颜色值&#xff1b; border:1px solid red; 图片对齐 水平对齐&#xff1a;text-align 语法 text-align:取值; 属性值 说明 left 左对齐(默认值) cent…

神经网络 代价函数

神经网络 代价函数 首先引入一些便于稍后讨论的新标记方法&#xff1a; 假设神经网络的训练样本有 m m m个&#xff0c;每个包含一组输入 x x x和一组输出信号 y y y&#xff0c; L L L表示神经网络层数&#xff0c; S I S_I SI​表示每层的neuron个数( S l S_l Sl​表示输出…

[英语学习][5][Word Power Made Easy]的精读与翻译优化

[序言] 今日完成第18页的阅读, 发现大量的翻译错误以及不准确. 需要分两篇文章进行讲解. [英文学习的目标] 提升自身的英语水平, 对日后编程技能的提升有很大帮助. 希望大家这次能学到东西, 同时加入我的社区讨论与交流英语相关的内容. [原著英文与翻译版对照][第18页] Wh…

基于SpringBoot的企业客户管理系统的设计与实现

摘 要 本论文主要论述了如何使用JAVA语言开发一个企业客户管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述企业客户管理系统的当前背景以及系统开发的目…

STM32---MDK工程创建

本节我们带领大家学习如何新建一个寄存器库版本MDK的详细步骤&#xff1b; 由于51单片机的学习时&#xff0c;所涉及的寄存器很少&#xff0c;所以往往几个头文件、驱动文件就可以完成相关的功能&#xff0c;但是对于STM32来讲&#xff0c;涉及的寄存器、头文件等都很多&#…

CleanMyMac X4.16.2最新2024注册许可证

都说苹果的闪存是金子做的&#xff0c;这句话并非空穴来风&#xff0c;普遍都是256G起步&#xff0c;闪存没升级一个等级&#xff0c;价格都要增加上千元。昂贵的价格让多数消费者都只能选择低容量版本的mac。而低容量的mac是很难满足用户的需求的&#xff0c;伴随着时间的推移…

005、简单页面-容器组件

之——布局 目录 之——布局 杂谈 正文 1.布局基础知识 2.Column 3.Row 4.实践 杂谈 布局容器组件。 一个丰富的页面需要很多组件组成&#xff0c;那么&#xff0c;我们如何才能让这些组件有条不紊地在页面上布局呢&#xff1f;这就需要借助容器组件来实现。 容器组件是…

elment Loading 加载组件动态变更 text 值bug记录

先上效果图: 倒计时4分钟组件方法 // 倒计时 4分钟getSencond() {this.countDown 4分00秒this.interval setInterval(() > {this.maxTime--;let minutes Math.floor(this.maxTime / 60);let seconds Math.floor(this.maxTime % 60);minutes minutes < 10 ? 0 minu…

AI 绘画 | Stable Diffusion 电商模特

前言 上一篇文章讲到如何给人物更换背景和服装。今天主要讲电商模特,就是服装电商们的固定服装产品图片如何变成真人模特穿上的固定服装产品效果图。如果你掌握了 《AI 绘画 | Stable Diffusion 人物 换背景|换服装》,这篇文章对你来说,上手会更轻松。 教程 提取服装蒙版…

解决Wireshark分析RTMP抓包时Unknown问题

使用Wireshark抓包时&#xff0c;经常出现很多Unknown包&#xff0c;但实际上的字节流实际是正常的。 其实&#xff0c;RTMPT设置里有一个最大包大小的设置&#xff0c;默认是32768&#xff0c;而且默认RTMPT协议配置了从多个TCP流中重组RTMPT的功能(应当是考虑基于HTTP的传输…

SpringBoot+SSM项目实战 苍穹外卖(2)

继续上一节的内容&#xff0c;本节完成新增员工、员工分页查询、启用禁用员工账号、编辑员工、导入分类模块功能代码。 目录 新增员工(完整流程分为以下五个部分)需求分析和设计代码开发功能测试代码完善 (ThreadLocal 线程局部变量)代码提交 员工分页查询代码完善 扩展Spring …