「C++」vector的使用及接口模拟详解

目录

前言

vector的使用

模拟实现

成员变量

reserve扩容

insert插入

erase删除

迭代器失效总结

resize

构造

默认构造和拷贝构造

迭代器区间构造

n个参数val的构造

赋值重载

模拟实现时的其他问题

typename语法

内置类型的构造函数

类模板中的类名简写

后记

附:完整代码


前言

大家好呀~欢迎来到海盗猫的CPP专栏------vector篇

本篇我们将以string的学习基础为对比,讲解vector的不同点和模拟实现中遇见的各种问题

vector是一个类模板,必需要显示实例化vector<typename>

vector的使用,与string相同的接口使用方法类似,但也有不同,可以查询文档

vector文档:

vector - C++ Reference

vector的使用

1.reserve规定不会缩小容量

在string中,reserve操作当n小于size时,不同的编译器会有不同的处理方法,vs2022中,reserve只会进行扩容;g++4.8则会缩小容量到有效字符的长度;

这是因为string规定中,没有明确写明这种情况的处理方式,所以不同的编译器可以有不同的处理方法;

而在vector的文档中查询可以看到

当n大于capacity容量时,才会扩容,其他情况均不会产生影响

2.resize会删除数据

当参数n小于当前size时会执行删除数据的操作,将第n个数据后的数据删除;

n大于capacity则会扩容到n

3.vector没有实现流插入和流提取的重载

这是由于vector能存储各种不同的类型,包括自定义类型,所以输出方式不尽相同,需要我们自行输出

4.insert,erase函数只有使用迭代器调用的重载(存在迭代器失效问题,后文模拟实现提及)

模拟实现

由于从vector容器开始,将使用类模板来进行其模拟实现,且成员变量替换为三个迭代器类型,所以模拟实现会出现许多不同的地方和注意点,据情况做出详细的解释;

成员变量

在string的模拟实现中三个成员变量为

cpp 复制代码
char* _str = nullptr;//指向数据存储的空间地址
size_t _size = 0;//有效字符串长度
size_t _capacity = 0;//容量大小,不包括'\0'

而到了vector,三个成员变量将使用iterator的迭代器类型来声明:

cpp 复制代码
public:
	typedef T* iterator;
private:
	iterator _start = nullptr;//有效空间开始位置的指针
	iterator _finish = nullptr;//有效空间结束位置的指针
	iterator _end_of_storage = nullptr;//容量最后位置的指针

reserve扩容

注意使用old_size临时变量来辅助更新迭代器

cpp 复制代码
	void reserve(size_t n){
		if (n > capacity()) {
			size_t old_size = size();
			T* tmp = new T[n];
			memmove(tmp, _start, old_size * sizeof(T));//第三参数为移动的空间字节数
			delete[] _start;
			_start = tmp;
			//_finish = _start + size();
			//若这时调用size(),由于_start已经更新为新地址,而此时的_finish还指向原来的就空间,
			//将会导致size()结果出错,进而导致此处_finish的更新出错
			//因此使用变量old_size,保存原本的size长度,再与已经更新的_start相加来得到更新后的_finish
			_finish = _start + old_size;
			_end_of_storage = _start + n;
		}	
	}

此时还有一个比较难以发现的缺陷,实例:

当我们使用string来测试扩容

可以看到代码运行错误了,内置类型并不会出现这种错误

这是因为:

string中我们知道,成员变量中有指针类型;而此时,我们++reserve中使用的使memmove来复制数据,而memmove的拷贝是以字节为单位的浅拷贝++。

浅拷贝将导致拷贝后的指针仍然指向原本的位置,随后delete[] _start;​将指向的空间释放,致使tmp变为野指针,进而_start也为野指针;且我们的测试代码放在一个函数中,在结束函数之前,对象会自动调用析构函数释放空间,这将导致_start指向的空间被析构两次,导致出现错误

所以拷贝数据的方法需要改变:

