vector的模拟实现

1.vector的简介

1.

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

2.

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

3.

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

2.vector的框架

在了解了vector的大致功能后,就要开始实现vector的功能,首先去看了一下库中vector的源代码,可以发现,成员变量是由三个指针组成。

分别命名为:

start:整个数组起始位置的指针

finish:最后一个数据的下一个位置的指针

endofstorage:指向内存最后面的下一个位置。

3.成员函数的实现

1.基本函数的实现

首先实现几个最基础的功能,好让我们的vector可以正常的运行。

1.构造函数
vector()
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr) {

}

构造函数需要将三个成员变量初始化一下,防止可能出现的bug,因为编译器不一定会默认将内置类型初始化,这是标准未定义的。

2.析构函数
	~vector() {
		delete[] _start;
		_start = _finish = _endofstorage = nullptr;
	}

析构函数直接用起始的指针进行释放就可以了,之后将三个指针置空(可以不置空,这里为了规整就置空了)。

3.capacity和size
size_t capacity()const {
	return _endofstorage - _start;
}
size_t size() const{
	return _finish - _start;
}

要求出两个指针中的数据个数,如果是一闭一开的区间的话(类似10和1,一闭一开的区间),两个指针相减就可以直接得到。endofstorage和finish都是开区间,start是闭区间,所以直接返回他们的插值,就是数据个数和开辟的空间大小了。

4.reserve
	void reserve(size_t cp) {
		if (cp > capacity()) {
			size_t sz = size();
			T* tmp = new T[cp];
			if (_start) {
				for (int i = 0; i < sz; i++) {
					tmp[i] = _start[i];
				}
				delete[] _start;
			}
			_start = tmp;
			_finish = _start + sz;
			_endofstorage = _start + cp;
		}
	}

reserve能够提前申请空间,在之后的成员函数会大量复用到,这里就先实现了

思路:首先比较需要的空间是否大于原来的空间,如果不大于,就不扩容,直接返回。

需要保存size的值,扩容之后访问size会因为地址的变化出现bug,申请新的动态内存空间,并进行判断,start不为空说明原本空间不为空(start为空,说明没有需要拷贝的地址),需要将数据拷贝到新的地址去,需要注意的是这里的拷贝必须要调用赋值,因为vector里面可能保存其他的自定义类型,赋值运算符会调用自定义类型的赋值运算,不会出现浅拷贝的问题。

最后,将start,finish,endofstorage修改为新的地址。

5.push_back
		void push_back(const T& val) {
			if (_finish == _endofstorage) {
                
				reserve((capacity() == 0) ? 4 : capacity() * 2) ;

			}
			*_finish = val;
			_finish++;
		}

思路:

要插入新的数据最先需要检查的就是空间是否足够,不足就进行扩容。这边用finish和endodstorage进行比较,相等,说明空间已经满了,需要进行扩容。

这边每次的扩容都是在原来的基础乘2倍,这是比较合理的大小。

之前已经实现了reserve函数,所以直接复用就可以,传过去的数需要进行三目运算符的比较,因为原本数据大小可能为0,0乘以2还是0,所以当内存大小为0时,传一个4进行扩容,不为0时才传原本内存大小的2倍。

将finish所在位置进行赋值,++finish。

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

思路:

要先和数据个数进行比较,如果不小于就说明越界了,直接报一个警告就行。

直接用start进行下标访问返回。

测试
	void test1() {
	vector<string> v;
	v.push_back("abc");
	v.push_back("123");
	v.push_back("456");
	v.push_back("789");
	v.push_back("end");

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

运行

这里用了string来作为vector的类型,如果用mencpy之类的直接拷贝,会有析构两次的风险。

2.迭代器的实现

1.重定义迭代器
typedef T* iterator;
const typedef T* const_iterator;

迭代器一般都分为可修改和不可修改,所以这里重定义也重定义了两个。

2.end和begin
iterator end() {
	return _finish;
}
iterator begin() {
	return _start;
}
const_iterator end()const {
	return _finish;
}
const_iterator begin()const {
	return _start;
}

begin返回start,end返回finish。也是分为可修改和不可修改。

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

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

}

