错误警示1

当 _start 被重新赋值后,size() 函数计算 _finish - _start 时,由于 _finish 还是指向旧内存的指针,而 _start 已经指向新内存。也就是说也就是说程序运行到_finish = _start + size();时相当于_finish = _start + _finish - _start ,_finish还是_finish,也就是nullptr从而导致空指针解引用,引发程序崩溃。

解决方案1:先更新_finish(不推荐,存在顺序问题)

解决方案2:在更新_start之前提前算好size的值(oldsize)(推荐)

错误警示2

const对象不能调用非const成员函数,也就是v(const)不能调用iterator begin()(return _start;)而且这里的范围for也不能正常遍历,因为范围for如果是const对象,也会转换成const迭代器,这里还要注意一下,范围for需要begin() const/end() const。而不是cbegin()/cend()

解决方案:提供const版本的begin和end


拓展:

我们在提供了const版本的begin和end后,PrintVector正常了,但是如果我们实例化出来一个存储double类型的vector之后之前的写法就不支持了,因为我们形参位置给的是const vector<int>& v,而我们需要的是const vector<double>& v,那要不要在写一个double类型的呢?其实不用,我们可以将PrintVector改造成函数模板

template<class T>
void PrintVector(const vector<T>& v)
{
	//vector<T>::const_iterator it = v.begin();//编译报错
    typename vector<T>::const_iterator it = v.begin();//ok
    //auto it=v.begin();//当然也可以使用auto  ok
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

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

但是这里就编译报错,这是因为我们在使用域作用限定符::取类里面的类型或者静态成员变量,这个类是已经存在的,而上面这种编译器从上往下编译,编译到vector<T>::const_iterator it = v.begin();编译器不认识vector<T>,因为类模板有一个原则就是在类模板没有实例化时,不能取类模板里面的类型或者静态成员变量,因为编译器区分不了const_iterator是类型还是静态成员变量,所以为了区分,要在前面加typename,也就是类型而不是静态成员变量

C++标准规定:

根据C++标准,在模板中对于依赖名称(依赖于模板参数的名称),如果要引用嵌套类型,必须使用 typename 关键字。

错误警示3

迭代器失效一(类似野指针):我们在实现insert的时候容易引起迭代器失效,我们发现上面这段代码只要进行了扩容操作就会有问题,这里是因为扩容时,会新开一个tmp(局部变量)数组,拷贝完数据后会将原空间释放,接着将迭代器更新,但是这里的pos依然指向原空间的某个位置,从而导致移动数据的逻辑错乱。引发一系列的问题。

void insert(iterator pos, const T& x)
{
	//扩容
	if (_finish == _endofstorage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
}

解决方案:在扩容之前记录一个相对位置

void insert(iterator pos, const T& x)
{
	//扩容
	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		//更新pos
		pos = _start + len;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
}

迭代器失效二(位置意义变化):

在 v1.insert(pos, 66); 之后,pos 已经失效,虽然有更新pos的机制(可能指向无效内存或旧位置)。后续尝试通过 (*pos) *= 10; 修改 pos 指向的值是未定义行为(可能导致崩溃或错误结果)。

解决方案:使用返回值更新迭代器:

iterator insert(iterator pos, const T& x)
{
	//扩容
	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		//更新pos
		pos = _start + len;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
	return pos;
}

auto pos = find(v1.begin(), v1.end(), x);
if (pos != v1.end())
{
	pos = v1.insert(pos, 66);  // 用返回值更新pos
	// 现在pos指向新插入的66,原来的x在pos+1位置
	*(pos + 1) *= 10;          // 安全操作
}

错误警示4

我们在实现erase的时候,依然会有迭代器失效的问题在测试下面程序

  • 会出现assert失败:这是因为当最后一个数据是偶数时,当最后一次挪动数据++it,同时--_finish,于是就出现Assertion failed:pos<_finish
  • 有时偶数没删除干净:这是因为当出现连续偶数时,会将下一个偶数往前挪动,此时it指向下一个偶数,接着就执行it++结果就把下一个偶数跳过了

void test_vector4()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(4);
	v1.push_back(5);
	PrintContainer(v1);
	//删除所有的偶数
	auto it = v1.begin();
	while (it!=v1.end())
	{
		if (*it % 2 == 0)
			v1.erase(it);
		it++;
	}
	PrintContainer(v1);
}

解决方案:修正erase函数和删除的循环逻辑:

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

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

	return pos; // 返回被删除元素的位置(现在指向下一个元素)
}
//正确的循环逻辑
auto it = v1.begin();
while (it != v1.end())
{
	if (*it % 2 == 0)
	{
		it = v1.erase(it);  // 用返回值更新it,(间接进行了it++)
	}
	else
	{
		++it;  // 只有不是偶数时才移动
	}
}

