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类型的函数以后,才能调用。而对于区间构造,就可以直接把值传上去,就不用那么费劲。

解决方案:

相关推荐
_饭团2 小时前
C 语言数据存储全解析:原反补码、大小端与 IEEE 754 浮点数
c语言·数据结构·算法·leetcode·面试·蓝桥杯·学习方法
j_xxx404_2 小时前
力扣--分治(归并排序)算法题II:计算右侧小于当前元素的个数,翻转对(无痛通关困难题)
开发语言·数据结构·c++·算法·leetcode
阿梅要做最快乐的仔2 小时前
链表环问题:快慢指针的经典应用
数据结构·链表
Sylvia-girl2 小时前
删除有序数组中的重复项
数据结构·算法
Wave8452 小时前
数据结构—栈与队列
数据结构
垫脚摸太阳2 小时前
二分查找经典算法题--数的范围
数据结构·算法
噜啦噜啦嘞好2 小时前
算法篇:二分查找
数据结构·c++·算法·leetcode
季明洵2 小时前
回溯介绍及实战
java·数据结构·算法·leetcode·回溯
minji...2 小时前
Linux 进程间通信(一)进程间通信与匿名管道
linux·运维·服务器·数据结构·数据库·c++