cpp 复制代码
	//II.使用for循环调用[]来进行数据的拷贝,此时即便为自定义类型,也会调用对应的[]和赋值重载,由此实现深拷贝
	void reserve(size_t n) {
		if (n > capacity()) {
			size_t old_size = size();
			T* tmp = new T[n];
			for (size_t i = 0; i < old_size; i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
			_start = tmp;
			//_finish = _start + size();
			//若这时调用size(),由于_start已经更新为新地址,而此时的_finish还指向原来的就空间,将会导致size()结果出错,进而导致此处_finish的更新出错
			//因此使用变量old_size,保存原本的size长度,再与已经更新的_start相加来得到更新后的_finish
			_finish = _start + old_size;
			_end_of_storage = _start + n;
		}
	}

此时即便为自定义类型,也会调用对应的[]和赋值重载,由此实现深拷贝,来让tmp指向构造出的新空间。

insert插入

对于vector容器,insert插入时,使用迭代器进行插入;

  • 由于insert插入牵扯到扩容机制,若调用insert时产生了扩容,调用对象的成员变量都已经指向新空间,而pos仍然指向旧空间;

迭代器失效的版本:

cpp 复制代码
	//I.
	//由于扩容机制的存在,扩容后成员变量全体都指向了新空间,而此处参数pos还指向旧空间
	void insert(iterator pos, const T& x) {
		assert(pos >= _start);
		assert(pos <= _finish);//等于_finish时为尾插
		//扩容
		if (_finish == _end_of_storage) {
			reserve(capacity() == 0 ? 4 : capacity() * 2);
		}
		//挪动数据
		iterator end = _finish - 1;
		//若pos迭代器没有更新,将导致迭代器失效
		//原因是:pos还指向原来旧空间的位置,而end是新空间_finish - 1的来的迭代器,大小关系无法确定
		while (end >= pos) {
			*(end + 1) = *end;
			--end;
		}
		*pos = x;
		++_finish;
	}
  • 此处为第一种迭代器失效:此时pos相当于一个野指针

解决办法为:

记录pos的相对位置,扩容完成后,再使用新_start来更新其指向

解决迭代器失效:

cpp 复制代码
	//II.
	//修正pos迭代器,防止迭代器失效
	void insert(iterator pos, const T& x) {
		assert(pos >= _start);
		assert(pos <= _finish);//等于_finish时为尾插
		//扩容
		if (_finish == _end_of_storage) {
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : capacity() * 2);
			//reserve扩容之后,由于存储空间的转变,pos迭代器失效,需要重置pos指向
			pos = _start + len;
		}
		//挪动数据
		iterator end = _finish - 1;
		//若pos迭代器没有更新,将导致迭代器失效
		//原因是:pos还指向原来旧空间的位置,而end是新空间_finish - 1的来的迭代器,大小关系无法确定
		while (end >= pos) {
			*(end + 1) = *end;
			--end;
		}
		*pos = x;
		++_finish;
	}

此时测试代码:

cpp 复制代码
void test_vector1() {
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	print_container(v);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	//查找数字x,在其位置上插入一个数字,并将原本该位置的数字*=10
	int x;
	cin >> x;
	//vector<int>::iterator
	auto p = std::find(v.begin(), v.end(), x);
	if (p != v.end()) {

		//insert返回插入数据的pos位置
		//p意义已经改变(迭代器失效)
		//原本p为指向x的迭代器,插入后,p已经指向了插入的数据的位置,此时p不能直接访问
		 v.insert(p, 40);
		(*p) *= 10;

		//p = v.insert(p, 40);
		//(*(p + 1)) *= 10;
	}
	print_container(v);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
}

输入x=2,此时结果为:

可见,我们原意为,在x=2的位置插入新数字,并将2*=10,但结果错误,原因为:

insert时产生了扩容,虽然在内部我们更新了形参pos的指向,但形参不影响实参,所以此时外部访问实参p,p仍然指向旧空间,此时迭代器失效,所以访问不到数字2的位置,导致修改失败;

  • 所以insert函数还需要将形参更新后的指向,传回给实参,因此给insert加上返回值,返回插入数据空间更新后的迭代器(地址);

返回形参pos更新后的内容:

