注意
实现仿cplus官网的的string类,对部分主要功能实现
实现
文件
#pragma once
#include <string>
#include <assert.h>namespace myvector
{template <class T>class vector{public://iteratortypedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//constructor//defaultvector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}//fill//匿名对象加const引用生命周期延长至变量结束vector(size_t n, const T& val = T()) :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){Reserve(n);for (size_t i = 0; i < n; i++){PushBack(val);}}//重载int版,防止调用迭代器区间vector(int n, const T& val = T()):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){Reserve(n);for (int i = 0; i < n; i++){PushBack(val);}}//rangetemplate <class InputIterator>vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) //可以使用默认值,不用初始化列表{//迭代器必须用不等于,因为list可能后边的指针更小while (first != last){PushBack(*first);first++;}}//copy 实现深拷贝,不然会浅拷贝vector(const vector<T>& x){//Reserve(x.Capacity());_start = new T[x.Capacity()];//深拷贝,不能用memcpy,避免T是结构仍浅拷贝//memcpy(_start, x._start, sizeof(T) * x.Size());for (size_t i = 0; i < x.Size(); i++){_start[i] = x._start[i];}_finish = _start + x.Size();_end_of_storage = _start + x.Capacity();}//addvoid PushBack(const T& x){if (_finish == _end_of_storage){size_t capacity = Capacity() == 0 ? 4 : Capacity() * 2;Reserve(capacity);}*_finish = x;_finish++;}iterator Insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t sz = pos - _start;//扩容后的迭代器失效问题,更新posReserve(Capacity() == 0 ? 4 : Capacity() * 2);pos = _start + sz;}iterator end = _finish;while (end > pos){*end = *(end - 1);end--;}*pos = x;_finish++;return pos;}//devoid PopBack(){assert(!Empty());_finish--;}iterator Erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator end = pos;while (end != _finish){*end = *(end + 1);end++;}_finish--;return pos;}//searchT& operator[](size_t pos){assert(pos < Size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < Size());return _start[pos];}//size capacitysize_t Size() const{return _finish - _start;}bool Empty() const{return Size() == 0;}size_t Capacity() const{return _end_of_storage - _start;}void Reserve(size_t n){if (n > Capacity()){T* tmp = new T[n];size_t size = Size();if (_start){//memcpy(tmp, _start, Size() * sizeof(T));for (size_t i = 0; i < Size(); i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + size;_end_of_storage = _start + n;}}//类型()调默认构造,内置类型也可以//首先判断设置的大小是否大于当前size,大于就需要设置参数的数据,同时还考虑扩容void Resize(size_t n, T x = T()){if (n > Size()){if (n > Capacity()){Reserve(n);}while (_finish != _start + n){*_finish = x;_finish++;}}_finish = _start + n;}//destructor~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}private:iterator _start;iterator _finish;iterator _end_of_storage;};
}
测试
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include "vector.h"using namespace myvector;
//void fun(const vector<int>& x)
//{
// for (auto ch : x)
// {
// std::cout << ch << std::endl;
// }
//}class Solution {
public:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv;vv.Resize(numRows, vector<int>());for (size_t i = 0; i < vv.Size(); ++i){vv[i].Resize(i + 1, 0);vv[i][0] = vv[i][vv[i].Size() - 1] = 1;}for (size_t i = 0; i < vv.Size(); ++i){for (size_t j = 0; j < vv[i].Size(); ++j){if (vv[i][j] == 0){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}}return vv;}
};//vector<int> fun2()
//{
// vector<int> s;
// return s;
//}
int main()
{vector<int> v1;v1.PushBack(1);v1.PushBack(2);v1.PushBack(8);v1.PushBack(3);v1.PushBack(10);v1.PushBack(6);v1.PushBack(1);v1.PushBack(2);v1.PushBack(8);v1.PushBack(3);v1.PushBack(10);v1.PushBack(6);/*std::vector<int> v2;v2.push_back(1);v2.push_back(2);v2.push_back(8);v2.push_back(3);*///v1.PushBack(6);/*vector<int>::iterator pos = std::find(v1.begin(), v1.end(), 2);v1.Insert(pos, 3);*//*vector<int>::iterator pos = v1.begin() + 2;v1.Erase(pos);for (int i = 0; i < v1.Size(); i++){std::cout << v1[i] << " ";}*//*std::vector<int>::iterator pos = v2.begin() + 2;for (int i = 0; i < v2.size(); i++){std::cout << v2[i] << " ";}std::cout << std::endl;*//*fun(v1);*//*v1.PopBack();v1.PopBack();v1.PopBack();*///v1.Resize(8, 4);//vector<int>::iterator pos = std::find(v1.begin(), v1.end(), 5);/*if (pos != v1.end()){pos = v1.Insert(pos, 8);}*///vs insert和erase后迭代器都不能再使用,强制检查//(*pos)++;/*std::vector<int>::iterator it = v2.begin();while (it != v2.end()){if (*it % 2 == 0){it = v2.erase(it);}else{it++;}}*//*std::vector<int>::iterator pos1 = std::find(v2.begin(), v2.end(), 2);v2.erase(pos1);pos1++;*//*vector<int>::iterator it2 = v1.begin();while (it2 != v1.end()){std::cout << *it2 << " ";it2++;}*//*vector<int> v3(10, 5);vector<int>::iterator it2 = v3.begin();while (it2 != v3.end()){std::cout << *it2 << " ";it2++;}*///std::cout << std::endl;//int ary[] = { 5,4,3,2,1 };//vector<int> v4(ary, ary + 3);//排序algorithm头文件的排序//template <class RandomAccessIterator>// sort(RandomAccessIterator first, RandomAccessIterator last);//std::sort(v4.begin(), v4.end());/*vector<int>::iterator it3 = v4.begin();while (it3 != v4.end()){std::cout << *it3 << " ";it3++;}*///降序,要传模板//template <class T> struct greater;/*std::greater<int> g;std::sort(ary, ary + sizeof(ary)/sizeof(ary[0]), g);for (auto ch : ary){std::cout << ch << " ";}*///vector<std::string> v2(3, "111");vector<vector<int>> v3 = Solution().generate(5);for (size_t i = 0; i < v3.Size(); ++i){for (size_t j = 0; j < v3[i].Size(); ++j){std::cout << v3[i][j] << " ";}std::cout << std::endl;}vector<int> v4;v4.Insert(v4.begin(), 4);;return 0;
}
注意事项
vector可以存储任意类型的元素,所以用模板,用三个指针,一个记录数组的首地址,finish记录最后一个元素的下一个地址,end_of_storage记录数组范围的地址,类型是模板T类型指针
vector(size_t n, const T& val = T())
对于T类型的值给默认值,不能默认给0,有可能是自定义结构,所以需要调用对应的构造函数,内置类型也可以这样构造
拷贝构造不能用memcpy,如果对象涉及资源管理就会浅拷贝,内存泄露或崩溃,所以要用赋值走运算符重载
赋值运算符重载,避免T是结构体的浅拷贝
Insert中元素的长度在扩容前需要记录,不然会失效
迭代器失效
迭代器的作用是让算法不用关心底层数据结构,实际就是一个指针,或者是对指针封装。比如:vector的迭代器就是原生态指针T*。因此迭代器失效,实际就是迭代器对应指针的指向的空间销毁了,使用一块已经被释放的空间,造成的后果就是程序崩溃
1.会引起底层空间改变的操作,都有可能是迭代器失效,如:resize、reserve、insert、assign、pushback等
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{1,2,3,4,5,6};auto it = v.begin();// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容// v.resize(100, 8);// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变// v.reserve(100);// 插入元素期间,可能会引起扩容,而导致原空间被释放// v.insert(v.begin(), 0);// v.push_back(8);// 给vector重新赋值,可能会引起底层容量改变v.assign(100, 8);/*出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
空间,而引起代码运行时崩溃。解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
赋值即可。*/while(it != v.end()){cout<< *it << " " ;++it;}cout<<endl;return 0;
}
插入时扩容会导致原pos的迭代器失效,所以要记录长度更新pos
2.指定位置元素的删除操作 erase
#include <iostream>
using namespace std;
#include <vector>
int main()
{int a[] = { 1, 2, 3, 4 };vector<int> v(a, a + sizeof(a) / sizeof(int));// 使用find查找3所在位置的iteratorvector<int>::iterator pos = find(v.begin(), v.end(), 3);// 删除pos位置的数据,导致pos迭代器失效。v.erase(pos);cout << *pos << endl; // 此处会导致非法访问return 0;
}
erase删除pos位置的元素后,pos位置之后的元素会往前移,没有导致底层空间改变,理论上迭代器不会失效,但如果pos刚好是最后一个元素,删完之后就是end的位置,end是没有元素的,pos就失效了。因此删除任意位置元素vs就认为迭代器已经失效,会报错
下面是删除所有偶数的功能,第2个是正确的,通过返回值更新pos,迭代器就不会失效
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)v.erase(it);++it;}return 0;
}
int main()
{vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)it = v.erase(it);else++it;}return 0;
}
3.liux下,g++编译器对迭代器的检测不严格,没有vs极端
// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{vector<int> v{1,2,3,4,5};for(size_t i = 0; i < v.size(); ++i)cout << v[i] << " ";cout << endl;auto it = v.begin();cout << "扩容之前,vector的容量为: " << v.capacity() << endl;// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效 v.reserve(100);cout << "扩容之后,vector的容量为: " << v.capacity() << endl;// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会// 虽然可能运行,但是输出的结果是不对的while(it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}
程序输出:
1 2 3 4 5
扩容之前,vector的容量为: 5
扩容之后,vector的容量为: 100
0 2 3 4 5 409 1 2 3 4 5
// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include <vector>
#include <algorithm>
int main()
{vector<int> v{1,2,3,4,5};vector<int>::iterator it = find(v.begin(), v.end(), 3);v.erase(it);cout << *it << endl;while(it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}
程序可以正常运行,并打印:
4
4 5// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{vector<int> v{1,2,3,4,5};// vector<int> v{1,2,3,4,5,6};auto it = v.begin();while(it != v.end()){if(*it % 2 == 0)v.erase(it);++it;}for(auto e : v)cout << e << " ";cout << endl;return 0;
}
========================================================
// 使用第一组数据时,程序可以运行
[@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[@VM-0-3-centos 20220114]$ ./a.out
1 3 5
=========================================================
// 使用第二组数据时,程序最终会崩溃
[@VM-0-3-centos 20220114]$ vim testVector.cpp
[@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[@VM-0-3-centos 20220114]$ ./a.out
Segmentation fault
从上面是三个例子可以看出:SGI STL迭代器失效并不一定崩溃,但运行结果肯定不对,it不在begin和end范围,肯定会崩溃
4.与vector类似,string插入+扩容+erase之后,迭代器也会失效
#include <string>
void TestString()
{string s("hello");auto it = s.begin();// 放开之后代码会崩溃,因为resize到20会string会进行扩容// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了// 后序打印时,再访问it指向的空间程序就会崩溃//s.resize(20, '!');while (it != s.end()){cout << *it;++it;}cout << endl;it = s.begin();while (it != s.end()){it = s.erase(it);// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后// it位置的迭代器就失效了// s.erase(it); ++it;}
}
迭代器失效,对迭代器重新赋值