当前位置: 首页 > news >正文

vector的实现

目录

1.vector的底层

2.vector构造函数的实现

①构造函数

②拷贝构造

3.访问函数实现

3.1迭代器iterator

3.2下标+[]访问

4.析构函数和计算size、capacity、swap简单函数的实现

①析构函数: 

②计算size:

③计算capacity:

④swap函数

5. reserve(提前开空间函数实现)

6.尾插尾删函数

7.insert指定位置插入实现

8.设计程序验证

源代码(vector.h)


1.vector的底层

我们在之前的文章顺序表的实现中了解到:

其底层存在一个指向动态开辟数组的指针、一个表示元素个数的size和一个表示顺序表容量大小的capacity。

而vector使用了一个start、一个finish和一个end_of_storage,虽然二者的表示形式不同,实则都有相同的道理,如图:

现在我们就依据此来实现一下vector。

2.vector构造函数的实现

①构造函数

class vector
{
public:vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}
private://全缺省为空iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;
}

②拷贝构造

//①传统写法//vector(const vector<T>& v)//{//	_start = new T[v.capacity()];//先开空间//	memcpy(_start, v._start, v.size() * sizeof(T));//	_finish = _start + v.size();//	_endofstorage = _start + v.capacity();//}//②现代写法vector(const vector<T>& v){reserve(v.capacity());//首先开一个与旧空间一样的新空间for (const auto& e : v)//添加引用&,防止深拷贝{push_back(e);//复用push_back函数}}

③析构函数 

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

3.访问函数实现

3.1迭代器iterator

①普通迭代器可读可写:

//普通迭代器 可读可写
typedef T* iterator;iterator begin(){return _start;}iterator end(){return _finish;}

②const迭代器可读不可写:

//const迭代器 可读不可写
typedef const T* const_iterator;
const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}

3.2下标+[]访问

①普通函数,可读可写:

		//①可读可写T& operator[](size_t pos){assert(pos < size());//检查是否越界return _start[pos];//返回pos位置字符的引用,既可读又可写}

②const函数,可读不可写:

        //②可读不可写const T& operator[](size_t pos) const {assert(pos < size());//检查是否越界return _start[pos];//返回pos位置字符的引用,既可读又可写}

4.析构函数和计算size、capacity、swap简单函数的实现

①析构函数: 

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

②计算size:

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

③计算capacity:

size_t capacity() const{return _endofstorage - _start;}

④swap函数

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

有了swap函数,我们就可以复用swap函数实现赋值构造函数:

//赋值vector<T>& operator=(vector<T> v)//注意这里不能添加&引用,如v3=v1//无&引用表示v是v1的临时拷贝,v3再拷贝v,出了作用域v就会被销毁,不会改变v1//而加上&引用则直接表示v1与v3交换了,即可以认为这里实现了swap函数的功能{swap(v);//复用swap函数return *this; }

5. reserve(提前开空间函数实现)