cpp 复制代码
//III.
//使insert返回pos形参的新指向,用于更新参数pos在该函数体外部的实参的指向,防止外部的实参还指向旧空间,致使迭代器失效
//ps:不能通过将形参pos设置为引用来解决pos实参的更新问题,因为使用&,即(iterator& pos),将导致参数为v.begin()+1这一类时出错,因为此时参数为一个具有常性的临时变量;
//这个问题也不能通过给引用加const,即(const iterator& pos)来解决,因为这样pos在insert函数内就不能修改了
iterator insert(iterator pos, const T& x) {
	assert(pos >= _start);
	assert(pos <= _finish);//等于_finish时为尾插
	//扩容
	if (_finish == _end_of_storage) {
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		//reserve扩容之后,由于存储空间的转变,pos迭代器失效,需要重置pos指向
		pos = _start + len;
	}
	//挪动数据
	iterator end = _finish - 1;
	//若pos迭代器没有更新,将导致迭代器失效
	//原因是:pos还指向原来旧空间的位置,而end是新空间_finish - 1的来的迭代器,大小关系无法确定
	while (end >= pos) {
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
	return pos;
}

此时将将测试代码插入语句修改为p = v.insert(p, 40);(*(p + 1)) *= 10;再测试:

即可获得正确的结果

  • 此处也引出了第二种迭代器失效:

即,insert以后,即便insert返回更新了实参p,但其实际意义也已经改变,所以我们使用了(*(p + 1)) *= 10;​来修改x=2的值,使其*=10​;

而在我们自己实现的insert中,若没有产生扩容,此时p即便没有通过返回值更新,也可以通过直接(*(p + 1)) *= 10;来对x=2修改(因为没有异地扩容,还是指向原来的位置),此时并不会报错,但实际上p的含义也已经改变(原本实参p是用来指向x=2这个数据的);

但在std::vector中,不论insert有没有扩容,迭代器实际有没有指向错误,编译器都会默认直接报错,以防止访问失效的迭代器,此时就必须更新迭代器才能继续访问;

erase删除

由于vector类模板可以接受许多不同类型以及自定义类型的数据,所以erase也只能使用迭代器来删除数据,且不再像string中提供len参数一样来删除指定长度;

vector中erase默认删除单个元素,或者删除某个迭代器区间(左闭右开)的元素;

由于erase删除也涉及了数据的挪动,所以erase和insert一样,执行操作后,都默认将导致迭代器失效(VS直接报错);

即和insert同理,即便在我们模拟实现的erase中,不存在内存空间的修改时,直接访问更新前的迭代器,理论上可以做到同样的修改操作,但在VS的std::vector中,由于严格检查,即便迭代器本身没有错误,也会认为其迭代器失效,必须要更新才可以访问使用。

  • 单元素删除erase模拟代码
cpp 复制代码
	iterator erase(iterator pos) {
		assert(pos >= _start);
		assert(pos < _finish);
		
		iterator it = pos + 1;
		while (it < _finish) {
			*(it - 1) = *it;
			++it;
		}
		--_finish;
		//返回删除位置的迭代器,即被删除元素在删除前的下一个位置的迭代器
		return pos;
	}

erase返回值为被删除元素或区间,在被删除前的指向下一个位置元素的迭代器

  • 但由于erase会把删除位置的后续元素往前挪动,所以实际上pos位置的迭代器直接就是指向目标位置的
cpp 复制代码
void test_vector2() {
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	//删除所有的偶数
	print_container(v);
	auto it = v.begin();
	while (it != v.end())
	{
		if ((*it % 2) == 0) {
			it = v.erase(it);//模拟实现没有严格的检查,即便此处it没有更新,也不会报错
			//但若改为std::vector,此处若没有it来接收返回更新后的迭代器,就会直接报错
		}
		else {//因为erase会返回删除元素被删除前的下一位元素的迭代器,所以it只有在没有调用erase时才++
			it++;
		}
	}
	print_container(v);
}
  • 区间删除erase
cpp 复制代码
	iterator erase(iterator begin, iterator end) {
		assert(begin >= _start, begin < _finish);
		assert(end >= _start, end < _finish);
		size_t len = end - begin;
		iterator it = end;
		while (it < _finish) {
			*(it - len) = *it;
			++it;
		}
		_finish -= len;
		return begin;
	}

迭代器失效总结

VS环境下严格检测,出现迭代器失效都会默认报错;而在;linux的g++环境下,没有出现实际错误的迭代器就不会报错

  1. 相当于野指针

    对内存进行修改之后,都认为迭代器失效(reserve中有讲)

  2. 迭代器意义改变

    insert后,由于对象内部的内容改变,所以insert之后默认参数迭代器的意义已经改变,所以使用时都不要直接访

    问,VS下访问会直接报错(不管这个迭代器参数实际上有没有)

    erase同理,删除数据后,其 实参迭代器的意义也会改变,也会默认报错

  3. string中也有迭代器失效,只是在我们之前的使用中,大多insert和erase都直接使用通过下标来操作的重载,而很少使用迭代器的重载版本

resize

由于vector的模拟实现使用类模板,且不能确定vector存储数据的类型,所以此处参数val的缺省值不能给类似数字0,或者字符'\0'一类的数据,而是使用模板类型T类型的默认构造函数,通过构造函数来构造一个匿名对象用于缺省值。

cpp 复制代码
	void resize(size_t n, T val = T()) {
		if (n < size()) {
			_finish = _start + n;
		}
		else {
			reserve(n);
			while (_finish < _start + n) {
				*_finish = val;
				++_finish;
			}
		}
	}

但原本内置类型是不存在构造的,将导致如果T为此时为内置类型,将无法兼容这里的用法

由此,cpp中引入了内置类型的构造和析构函数的概念,内置类型也可以像自定义类型一样,使用默认构造来初始化变量int a(0);int b(1);

构造

前文中,我们没有书写构造函数,代码也可以正常运行,这是因为编译器在类没有构造函数时,会自动生成一个无参构造函数,且成员变量不论是否在构造函数中显示构造,都会自动进行初始化列表,所以前文中的三个迭代器都使用了默认值nullptr来进行了构造

默认构造和拷贝构造

cpp 复制代码
	vector() = default;//强制生成默认构造

	//直接使用范围for与尾插来拷贝数据
	vector<T>(const vector<T>& v) {
		reserve(v.size());
		for (auto i : v) {
			push_back(i);
		}
	}

迭代器区间构造

使用迭代器区间来构造

cpp 复制代码
	template <class InputIterator>
	vector(InputIterator first, InputIterator last) {
		while (first != last) {
			push_back(*first);
			++first;
		}
	}

此处使用函数模板来实现,这是因为,当传入的迭代器区间不是vector,但数据类型与调用构造的对象相同时,也使其可以构造出该对象

cpp 复制代码
list<int> i(5, 2);
vector<int> v1(i.begin(),i.end());//使用list的迭代器区间来构造vector

若不使用模板,而是直接使用vector内的iterator类型就会导致出错

n个参数val的构造

使用n个val值来构造

cpp 复制代码
	//n个val构造
	vector(size_t n, const T& val = T()) {
		reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

测试代码:

当只传参数n时,代码正常运行

此时出现了意外情况:

当我们传入参数val值时,却出现了错误,且报错信息显示错误点是在迭代器区间的构造函数中:

这说明此处函数调用就出现了错误,其原因为:

1.)我们知道,编译器会自动匹配与传参类型最相近的函数来调用;

2.)而上述情况中,由于编译器对于整型默认判断为int类型,由于我们的参数n为size_t类型,这里就会产生隐式类型转换,且val为int导致两个参数类型不同;

