effective STL 读书笔记——第二章:vector和string

条款13:尽量使用vector和string来代替动态分配的数组

理由如下:

  1. 通过vector、string代替动态分配的数组,你可以享受标准stl算法库的好处
  2. 你不需要考虑何时放内存,不会存在麻烦的内存泄露问题
  3. 你不需要考虑到底是用delete还是delete[],也不需要顾虑资源重复释放的问题

使用容器代替动态分配的数组可能在以下2个地方存在问题:

  1. 由于STL实现的string可能存在引用计数,在多线程环境中使用了引用计数的字符串,你可能发现避免分配和拷贝所节省下的时间都花费在后台并发控制上了,但是它还是可以通过其他方式避免的,见 条款15
  2. 如果要构造bool的数组,但是选择了vector,它将存在问题,见 条款18

条款14:使用reserve来避免不必要的重新分配

对于序列容器四个关于容器大小的函数:

size():容器中有多少元素
capacity():容器在它已经分配的内存中可以容纳多少元素
resize():强制把容器改为容纳n个元素
reserve():强制容器把它的容量改为至少n,提供的n不小于当前大小(如果小于,则忽略)

对于vector其内存分配是以2为因数进行增长的,要注意如果进行push_back的时候,刚好要进行内存分配,则以前的迭代器都会失效。最好的办法是先判断capacity与size。而且由于默认的分配是以2为因数进行内存增长,为了避免频繁的分配内存,可以一次指定分配固定大小的内存,需要的时候再让其动态增长:

vector<int> v;
for (int i = 1; i <= 1000; ++i) v.push_back(i);// 未指定大小,push_back的过程中会多次分配内存

vector<int> v;
v.reserve(1000);// 指定元素个数,一次性分配好内存,避免频繁分配内存
for (int i = 1; i <= 1000; ++i) v.push_back(i);


string s;
//...
if (s.size() < s.capacity()) {
    s.push_back('x');
}

条款15:小心string实现的多样性

条款16: 如何将vector和string的数据传给遗留的API

如果需要兼容已存在的C-API的代码,要将元素作为数组传递给C函数,形式如下:

void dosomething(const int *pints,const int size);

你一定可以通过选择合适的容器来解决该问题。比如你的C++代码中,可以构建一个vector如下使用:

vector<int> vc;
// fill vc
dosomething(&vc[0],vc.size());// 使用时,应该检查vector非空

vc.begin()作为vector的迭代器起始地址,并不适合作为dosomtthing的参数,原因如下:

  1. vector的迭代替虽然一般情况下是以指针形式实现的,但是并不一直都是的,你不应该依赖它
  2. 如果由于某些原因,你要是用vc.begin(),应该使用&*v.begin()代替,但是它既不直观也不好写

如果需要字符串,可以使用string:

void doSomething(const char *pString);
doSomething(s.c_str());

当对容器的元素取地址,然后作为参数传递给C-API时,以下为约定的事情,否则可能引发未定义行为:

 被调用的函数绝不能试图改变vector中元素的个数——既不能增加元素个数,也不能减少元素个数

如果要将C-API返回值初始化一个vector,可以使用如下形式,只有vector可以使用如下形式,因为只有vector承诺了与数组具有相同的潜在内存分布:

// C API:此函数需要一个指向数组的指针,数组最多有arraySize个double
// 而且会对数组写入数据。它返回写入的double数,不会大于arraySize
size_t fillArray(double *pArray, size_t arraySize);
vector<double> vd(maxNumDoubles); // 建立一个vector,
// 它的大小是maxNumDoubles
vd.resize(fillArray(&vd[0], vd.size())); // 让fillArray把数据写入vd,然后调整vd的大小为maxNumDoubles

如果需要将C API返回值放入string,可以先将数据放入vector,然后再拷贝到string中:

// C API:此函数需要一个指向数组的指针,数组最多有arraySize个char
// 而且会对数组写入数据。它返回写入的char数,不会大于arraySize
size_t fillString(char *pArray, size_t arraySize);
vector<char> vc(maxNumChars); // 建立一个vector,
// 它的大小是maxNumChars
size_t charsWritten = fillString(&vc[0], vc.size()); // 让fillString把数据写入vc
string s(vc.begin(), vc.begin()+charsWritten); // 从vc通过范围构造函数
//deque d(vd.begin(), vd.end()); // 拷贝数据到deque
//list l(vd.begin(), vd.end()); // 拷贝数据到list
//set s(vd.begin(), vd.end()); // 拷贝数据到set

条款17:使用“交换技巧”来修整过剩容量

虽然容器使用的内存能够动态的增长,也可以使用clear,erase删除容器中的元素,但是它并不回收真正的内存,直到容器被销毁。如果一个容器分配了10000个元素,最终留下的只有100个,如果删除已经不使用的内存?可以通过swap来完成
class Contestant {...};
vector contestants;
for(int i = 0;i < 10000;++i)
    contestants.push_back(i);
for(int i = 0;i < 9900;++i)
    contestants.pop_back();// 虽然元素被释放掉,但是以前使用过的内存没有被回收

// 通过vector(contestants)构造一个匿名变量,然后交换他们
vector(contestants).swap(contestants);

vector的构造函数,只为新的对象分配contestants.size()个元素容纳的内存,然后交换他们,匿名变量拥有原来较大的元素的空间,contestants拥有size个元素的空间,然后匿名变量马上析构掉。

同样的,string也可以通过这样的方式释放掉不需要的内存:

string s;
// 使s变大,然后删除所有
string(s).swap(s);

如果要清空容器并回收内存,并不是使用clear,而是使用以下方式:

vector v;
string s;
... // 使用v和s
vector().swap(v); // 清除v而且最小化它的容量
string().swap(s); // 清除s而且最小化它的容量

条款18:避免使用vector

vector的2个问题:

第一,它不是一个STL容器。
第二,它并不容纳bool

一个东西要成为STL容器必须满足一定的条件,至少要满足下面的这个必要条件:

如果c是一个T类型对象的容器,且c支持operator[],那么T *p = &c[0];必须能够编译 

vector实际上是一个标准容器库,但是它内部却不是使用的bool值作为成员变量,取而代之的是一个代理类,代理类把size个bool值对应到了每一位上,而且vector的[]操作并不是返回的bool的引用,而是通过代理类返回的一个临时的bool值,你不可能通过取地址符得到它在内存中的实际地址。

你可以使用deque和bitset代替vector。deque是一个标准库容器,但是其内存不是连续的,因此不能被传递给一个数组作为参数的C-API函数。bitset不是一个STL容器,但它是C++标准库的一部分。与STL容器不同,它的大小(元素数量)在编译期固定,因此它不支持插入和删除元素。

你可能感兴趣的:(effective,stl,string,stl,vector)