《C++ vector详解》
目录
1.结构
2.构造函数
无参构造
指定参数构造
迭代器构造
初始化列表构造
3.拷贝构造
4.析构函数
5.遍历
重载【】
5.插入
扩容
尾插
指定位置插入
6.删除
尾删
指定位置删除
1.结构
vector是一个类似于数组一样的容器,里面可以放各种各样的元素。我们只需在其中储存空间的地址即可,所以包含三个私有元素,分别是_start,_finish,_end_of_storage。使用模板T可以使vector储存各种各样的元素。将元素指针类型typedef成迭代器。代码如下。
template <class T>typedef T* iterator; typedef const T* const_iterator;
private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;
2.构造函数
包含无参构造, 指定参数构造,迭代器构造,初始化列表构造。
无参构造
不用传参,使用缺省参数或者初始化列表中的参数。
指定参数构造
第一个参数n表示空间大小,第二个参数const T& val = T()是一个缺省参数,当没有传递第二个参数的时候,会调用T的默认构造函数,用val来引用它。比如说T是int类型,那么val就是0的引用所以这里要加const,然后再用val去初始化剩余的空间。
为什么要n有size_t和int两种类型呢?
如果只定义size_t,那么传参(6,5)的时候,编译器默认6是int类型,与size_t类型不符合,那么就会找更匹配的构造函数,这时候会找到迭代器构造,而此时就会出现非法的间接寻址(int类型的值无法解引用)编译错误。
如果只定义int,那么传递的参数指定是size_t类型,如(6u,5)(后缀加u表示unsign int类型),同样会产生相同的问题。所以两者都显示定义了。
迭代器构造
参数为迭代器,迭代器是与指针类似的东西,可以进行解引用,+,-等操作。
这里为什么不使用前面的迭代器iterator,而是要使用一个成员函数模板?前面的iterator只能表示vector容器的迭代器,当我们想用其他容器的迭代器来初始化vector的时候就不适用了,所以要定义一个新的函数模板。
其实我自己在学的时候还有一个疑问就是为什么std::list<double> iterator能初始化vevtor<int>?
一是本质上使用的是(*first)来初始化的,拿到的是double类型的元素,
二是这其中实际上涉及到了隐式类型转换将double转换成了int。
初始化列表构造
std::initializer_list<T>是C++11 标准类型,专门处理花括号 {}
初始化(如 {1, 2, 3}
),之后再遍历尾插。这种方法可以避免重复地push_back。
//无参
vector<int> v1;
//指定参数
vector<int> v2(6);
//迭代器构造
string s1("wit lzk");
vector<int> v3(s1.begin(), s1.end());
//花括号
vector<int> v4 = { 1,2,3,5 };
//构造函数vector(){}vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(std::initializer_list<T> il){reserve(il.size());for (auto t& : il){push_back(t);}}
3.拷贝构造
旧版拷贝构造分为开空间,遍历,拷贝。
//拷贝构造vector(const vector<T>& v){reserve(v.capacity());for (auto& t : v){push_back(t);}}
赋值本质上也复用了拷贝构造。比如说v2 = v1把v1赋值给v2,v1进行传值传参,调用拷贝构造函数,v1会生成一个临时副本拷贝给v,然后利用swap将v的值和v2交换,实现赋值操作。同时v是一个临时对象,在和v2进行交换后函数结束时就会将原空间析构。
void swap(vector<T> v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}vector<T>& operator=(vector<T> v){swap(v);return *this;}
4.析构函数
析构函数还是看构造函数构造了什么,析构函数就析构什么。
/析构函数~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}
5.遍历
得到对象空间大小,和实际数据个数。
size_t size() const
{return _finish - _start;
}
size_t capacity() const
{return _end_of_storage - _start;
}
重载【】
T operator[](size_t n)
{assert(n < size());return _start[n];
}
vector<int> v1 = { 1,2,3,4 };
//[]遍历
for (size_t t = 0; t < v1.size(); t++)
{cout << v1[t] << " ";
}
cout << endl;
//范围for遍历
for (auto ch : v1)
{cout << ch << " ";
}
cout << endl;
5.插入
插入前需要判断空间是否足够,不够需要扩容。拷贝数据到新空间时,不能使用memcpy,如果是内置类型还好,如果是自定义类型,浅拷贝将会导致空间析构两次。所以直接暴力拷贝数据。因为是异地扩容,所以拷贝完毕要重新给类成员变量赋值。
扩容
//扩容
void reserve(size_t n)
{//扩容是改变(_end_of_storage-start)的值/*if (n <= _end_of_storage - _start){_finish = _start + n;}*/if(n > capacity()){size_t pre_size = size();T* tmp = new T[n];//_start不能为空if (_start){//使用memcpy为浅拷贝,资源会被析构两次//memcpy(tmp, _start, sizeof(_start));for (size_t i = 0; i < size(); i++){tmp[i] = _start[i];}//数据拷贝完要把旧空间删除delete[] _start;}_start = tmp;_finish = _start + pre_size;_end_of_storage = _start + n;}
}
尾插
//尾插
void push_back(const T& v)
{size_t len = _finish - _start;//写法太冗余//if (_finish < _end_of_storage)//{// _start[len] = v;//}//else {// //空间不够扩容// reserve(capacity() == 0 ? 4 : 2 * capacity());// _start[len] = v;//}//_finish++;//相等就扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}//无论满没满都要进行这两步(*_finish) = v;_finish++;
}
指定位置插入
这里需要注意迭代器失效的问题,异地扩容后_start被改变,pos也就失效了。所以要先记录pos到_start的距离,扩容后重新赋值。
//指定位置插入
iterator insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容完后的_start会改变//原来的pos会失效(迭代器失效了)pos = _start + len;}//移动数据iterator it = _finish;while (it >= pos){*it = *(it - 1);it--;}*pos = x;_finish++;return pos;
}
6.删除
尾删
改变一下_finish的位置即可。
//尾删
void pop_back()
{assert(_finish > _start);_finish--;}
指定位置删除
那么在这个地方vs实现的vector会出现迭代器失效的问题。vs会认为erase一次后it就失效了,所以必须在使用erase之后,重新给it赋值。 当然自己实现的vector中不会出现这个问题。
vector<int> v = { 1,1,2,3,3,4,5,6,7,8,9,9 };
for (auto ch : v)
{cout << ch << " ";
}
cout << endl;
//删除所有奇数
vector<int>::iterator it = v.begin();
while (it != v.end())
{if (*it % 2 == 1){//重新赋值it = v.erase(it);}else {it++;}
}
for (auto ch : v)
{cout << ch << " ";
}
cout << endl;
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos <= _finish);iterator it = pos;while (it < _finish){*it = *(it + 1);it++;}_finish--;return pos;
}
"Happy Coding! 🚀"