3.)而对于迭代器区间的构造函数模板,此处两个参数传值都为int类型其类型相同,模板函数中俩个参数类型也相同,可直接推导为int,所以对于编译器来说,迭代器区间的构造函数显然更符合所传参数的类型;

解决方法很简单,将参数n类型换位int即可,因为此时对于这种特殊情况来说,就有了符合两个参数都为int的函数,这样也不会去使用迭代器区间构造的函数模板了(当有符合条件的函数存在时,不会使用函数模板来推导出一个新函数)

cpp 复制代码
	vector(int n, const T& val = T()) {
		reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

赋值重载

赋值重载与string中一样,使用自定义的swap函数,来直接交换底层的迭代器指向;而赋值重载的参数则使用传值传参,过程中直接就会调用拷贝构造来进行深拷贝出一个新对象

cpp 复制代码
	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;
	}

模拟实现时的其他问题

typename语法

详见笔记 模板初阶:函数模板 模块

cpp 复制代码
template <class T>
void print_vector(const vector<T>& v) {
	//规定,没有实例化的类模板里取东西,编译器区分其实际意义
	//此处const_iterator,编译器就不能自动分别其为类型,还是一个静态成员变量
	//所以手动使用typename来告诉编译器,其为类型;或者使用auto直接使用v.begin()推导其类型
	//vector<T>::const_iterator it = v.begin();
	typename vector<T>::const_iterator it = v.begin();
	//auto it = v.begin();
	while (it != v.end()) {
		cout << *it << ' ';
		++it;
	}
}

