C++ Primer第五版之(六)顺序容器

顺序容器

容器库

  1. vector,可变大小数组,支持快速随机访问,在尾部位置插入、删除速度很快。
    ①string用于保存字符,与vector相似的容器,支持快速随机访问,在尾部位置插入、删除速度很快。
    ②vector和string将元素保存在连续的内存空间中,由下标计算元素地址非常快速。
    ③但在容器中间位置添加或删除元素非常耗时,需要移动插入、删除位置之后的所有元素,来保持连续存储,可能还需要额外分配存储空间。

  2. deque,双端队列,支持快速随机访问,在头尾位置插入、删除速度很快。
    ①deque支持快速随机访问,与vector和string一样在中间位置添加或删除元素代价很高,但在两端添加或删除元素很快。

  3. list,双向链表,支持双向顺序访问,在任何位置插入、删除速度都很快。
    ①list和forward_list容器的设计是令容器在任何位置添加和删除都很快速,但不支持随机访问。
    ②与vector、deque和array相比,这两个容器的额外内存开销也很大。

  4. forward_list,单向链表,支持单向顺序访问,在任何位置插入、删除速度都很快。
    ①forward_list设计目标是达到最好的手写单向链表数据结构相当的性能,因此没有size操作,保存和计算大小会比手写链表多出额外开销。

  5. array,固定大小数组,支持快速随机访问,不能添加或删除元素。
    ①与内置数组相比,array是一种更安全、更容易使用的数组类型,大小固定,不支持添加和删除元素以及改变容器大小的操作。

选用标准

  1. 现代C++程序应使用标准库容器,而不是原始数据结构,例数组。

  2. 除非有很好理由选用其他容器,否则使用vector。

  3. 如果有很多小的元素,且空间开销很重要,不要选用list和forward_list。

  4. 如果要求随机访问元素,选用vector和deque。

  5. 如果需要头尾插入、删除元素,但不会在中间位置插入、删除,选用deque。

  6. 如果只有在读取输入时需要在容器中间位置插入元素,随后需要随机访问元素:
    ①确定是否需要在容器中间位置插入元素,当处理输入数据时,通常可以向vector追加数据,再调用标准库sort函数重排容器中的元素,从而避免在中间位置添加元素。
    ②如果必须在中间位置插入元素,考虑在输入阶段使用list,输入完成后将内容拷贝至vector。
    ③如果不确定使用哪种容器,可以使用vector和list的公共操作,使用迭代器操作,不使用下标操作,避免随机访问。

容器定义和初始化

  1. 每个容器都定义了默认构造函数,除array外其他容器的默认构造函数都会创建一个指定类型的空容器,可接受指定容器大小和元素初始值的参数。

  2. 构造函数
    ①C c; 默认构造函数,构造空容器;C是array时,c中元素按默认方式初始化。
    ②C c1(c2); 构造c2的拷贝c1,c1和c2容器相同元素类型相同,array时c1和c2大小相同。
    ③C c(b, e); 构造c,将迭代器b和e指定范围内的元素拷贝到c中,array不支持。
    ④C c{a, b, c…}; 列表初始化c,列表中元素类型与容器元素类型相同,array类型时列表中元素数目小于或等于容器大小,其他元素值初始化。
    ⑤C c = {a, b, c…}; 列表初始化c。

  3. 顺序容器(不包括array)的构造函数可接受大小参数。
    ①构造函数接受大小参数时,使用元素类型的默认构造函数初始化。
    ②如若元素类型没有默认构造函数,则在构造容器时不能只传递元素数目,必须提供一个元素初始化器C c1(n, init)。
    ③C seq(n); seq包含n个元素,元素值初始化,此构造函数是explicit的(string不适用)。
    ④C seq(n, t); seq包含n个初始化值为t的值。

  4. 将一个容器初始化为另一个容器的拷贝。
    ①直接拷贝整个容器,要求容器类型及元素类型一致。
    ②拷贝一个迭代器指定的元素范围(array不支持),不要求容器与元素范围的类型相同,只要能将拷贝元素转换为容器元素类型即可。

