vector的实现

注意:vector是个模板,所以不能声明和定义分离,所以实现它用一个vector.h文件和一个test.cpp文件就行了。

一.成员变量

由三个迭代器 构成:①._start 指向顺序表首个元素

②._finish 指向最后一个数据的后一个位置

③._end_of_capacity 指向开辟空间的最后一个空间的下一个元素的位置。

说是迭代器,其实是typedef指针的结果。所以本质上还是指针。

二.reserve

遭遇的大坑

此时_start已指向开辟好的新空间,而size()里的_finish指向扩容前的空间,而这个空间被delete了,所以为空。那么这样减出来的size也为空,绝对不是原空间的size。所以需要在开辟新空间之前,用一个新变量old_size储存原size。

三.begin(),end()

四.print_vector

还可以这么解决:

五.insert

复制代码
	void insert(iterator pos, const T& x)
	{
		assert(pos >= _start);
		assert(pos <= _finish);
		//扩容
		if (_finish == _end_of_capacity)
		{
			
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : capacity() * 2);
			pos = _start + len;//更新pos
		}
		iterator end = _finish - 1;
		while (end >= pos)
		{
			*(end + 1) = *end;
			end--;
		}
		*pos = x;
		++_finish;//不要忘了_finish!!
	}

六.erase

代码:

复制代码
	void erase(iterator pos)
	{
		assert(pos >= _start);
		assert(pos < _finish);
		iterator it = pos + 1;
		while (it != end())
		{
			*(it - 1) = *(it);
			++it;
		}
		--_finish;
	}

迭代器失效

几乎所有容器在erase以后都会迭代器失效,而insert要看具体情况。迭代器失效以后不要再进行访问,如果一定要访问,需要更新一下迭代器。

insert

情况1.野指针(扩容)

对于需要扩容的情况,pos指向的是原空间,并未移到新空间上,原空间释放后pos仍旧指向那片空间,就变成了野指针。

解决方法:

用一个长度确认相对位置

然后更新pos

以上解决方法在函数内是对的,在扩容以后,在新空间里更新pos,但由于是传值调用,pos是个形参,是个临时拷贝,在函数里确实是更新(改变)了;但在出了函数作用域以后,它的值还是原先的值。所以这么做在函数内部用来执行插入操作是没有问题的,但出了函数就请一定不要再访问位置参数。

情况2.位置意义改变(没有扩容)

如果在insert以后仍旧对p进行访问(利用p去修改空间里的值),p指向它不该指的地方,那么p以后的数据全部乱套了,逻辑上不再对应原本期望的位置。

erase

当用erase实现删除偶数时,在执行erase后,进行it的++(erase以后访问pos),那么此时就会出现迭代器失效。it仍旧指向它不该指的地方。

解决方案:

写erase时设置一个返回值,返回一个迭代器,指向被删除元素的下一个位置。此时更新it,即

it = v.erase(it),再访问(it++)

七.resize

功能:开空间以及用val进行初始化。

1.关于缺省值的探讨

自定义类型的数据给缺省值需要自己类型的数据,则调用默认构造函数构造一个匿名对象,这个对象就是用默认构造函数的缺省值构造的。

内置类型的数据没有默认构造,但非要用上面的方式给缺省值,系统会自动处理数据,int 类型的会被初始化为0,指针类型的会被初始为nullptr。

2.空间的几种情况

①.n < size

删除n以后的数据

②.n >= size

无论size有没有大于_capacity,统统扩容。然后插入数据。

八.operator=,clear

传统派:

在赋值以前,一定要记得把对象里原来的内容给释放(clear)掉,不然会造成空间的泄漏。clear接口能够做到**将空间内的内容清理掉,但不释放空间。**被清理以后的空间可以用来存储要赋值的内容。

复制代码
	vector<T>& operator=(const vector<T>& v)
	{//传统派
		if (this != &v)
		{
			clear();
			for (auto& e : v)
			{
				push_back(e);
			}
			return *this;
		}
	}

现代派:

实现一个swap函数,交换一下v与*this就行了

复制代码
	void swap(const vector<T>& v)
	{
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_end_of_capacity, v._end_of_capacity);
	}	

vector<T>& operator=(const vector<T>& v)
	{//现代派
		swap(v);
		return *this;
	}

自己遭遇的问题

1.对于没有把测试函数包含进命名域的情况,在测试时创建一个vector对象的时候记得带上自己的命名域,不然系统会默认这个vector是std里面的。同时访问命名域里的函数要带上命名域。

2.测试用例的main函数不要放在命名域内。

3.范围for的底层是函数,所以e那里相当于是形参,所以要传引用,避免传值调用拷贝构造带来的开销。

4.在类里面可以用类名替代类类型,类外不可以。

5.在测试用n个val构造的构造函数时,假如测试用例是10个1,就会报下面的错误:

我明明测试的是vector(size_t n,const T& val),为什么会在区间构造函数处报错呢??

原因在于系统把10和1两个实参识别成了两个迭代器,然后调用的是区间构造函数,而非vector(size_t n,const T& val), 进了区间构造函数以后,会遇到first的解引用,然而first对应的参数10是个int类型的数据,是无法解引用的

那为啥就非得识别成迭代器,走区间构造呢?

原因在于系统也是有惰性的,它会选择轻松的那个。用n个val构造时,还需要将函数模板实例化为int类型的函数以后,才能调用。而对于区间构造,就可以直接把值传上去,就不用那么费劲。

解决方案:

相关推荐
纽扣6674 小时前
【算法进阶之路】链表进阶:删除、合并、回文与排序全解析
数据结构·算法·链表
xvhao20137 小时前
单源、多源最短路
数据结构·c++·算法·深度优先·动态规划·图论·图搜索算法
m0_629494739 小时前
LeetCode 热题 100-----17.缺失的第一个正数
数据结构·算法·leetcode
hnjzsyjyj10 小时前
洛谷 P1443:马的遍历 ← BFS
数据结构·bfs
做时间的朋友。10 小时前
精准核酸检测
java·数据结构·算法
如君愿10 小时前
考研复习 Day28 | 习题--计算机网络第四章(网络层 中)、数据结构(树与二叉树 下)
数据结构·计算机网络·考研·课后习题·记录考研
江南十四行11 小时前
排序算法进阶:直接插入排序(简单排序)与希尔排序
数据结构·算法·排序算法
洛水水11 小时前
【Redis入门】一篇详解Redis五大数据结构
数据结构·数据库·redis
CoderCodingNo11 小时前
【CSP】CSP-J 2021真题 | 插入排序 luogu-P7910 (适合GESP四-六级及以上考生练习)
数据结构·算法·排序算法
努力努力再努力wz12 小时前
【MySQL进阶系列】一文打通事务机制:从锁、Undo Log 到 MVCC 与隔离级别
c语言·数据结构·数据库·c++·mysql·算法·github