错误警示5

我们在实现resize的时候,缺省值处会给对象的默认构造,而不是一个具体的值,这是因为当模板类型 T 不是数值类型时,0 可能无法转换为 T 类型。比如如果 T 是 std::string0 无法转换为字符串。这里在注意一下C+++标准规定:绑定到const引用的临时对象生命周期会延长,也就是说匿名对象的生命周期延长到引用val的作用域

//void resize(size_t n,const T&val=0))错误
//void resize(size_t n, T val = T())//用默认构造构造匿名对象,在调用拷贝构造
void resize(size_t n,const T&val=T())//用const引用去引用匿名对象
{/*...*/}

T如果是:

  • 自定义类型-------->去调用该类型的默认构造函数
  • 内置类型----------->也支持int i=();int j=int(1);int k(2);
  • 数组类型----------->每个元素进行值初始化

错误警示6

我们在实现完用n个val构造会与之前实现的迭代器区间构造冲突

    vector<int> v(10, 20);
// 编译器困惑:这是要构造10个20?还是用迭代器区间[10, 20]?

为什么之前的不会出错,其实编译器有一个原则,就是编译器回去匹配类型最匹配的,前面两个不会匹配到迭代器是因为类型不匹配

解决方案1:指定访问vector<int> v6(10u,1);(加u就是unsigned int)

解决方案2:提供更匹配的函数(推荐)(之前说过,非模板函数可以与同名函数模板同时存在“有现成用现成的”)

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

错误警示7

memcpy的浅拷贝问题:

测试以下程序,我们发现,程序崩溃了,并且打了一堆乱码。打上断点,调试发现当程序在扩容时,临时对象tmp里面的值在运行delete[] _start;之前是正常的,一执行这一段代码tmp里面的值就变成了随机值(乱码),这里的核心问题就是memcpy的浅拷贝问题。

memcpy是浅拷贝,于是将原空间的每个指针(_str)拷贝给tmp的每个_str于是新的空间指针指向了原空间,执行delete[] _start,就会先调用析构函数将每块空间所指向的堆资源清理了,接着调用operator delete[],去把v的空间释放,于是新空间每一个_str就类似于野指针,也就打印了乱码。其实不只是string类有这样的问题vector<T>,只要T是内置类型就正常,T是深拷贝类型就会出现问题如vector<vector<int>>

解决方案:使用深拷贝调用赋值重载

void reserve(size_t n)
{
	size_t oldsize = size();
	if (n > capacity())
	{
		T* tmp = new T[n];
		//memcpy(tmp, _start, sizeof(T) * size());
		for (size_t i=0;i<oldsize;i++)
		{
			 tmp[i] = _start[i];
		}
		delete[] _start;
		
		//更新迭代器
		_start = tmp;
		_finish = tmp + oldsize;
		_endofstorage = tmp + n;
	}
}

这里本质就是调用string的赋值(两个已经存在的对象)

vector的拷贝问题

1.拷贝构造

前面说过,当我们没有显示实现拷贝构造函数时,编译器默认生成的是浅拷贝的拷贝构造函数,在显示实现析构函数时,就会析构同一块空间,引发程序崩溃。所以当我们要实现vector<int> v2 = v1;(区分赋值重载),就要自己实现拷贝构造函数。这里特别注意一下,当我们没有显示写任何构造函数,编译器会默认生成,但是当我们只要写了任意构造函数,编译器就不会生成,而拷贝构造也是构造函数,因此当我们只显示写拷贝构造函数,就不会生成默认构造函数了。

//vector()//走初始化列表
//{}
//C++11的用法,强制生成默认构造函数
vector() = default;

vector(const vector<T>& v)
{
	reserve(v.size());//减少扩容
	for (auto& e : v)//传引用减少拷贝
	{
		push_back(e);
	}
}

2.赋值重载

void clear(){_finish = _start;}
//v1=v3传统写法
//vector<T>& operator=(const vector<T>& v)
//{
//	if (this != &v)
//	{
//		clear();
//		reserve(v.size());
//		for (auto& e : v)
//		{
//			push_back(e);
//		}
//	}
//	return *this;
//}
void swap(vector<T>& v)
{
	std::swap(_start,v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v._endofstorage);
}
//vector& operator=(vector v)//可以在类里面使用类名来替代类型
vector<T>& operator=(vector<T> v)//现代写法注意传值
{
	swap(v);
	return *this;
}