赋值与swap

  1. 赋值
    ①c1 = c2; 将c1中的元素替换为c2中的元素。
    ②c1 = {a, b, c…}; 将c1中的元素替换为列表中元素(不适用于array)。
    ③与内置数组不同,标准库array允许赋值,但赋值左右两边的元素类型及大小必须相同。
    ④不能将一个花括号列表赋予数组名,数组不支持第二种赋值。

  2. assign操作不适用于关联容器及array。
    ①seq.assign(b, e); 将seq中的元素替代为迭代器b和e指定范围的元素,b和e不能指向seq。
    ②seq.assign(i1); 将seq中的元素替换为初始化列表i1中的元素。
    ③seq.assign(n ,t); 将seq中的元素替换为n个值为t的元素。

  3. swap
    ①a.swap(b); 交换a和b的元素,函数版本的swap。
    ②swap(a, b); 交换a和b的元素,非成员版本的swap。
    ③赋值相关运算会导致指向容器内部左边的迭代器、引用和指针失效,而swap操作(array和string除外)不会。
    ④swap对元素本身未交换,只交换了容器内部数据结构,但对一个string调用swap会导致迭代器、指针和引用失效,swap两个array会真正交换它们的元素。
    ⑤新标准库中,容器既提供成员函数版本的swap也提供非成员版本的swap,非成员版本的swap在泛型编程中非常重要,通常情况下使用非成员版本swap较好。

插入元素和删除元素(forward_list不支持)

  1. 除array外标准容器提供灵活的内存管理,在运行时可动态添加或删除元素来改变容器大小。

  2. vector和string不支持push_front和emplace_front。
    ①c.push_back(t); 在c的尾部创建一个值为t的元素,返回void。
    ②c.emplace_back(args);在c的尾部创建由args构造的元素,返回void。
    ③c.push_front(t); 在c的头部创建一个值为t的元素,返回void。
    ④c.emplace_front(args);在c的头部创建由args构造的元素,返回void。
    ⑤c.insert(p, t); 在迭代器p指向的元素之前插入一个值为t的元素,返回指向新添加元素的迭代器。
    ⑥c.emplace(p, args);在迭代器p指向的元素之前插入由args构造的元素,返回指向新添加元素的迭代器。
    ⑦c.insert(p, n, t); 在迭代器p指向的元素之前插入n个值为t的元素,返回指向新添加的第一个元素的迭代器,若n为0返回p。
    ⑧c.insert(p, b, e); 将迭代器b和e指定范围内的元素插入到迭代器p指向元素之前,b和e不能指向c中的元素,返回指向新添加的第一个元素的迭代器,若范围为空返回p。
    ⑨c.inset(p, i1); i1是一个花括号包围的元素值列表,将给定值插入到迭代器p指向的元素之前,返回指向新添加的第一个元素的迭代器,若列表为空返回p。

  3. insert插入头位置时等价于push_front。

  4. push_back、push_front和insert是拷贝元素到容器中,emplace是操作构造函数,将参数传递给元素类型的构造函数。

  5. push_back可以调用类的实参创建临时对象压入容器中,emplace_back会在容器管理的内存空间中直接创建对象,emplace函数的参数必须与元素类型的构造函数匹配。

  6. vector和string不支持pop_front。
    ①c.pop_back(t); 删除c中尾元素,若c为空,函数行为未定义,返回void。
    ②c.pop_front(t); 删除c中首元素,若c为空,函数行为未定义,返回void。
    ③c.erase( p); 删除迭代器中p所指向的元素,返回指向被删元素之后元素的迭代器,若p指向尾元素,返回尾后迭代器,若p为尾后迭代器,函数行为未定义。
    ④c.erase(b, e); 删除迭代器b和e指定范围内的元素,返回指向最后一个被删元素之后元素的迭代器,若e为尾后迭代器,返回尾后迭代器。
    ⑤c.clear(); 删除c中所有元素,返回void。

特殊的forward_list操作

  1. forward_list有专用版本的insert、emplace和erase,不支持push_back、emplace_back和pop_back。

  2. 当添加或删除元素时,添加或删除元素之前元素的后继会发生改变,而在单向链表中,没有简单的办法获取元素的前驱,以改变前驱的链接。

  3. 在forward_list中添加删除元素的操作是给定元素之后元素的操作。
    ①lst.before_begin(); 返回指向链表首元素之前不存在元素的迭代器,此迭代器不可解引用。
    ②lst.cbefore_begin(); 返回const_iterator。
    ③lst.insert_after(p, t); 在迭代器p之后的位置插入元素,t是一个对象,返回指向插入元素的迭代器。
    ④lst.insert_after(p, n, t); 在迭代器p之后的位置插入元素,t是一个对象,n是数量,返回指向最后一个插入元素的迭代器。
    ⑤lst.insert_after(p, b, e); b和e是表范围的迭代器,不能指向lst内,范围为空,返回p。
    ⑥lst.insert_after(p, i1); i1是一个花括号列表,若p为尾后迭代器,函数行为未定义。
    ⑦lst.emplace_after(p, args); 使用args在p指定位置之后创建一个元素,返回指向这个新元素的迭代器,若p为尾后迭代器,则函数行为未定义。
    ⑧lst.erase_after( p); 删除p指向位置之后的元素,返回一个指向被删元素之后元素的迭代器。
    ⑨lst.erase_after(b, e); 删除b之后到e之间元素,p指向尾元素或尾后元素函数行为未定义。

