vector和string:13~18

第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中的元素个数
v.capacity();   //为该容器已分配的内存最多可以容纳多少个元素
v.resize(container::size_type n);  //强行使容器包含n个元素,若n>v.size(),则多出来的部分按默认构造函数初始化(注:若n>v.capaciy(),则先重新分配内存,再在新内存中进行resize操作);
								  //若n<v.size(),则析构多余的元素;
v.reserve(container::size_type n);   //调整容器的capacity,若想调小,则会被无视;
									//若想调大,则会分配一块至少能存放n个元素的内存(具体分配多大视编译器而定)
									//与resize的区别是:reserve仅仅修改容量,而不会去更改元素;resize可能会析构旧元素或默认初始化新元素
v.clear();  //把容器中的元素都清空,即v.size() = 0, 但v.capacity()不变,只是里面的内容没了;
vector<int>().swap(v);  //把v清空,不只元素clear,容量也清空为0
  • 最好在容器刚被创建出来,就调用v.reserve()为元素预留足够大的空间
vector<int> v;
for(int i = 0; i < 1000; ++i){
	v.push_back(i);
}//可能会最多重新分配10次空间(2^10≈1000),每次重新分配的空间都是翻倍

1.若提前知道或预估容器最终有多少个元素,可以在插入元素之前先reverse,预留一部分空间
vector<int> v;
v.reserve(1000);  //知道最终v存1000个元素,因此提前预留好位置,避免多次重新分配内存
for(int i = 0; i < 1000; ++i){
	v.push_back(i);
}

2.即使不知道最终需要多大空间,也可以多预留一点,如果最后多了,再去掉多余部分
vector<int> v;
v.reserve(2000);  //知道最终v存1000个元素,因此提前预留好位置,避免多次重新分配内存
for(int i = 0; i < 1000; ++i){
	v.push_back(i);
}
vector<int>(v).swap(v); //详情看下面17条

第15条:注意string实现的多样性

  • string实现有很多种:
  • 一个string对象通常包含:分配子;size; capacity; 保存的字符串值;可能还有引用计数等等;不同的string实现采用的结构也不同
  • 不同string实现的区别:
  1. 一个string对象的大小也是不确定的(通常是一个指针大小的1~7倍)
  2. 引用计数:有的string实现支持(引用计数在多线程环境下不友好,会出问题),有的不支持
  3. 创建一个string对象,可能需要多次动态分配内存
  4. string对象对于size和capacity可能共享,也可能不共享; (这点没懂)
  5. 不同的string实现对于字符内存的最小分配单位有不同策略(不懂)
  6. 有的string实现可能支持单个对象的分配子,也可能不支持(不懂)
  • 平时使用string没有什么区别,但在对于内存性能要求很高或需要在不同STL平台运行时,需要注意使用的是哪种实现;根据需要,权衡的选择所需的string实现

第16条:了解如何把vector和string数据传给旧的API

  • 有些API需要的参数是原始指针,而不能是迭代器;因此需从vector中获得原始指针
vector<int> v;
...
&v[0]
  • 因为vector和数组一样在内存中都是连续存储,因此可以获得后面元素的指针:
&v[i]
  • vector和数组有相同的内存布局:
void fillArray(int* pArray, size_t arraySize);  //API:向一个数组内写入内容
1.从数组得到vector
//提前创建一个vector,然后将vector获得的指针传入API,此API以为处理的是数组,实际在vector的内存上写入操作,这样就通过一个处理数组的API得到了一个vector
vector<int> v(10);
fillArray(&v[0], 10); 
//可以用得到的vector去做别的事情
string s(v.begin(), v.end());  //例如用来初始化别的STL容器
2. 将某个STL容器传入一个需要数组的API
//先由STL容器得到一个vector,再用vector去做数组该做的事(数组和vector有相同的内存布局,因此只能用vector作为中间量)
//之所以不直接将STL容器复制数据得到一个数组:①需要在编译期知道数组的大小 ②若提前不知道大小,只能new[]动态分配数组,但new[]没有vector好
set<int> st;
... 
vector<int> v(st.begin(), s.end()); //set得到vector
fillArray(&v[0], v.size());  //vector当作数组来用

第17条:使用“swap技巧”除去多余的容量

  • 当调用vector的构造函数创建一个vector时,只会为此vector分配刚刚好容纳元素的内存;
    而在insert或push_back等方式向容器添加元素时,若添加后超出了容量capacity,则会重新分配内存;
  • 分配策略:通常时内存翻倍(若插入新元素之后,翻倍的空间可以装得下的话);
    若翻倍都装不下,则分配刚刚能装下插入新元素之后所需的内存空间;
vector<int> v1;   //v1.size() = 0; v1.capacity() = 0;
vector<int> v2{1, 2, 3}; //v2.size() = 3; v2.capacity() = 3;
vector<int> v3(3, 1);  //v3.size() = 3; v3.capacity() = 3;
vector<int> v4(v2);  //v4.size() = 3; v4.capacity() = 3;

v4.push_back(5);   //v4.size() = 4; v4.capacity() = 6;  3翻倍变成6
v4.insert(v4.end(), 100, 6);  //v4.size() = 104; v4.capacity() = 104; 4个再多100个,容量就变成104个,刚好装下这些插入的新元素;
  • 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;//1 2 3
cout << v.size() << ' ' << v.capacity() << endl; // 3 100000

//以v为参数调用拷贝构造函数创建一个临时vector<int>,它的容量没有多余部分,与含多余部分的v交换;此语句执行完后,临时变量(现在保存的是含多余部分的
//那个vector<int>)生存期结束被析构;只剩下去除冗余部分的v;
vector<int>(v).swap(v);  //巧妙使用一个临时变量作swap,且该临时变量在该语句执行完之后会被析构,看起来好像单纯的删掉了v的容量中的多余部分
for(auto c : v){
    cout << c << ' ';
}
cout << endl; //1 2 3
cout << v.size() << ' ' << v.capacity() << endl; // 3 3 
  • 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的两种替代品:
  1. deque
    deque中存储的元素确实是单个bool;
    但deque中的元素在内存中不连续,因此不提供reserve和capacity,且无法将deque传递给一个需要bool*的API(即使转化为vector作为中间量也不行,因为&v[0]并不能得到数组的地址);
  2. bitset
    每个元素也只占1bit;
    不是STL标准容器,因此不支持迭代器;大小在编译期确定,因此无法动态改变大小;
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值