v1=v3,v3就传给v,v1就传给*this,v3是传值传参调用之前的深拷贝构造函数,于是v就有与v3相同的数据,于是将这块数据利用swap函数与this交换,新空间给了v1(达成目的),旧空间(v1原本的空间)就给了v,v是局部对象,出作用域就调用析构函数释放了。


这里提一个特殊用法:在类作用域内,可以直接使用类名,不需要模板参数。但是我们为了提高代码的可读性,还是建议将模板参数带上

vector迭代器区间构造

在模拟实现vector的迭代器区间构造时,要了解一个概念:类模板的成员函数可以是类模板

//迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)//不建议用<,当空间不连续时就不支持
	{
		push_back(*first);
		first++;
	}
}

这里不使用之前的iterator作为迭代器类型是因为如果使用了之前的iterator迭代器,就只能是vector的迭代器了,就只支持vector的区间构造,后续如果向将一个存储与list数据取一段区间构造出一个vector对象就不兼容了(前提类型匹配)。这里要注意,< 操作需要随机访问迭代器,而 != 只需要输入迭代器。vector提供随机访问迭代器,list只提供双向迭代器,因此通用代码应该使用 != 来保证兼容性。

vector模拟实现

Vector.h

#include<iostream>
#include<vector>
#include<assert.h>
#include<list>
#include<string>
using namespace std;