其他操作

  1. resize增大或缩小容器,array不支持。
    ①c.resize(n, t);
    ②如果当前大小大于要求的大小,容器后部的元素被删除,如果当前小于新大小,将新元素添加到容器后部。
    ③resize操作接受一个可选的元素值参数,如果调用者未提供此参数,新元素值初始化。
    ④如果容器保存的是类类型元素,则必须提供初始值,或者元素类型有默认构造函数。

  2. vector对象的增长
    ①为了支持快速随机访问,vector元素连续存储。
    ②当空间需要重新分配时,vector和string通常会分配比需求空间更大的内存空间,容器预留这些空间作为备用,用来保存更多新元素。

  3. 顺序容器都有front成员函数(包括array),顺序容器都有back成员函数(除forward_list),分别返回首元素和尾元素的引用,容器非空时引用。

  4. at和下标操作只适用于string、vector、deque和array,at会提示越界,下标不会。

迭代器、引用和指针

  1. 向vector、string插入元素,如果存储空间重新分配,指向容器的迭代器、引用和指针失效,如果存储空间未重新分配,指向插入位置之前元素的迭代器、引用和指针仍有效,但指向插入位置之后元素的迭代器、引用和指针失效。

  2. vector和string指向被删位置之前的迭代器、引用和指针仍有效,删除无论什么位置尾后迭代器一定失效。

  3. deque插入首尾之外位置的迭代器、引用和指针失效,但在首尾位置添加元素,迭代器会失效,指向元素的引用和指针不会失效。

  4. 删除deque中首尾之外位置迭代器、引用和指针失效,如果删除尾元素则尾后迭代器会失效,其他迭代器、引用和指针不受影响,删除首元素都不受影响。

  5. 插入和删除,list和forward_list指向容器的迭代器(包括尾后和首前)、引用和指针仍有效。

容器操作

  1. 类型别名
    ①iterator,此容器类型的迭代器类型。
    ②const_iterator,可以读取元素,但不能修改元素的迭代器类型。
    ③size_type,无符号整数类型,足够保存此种容器类型最大可能容器的大小。
    ④difference_type,带符号整数类型,足够保存两个迭代器之间的距离。
    ⑤value_type,元素类型。
    ⑥reference,元素的左值类型,与value_type&含义相同。
    ⑦const_reference,元素的const左值类型,即const_value_type&。

  2. 大小
    ①c.size(); c中元素的数目(forward_list不支持)
    ②c.max_size(); c中可保存最大元素数目
    ③c.empty(); c中存储了元素返回false,否则返回true

  3. 添加/删除元素(不适用于array),不同容器中,操作的接口不同
    ①c.insert(args); 将args中的元素拷贝进c
    ②c.emplace(inits); 使用inits构造c中的一个元素
    ③c.erase(args); 删除args指定的元素
    ④c.clear(); 删除c中所有元素,返回void
    ⑤==,!= 所有容器都支持相等(不等)运算符
    ⑥<,<=,>,>= 关系运算符(无序关联容器不支持)

  4. 获取迭代器
    ①c.begin(),c.end(); 返回指向c的首元素和尾元素之后位置的迭代器
    ②c.cbegin(),c.cend(); 返回const_iterator

  5. 反向容器的额外成员(forward_list不支持)
    ①reverse_iterator; 按逆序寻址元素的迭代器
    ②const_ reverse_iterator; 不能修改元素的逆序迭代器
    ③c.rbegin(),c.rend(); 返回指向c的尾元素和首元素之前位置的迭代器
    ④c.crbegin(),c.crend(); 返回const_ reverse_iterator

  6. 容器关系运算符
    ①除了无序关联容器所有容器都支持关系运算符>、>=、<、<=,运算对象是具有相同类型的元素和相同类型的容器。
    ②容器的关系运算符使用元素的关系运算符完成比较,当元素定义了相应的比较运算符时,才可使用关系运算符比较容器。
    ③运算符的工作方式与string关系运算符类似。

