目录
3.3cbegin(),cend(),crbegin(),crend()
1.vector的介绍
注意,我是先写的strin
跟我string的文章一样,这种介绍网上太多了,我就直接cv了
1. vector 是表示可变大小数组的序列容器。2. 就像数组一样, vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。3. 本质讲, vector 使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候, vector 并不会每次都重新分配大小。4. vector 分配空间策略: vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。5. 因此, vector 占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。6. 与其它动态序列容器相比( deque, list and forward_list ), vector 在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起 list 和 forward_list统一的迭代器和引用更好。
2.vector的定义
4种定义方式(98文档里还是4种,14有6种。但现在有些编译器,已经有很多种了,我vs里面就有10种构造函数)
第一种,就是无参构造,也就是
vector (const allocator_type& alloc = allocator_type()); 默认占据的空间大小取决于编译器。数据个数是0 vector<int>o; 注意,vector是个模板,我们给什么类型参数,实例化后就会是什么类型的顺序表 比如这里给了int,那么实例化后,o这个顺序表里面数据的数据类型就只能是int类型
第二种,给定空间大小和指定值
vector (size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type()); vector<int>ans(10),这样顺序表的size就会变成10, 具体的值,一般来说都是默认0(取决于编译器) 这里再补充下,我自己其实也不太清楚这样的写法是遵循哪个构造函数的, 因为现在vector我定义的时候, vs编译器给我弹了10种定义方式,我也不知道这样写是满足哪种。 vector<vector<int>>ans(10,vector<int>(10)) 这样ans是个二维的顺序表,且每个元素都是一维的顺序表, 并且初始ans的size是10,一维顺序表(元素)的size也是10.
第3种,给迭代器的方式
vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type()); vector<int>o; vector<int>a(o.begin(), o.end()); 注意,这里的迭代器不限定一定是int类型的vector vector<char>o = { 's','d','g' }; vector<int>a(o.begin(), o.end()); 那么a顺序表里存的就是s,d,g的ascll码了。 同样也不限定是vector的迭代器 string o = { "dwadwada" }; vector<int>a(o.begin(), o.end()); 同理,存的也是字符的ascll码 其他的容器没试过,我们平时可以试试
第4种,也就是非常简单的拷贝构造
vector (const vector& x); vector<int>o = { 353533 }; vector<int>a(o);
这里补充下,就是赋值操作
vector<int>ans = { 3,4,5,6,7,8 }; vector<int>ans1 = ans; vector<int>ans2; ans2 = ans;
3.vector的迭代器
3.1begin(),end()
vector<int>ans = { 3,4,5,6,7,8 }; vector<int>::iterator it = ans.begin(); while (it != ans.end()) { cout <<(*it) << " "; it++; } 比较经典的迭代器操作 用的时候当成指针就好,反正平时也就拿来正向遍历容器。 具体迭代器的内容,看情况,我再发
3.2rbegin(),rend()
vector<int>ans = { 3,4,5,6,7,8 }; vector<int>::reverse_iterator it = ans.rbegin(); while (it != ans.rend()) { cout <<(*it) << " "; it++; } 是倒着遍历的
3.3cbegin(),cend(),crbegin(),crend()
这个就是加了const修饰
const vector<int>ans = { 3,4,5,6,7,8 }; vector<int>::const_iterator it = ans.begin(); while (it != ans.end()) { cout <<(*it) << " "; //const_iterator是保护*it不被修改 //const vector<int>::const_iterator it 是保护it不被修改 it++; } 而加了c,就是说传递的迭代器是const_iterator类型的
4.容量操作
vs下capacity是1.6倍增长,gcc下是2倍
4.1size()
vector<int>ans = { 3,4,5,6,7,8 }; cout << ans.size(); 返回当前有效数据个数
4.2max_size()
vector<int>an; cout << ans.max_size(); 返回的值根据环境决定 没多大用
4.3resize()
resize既可以影响容量也可以影响数据,比如给的值大于capacity,那么会自动扩容,且会填充默认值在string对象里,如果不给数据,填充的是0,如果给定数据比如a1.resize(100,3)那么会填充3。
如果给定值大于size但小于capacity呢,在vs和g++环境下,size会变成给定值,capacity不会变。
如果给定值小于size呢,在vs环境下,size会变成给定值,capacity不会变,相当于会保留前给定值个数据,多出来的数据相当于删除。
vector<int>ans = { 3,4,5,6,7,8 }; 此时size是6,capacity根据编译器决定,可能大于6, ans.resize(100,3); 此时capacity是100,多出来的94个元素都是3
4.4capacity()
vector<int>ans = { 3,4,5,6,7,8 }; cout << ans.capacity(); 返回现在顺序表开辟了多大的空间
4.5empty()
vector<int>ans = { 3,4,5,6,7,8 }; if (!ans.empty())cout << ans[0]; 判断当前顺序表是不是空的 是返回true,不是返回false
4.6reverse()
capacity一般我们不用管,因为顺序表会自动扩容。但reserve也有意义,可以减少不必要的重复扩容操作,可以一步到位(前提是我们自己确定要多少空间)而且reserve设置的空间也是要按规律走的,比如vs下的15,31,47,我们如果reserve给的不是这些值,编译器开的时候会自动向这么数值上靠,比如给40,可能开的47。但也不一定,有一定随机性。但这是vs环境,别的环境可能也不一样(比如g++下就比较标准,直接按你给的值开)
另外,关于reserve能不能缩空间呢,vs环境下是不能缩的,不管是否有数据,但是在g++环境下,可以缩(如果无数据,就按你给的缩,有数据并且给的值小于数据空间大小,就缩到数据的长度(简而言之,就是在不影响数据的情况下缩))
但不管什么环境,可以确定的是,不会影响数据
vector<int>ans = { 3,4,5,6,7,8 }; ans.reserve(100); 只影响capacity 不影响size
4.7shirink_to_fit()
vector<int>ans = { 3,4,5,6,7,8 }; ans.reserve(100); cout << ans.capacity() << endl; 此时是100 ans.shrink_to_fit(); cout << ans.capacity(); 此时就是6
让容量跟现有size()匹配,不影响数据
5.访问
5.1[]
注意,[]的重载之后,既可以返回该下标的元素,也能对该下标字符进行修改
因为有两种重载,一个是返回const 类型字符的引用,一个是返回普通字符的引用
虽然非const可以调用const的那条重载,但是const的不能调用非const的那条重载所以有2条。而且利用const,可以避免修改const类型的字符串。
5.2at
在使用类似,格式稍微变化,但是注意,[]对于越界访问是通过assert断言的,而at是通过异常,异常看我的其他的文章(目前还没写,略略略)。
5.3front,back
front是返回首元素,跟[]一样,有两个重载,可以修改,可以返回。back同理
5.4data
本质就是将顺序表中数组的部分返回,返回数组的地址,所以用指针接受,利用[]本质是地址增减,同样做到对数组的访问和修改
6.修改
6.1assign()
注意,assign是替换
vector<int>ans = { 3,4,5,6,7,8 }; vector<int>b; b.assign(ans.begin(), ans.end()); //迭代器给个左闭右开的区间,然后替换掉。 //此时b有6个元素 int ab[3] = { 0,1,2 }; b.assign(ab, ab + 3); //将ab数组的直接替换掉b原来的数组,此时b只有3个元素 b.assign(5, 100); //此时b的元素一共5个,每个元素都是int类型的100
6.2push_back和pop_back()
vector<int>ans = { 3,4,5,6,7,8 }; ans.push_back(3); //此时ans是3,4,5,6,7,8,3 ans.pop_back(); //此时ans是3,4,5,6,7,8 push是尾插 pop是尾删
6.3insert()
vector<int>ans = { 3,4,5,6,7,8 }; vector<int>::iterator h = ans.begin(); while ((*h)!=6) { h++; } //ans.insert(h, 4); //3 4 5 4 6 7 8 //第一种是给迭代器,在迭代器指向的位置前面插入数据 ans.insert(h, 10, 3); //3 4 5 3 3 3 3 3 3 3 3 3 3 6 7 8 //第二种是在指定位置前面添加n个指定的数据,比如这里是10个3 vector<int>b; b.insert(b.begin(), ans.begin(), ans.end()); //此时b是3 4 5 3 3 3 3 3 3 3 3 3 3 6 7 8 int a[3] = { 2,4,5 }; b.insert(b.begin(), a, a + 3); //此时b是2 4 5 3 4 5 3 3 3 3 3 3 3 3 3 3 6 7 8 //这两种都是在被插入的顺序表给定的迭代器指向的位置,开始插入给定的区间里的数据
6.4erase()
vector<int>ans = { 3,4,5,6,7,8 }; vector<int>::iterator h = ans.begin(); while ((*h)!=6) { h++; } //ans.erase(h); //3 4 5 7 8 //第一种是删除迭代器指向的数据 ans.erase(h,ans.end()); //3 4 5 //删除迭代器指定的区间里的数据
6.5swap()
vector<int>ans = { 3,4,5,6,7,8 }; vector<int>bns = { 5,5,5,5,5 }; ans.swap(bns); //此时ans是5,5,5,5,5 //此时bns是3,4,5,6,7,8
6.6clear()
vector<int>ans = { 3,4,5,6,7,8 }; ans.clear(); //清空数据(size==0),容量不清空
6.7关于vector的find(不是成员函数)
(注意,stl的vector里面没有find的成员函数,因为对于vector和list这种容器,find完全可以写入通用函数,也就是algorithm这个头文件中)
vector<int>ans = { 3,4,5,6,7,8 }; vector<int>::iterator it = find(ans.begin(), ans.end(), 3); cout << (*it); 注意,从给定的左端点开始往右端点找
6.8其他
其实还有个emplace,但这个要把可参数模板(好像是叫这个)学了先,我写这篇文章的时候还没学,xdm等我哪天想起来了再补吧
7运算符重载
template <class T, class Alloc> bool operator== (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs); (2) template <class T, class Alloc> bool operator!= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs); (3) template <class T, class Alloc> bool operator< (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs); (4) template <class T, class Alloc> bool operator<= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs); (5) template <class T, class Alloc> bool operator> (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs); (6) template <class T, class Alloc> bool operator>= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
自己看吧,照习惯用就好
8.vector模拟
#pragma once #include<string.h> #include<assert.h> namespace zl { //模板,根据传的类型,决定生成什么类型的模板 template<class T> class vector { public: //定义迭代器 typedef T* iterator; typedef const T* const_iterator; //构造函数,赋值空,如果继续优化,可以直接在private那边的变量声明中将变量的缺省值直接设置为空 //这样无参构造和下面没有被注释的拷贝构造,不用赋值这三个变量了 vector() :_start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) {} /*vector(const vector<T>&v) { _start = new T[v.capacity()]; memcpy(_start, v._start, sizeof(T) * v.size()); _finish = _start + v.size(); _end_of_storage = _start + v.capacity(); }*/ //拷贝构造 vector(const vector<T>& v) :_start(nullptr) , _finish(nullptr) , _end_of_storage(nullptr) { reserve(v.capacity());//只用扩容一次 for (auto& e : v) { push_back(e); } } //泛型的利用迭代器区间初始化(普通数组也可以通过给指针的形式,因为指针是天然的迭代器,物理上连续,注意其他容器的迭代器不一定是指针) template<class InputIterator> vector(InputIterator first, InputIterator end) { while (first != end) { push_back(*first); first++; } } //匹配问题所以我们要加个int类型的 vector ( size_t n, const T& val = T()) { resize(n, val); } vector(int n, const T& val = T()) { resize(n, val); } //交换 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; } //两个经典开头和结尾,注意,左闭右开 iterator begin() { return _start; } iterator end() { return _finish; } //为了防止传const修饰的实体类 const_iterator begin()const { return _start; } const_iterator end()const { return _finish; } //尾插,注意扩容 void push_back(const T& x) { if (_finish == _end_of_storage) { size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newcapacity); } *_finish = x; ++_finish; } //经典操作,--即可 void pop_back() { assert(size() > 0); --_finish; } //这里我们要注意如果发生了扩容,pos迭代器会失效(我们这里vector迭代器是指针,也就是说pos变成了野指针) //所以要用len记录长度,从而更新pos 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 : capacity() * 2); pos = _start + len; } //memmove(pos + 1, pos, sizeof(T) * (_finish - pos)); // 这里也有问题,浅拷贝 //我们要用深拷贝,否则自定义类型会出事 iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; _finish++; return pos; //返回值的原因看下面erase的,也是迭代器失效的问题 //本质除了环境强制检查外,就是存储数据的空间发生了变化,造成的迭代器失效 } iterator erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator it = pos + 1; while (it < _finish) { *(it - 1) = *it; it++; } _finish--; return pos; //注意,为什么要有返回值? //因为在我们这个版本中erase的删除是不采取缩容实现。 //何为缩容实现,比如1 3 5 6,我们删了5,缩容实现就是 //将1 3 6放在一个新的空间里,而不是如我们现在这样写的 //在原空间上向前覆盖的形式 //那这样,如果pos迭代器我们在外面还会继续使用 //就像reserve那一样,是会出现迭代器失效的问题。 //还有,如果在vs环境下,会强制检查迭代器,假如 //迭代器放入了erase,insert这些函数中,那么就不能再用 //vs会直接强制检查。 //因此,我们采用返回值的形式,这样不管是强制检查,还是采取缩容 //我们都可以利用返回值重新覆盖外面pos迭代器的方式来完成我们的代码 } //注意,c++为了兼容模板,升级了内置类型,内置类型也有构造函数 //因为不确定里面T到底什么类型,所以缺省值,我们干脆用相应的无参构造即可 void resize(size_t n, T val = T()) { if (n > size()) { reserve(n); while (_finish < _start + n) { *_finish = val; _finish++; } } else { _finish = _start + n; } } //扩容操作,注意申请的新空间,记得把之前的东西复制过来 void reserve(size_t n) { if (n > capacity()) { size_t _size = size(); T* tmp = new T[n]; if (_start) { //memcpy(tmp, _start, _size* sizeof(T));这个没法处理自定义类型(像string这样内部很可能有指针指向其他空间) //也就是浅拷贝和深拷贝的问题 for (int i = 0; i < _size; i++) { tmp[i] = _start[i]; //如果是自定义类型,会调用赋值操作符,并且都是深拷贝(像std这样都是有考虑深拷贝的) //如果用内存池,利用定位new,显示调用构造函数,也可以做到 } } delete[] _start; _start = tmp; _finish = _start + _size; _end_of_storage = _start + n; } } //经典size,有效数据个数 size_t size() const { return _finish - _start; } //容量 size_t capacity()const { return _end_of_storage-_start; } //经典[]操作符重载 T& operator[](size_t pos) { assert(pos < size()); return *(_start + pos); } //可读不可改 const T& operator[](size_t pos) const { assert(pos < size()); return *(_start + pos); } //析构函数 ~vector() { if (_start) { delete[] _start; _start = _finish = _end_of_storage = nullptr; } } private: //开始,结束,容量 iterator _start; iterator _finish; iterator _end_of_storage; }; }