c++容器真的很简单,你学废了吗?

发布于:2022-12-12 ⋅ 阅读:(690) ⋅ 点赞:(0)

以下内容参考c++ primer 和c++ primer,没法子。

 

学计算机,一定要有个非常强大的心理状态。计算机的所有的东西,都是人做出来的,别人能想出来的,我也一定能想出来,在计算机里面,没有任何黑魔法,所有的东西只不过我现在不知道,总有一天,我会把里面所有细节,所有内部东西全都搞明白。

------浙江大学翁恺

容器类型

vector 可变数组,支持快速随机访问(在尾部之外的位置插入/删除元素很慢)
deque 双端队列,支持快速随机访问。在头尾位置插入(删除和很快)
list 双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除很慢都很快)
forward_list 单项链表。只支持单向顺序访问。在链表任何位置进行插入都很快
array 固定大小数组。支持快速随机访问。不能删除或增加元素
string 与vector相似的容器。但专门用于保存字符。随机访问快。在尾部插入,删除速度快

注:List 和foward_list两个容器的设计目的是令容器任何位置的插入和删除操作都很快。但是,不能随机访问数组。访问一个元素,只能遍历整个容器。与vector、deque、array,这两个容器内存开销很大.


容器这么多,如何确定使用哪种容器?

  1. vector优先原则

  2. 如果空间要求非常高,空间要求非常小。可以使用,list,或forward_list。

  3. 头尾插入(删除),中间不需要插入(删除),可以使用deque

  4. 读输入时,需要在容器中间插入元素,随后又要随机访问元素,可以使用list


容器的操作

类型别名 解释
iterator 此容器的迭代器
con_iterator 可以读取元素,但不能修改元素的迭代器类型
size_type 无符号整数类型,足够保存此种容器类型最大可能容器的大小
diference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型;与value_type& 含义相同
const_refence 元素的const左值类型(const value_type&)
构造函数
C c; 默认构造函数
C c1(c2); 拷贝c2的拷贝c1
C c(b,e); 构造c,将迭代器b和e的指定范围内的元素拷贝到c(array 不支持)
C c(a,b,c...) 初始化列表c
赋值与swap
c1=c2 将C1种的元素替换为c2中元素
c1=(a,b,c...) 将c1中的元素替换为列表中元素(不适用于array)
a.swap(b) {或者swap(a,b)} 交换a和b的元素
大小
c.size() c元素的数目
c.max_size() c可保存的最大数目
c.empty() 若c中存储了元素,返回fasle,满了返回true
添加/删除(不适用arry) 在不同的容器中操作的接口都不一样
c.insert(args) 将args中元素拷贝到c
c.emplace(inits) 使用inits构造c中的一个元素
c.erase(args) 删除args指定的元素
c.clear() 删除c中所有元素,返回void
获取迭代器
c.begin(),c.end() 返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin(),c.cend() 返回const_iterator
反向容器的额外成员(不支持forward_list)
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改的逆序迭代器
c.rebegin(),c.rend() 返回指向c的尾元素和首元素之前位置的迭代器
c.crebegin(),c.crend() 返回指向const_reverse_iterator

迭代器

迭代器,类似容器的广义指针

迭代器的范围 [begin,end)

几个重要操作(bushi)容器的操作可能使迭代器失效

向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效

下面是具体的解释:

在向容器中添加元素后:

  • 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。

    如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍然有效,但指向插入位置之后的跌倒器、引用、指针将会失效。

  • 对于deque,插入到除首尾位置之外的任何元素都会导致迭代器、指针和指针失效。

如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。

  • 对于List和forward_list,指向容器的跌代器(包括尾后迭代器和首前迭代器)、指针和引用仍然有效。

当我们从一个容器中删除元素后。指向被删除元素的迭代器、指针和引用会失效,这应该不会令人惊讶。

  • 对于list和forward_list,指向容器其它位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍然有效。

  • 对于deque,如果在首尾之外的任何元素删除元素,那么指向被删除元素外其他元素的迭代器也会失效,但其他迭代、引用和指针不受影响;如果是删除首元素,这些也不会受到影响。

  • 对于vector和string,指向被删除元素之前的跌代器、引用和指针仍有效。注意:当我们删除元素时,尾后跌代器总会失效。

注意:

当你使用迭代器(或指向容器元素的引用或指针)时,最小化要求迭代器必须有效的程序片段是一个好的方法。由于向迭代器添加元素和从迭代器删除元素的代码可能使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。尤其对于vector、string、deque尤其重要。


编写改变容器的循环程序

添加(删除)vecto、string或deque元素的循环程序必须考虑迭代器、引用、和指针可能失效的问题。如果循环中调用的是insert或erase,那么更新迭代器很容易。这些操作都都返回迭代器,我们可以用来更新迭代器都很容易。

 //傻瓜循环,删除偶数元素,复制每个奇数元素
 vector<int> vi = {10,2,3,4,5,6,7,8,9};
 auto iter = vi.begin(); //调用begin而不是cbegin,因为我们要改变vi
 while (iter != vi.end())
 {
     if(*iter %2)
     {
         iter = vi.insert(iter,*iter);//复制当前元素
         iter += 2;//向前移动迭代器,跳过当前元素以及插入到它之前的元素
     }
     else
         iter = vi.erase(iter);//删除偶数元素
     //不应向前移动迭代器,iter指向我们删除的元素之后的元素
 }