管理容量的成员函数

  1. capacity和reserve只适用于vector和string。
    ①c.capacity(); 不重新分配内存空间的话,c可以保存多少元素。
    ②c.reserve(n); 分配至少能容纳n个元素的内存空间。

  2. shrind_to_fit只适用于vector、string和deque。
    ①c.shrind_to_fit(); 将capacity()减少为与size()相同大小。

  3. 当需求小于或等于当前容量,reserve什么也不做,容器不会退回内存空间。

  4. shrink_to_fit会退回不需要的内存空间,但具体实现可以选择忽略此请求,并不保证一定退回内存空间。

  5. size()是指已经保存的元素数目,capacity是在不分配新内存的前提下最多可保存多少元素。

编写改变容器的循环程序

  1. 添加删除vector、string和deque元素的循环程序必须考虑迭代器、引用和指针失效的问题。
    ①程序必须保证每个循环中更新迭代器、引用和指针,如果循环中调用的是insert和erase操作会返回迭代器,可用来更新。

  2. 不要保存end返回的迭代器。
    ①当向vector、string添加删除元素,或在deque中首位置之外添加删除元素,尾后迭代器都会失效,循环程序必须反复调用end,不能在循环之前保存end返回的迭代器。

额外的string操作

构造函数

  1. 除了string已有的构造函数,与顺序容器相同的构造函数,string类型还支持三个构造函数。
    ①string s(cp, n); s是cp指向的数组中前n个字符的拷贝,此数组至少包含n个字符。
    ②string s(s2, pos2); s是string s2从下标pos2开始的字符的拷贝。
    ③string s(s2, pos2, len2); s是string s2从下标pos2开始的len2个字符的拷贝,若pos2>s2.size(),构造函数行为未定义,构造函数至多拷贝s2.size()-pos2个字符。

  2. 当从const char*创建string时,指针指向数组必须以空字符结束,拷贝操作遇到空字符时停止。
    ①如果给构造函数传递一个计数值时,数组不必以空字符结束。
    ②如果数组不以空字符结束也未传递计数值,或给定计数值大于数组大小,构造函数的行为未定义。

  3. 当从一个string拷贝字符时,可提供一个可选的开始位置和一个计数值。
    ①开始位置必须小于或等于给定的string大小,如果传递了计数值,不管要求拷贝多少,标准库最多拷贝到string结尾。
    ②s.substr(pos, n); 返回一个string,原始string的一部分或全部的拷贝,开始位置不可超过string的大小,最多拷贝到string的末尾。

插入和删除

  1. insert和erase除了接受迭代器的版本外,string还提供了接受下标的版本,并提供接受C风格字符串数组的版本。
    ①s.insert(pos, args); 在pos之前插入args指定字符,pos可以是一个下标或一个迭代器,接受下标的版本返回指向s的引用,接受迭代器的版本返回指向第一个插入字符的迭代器。
    ②s.erase(pos, len); 删除从位置pos开始的len个字符,len可选,不提供len时删除pos开始直至s末尾所有字符,返回一个指向s的引用。
    ③s.assign(args); 将s中的字符替换为args指定的字符,返回指向s的引用。

  2. string类还定义了两个额外的成员函数append和replace。
    ①s.append(args); 将args追加到s,返回一个指向s的引用。
    ②s.replace(range, args); 删除range内字符,替换为args的字符,range是一个下标或一个长度或一对指向s的迭代器,返回指向s的引用。

  3. args可以是下列形式之一,append和assign可以使用所有形式,str不能与s相同,迭代器b和e不能指向s。
    ①str,字符串str。
    ②str, pos, len,str中从pos开始的(最多)len个字符。
    ③cp, len, 数组cp前(最多)len个字符。
    ④cp,以空字符结尾的字符数组cp。
    ⑤n, c, n个字符c。
    ⑥b, e,迭代器b和e指定范围内字符
    ⑦初始化列表,花括号包围的,以逗号分隔的字符列表。

  4. replace和insert允许的args依赖于range和pos如何指定。

