《C++ Primer》读书笔记-第九章 03 顺序容器操作

作者:马志峰
链接:https://zhuanlan.zhihu.com/p/24432568
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

声明:

  • 文中内容收集整理自《C++ Primer 中文版 (第5版)》,版权归原书所有。
  • 原书有更加详细、精彩的释义,请大家购买正版书籍进行学习。
  • 本文仅作学习交流使用,禁止任何形式的转载

正文

  1. 添加元素
  2. 访问元素
  3. 删除元素
  4. 改变容器大小
  5. 其他

向顺序容器添加元素

  • push
    • push_back
    • push_front
  • insert
  • implace

向vector、string或deque插入元素有可能使所有指向容器的迭代器、引用和指针失效。因为添加元素时可能引起整个对象空间的重新分配(旧的空间不足以容纳插入新元素后的对象)。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。

作者说:

使用添加、删除等容器操作时,首先考虑容器分配元素空间的策略

push_back

向容器尾部添加元素

除array和forward_list外,每个顺序容器都支持。(不需要强记,考虑容器分配元素空间的策略)

container.push_back(word);

作者说:

当我们用一个对象来初始化容器,或将一个对象插入到容器中时,实际上放入的是对象值的一个拷贝,而不是对象本身。也不是对象的引用,两者之间没有任何关联。

push_front

将元素插入到容器头部

仅list、forward_list和deque支持(不需要强记,考虑容器分配元素空间的策略)

for(size_t ix = 0; ix != 4; ++ix )
{
    ilist.push_front(ix);
}

每个元素都插入到容器的头部,最终形成逆序。
这一特性我们可以加以利用,比如重写一下Leetcode整数反序?

insert

在任意位置插入0个或多个元素

vector、deque、list和string都支持
forward_list有特殊版本的insert

insert有多个重载函数

  • insert(itr, t) 在itr所指位置之前插入t
  • insert(itr, n, t) 在itr所指位置之前插入n个t
  • insert(itr, itr1, itr2) 在itr所指位置之前插入[itr1,itr2)范围内的元素
  • insert(itr, il) 在itr所指位置之前插入一个{}列表

共同特点:

  1. 第一个参数都是迭代器,用来指定插入位置
  2. 都将元素插入到给定迭代器所指位置之前
  3. 返回一个迭代器,指向新添加的第一个元素

insert函数也具备push_back和push_front的功能

list.insert(list.begin(), 1);
list.insert(list.end(), 1);

在首元素之前插入,即是push_front
在尾后元素之前插入,即是push_back

利用insert函数的返回值

list<string> lst;
auto iter = lst.begin();
while( cin >> word )
{
    iter = lst.insert( iter, word );
}

等价于一直在调用push_front

emplace

新标准引入

push和insert插入的都是现成的对象
emplace则是创建一个对象插入到容器中
因此,它的参数与容器元素类型的构造函数的参数一致

struct Sales_data{
    Sales_data(const string &s, unsigned n, double p);
};

list<Sales_data> c;
c.emplace("934", 25, 1.2);

使用emplace只需要记住这一区别就可以了,其他可以作类比

push_front----emplace_front
push_back----implace_back
insert-----implace

访问元素

  • back
  • front
  • 下标
  • at

返回值是对应位置元素的引用

想要得到容器首尾位置的元素,可以

auto val1 = *c.begin();
auto last = c.end();
auto val2 = *(--last);

更为简单的方法是使用front和back

auto val1 = c.front();
auto val2 = c.back();

注意使用之前需要判断容器是否为空

at和下标相比,如果出现下标越界的情况,at会抛出一个out_of_range异常

删除元素

  • pop
    • pop_back
    • pop_front
  • erase
  • clear

可以和插入的操作对比来理解

  • erase(itr)
  • erase(itr1, itr2)

erase的返回值也是一个迭代器,指向最后一个被删除元素之后

一个典型的例子,删除List中的所有奇数元素

list<int> lst = {0,1,2,3,4,5,6,7,8,9};
auto it = lst.begin();
while( it != lst.end() )
{
    if( *it % 2 )
    {
        it = lst.erase(it);
    }
    else
    {
        ++it;
    }
}

forward_list操作

前面提到,首先考虑容器分配元素空间的策略。forward_list正是因为分配策略的不同,需要特殊的操作来支持

ele1-->ele2-->ele3-->ele4  
ele1-->ele2--------->ele4

每个元素除了保存元素值之外,还要保存后一个元素的地址。
因此删除ele3,需要修改ele2的后继,而我们又没有办法通过ele3拿到ele2。

forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。

insert_after(p,t)
insert_after(p,n,t)
insert_after(p,b,e)
insert_after(p, il)
erase_after(p)
erase_after(b,e)
implace_after(p,args)

同样的道理,尾后迭代器对forward_list来说也是没有意义的,因此增加了首前迭代器

before_begin()

改变容器大小

c.resize(n, t)

如果缩小容器,则容器后部的元素会被删除
如果放大容器,则使用t来初始化新添加的元素

t是可选的

迭代器失效

前面也提到了,添加、删除元素可能导致迭代器失效

建议在改变容器的操作后面,都重新定位迭代器

vector<int> vi = {0,1,2,3,4,5,6,7,8,9};
auto itr = vi.begin();

while( itr != vi.end() )
{
    if( *itr % 2 )
    {
        itr = vi.insert( itr, *itr );
        itr += 2;
    }
    else
    {
        itr = vi.erase( itr );
    }
}

这里使用了insert和erase的返回值来重新定位迭代器

而且循环条件中,使用了vi.end(),而不是提前获取

auto end = vi.end();
wihle( begin != end ){}

增删操作后,end同样会发生改变,因此应当即时获取

  1. 使用返回值重新定位迭代器
  2. 即时获取end,不要缓存end值

猜你喜欢

转载自blog.csdn.net/qq_26751117/article/details/53730436