不要保存end返回的迭代器

当我们删除/添加vector或string的元素后,或在deque中首元素之外任何位置添加/删除元素后,原来的end返回的迭代器总是会失效。因此,添加或删除元素的循环程序必须反复调用end,而不能在循环之前保存end返回的迭代器,一直当作容器末尾使用。

 //灾难:此循环的行为是未定义的
 auto begin = v.begin(),
 end = v.end();//保存尾迭代器的值是一个坏主意
 while(begin != end)
 {
     //做一些处理
     //插入新值,对begin重新赋值,否则的话它就会失效
     ++begin;//向前移动begin,因为我们向在此元素之后插入元素
     begin = v.insert(begin,42);//插入新值
     ++begin;//向前移动begin跳过我么加入的元素
 }

vector 对象是如何增长的

在介绍之前我们先介绍几个常见的容器函数的功能及其作用。先暂时不讨论有关的内存的操作。

capacity():在不分配新的内存的前提下它最多可以保存多少元素。也可以理解为预分配的内存空间

为啥有这个函数捏,我们细细道来。

举个栗子,就明白了。上代码。

 #include <vector>
 #include <iostream>
 using namespace std;
 int main() 
 {
     vector<int> ivec;
     //此时size应该为0;capacity的值依赖具体的实现。
     cout << "ivec的大小为:" << ivec.size() << " capacity:" << ivec.capacity() << endl;
 ​
     for (vector <int>::size_type ix = 0; ix != 24; ++ix)
     {
         ivec.push_back(ix);     //向ivec添加24个元素
         cout << "ivec的大小为:" << ivec.size() << " capacity:" << ivec.capacity() << endl;
     }
         
     //size应该为24,capacity应该大于等于24,具体值依赖标准库实现
     cout << "ivec的大小为:" <<  ivec.size() << " capacity:" << ivec.capacity() << endl;
     system("pause");
 }

输出结果:

  1. ivec的大小为:0 capacity:0

  2. ivec的大小为:1 capacity:1

  3. ivec的大小为:2 capacity:2

  4. ivec的大小为:3 capacity:3

  5. ivec的大小为:4 capacity:4

  6. ivec的大小为:5 capacity:6

  7. ivec的大小为:6 capacity:6

  8. ivec的大小为:7 capacity:9

  9. ivec的大小为:8 capacity:9

  10. ivec的大小为:9 capacity:9

  11. ivec的大小为:10 capacity:13

  12. ivec的大小为:11 capacity:13

  13. ivec的大小为:12 capacity:13

  14. ivec的大小为:13 capacity:13

  15. ivec的大小为:14 capacity:19

  16. ivec的大小为:15 capacity:19

  17. ivec的大小为:16 capacity:19

  18. ivec的大小为:17 capacity:19

  19. ivec的大小为:19 capacity:19

  20. ivec的大小为:20 capacity:28

  21. ivec的大小为:21 capacity:28

  22. ivec的大小为:22 capacity:28

  23. ivec的大小为:23 capacity:28

  24. ivec的大小为:24 capacity:28

  25. ivec的大小为:24 capacity:28

分析一下,此时你会懵逼,为啥容器的capacity()为和size()不一致。这是因为,我们插入了元素,插入了元素。超出了原来的容器的容量的大小,就需要重新分配内存。这是我们标准容器库设计者采取了内存分配的策略,并不是按照插入一个元素,分配一块内存,二是分配一块比新的需求还要大的内存。预留这些空间作为备用,用来保存更多的新元素。这样,不需要每次都申请,减少时间成本。

正在上传…重新上传取消

reverce(n):容器至少能够存储n个的内存空间。

c.resize(n):指定容器的大小为n个。若n<c.size()容器的大小,则多出去的元素被丢掉。

c.resize(n,t):调整c的大小为为n个元素,每个元素都初始化为t。

resize不适用arry容器

 List<int> ilist(10,90); //10个int,每个的值都是42
 ilist.resize(15);       //此时容器的大小为15,前面15个为42,将剩余的5个值为0的元素添加ilist的尾部
 ilist.resize(25,-1);//此时容器大小为25,将10个值为-1的元素添加到ilist的末尾
 ilist.resize(5);//从ilist尾部删除20个元素

重点(bushi,自己容易忘记的点)

  1. resize()既修改capacity大小,也修改size大小

  2. 如果resize缩小容器,则刚刚被删除的容器的元素的迭代器、引用、指针都会失效。因为你被删除了。

  3. 既分配了空间,也创建了对象。这里空间就是capacity,对象就是容器中的元素。

本文含有隐藏内容,请 开通VIP 后查看