C++基础:模拟实现vector(有存在深层次的浅拷贝问题)
目录
引言
一、vector的基本框架
二、尾插push_back、reserve扩容、任意位置插入insert(增)
1.reserve扩容
2.push_back尾插
3.深层次的浅拷贝问题
4. 任意位置插入数据insert(会使迭代器失效)
三、构造、析构、拷贝构造函数
1.构造函数
1.1无参构造
1.2initializer_list 类作为参数来构造。
1.3用任意类型的迭代器来构造
2.拷贝构造
3.析构函数
四、判空函数(empty)、尾删(pop_back)、删除任意位置元素(erase)
1.判空
2.尾删
3.删除任意位置元素(会使迭代器失效)
五、[]运算符重载、=赋值运算符重载、交换vector函数
1.swap函数
2.=赋值运算符重载
3.[]运算符重载
六、vector.h代码
引言
手把手带你实现vector
实现vector的意义:对底层有个更深的理解。锻炼代码能力。
_涂色_-博客主页
C++基础专栏
为了与STL中的vector区分开,这里都写在命名空间stl中,并且实现类函数的声明和定义分离。
分3个文件来写:
- string.h:string类的声明。
- text.c :测试string的接口是否正确。
因为:模板声明和定义不能分离定义到两个文件。所以都在h .cpp文件中。
一、vector的基本框架
这里是为了保持和STL源码的私有成员一样所以这样设计。
这里的迭代器只是以简单的形式来实现的,真实的迭代器不是这样的。
namespace stl
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;const_iterator begin() const //vector开始位置{return _start;}const_iterator end() const //vector结束位置{return _finish;}iterator begin(){return _start;}iterator end(){return _finish;}int capacity() const //vector里面的个数{return _endofstorage - _start;}int size() const //计算出vector数组中元素个数{return _finish - _start;}private:iterator _start = nullptr; // 存储数据的数组iterator _finish = nullptr; //数组结尾的写一个位置iterator _endofstorage = nullptr; //数组的空间的最后一个位置};
}
二、尾插push_back、reserve扩容、任意位置插入insert(增)
1.reserve扩容
先实现reserve扩容
注意:必须先计算出原来的元素个数,若在下面计算元素个数的时候,由于_start已经发生了改变,会使得计算元素个数错误。
关于深层次的浅拷贝问题,写出push_back来给出说明。
void reserve(const size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//拷贝旧空间数据到新空间数据if (_start){//memcpy(tmp, _start, sizeof(T*) * old_size); 会存在深层次的浅拷贝问题。for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}
}
2.push_back尾插
先看空间够不够,不够就先扩容,然后尾插即可。
void push_back(const T& x)
{if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
3.深层次的浅拷贝问题
运行下面代码,当尾插5个string类型的数据时,由于需要扩容,就需要拷贝原理的数据。
namespace stl
{ template<class container>void Print(const container& con){for (auto e : con){cout << e << " ";}cout << endl;}void test_vector1(){vector<string> v1;v1.push_back("11111111111111111111111");v1.push_back("11111111111111111111111");v1.push_back("11111111111111111111111");v1.push_back("11111111111111111111111");v1.push_back("11111111111111111111111");Print(v1);}
}int main()
{stl::test_vector1();return 0;
}
但是,如果使用memcpy拷贝数据的话,就会形成生层次的浅拷贝问题。
当使用memcpy拷贝时:
所以这里不能使用memcpy来进行拷贝,要通过赋值操作符,调用函数的拷贝构造,来进行拷贝。
4. 任意位置插入数据insert(会使迭代器失效)
在 C++ 里,迭代器失效是一个重要概念,它指的是由于容器结构发生改变,导致原本指向容器中某个元素的迭代器不再有效。此时若继续使用这个迭代器,可能会引发程序崩溃、得到错误结果等严重问题。
- 先看空间够不够,不够的话就先扩容,扩容的时候,需要更新pos指针,否则pos位置就被释放了(pos就是野指针了)。
- 将pos位置和后面的元素向后移动一位
- 最后在pos位置插入元素x
iterator insert(iterator pos, const T& x) //给返回值是解决迭代器失效
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 扩容后这里需要更新pos,否则pos就是野指针了pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}
三、构造、析构、拷贝构造函数
1.构造函数
1.1无参构造
可以手动写,但是没有必要,因为成员给了缺省值,所以,不带惨的构造使用编译器生成的即可。
vector():_start = nullptr,_finish = nullptr, _endofstorage = nullptr{}
但是下面会写拷贝构造,所以编译器不会自己生成构造函数了,所以:
// 强制编译器生成默认构造vector() = default;
1.2initializer_list<T> 类作为参数来构造。
使用 initializer_list<T> 类作为参数来构造。
vector(initializer_list<T> il)
{reserve(il.size());for (auto& e : il){push_back(e);}
}
通过这种构造,可以这样初始化vector:
vector<int> v1 = { 1,2,3,4,5,5 };
vector<int> v2 = { 1,2,3,4,5,51,1,1,1,1,1,1,1 };
原因就是通过 initializer_list<T>类来进行初始化。
1.3用任意类型的迭代器来构造
当我们想用string的一段,来初始化vector时:
// 函数模板// 任意类型容器迭代器初始化template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}
运行下面这段代码
string s1("hello world");
vector<int> v6(s1.begin(), s1.end());
Print(v6);
2.拷贝构造
直接开一样的空间,进行尾插来拷贝构造
//v2(v)vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}
3.析构函数
释放数组,然后都置为空。
~vector<T>(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}
四、判空函数(empty)、尾删(pop_back)、删除任意位置元素(erase)
1.判空
- 是空,就返回真,
- 不是空,就返回假
bool empty() const
{return _start == _finish;
}
2.尾删
有了判空函数,就不需要使用assert来断言了。
void pop_back()
{//assert(_finish > _start);assert(!empty());--_finish;}
3.删除任意位置元素(会使迭代器失效)
iterator erase(const iterator pos) //给返回值是解决迭代器失效
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;++it;}return pos;
}
五、[]运算符重载、=赋值运算符重载、交换vector函数
1.swap函数
交换两个vector
void swap(vector<T> v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}
2.=赋值运算符重载
tmp是v2的拷贝,然后和v1直接交换,就使得v1 等于 v2了。
// V1 = V2
vector<int>& operator=(vector<T> tmp)
{swap(tmp);return *this;
}
3.[]运算符重载
可以通过下标来访问vector了
T& operator[](size_t i)
{assert(i < size());return _start[i];
}const T& operator[](size_t i) const
{assert(i < size());return _start[i];
}
六、vector.h代码
#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;
// 模板 声明和定义不能分离定义到两个文件.h .cppnamespace stl
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}iterator begin(){return _start;}iterator end(){return _finish;}// 强制编译器生成默认构造vector() = default;vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}//v2(v)vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}~vector<T>(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}// V1 = V2vector<int>& operator=(vector<T> tmp){swap(tmp);return *this;}// 函数模板// 任意类型容器迭代器初始化template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}/*vector(iterator first, iterator last){while (first != last){push_back(*first);++first;}}*/vector(int n, int val = T()){resize(n, val);}vector(size_t n, T val = T()){resize(n, val);}void swap(vector<T> v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}T& operator[](size_t i){assert(i < size());return _start[i];}const T& operator[](size_t i) const{assert(i < size());return _start[i];}void reserve(const size_t n){if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//拷贝旧空间数据到新空间数据if (_start){//memcpy(tmp, _start, sizeof(T*) * old_size); 会存在深层次的浅拷贝问题。for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}int capacity() const{return _endofstorage - _start;}int size() const{return _finish - _start;}void push_back(const T& x){if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}void pop_back(){//assert(_finish > _start);assert(!empty());--_finish;}iterator insert(iterator pos, const T& x) //给返回值是解决迭代器失效{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 扩容后这里需要更新pos,否则pos就是野指针了pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}iterator erase(const iterator pos) //给返回值是解决迭代器失效{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;++it;}return pos;}bool empty() const{return _start == _finish;}void resize(size_t n, T val = T()){if (n > size()){reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}else{_finish = _start + n;}}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};
}
完。