auto的底层实现就是靠迭代器的,使用auto能够通过就说明了迭代器已经完成了。

运行

3.常用函数的实现

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

思路:

直接使用库里的函数swap交换三个成员变量。

2.resize
void resize(size_t sz,T val = T()) {
	if (sz <= size()) {
		_finish = _start + sz;
	}
	else {
		reserve(sz);
		while (_finish != _endofstorage) {
			*_finish = val;
			_finish++;
		}
	}
}

resize函数中,val的缺省值是默认构造,因为vector支持内置类型和自定义类型,所以缺省值不能给0这样固定的数,需要去调用这种类型的构造参数,为了支持这个功能,在c++中内置类型也是有构造函数的。

也就是说可以这样赋值

int i = int(1);

思路:

第一步比较所需的数据个数和目前的数据个数。

若不大于,则直接修改finish即可。

若大于,无需判断内存是否足够,直接复用reserve即可,reserve会进行检查,之后填充数据。

3.insert
void insert(iterator pos,const T& val) {
	assert(pos >= _start);
	assert(pos <= _finish);
	
	if (_finish == _endofstorage) {
		 size_t len = pos - _start;
		reserve((capacity() == 0) ? 4 : capacity() * 2);
		pos = _start + len;
	}
	iterator end = _finish-1;
	while (pos <= end) {
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	++_finish;
}

insert的实现是传一个迭代器的位置pos和一个需要插入的数据val过来,在pos之前插入val。

思路:

第一步检查pos是否在可插入的范围内,即在start到finish这部分地址之中,不存在即插入违规,直接报错。

第二步检查内存是否足够,如果finish和endofstorage相同,说明,需要扩容。扩容会产生一个问题,原本的pos位置也会被释放,所以需要先保存pos和start的差值,才能在扩容后找到应该插入数据的位置。

第三步,开始向后移动数据,直到pos位置的数据向后移位结束。在pos位置插入val,++finish。

4.erase
iterator erase(iterator pos) {
	assert(pos >= _start);
	assert(pos < _finish);
	iterator end = pos+1;
	while (end < _finish) {
		*(end-1) = *end;
		end++;
	}
	--_finish;
	return pos;
}

erase的实现是传一个迭代器的位置过来,删除pos位置的数据。

思路:

第一步检查pos是否在可删除的范围内,即在start到(finish-1)这部分地址之中,不存在即删除违规,直接报错。

第二步,开始向前移动数据,直到pos的后一位数据移到pos位置向前移位结束,--finish。

5.拷贝构造函数
vector(const vector<T>& v) 
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr){
	reserve(v.capacity());
	for (auto& e : v) {
		push_back(e);
	}
}

思路:

先在初始化列表那边,全部置空,因为编译器对内置类型不一定处理,尾插的时候就会出问题,也会有其他的bug。

之后先提前开好空间,然后从被拷贝的对象中去数据进行尾插。

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

思路:

直接使用swap和拷贝过来的tmp进行交换。

测试
		void test3() {
			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> v1;
			v1 = v;
			for (auto e : v) {
				cout << e << ' ';
			}
			cout << endl;
			for (auto e : v1) {
				cout << e << ' ';
			}
			cout << endl;
			v.insert(v.begin(), 0);
			v.insert(v.end(), 6);
			for (auto e : v) {
				cout << e << ' ';
			}
			cout << endl;
			v.erase(v.begin());
			for (auto e : v) {
				cout << e << ' ';
			}
			cout << endl;
			v1.resize(9, 1);
			for (auto e : v1) {
				cout << e << ' ';
			}
			cout << endl;


		}

将新实现的函数都用一下

没有问题。

但是insert和erase使用的时候可能会有迭代器丢失的问题,所以一般来说insert和erase之后这个迭代器就能使用了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值