namespace name
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//vector()//走初始化列表
		//{}
		//C++11的用法,强制生成默认构造函数
		vector() = default;

		vector(const vector<T>& v)
		{
			reserve(v.size());//减少扩容
			for (auto& e : v)//传引用减少拷贝
			{
				push_back(e);
			}
		}

		//类模板的成员函数,还可以继续是函数模板
		//为什么不能直接使用iterator,而是有套了一层函数模板?
		//这是因为如果使用iterator,那么这个迭代器就只能是vector的迭代器,写成模板可以支持任意类型的迭代器
		//迭代器区间构造
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)//不建议用<,当空间不连续时就不支持
			{
				push_back(*first);
				first++;
			}
		}
	
		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);
			}
		}

		void clear()
		{
			_finish = _start;
		}

		//v1=v3
		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this != &v)
		//	{
		//		clear();
		//		reserve(v.size());
		//		for (auto& e : v)
		//		{
		//			push_back(e);
		//		}
		//	}
		//	return *this;
		//}
		void swap(vector<T>& v)
		{
			std::swap(_start,v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		//vector& operator=(vector v)//可以在类里面使用类名来替代类型
		vector<T>& operator=(vector<T> v)//现代写法注意传值
		{
			swap(v);
			return *this;
		}

		~vector()
		{
			if (_start != nullptr)
			{
				delete[] _start;
				_start = _finish = _endofstorage = nullptr;
			}
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		void reserve(size_t n)
		{
			size_t oldsize = size();
			if (n > capacity())
			{
				T* tmp = new T[n];
				//memcpy(tmp, _start, sizeof(T) * size());
				for (size_t i=0;i<oldsize;i++)
				{
					 tmp[i] = _start[i];
				}
				delete[] _start;
				
				//更新迭代器
				_start = tmp;
				_finish = tmp + oldsize;
				_endofstorage = tmp + n;
			}
		}

		//void resize(size_t n, T val = T())//用默认构造构造匿名对象,在调用拷贝构造
		void resize(size_t n,const T&val=T())//用const引用去引用匿名对象
		{
			//n<size  size<n<capacity  n>capacity
			if (n < size())
			{
				_finish = _start + n;
			}
			else//插入数据
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

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

		size_t capacity() const
		{
			return _endofstorage - _start;
		}
		
		bool empty()
		{
			return _start == _finish;

		}

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

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

		T& operator[](size_t n)
		{
			assert(n<size());

			return _start[n];
		}

		const T& operator[](size_t n)const
		{
			assert(n < size());

			return _start[n];
		}

		iterator insert(iterator pos, const T& x)
		{
			assert(pos>=_start);
			assert(pos <= _finish);
			//扩容
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//更新pos
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
			return pos;
		}

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

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

			return pos; // 返回被删除元素的位置(现在指向下一个元素)
		}

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _endofstorage = nullptr;
	};

	//void PrintVector(const vector<int>& v)
	//{
	//	vector<int>::const_iterator it = v.begin();
	//	while (it != v.end())
	//	{
	//		cout << *it << " ";
	//		++it;
	//	}
	//	cout << endl;

	//	for (auto e : v)
	//	{
	//		cout << e << " ";
	//	}
	//	cout << endl;
	//}
	
	template<class T>
	void PrintVector(const vector<T>& v)//适合vector容器
	{
		//vector<T>::const_iterator it = v.begin();//编译报错
		typename vector<T>::const_iterator it = v.begin();//ok
		//auto it=v.begin();//当然也可以使用auto  ok
		/*while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;*/

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

	template<class Container>//适合任意容器
	void PrintContainer(const Container& v)
	{
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}

Test.cpp

#include"Vector.h"
namespace name
{
	void test_vector1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);//二次扩容

		for (int 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;

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

		PrintVector(v);
	}

	void test_vector2()
	{
		vector<double> v1;
		v1.push_back(1.1);
		v1.push_back(2.2);
		v1.push_back(3.3);
		v1.push_back(4.4);
		v1.push_back(5.5);
		PrintVector(v1);
	}

	void test_vector3()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(4);
		v1.insert(v1.begin() + 2, 8);
		PrintVector(v1);
		//配合查找插入使用
		int x;
		cin >> x;
		//c++所有的算法传的迭代器区间都是左闭右开的
		//find没有找到就返回最后一个位置
		//迭代器失效二(迭代器insert后就失效了,不要访问)
		/*auto pos = find(v1.begin(),v1.end(),x);
		if (pos != v1.end())
		{
			v1.insert(pos,66);
			(*pos) *= 10;
		}*/

		auto pos = find(v1.begin(), v1.end(), x);
		if (pos != v1.end())
		{
			pos = v1.insert(pos, 66);  // 用返回值更新pos
			// 现在pos指向新插入的66,原来的x在pos+1位置
			*(pos + 1) *= 10;          // 安全操作
		}

		PrintVector(v1);
		//PrintContainer(v1);
	}

	void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(4);
		v1.push_back(5);
		v1.push_back(4);
		v1.push_back(4);
		PrintContainer(v1);
		auto it = v1.begin();
		while (it != v1.end())
		{
			if (*it % 2 == 0)
			{
				it = v1.erase(it);  // 用返回值更新it,不执行it++
			}
			else
			{
				++it;  // 只有不是偶数时才移动
			}
		}
		PrintContainer(v1);
	}
	void test_vector5()
	{
		int i = int();
		int j = int(2);
		int k(2);

		vector<int> v;
		v.resize(10, 1);
		v.reserve(20);
		PrintVector(v);
		cout << v.size() << endl;
		cout << v.capacity() << endl;

		v.resize(15, 2);
		PrintVector(v);

		v.resize(25, 3);
		PrintVector(v);

		v.resize(5);
		PrintVector(v);
	}

	void test_vector6()
	{
		/*std::vector<int> v1;*/
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		PrintContainer(v1);

		/*std::vector<int> v2 = v1;*/
		vector<int> v2 = v1;
		PrintContainer(v2);

		vector<int> v3;
		v3.push_back(10);
		v3.push_back(20);
		v3.push_back(30);
		v3.push_back(40);
		v1 = v3;
		PrintContainer(v1);
		PrintContainer(v3);
	}

	void test_vector7()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);
		v1.push_back(6);
		PrintContainer(v1);
		vector<int> v2(v1.begin(), v1.end() - 3);
		PrintContainer(v2);

		cout << "*****list******" << endl;
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		vector<int>v3(lt.begin(), lt.end());//也支持使用不同容器的iterator,但要求数据类型匹配
		PrintContainer(lt);
		PrintContainer(v3);
		cout << "*****string******" << endl;
		vector<string> v4(10, "string");
		PrintContainer(v4);

		vector<int> v5(10);
		PrintContainer(v5);

		vector<int> v6(10, 1);
		PrintContainer(v6);//选择最匹配的

		/*vector<int> v6(10u,1);
		PrintContainer(v6);*/
	}

	void test_vector8()
	{
		vector<string> v;
		v.push_back("11111111111111111");
		v.push_back("11111111111111111");
		v.push_back("11111111111111111");
		v.push_back("11111111111111111");
		PrintContainer(v);
		v.push_back("11111111111111111");//扩容出现问题
		PrintContainer(v);
	}
}
int main()
{
	//name::test_vector1();
	//name::test_vector2();
	//name::test_vector3();
	//name::test_vector4();
	//name::test_vector5();
	//name::test_vector6();
	//name::test_vector7();
	name::test_vector8();
	return 0;
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