内置类型的构造函数

为了兼容自定义类型在传参时使用默认构造来实现缺省值,cpp中将内置类型也加入了构造概念,可以像类一样,用使用构造的方法来定义内置类型

cpp 复制代码
	int a(0);
	int b(1);
	int c = int();//匿名对象
	double d = double();

类模板中的类名简写

在类模板中,使用类型名称时,可以直接用类名来替代如vector<T>​在类模板内部,就可以直接使用vector​来替代

例如:

cpp 复制代码
		vector(const vector& v) {
			reserve(v.size());
			for (auto i : v) {
				push_back(i);
			}
		}
		vector<T>(const vector<T>& v) {
			reserve(v.size());
			for (auto i : v) {
				push_back(i);
			}
		}

后记

本期对于vector的学习就到这里了,文中只对常用的接口进行了解释和模拟,有错误的地方感谢大家指出,我们下期再见~

本期专栏:C++_海盗猫鸥的博客-CSDN博客

个人主页:海盗猫鸥-CSDN博客

附:完整代码

vector.h

cpp 复制代码
#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
namespace hdmo {
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector<T>() = default;//强制生成默认构造

		vector(const vector& v) {
			reserve(v.size());
			for (auto i : v) {
				push_back(i);
			}
		}
		//vector<T>(const vector<T>& v) {
		//	reserve(v.size());
		//	for (auto i : v) {
		//		push_back(i);
		//	}
		//}
		//迭代器区间构造
		//vector(iterator first, iterator last) {
		//	while (first != last) {
		//		push_back(*first);
		//		++first;
		//	}
		//}
		template <class InputIterator>
		vector<T>(InputIterator first, InputIterator last) {
			while (first != last) {
				push_back(*first);
				++first;
			}
		}
		//n个val构造
		//vector(size_t n, const T& val = T()) {
		//	resize(n);
		//	//reserve(n);
		//	for (size_t i = 0; i < n; i++)
		//	{
		//		push_back(val);
		//	}
		//}
		vector<T>(int n, const T& val = T()) {
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		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<T>() {
			if (_start) {
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}
		//迭代器
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}

		//错误示范,[]返回应为对应位置的对象,而不是指针
		//iterator operator[](size_t pos) {
		//	assert(pos < size());
		//	return _start + pos;
		//}
		T& operator[](size_t pos) {
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const {
			assert(pos < size());
			return _start[pos];
		}
		//I.memmove移动数据,存在缺陷
		//这是因为,memmove函数是以字节为单位拷贝数据的,但对于自定义类型,存在指针时。就会出现浅拷贝的情况,导致memmove拷贝的指针和原指针指向同一个地址,导致delete[]释放空间后,出现野指针
		//void reserve(size_t n) {
		//	if (n > capacity()) {
		//		size_t old_size = size();
		//		T* tmp = new T[n];
		//		memmove(tmp, _start, old_size * sizeof(T));//第三参数为移动的空间字节数
		//		delete[] _start;
		//		_start = tmp;
		//		//_finish = _start + size();
		//		//若这时调用size(),由于_start已经更新为新地址,而此时的_finish还指向原来的就空间,将会导致size()结果出错,进而导致此处_finish的更新出错
		//		//因此使用变量old_size,保存原本的size长度,再与已经更新的_start相加来得到更新后的_finish
		//		_finish = _start + old_size;
		//		_end_of_storage = _start + n;
		//	}
		//}
		//II.使用for循环调用[]来进行数据的拷贝,此时即便为自定义类型,也会调用对应的[]和赋值重载,由此实现深拷贝
		void reserve(size_t n) {
			if (n > capacity()) {
				size_t old_size = size();
				T* tmp = new T[n];
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
				_start = tmp;
				//_finish = _start + size();
				//若这时调用size(),由于_start已经更新为新地址,而此时的_finish还指向原来的就空间,将会导致size()结果出错,进而导致此处_finish的更新出错
				//因此使用变量old_size,保存原本的size长度,再与已经更新的_start相加来得到更新后的_finish
				_finish = _start + old_size;
				_end_of_storage = _start + n;
			}
		}
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		void clear(){
			_finish = _start;
		}
		bool empty() const{
			return (size() == 0);
		}
		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;
		}
		/*
		//I.
		//由于扩容机制的存在,扩容后成员变量全体都指向了新空间,而此处参数pos还指向旧空间
		void insert(iterator pos, const T& x) {
			assert(pos >= _start);
			assert(pos <= _finish);//等于_finish时为尾插
			//扩容
			if (_finish == _end_of_storage) {
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			//挪动数据
			iterator end = _finish - 1;
			//若pos迭代器没有更新,将导致迭代器失效
			//原因是:pos还指向原来旧空间的位置,而end是新空间_finish - 1的来的迭代器,大小关系无法确定
			while (end >= pos) {
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
		}

		//II.
		//修正pos迭代器,防止迭代器失效
		void insert(iterator pos, const T& x) {
			assert(pos >= _start);
			assert(pos <= _finish);//等于_finish时为尾插
			//扩容
			if (_finish == _end_of_storage) {
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//reserve扩容之后,由于存储空间的转变,pos迭代器失效,需要重置pos指向
				pos = _start + len;
			}
			//挪动数据
			iterator end = _finish - 1;
			//若pos迭代器没有更新,将导致迭代器失效
			//原因是:pos还指向原来旧空间的位置,而end是新空间_finish - 1的来的迭代器,大小关系无法确定
			while (end >= pos) {
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
		}
		*/
		//III.
		//使insert返回pos形参的新指向,用于更新参数pos在该函数体外部的实参的指向,防止外部的实参还指向旧空间,致使迭代器失效
		//ps:不能通过将形参pos设置为引用来解决pos实参的更新问题,因为使用&,即(iterator& pos),将导致参数为v.begin()+1这一类时出错,因为此时参数为一个具有常性的临时变量;
		//这个问题也不能通过给引用加const,即(const iterator& pos)来解决,因为这样pos在insert函数内就不能修改了
		iterator insert(iterator pos, const T& x) {
			assert(pos >= _start);
			assert(pos <= _finish);//等于_finish时为尾插
			//扩容
			if (_finish == _end_of_storage) {
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//reserve扩容之后,由于存储空间的转变,pos迭代器失效,需要重置pos指向
				pos = _start + len;
			}
			//挪动数据
			iterator end = _finish - 1;
			//若pos迭代器没有更新,将导致迭代器失效
			//原因是:pos还指向原来旧空间的位置,而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 < _finish) {
				*(it - 1) = *it;
				++it;
			}
			--_finish;
			return pos;
		}
		iterator erase(iterator begin, iterator end) {
			assert(begin >= _start, begin < _finish);
			assert(end >= _start, end < _finish);
			size_t len = end - begin;
			iterator it = end;
			while (it < _finish) {
				*(it - len) = *it;
				++it;
			}
			_finish -= len;
			return begin;
		}
		void resize(size_t n, T val = T()) {
			if (n < size()) {
				_finish = _start + n;
			}
			else {
				reserve(n);
				while (_finish < _start + n) {
					*_finish = val;
					++_finish;
				}
			}
		}


