C++ Primer第九章②
C++ Primer
顺序容器
顺序容器操作
前面介绍的那些是所有容器都支持的,我们接下来介绍的只适用于顺序容器(以后还会介绍关联容器)。
向顺序容器添加元素
不知道为啥原书篇幅超多,我觉得直接看代码就很明了,所以我就写代码了:
list<int> a = {1, 2, 3}; //注释为a的元素内容{1, 2, 3} a.push_back(4); //{1, 2, 3, 4},array和forwar_list不支持 a.push_front(0); //{0, 1, 2, 3, 4},只有list,forward_list和deque支持
以上两种可以方便地在头尾添加元素,更进一步,insert允许我们在容器任意位置中插入多个元素,vector,deque,list,string都支持insert:每个insert接受一个迭代器作为第一个参数:
list<string> slist = {Jay"}; auto iter = slist.begin(); slist.insert(iter, "Hello"); //将Hello添加到iter之前的位置 //insert还有重载版本 vector<string> svec; svec.insert(svec.end(), 10, "May"); //在末尾插10个May vector<string> a = {"1", "2"}; slist.insert(slist.begin(), a.end()-1, a.end()); //插入了"2" slist.insert(slist.begin(), slist.begin(), slist.end()); //错误,要拷贝的迭代器不能指向自己
现在我要在vector中实现一个功能,每次插入的单词都插在头部:
vector<string> a; auto it = a.begin(); string word; while(cin >> word) { it = a.insert(it, word); }
因为insert会返回当前位置,调用insert会在前一个位置插入,相当于调用了push_front
新标准还引入了三个新成员-emplace_front,emplace和emplace_back,这些操作时构造元素,不是拷贝元素,它们仨分别于push_front,insert和push_back对应:
vector<Sales_data> c; c.emplace_back("1", 25, 16.8); //在c的末尾构造一个Sales_data对象(调用三个参数的构造函数) c.emplace_back("1", 25, 16.8); //这样不对的 c.emplace_back(Sales_data("1", 25, 16.8)) //这样可以
访问元素
每个顺序容器都有一个front函数,返回首元素的引用;除forward_list之外的都有一个back,返回尾元素的引用,还是看代码好理解:
vector<string> c = {"1", "2"}; if(!c.empty) { auto v1 = *c.begin(), v2 = c.front(); //v1,v2都是首元素的拷贝 auto last = c.end(); auto v3 = *(--last); //除forward_list之外 auto v4 = c.back(); //也除forward_list之外 }
c[n]和c.at(n) 返回下标为n的元素的引用:
if(!c.empty) { auto &v = c.back(); //获得尾元素的引用 v = “3”; //改变了c中的元素 auto v2 = c.back(); v2 = "0"; //没改c的元素,是个引用 }
与以前一样,如果用auto变量保存这些函数的返回值,并且要改变容器内的值,必须要将变量定义为引用类型。 下标一定要在合理范围内,这个程序员自己要注意,来个错误示范:
vector<int> a; cout << a[0]; //错了
删除元素
vector<int> a = {1, 2, 3, 4, 5, 6}; a.pop_front(); //删除首元素 a.pop_back(); //删除尾元素 //删除a中所有奇数 auto it = a.begin(); while(it != a.end()) { if(*it % 2) { it = a.eraser(it); //删除奇数 } else { ++it; } } //删除所有元素,两种方式 a.clear(); a.eraser(a.begin(), a.end());
特殊的forward_list操作
全部在下图了,也好记,用了再找也行:
把删除奇数元素的代码用forward_list重写一遍,要注意两个迭代器,一个指向要处理的元素,一个指向它前面那个元素:
forward_list<int> a = {1, 2, 3, 4, 5, 6}; auto pre = a.before_begin(); //首前元素 auto cur = a.begin(); //首元素 while(cur != a.end()) { if(*it % 2) { cur = a.eraser_after(pre); //删除并移动cur } else { pre = cur; //下一个 ++cur; } }
改变容器大小
除了array之外,我们可以用resize来改变容器大小:
- 如果当前大小大于所要求的的大小,容器后面的元素会被删除
- 如果当前大小小于新大小,会将新元素添加到容器后部
list<int> a(10, 42); //10个42 a.resize(15); //后面再加5个0,是默认初始化的, //如果是类类型,要么就有默认构造函数,要么就提供初始值 a.resize(25, -1); //后面再加10个-1 a.resize(5); //从末尾删除20个元素,就剩5个42
缩小容器,指向被删除元素的迭代器、引用和指针都会失效;对vector、string和deque进行resize也可能导致它们失效。
容器操作可能使迭代器失效
使用失效的指针、引用或迭代器是很严重的错误,我们来仔细分析一下,分添加和删除两种情况
添加元素:
- 容器是vector或string:
- 如果存储空间被重新分配,则指针等全部失效
- 未重新分配,插入位置之后的那些都失效
总之就是内存位置变了就不行了 - 对于deque,插入除了首尾之外的位置就会失效;如果在首尾添加,迭代器失效,引用和指针不会失效
- 对于list和forward_list,都还是有效的
删除元素:
(被删除的元素对应的那三样肯定挂了)
- 对于list和forward_list都有效
- 对于deque,
- 如果在首尾之外的任何位置删除,都失效;
- 如果删除尾元素,尾后迭代器失效,其他不影响,如果删除首元素
- 如果删除首元素,都不受影响
- 对于vector和string,被删除元素之前的都有效,所以尾后迭代器总会失效
注意函数返回迭代器的位置
我们来写一个函数,删除偶数元素,复制每个奇数元素:
vector<int> vi = {0, 1, 2, 3, 4, 5}; auto iter = vi.begin(); while(iter != vi.end()) { if(*iter % 2) { iter = vi.insert(iter, *iter); iter += 2; //跳过当前元素和复制元素 } else { iter = vi.eraser(iter); //删除偶数元素 //不用向前移动迭代器,因为eraser返回删除元素的下一个 } }
不要保存end返回的迭代器
vector<int> vi = {0, 1, 2, 3, 4, 5}; auto begin = vi.begin(); auto end = vi.end(); while(begin != end) { begin = v.insert(begin, 42); ++begin; //跳过刚刚加入的元素 }
有问题的,因为end记住了一个以前的end,更好的应该这样:
vector<int> vi = {0, 1, 2, 3, 4, 5}; auto begin = vi.begin(); while(begin != vi.end()) { begin = v.insert(begin, 42); ++begin; }#C++工程师#