find和compare

  1. string类提供了6个不同的搜索函数,每个函数都有4个重载版本。
    ①s.find(args); 查找s中args第一次出现的位置。
    ②s.rfind(args); 查找s中args最后一次出现的位置。
    ③s.find_first_of(args); 在s中查找args任何一个字符第一次出现的位置。
    ④s.find_last_of(args); 在s中查找args任何一个字符最后一次出现的位置。
    ⑤s.find_first_not_of(args); 在s中查找第一个不在args中的字符。
    ⑥s.find_last_not_of(args); 在s中查找最后一个不在args中的字符。

  2. args必须是下列形式之一:
    ①c, pos,从s中位置pos开始查找字符c,pos默认为0。
    ②s2, pos,从s中位置pos开始查找字符串s2,pos默认为0。
    ③cp, pos,从s中位置pos开始查找数组cp以空字符结尾C风格字符串,pos默认为0。
    ④cp, pos, n,从s中位置pos开始查找以数组cp前n个字符,pos默认为0。

  3. string提供的compare函数与C标准库strcmp函数相似,根据s与指定字符串比较,compare返回0、正数、负数,args有6个版本:
    ①s2; 比较s和s2
    ②pos1, n1, s2; 将s中从pos1开始的n1个字符与s2进行比较
    ③pos1, n1, s2, pos2, n2; 将s中从pos1开始的n1个字符与s2中pos2开始的n2个字符进行比较
    ④cp; 比较s与cp指向的以空字符结束的字符数组
    ⑤pos1, n1, cp;将s中从pos1开始的n1个字符与cp指向的以空字符结束的字符数组进行比较
    ⑥pos1, n1, cp, n2; 将s中从pos1开始的n1个字符与cp指向的n2个字符进行比较

其他

  1. 数值转换
    ①to_string(val); 返回数值val的string表示,val可以是任何算术类型
    ②stoi(s, p, b);
    ③stol(s, p, b);
    ④stoul(s, p, b);
    ⑤stoll(s, p, b);
    ⑥stoull(s, p, b);
    ⑦stof(s, p);
    ⑧stod(s, p);
    ⑨stold(s, p);

  2. b表示转换所用基数,默认为10,p保存第一个非数值字符的下标,默认为0。

  3. string参数第一个非空白字符必须是符号+、-或数字,可以以0x或0X开头来表示十六进制,将字符串转换为浮点值的参数也可以以小数点开头,并可以包含e或E来表示指数部分。

容器适配器

顺序容器适配器

  1. 除了顺序容器,标准库定义三个顺序容器适配器stack、queue和priority_queue,适配器接受已有的容器类型,使其行为看起来像不同的类型。

  2. 容器适配器支持的类型:
    ①size_type,一种类型,足以保存当前类型的最大对象的大小
    ②value_type,元素类型
    ③container_type,实现适配器的底层容器类型
    ④关系运算符,适配器支持所有关系运算符: ==、!=、<、<=、>、>=,运算符返回底层容器的比较结果

  3. 容器适配器支持的操作
    ①A a; 创建名为a的空适配器
    ②A a( c); 创建名为a的适配器,带有容器c的拷贝
    ③a.empty(); 若a包含元素,返回false,否则返回true
    ④b.size(); 返回a中元素数目
    ⑤swap(a, b); 交换a和b的内容,a和b具有相同的内容,底层容器类型相同
    ⑥a.swap(b);

定义适配器

  1. 每个适配器定义两个构造函数,默认构造函数创建空对象,接受容器的构造函数拷贝容器来初始化适配器。

  2. 默认情况下,stack和queue基于deque实现,priority_queue基于vector实现;创建适配器时可将顺序容器作为第二个类型参数来重载默认容器类型。

  3. 适配器要求容器具有添加删除元素和访问尾元素的能力,因此适配器不能构造在array上,同样不可构造在forward_list上。
    ①stack要求push_back、pop_back、back操作;可构造在除array和forward_list之外的容器上。
    ②queue要求push_back、pop_front、back、front操作;可构造在list和deque上。
    ③priority_queue要求push_back、pop_back、back操作和随机访问能力,可构造在vector和deque上。

栈适配器

  1. 定义在stack头文件中,支持如下操作:
    ①s.pop(); 删除栈顶元素,不返回元素值
    ②s.push(item); 拷贝或移动item压入栈顶
    ③s.emplace(args); args构造新元素压入栈顶
    ④s.top(); 返回栈顶元素,元素不弹出栈

  2. 容器适配器基于底层容器类型定义了自己的特殊操作,只可以使用适配器操作,不可使用底层容器操作。

队列适配器

  1. queue和priority_queue定义在queue头文件中,支持以下操作:
    ①q.pop(); 弹出queue首元素或priority_queue最高优先级元素
    ②q.front(); 返回首元素或尾元素,不删除此元素
    ③q.back(); 只适用于queue
    ④q.top(); 返回最高优先级元素,不删除该元素
    ⑤q.push(item); 在queue末尾或priority_queue适当位置创建新元素
    ⑥q.emplace(args); 使用args构造新元素

  2. 标准库queue使用先进先出的存储和访问策略。

  3. priority_queue允许为队列中元素建立优先级,新加入元素排在优先级比它低的已有元素之前。
    ①默认情况下,标准库在元素类型上使用<运算符确定相对优先级。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值