	private:
		iterator _start = nullptr;//有效空间开始位置的指针
		iterator _finish = nullptr;//有效空间结束位置的指针
		iterator _end_of_storage = nullptr;//容量最后位置的指针
	};
}

测试代码:

cpp 复制代码
#include"vector.h"
#include<list>
#include<string>
using std::list;
using std::string;
using namespace hdmo;
using std::cout; 
using std::cin;
using std::endl;
template <class T>
void print_vector(const vector<T>& v) {
	//规定,没有实例化的类模板里取东西,编译器区分其实际意义
	//此处const_iterator,编译器就不能自动分别其为类型,还是一个静态成员变量
	//所以手动使用typename来告诉编译器,其为类型;或者使用auto直接使用v.begin()推导其类型
	//vector<T>::const_iterator it = v.begin();
	typename vector<T>::const_iterator it = v.begin();
	//auto it = v.begin();
	while (it != v.end()) {
		cout << *it << ' ';
		++it;
	}

	//for (size_t i = 0; i < v.size(); i++)
	//{
	//	cout << v[i] << ' ';
	//}
	//for (auto i : v) {
	//	cout << i << ' ';
	//}
	cout << endl;
}

template <class Container>
void print_container(const Container& v) {
	auto it = v.begin();
	while (it != v.end()) {
		cout << *it << ' ';
		++it;
	}
	cout << endl;
}

