第13条:vector和string优先于动态分配的数组
用vector和string来替代new T[ ]; //T是char则使用string,其它都用vector; vector和string会自己管理内存(在需要时会分配更大的内存,在vector和string容器被析构时,会自动析构所包含的元素并释放它们的内存);并适用于所有支持顺序容器的STL算法,并且提供了很多成员函数; string的实现采用了引用计数技术,可以消除不必要的内存分配和字符拷贝;但若在多线程环境下,引用计数会导致线程不安全; 解决:用vector替换string,vector的实现没有采用引用计数;虽无法再使用string的很多成员函数,但仍有很多STL其他算法可以替代
第14条:使用reverse来避免不必要的重新分配
内存重新分配的过程: ①分配一块新内存 ②将旧元素全部拷贝过去 ③析构旧内存中的所有元素 ④释放旧内存 ⑤在新内存中再执行想要的操作(往往是插入元素) 内存重新分配的开销:①容器内元素的拷贝,析构;② 重新分配内存 ③迭代器,指针,引用会失效 string和vector的四个成员函数:size,capacity,resize,reserve
v. size ( ) ;
v. capacity ( ) ;
v. resize ( container:: size_type n) ;
v. reserve ( container:: size_type n) ;
v. clear ( ) ;
vector< int > ( ) . swap ( v) ;
最好在容器刚被创建出来,就调用v.reserve()为元素预留足够大的空间
vector< int > v;
for ( int i = 0 ; i < 1000 ; ++ i) {
v. push_back ( i) ;
}
1. 若提前知道或预估容器最终有多少个元素,可以在插入元素之前先reverse,预留一部分空间
vector< int > v;
v. reserve ( 1000 ) ;
for ( int i = 0 ; i < 1000 ; ++ i) {
v. push_back ( i) ;
}
2. 即使不知道最终需要多大空间,也可以多预留一点,如果最后多了,再去掉多余部分
vector< int > v;
v. reserve ( 2000 ) ;
for ( int i = 0 ; i < 1000 ; ++ i) {
v. push_back ( i) ;
}
vector< int > ( v) . swap ( v) ;
第15条:注意string实现的多样性
string实现有很多种: 一个string对象通常包含:分配子;size; capacity; 保存的字符串值;可能还有引用计数等等;不同的string实现采用的结构也不同 不同string实现的区别:
一个string对象的大小也是不确定的(通常是一个指针大小的1~7倍) 引用计数:有的string实现支持(引用计数在多线程环境下不友好,会出问题),有的不支持 创建一个string对象,可能需要多次动态分配内存 string对象对于size和capacity可能共享,也可能不共享; (这点没懂) 不同的string实现对于字符内存的最小分配单位有不同策略(不懂) 有的string实现可能支持单个对象的分配子,也可能不支持(不懂)
平时使用string没有什么区别,但在对于内存性能要求很高或需要在不同STL平台运行时,需要注意使用的是哪种实现;根据需要,权衡的选择所需的string实现
第16条:了解如何把vector和string数据传给旧的API
有些API需要的参数是原始指针,而不能是迭代器;因此需从vector中获得原始指针
vector< int > v;
. . .
& v[ 0 ]
因为vector和数组一样在内存中都是连续存储,因此可以获得后面元素的指针:
& v[ i]
void fillArray ( int * pArray, size_t arraySize) ;
1. 从数组得到vector
vector< int > v ( 10 ) ;
fillArray ( & v[ 0 ] , 10 ) ;
string s ( v. begin ( ) , v. end ( ) ) ;
2. 将某个STL容器传入一个需要数组的API
set< int > st;
. . .
vector< int > v ( st. begin ( ) , s. end ( ) ) ;
fillArray ( & v[ 0 ] , v. size ( ) ) ;
第17条:使用“swap技巧”除去多余的容量
当调用vector的构造函数创建一个vector时,只会为此vector分配刚刚好容纳元素的内存; 而在insert或push_back等方式向容器添加元素时,若添加后超出了容量capacity,则会重新分配内存; 分配策略:通常时内存翻倍(若插入新元素之后,翻倍的空间可以装得下的话); 若翻倍都装不下,则分配刚刚能装下插入新元素之后所需的内存空间;
vector< int > v1;
vector< int > v2{ 1 , 2 , 3 } ;
vector< int > v3 ( 3 , 1 ) ;
vector< int > v4 ( v2) ;
v4. push_back ( 5 ) ;
v4. insert ( v4. end ( ) , 100 , 6 ) ;
vector若容量很大,但一小部分就够用了,就需要把多余部分删掉(即调整capacity为刚好装得下vector内的元素); v.shrink_to_fit(); 等价于 vector(v).swap(v);
vector< int > v{ 1 , 2 , 3 } ;
v. reserve ( 100000 ) ;
for ( auto c : v) {
cout << c << ' ' ;
}
cout << endl;
cout << v. size ( ) << ' ' << v. capacity ( ) << endl;
vector< int > ( v) . swap ( v) ;
for ( auto c : v) {
cout << c << ' ' ;
}
cout << endl;
cout << v. size ( ) << ' ' << v. capacity ( ) << endl;
v1.swap(v2); //实际上只是修改了两个寄存器的值(交换了两个寄存器中的地址),因此原先两容器的迭代器,指针,引用依然有效,且依然指向原本的元素(个人理解)
第18条:避免使用vector
vector不是一个STL容器,且它的每个元素存储的并不是一个bool值; ①vector中的每个bool只占1bit,即一个字节包含8个bool值,因此无法通过&v[i]获得i号元素的地址(因此无法使用16条的技术将一个vector传入一个需要数组的API) ②STL容器要求T* p = &c[0] 能通过编译 代理对象?? vector的两种替代品:
deque deque中存储的元素确实是单个bool; 但deque中的元素在内存中不连续,因此不提供reserve和capacity,且无法将deque传递给一个需要bool*的API(即使转化为vector作为中间量也不行,因为&v[0]并不能得到数组的地址); bitset 每个元素也只占1bit; 不是STL标准容器,因此不支持迭代器;大小在编译期确定,因此无法动态改变大小;