void reserve(size_t n){if (n > capacity())//如果原容量<需要的新空间,就开辟新空间{T* tmp = new T[n];//开辟新空间if (_start)//判断原vector是否为空{//如果原vector不为空,就将从_start位置开始的n*sizeof(T)个数据拷贝到新空间tmp里memcpy(tmp, _start, n * sizeof(T));delete[] _start;//释放旧空间}//再指向新空间_start = tmp;_finish = _start + size();_endofstorage = _start + capacity();}

这样写程序出现了bug:

这是因为

因为size()=_finish - _start;
 所以_finish=_start+_finish-_start=_finish
可是这里的_start已经指向了新空间,而_finish指向的还是旧空间
一个空间的末尾 - 另一个空间的开头 ==== 错误!
_endofstorage = _start + capacity();
同样的道理,capacity()还是旧空间,_start是新空间 也= 错误!
所以这里正确的做法应该是_endofstorage = _start + n;

正确代码:

void reserve(size_t n){if (n > capacity())//如果原容量<需要的新空间,就开辟新空间{size_t old = size();//在扩容之前将size()先保存一下T* tmp = new T[n];//开辟新空间if (_start)//判断原vector是否为空{//如果原vector不为空,就将从_start位置开始的old*sizeof(T)个数据拷贝到新空间tmp里memcpy(tmp, _start, old * sizeof(T));delete[] _start;//释放旧空间}//再指向新空间_start = tmp;_finish = _start + old;_endofstorage = _start + n;}}

完成了reserve函数,我们就可以复用reserve函数实现拷贝构造的现代写法:

//②现代写法vector(const vector<T>& v){reserve(v.capacity());//首先开一个与旧空间一样的新空间for (const auto& e : v)//添加引用&,防止深拷贝{push_back(e);//复用push_back函数}}

6.尾插尾删函数

        //尾插函数void push_back(const T& x){if (_finish == _endofstorage)//判断vector有没有满{size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;//如果满了就扩容reserve(newcapacity);}//扩容之后赋值*_finish = x;++_finish; }//尾删函数void pop_back(){assert(size() > 0);--_finish;}

7.insert指定位置插入实现

void insert(iterator pos, const T& x)//在pos位置之前插入x{assert(pos <= _finish && pos >= _start);//检查pos是否合法//检查vector是否充满if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容}//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置memmove(pos + 1, pos, sizeof(T) * (_finish - pos));*pos = x;//挪动之后再把x放到pos位置++_finish;}

这段代码在实际运行时有时会出现错误,这是为什么呢?

原因是:

reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
观察reserve函数:不扩容还好,当发生扩容时,reserve会销毁旧空间
此时_start指向新空间,pos仍然指向旧空间,pos在扩容之后失效了
因此这里的解决办法就是计算偏移量,更新一下pos的值

正确代码:

void insert(iterator pos, const T& x)//在pos位置之前插入x{assert(pos <= _finish && pos >= _start);//检查pos是否合法//检查vector是否充满if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容pos = _start + len;}//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置memmove(pos + 1, pos, sizeof(T) * (_finish - pos));*pos = x;//挪动之后再把x放到pos位置++_finish;}

8.设计程序验证

void test_vector(){vector <int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//迭代器访问vector <int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;//范围for访问for (auto e : v1){cout << e << " ";}cout << endl;//下标+[]访问for (size_t i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;}

源代码(vector.h)

#pragma once#include <iostream>
#include <assert.h>using namespace std;namespace xxk
{template<class T>class vector{public:typedef T* iterator;//普通迭代器 可读可写typedef const T* const_iterator;//const迭代器 可读不可写//构造函数vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}//拷贝构造//①传统写法//vector(const vector<T>& v)//{//	_start = new T[v.capacity()];//先开空间//	memcpy(_start, v._start, v.size() * sizeof(T));//	_finish = _start + v.size();//	_endofstorage = _start + v.capacity();//}//②现代写法vector(const vector<T>& v){reserve(v.capacity());//首先开一个与旧空间一样的新空间for (const auto& e : v)//添加引用&,防止深拷贝{push_back(e);//复用push_back函数}}//赋值vector<T>& operator=(vector<T> v)//注意这里不能添加&引用,如v3=v1//无&引用表示v是v1的临时拷贝,v3再拷贝v,出了作用域v就会被销毁,不会改变v1//而加上&引用则直接表示v1与v3交换了,即可以认为这里实现了swap函数的功能{swap(v);//复用swap函数return *this; }//析构函数~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}//迭代器begin()和end()实现//普通迭代器 可读可写iterator begin(){return _start;}iterator end(){return _finish;}//const迭代器 可读不可写const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//size capacity函数size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}//下标+[]访问函数的实现//①可读可写T& operator[](size_t pos){assert(pos < size());//检查是否越界return _start[pos];//返回pos位置字符的引用,既可读又可写}//②可读不可写const T& operator[](size_t pos) const {assert(pos < size());//检查是否越界return _start[pos];//返回pos位置字符的引用,既可读又可写}//reserve扩容函数//error://void reserve(size_t n)//{//	if (n > capacity())//如果原容量<需要的新空间,就开辟新空间//	{//		T* tmp = new T[n];//开辟新空间//		if (_start)//判断原vector是否为空//		{//			//如果原vector不为空,就将从_start位置开始的n*sizeof(T)个数据拷贝到新空间tmp里//			memcpy(tmp, _start, n * sizeof(T));//			delete[] _start;//释放旧空间//		}//		//再指向新空间//		_start = tmp;//		_finish = _start + size();//		//因为size()=_finish - _start;//		//所以_finish=_start+_finish-_start=_finish//		//可是这里的_start已经指向了新空间,而_finish指向的还是旧空间//		//一个空间的末尾 - 另一个空间的开头 ==== 错误!//		_endofstorage = _start + capacity();//		//同样的道理,capacity()还是旧空间,_start是新空间 也= 错误!//		//所以这里正确的做法应该是_endofstorage = _start + n;//	}//}void reserve(size_t n){if (n > capacity())//如果原容量<需要的新空间,就开辟新空间{size_t old = size();//在扩容之前将size()先保存一下T* tmp = new T[n];//开辟新空间if (_start)//判断原vector是否为空{//如果原vector不为空,就将从_start位置开始的old*sizeof(T)个数据拷贝到新空间tmp里memcpy(tmp, _start, old * sizeof(T));delete[] _start;//释放旧空间}//再指向新空间_start = tmp;_finish = _start + old;_endofstorage = _start + n;}}//resize函数//尾插函数void push_back(const T& x){if (_finish == _endofstorage)//判断vector有没有满{size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;//如果满了就扩容reserve(newcapacity);}//扩容之后赋值*_finish = x;++_finish; }//尾删函数void pop_back(){assert(size() > 0);--_finish;}//指定位置插入(insert)函数//error://void insert(iterator pos, const T& x)//在pos位置之前插入x//{//	assert(pos <= _finish && pos >= _start);//检查pos是否合法//	//检查vector是否充满//	if (_finish == _endofstorage)//	{//reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容观察reserve函数:不扩容还好,当发生扩容时,reserve会销毁旧空间此时_start指向新空间,pos仍然指向旧空间,pos在扩容之后失效了因此这里的解决办法就是计算偏移量,更新一下pos的值//	}//	//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置//	memmove(pos + 1, pos, sizeof(T) * (_finish - pos));//	*pos = x;//挪动之后再把x放到pos位置//	++_finish;//}void insert(iterator pos, const T& x)//在pos位置之前插入x{assert(pos <= _finish && pos >= _start);//检查pos是否合法//检查vector是否充满if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容pos = _start + len;}//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置memmove(pos + 1, pos, sizeof(T) * (_finish - pos));*pos = x;//挪动之后再把x放到pos位置++_finish;}//swap函数void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}private://全缺省为空iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};void test_vector(){vector <int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//迭代器访问vector <int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;//范围for访问for (auto e : v1){cout << e << " ";}cout << endl;//下标+[]访问for (size_t i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;}
}

http://www.mrgr.cn/news/19165.html

相关文章:

  • Python参数传递的艺术:解锁编程灵活性的秘密武器
  • 简单的C++ CMake构建程序
  • 5.2.数据结构-c/c++二叉树详解(下篇)(算法面试题)
  • ASP.NET Core 入门教学八 集成RocketMQ消息队列
  • 国际化产品经理的挑战与机遇:跨文化产品管理的探索
  • 【软件测试专栏】测试分类篇
  • Datawhale X 李宏毅苹果书 AI夏令营(深度学习 之 实践方法论)
  • Linux多线程——利用C++模板对pthread线程库封装
  • HALCON 错误代码 #7709
  • C++封装:栈、队列
  • 技术Leader在训练团队思考力中的核心职责
  • MySQL三大日志详解
  • MyBatis 源码解析:Executor 接口的核心作用
  • WEB服务与虚拟主机/IIS中间件部署
  • 服务器搭建NFS服务,将文挂载到windows
  • SpringMvc--后续(参数问题)
  • Python世界:文件自动化备份实践
  • 如何开启事务、确认提交事务、事务回滚、自动提交和禁止自动提交?
  • Tomcat部署及优化
  • Leetcode面试经典150题-54.螺旋矩阵