void test_vector1() {

	std::vector<int> v;

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


	print_container(v);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	//cout << v.empty();
	//v.pop_back();
	//vector<int>::iterator it1 = v.insert(v.begin(), 8);
	//vector<int>::iterator it2 = v.insert(v.end(), 0);
	//cout << it1 - v.begin() << endl;
	//cout << it2 - v.begin() << endl;
	//print_vector(v);


	//查找数字x,在其位置上插入一个数字,并将原本该位置的数字*=10
	int x;
	cin >> x;
	//vector<int>::iterator
	auto p = std::find(v.begin(), v.end(), x);
	if (p != v.end()) {

		//insert返回插入数据的pos位置
		//p意义已经改变(迭代器失效)
		//原本p为指向x的迭代器,插入后,p已经指向了插入的数据的位置,此时p意义改变
		// v.insert(p, 40);
		//(*p) *= 10;

		v.insert(p, 40);
		(*(p + 1)) *= 10;
	}
	print_container(v);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
}

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

	//删除所有的偶数
	//print_container(v);
	//auto it = v.begin();
	//while (it != v.end())
	//{
	//	if ((*it % 2) == 0) {
	//		it = v.erase(it);
	//	}
	//	else {
	//		it++;
	//	}
	//}
	//print_container(v);
	//cout << v.size() << endl;
	//cout << v.capacity() << endl;

	//v.erase(v.begin(), v.end());
	////v.erase(v.end() - 1, v.end());
	//print_container(v);
	//cout << v.size() << endl;
	//cout << v.capacity() << endl;

	v.resize(5, 0);
	v.reserve(20);
	print_container(v);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	v.resize(10, 3);
	v.reserve(20);
	print_container(v);
	cout << v.size() << endl;
	cout << v.capacity() << endl;

	v.resize(25, 7);
	v.reserve(20);
	print_container(v);
	cout << v.size() << endl;
	cout << v.capacity() << endl;
}
void test_vector3() {
	//int a(0);
	//int b(1);
	//int c = int();//匿名对象
	//cout << a << b << c << endl;
	////double d = double();
	vector<int> v1;
	v1.resize(5, 1);
	vector<int> v2(v1);
	vector<int> v3;
	v3 = v2;
	print_container(v2);
	print_container(v3);
}
void test_vector4() {
	
	//list<int> i(5, 2);
	//vector<int> v2;
	//v2.push_back(1);
	//v2.push_back(2);
	//v2.push_back(3);
	//v2.push_back(4);

	//vector<int> v1(i.begin(),i.end());
	//vector<int> v1(v2.begin(),v2.end() - 1);
	//print_container(v1);

	//vector<int> v3(10);
	//print_container(v3);

	vector<char> v4(10,'x');
	print_container(v4);
	v4.push_back('y');
	print_container(v4);
	
	//vector<string> v5(10,"xxx");
	//print_container(v5);
	//v5.push_back("yyy");
	//print_container(v5);
}
int main() {
	test_vector4();
	return 0;
}
相关推荐
wjs20242 小时前
CSS 下拉菜单:设计与实践指南
开发语言
天道有情战天下2 小时前
Lua使用
开发语言·lua
liulilittle2 小时前
CPU亲和性深度实践:从基础原理到Intel大小核架构优化
c++·线程·进程·cpu·量化·高频·亲核性
曲鸟2 小时前
用Python和MediaPipe实现实时手指识别
开发语言·python
weixin_307779133 小时前
破解遗留数据集成难题:基于AWS Glue的无服务器ETL实践
开发语言·云原生·云计算·etl·aws
毕设源码-钟学长3 小时前
【开题答辩全过程】以 基于Java的相机专卖网的设计与实现为例,包含答辩的问题和答案
java·开发语言
Albert Edison3 小时前
【项目设计】基于正倒排索引的Boost搜索引擎
linux·网络·c++·后端·http·搜索引擎
简单点好不好3 小时前
大恒相机-mono12-python示例程序
开发语言·python·数码相机
liu****3 小时前
12.线程同步与互斥
linux·数据结构·c++·算法·1024程序员节