因为在写一些算法题,一般的在线编译器好像都是用vector作为参数,所以有必要对vector总结一下:
7.3 vector
vector的本质是一个动态数组(dynamic array),类似于c用malloc分配空间。在<vector>头文件内,vector的定义如下:
namespace std { template <typenameT,typename Allocator = allocator<T>> class vector; }
注意:第二个模板参数可省略,用默认的内存分配器就好了。
7.3.1 vector的能力
因为内存是连续分配的,所以提供随机访问的能力,如果在末尾添加或删除元素,则性能相当的好。我想,因该是将类成员的size直接减1(原谅我还看不懂满是宏的源码==)。然而在中间删除或添加则会导致O(n)的操作时间,或者当size满了之后再添加也需要O(n)的时间,所以提前定好容量显得至关重要。
下面介绍初始化函数:
vector<Elem> c //空vector vector<Elem> c(c2) //构造一个与c2vector的拷贝,包括容量与元素 vector<Elem> c=c2 //同上 vector<Elem> c(rv) //使用右值来构造(move) vector<Elem> c=rv //同上 vector<Elem> c(n) //用elem的default构造函数来初始化n个elelm,容量大小为n。 vector<Elem> c(n,elem) //elem是类型Elem的一个实例,使用它的拷贝构造函数来初始化n个Elem。 vector<Elem> c(beg,end) //使用[beg,end)之间的迭代器来初始化。 vector<Elem> c(initlist) //使用初始化列来初始化 vec.~vector() //析构函数
7.3.2 vector的操作
非易更性操作(Nonmodifying operation)
c.empty() //bool,是否为空。但用size()==0也许更快 c.size() //返回大小 c.max_size() //返回元素个数的最大可能数 c.capacity() //返回容量 c.reserve(num) //当且仅当容量不足的时候才扩大 c.shrink_to_fit() //降低容量 c1==c2 //逐个元素比较大小 c1!=c2 //逐个元素调用!= c1<c2 //之类的大小比较就不写了
在这里,非易更性操作指的是调用这些函数之后,原有的指针与迭代器仍然有效。然而这里的坑需要注意的是reserve与shrink_to_fit调用成功后指针与迭代器无效,只是原有的逻辑内容未发生变化。
赋值(assignment)
c=c2 //c2的内容拷贝赋值给c c=rv //用右值的内容移动给c(move) c=initlist //用初始化列初始化 c.assign(n,elem) //记住,是从开始复制n个元素。n超过了容量大小会自动适配 c.assign(beg,end) //[beg,end) 复制范围内的元素 c.assign(inilist) c1.swap(c2) //置换c1与c2的数据,然而这里原有的指针迭代器也会被置换。 swap(c1,c2) //同上
元素访问(Element Access)
c[idx] //不检查范围 c.at[idx] //超出范围会抛出异常,安全的做法 c.front() //返回第一元素,不检查容器是否为空 c.back() //返回最末元素,不检查容器是否为空
总结一下:使vector原有的迭代器与指针失效有如下两种情况:
(1)在索引位置上安插或移除元素
(2)由容量变化而引起的内存重新分配
迭代器(iterator)
c.begin() c.end() c.cbegin() //常量迭代器 c.cend() c.rbegin() //反向迭代器,指向了容器最末尾的元素,++往着首位 c.rend() c.crbegin() c.crend()
安插与移除(Inserting and Removing)
c.push_back(Elem) c.pop_back() //移除最后一个元素 c.insert(pos,Elem) //在Iterator pos位置前插入元素 c.insert(pos,n,Elem) //插入n个元素 c.insert(pos,beg,end) //在位置pos之前插入[beg,end)个元素 c.insert(pos,initlist) c.emplace(pos,args...) //调用Elem的以args为参数的构造函数初始化 c.emplace_back(args...) //在末尾添加 c.erase(pos) //删除元素并返回之后的迭代器 c.rease(begin,end) c.resize(num) //将元素数量改为num,如果size变大,剩下的调用默认初始化 c.resize(num,Elem) //不同的是,调用Elem的拷贝 c.clear() //移除所有元素,清空,但内存仍然保留,不同的size变为0
需要注意的是emplace与insert不同的是,insert插入的是拷贝,而emplace插入的是以args初始化的元素
7.3.3 将vector当作C-Style Array使用
可以调用c.data()获得首地址,注意这个首地址不同于迭代器,通常少用。
7.3.4 异常处理(Exception Handling)
有几种清空C++STL做出保证会抛出异常
1.push_back()时发生异常,元素不会被插入到末尾,即不发生效用 |
2.如果元素的copy/move未发生异常,则容器的相关操作要么成功要么不发生效用 |
3.pop_back()不会发生异常 |
4.swap(),clear()不发生异常 |
5.如果元素的copy/move未发生异常,则容器的相关操作要么成功要么不发生效用,包括POD(plain old data)简朴的老式数据,如C-struct。 |
7.3.6 Class vector<bool>
这是模板特化类,与bool不同的是,这里的bool只占用一个位,而不是一个字节,所以迭代器在这里无效,同时内存空间也是原来的八分之一。
操作 | 效果 |
c.flip() | 将所有bool元素反相(negate) |
c[idx].flip() | 将idx所指位置反相 |
c[idx]=val | 将idx位置复制第一bit(0或1) |
c[idxl]=c[idx2] |
这里需要注意的是front与back,at等函数返回的都是引用(reference)
谈一谈初学c++时遇到的一个坑,考虑如下函数
for(auto i=v.begin(),i!=v.end();i++) { if(condition) { v.erase(i); } }
错误的原因是删除了之后内存布局有变化,所以迭代器会失效,为nullptr,所以会错误。、
扩充一下例子:
vector<int> a{ {1,1,2,3,4,5,1} }; for (auto i = a.begin(); i != a.end(); i++) { if(*i == 1) { i=a.erase(i); } }
vector<int> a{ {1,1,2,3,4,5,1} }; for (auto i = a.begin(); i != a.end(); i++) { while(*i == 1) { i=a.erase(i); if (i == a.end()) break; } }
vector<int> a{ {1,1,2,3,4,5,1} }; for (auto i = a.begin(); i != a.end(); i++) { while(*i == 1) { i=a.erase(i); if (i == a.end()) break; } if (i == a.end()) break; }
只有最后一个才是对的,在自己的机子上调试一下吧。
ps:在容器中删除相同元素的简单做法:vec.erase(remove(vec.begin(),vec.end(),val),vec.end())。