【C++Primer笔记】第九章 顺序容器

(本章笔记作为编程参考)

一个容器就是一些特定类型对象的集合。

顺序容器的存储访问与加入容器时的位置对应;有序和无序关联容器则根据关键字的值来存储元素。

顺序容器概述

回顾数据结构,每个顺序容器(顺序表、队列、链表等)都有利有弊:是否支持随机访问、插入元素是否快速。

  • vector:可变大小数组,支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
  • deque:双端队列,支持快速随机访问。在头尾位置插入/删除速度很快
  • list:双向链表,只支持双向顺序访问,在list中任意位置进行插入/删除都很快(链表有额外内存开销)
  • forward_list:单向链表,只支持单向顺序访问,在链表任何位置进行插入/删除都很快,不能使用size
  • array:固定大小数组,支持快速随机访问,不能添加或者删除元素。相比于内置数组,是更安全、更易使用的数组类型
  • stringvector相似,但只用于保存字符,随机访问快,在尾部插入/删除速度快

选择容器的基本原则

  • 除非你有很好的理由选择其他容器,否则使用vector是最好的选择
  • 如果你的程序有很多很小的元素,且空间的额外开销很重要,那么不要使用listforward_list
  • 如果程序要求随机访问元素,那么使用vectordeque
  • 如果程序要求在容器的中间插入或者删除元素,应使用listforward_list
  • 如果程序只需要在头尾位置插入或者删除元素,但不会在中间位置进行插入或删除操作,则使用deque
  • 如果程序只有在读取输入时才需要在容器中间插入元素,那么你有两种做法:一是使用vector添加元素,然后调用标准库的sort重排;二是在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector

容器库概览

每个容器都定义在同名头文件中。

如果我们定义一个容器,其元素类型是另一个类型,要注意特殊操作是否能使用(如vector<noDefault> v(10);中,如果noDefault没有默认构造函数,将是不合法的)。

容器公共操作

类型别名(必须使用作用域运算符,如vector<int>difference_type count

  • iterator:此容器类型的迭代器类型
  • const_iterator:可以读取元素,但是不能修改元素的迭代器类型
  • size_type:无符号整数类型,足够保存此种容器类型最大可能容器的大小
  • difference_type:带符号整数类型,足够保存两个迭代器之间的距离
  • value_type:元素类型
  • reference:元素的左值类型,与value_type& 含义相同
  • const_reference:元素的const左值类型,即const value_type&

构造函数

C c; //默认构造函数, 构造空容器
C c(c2); //构造c2的拷贝c1
C c(b, e); //构造c, 将迭代器b和e指定范围内的元素拷贝到c
C c{a, b, c...}; //列表初始化c

赋值与swap

c1 = c2; //将c1中的元素替换为c2
c1 = {a, b, c...}; //将c1中的元素替换为列表中元素(array不可)
swap(a, b); //交换a和b的元素

大小

c.size(); //c中元素的数目, 不支持forward_list
c.max_size(); //c可保存的最大元素数目
c.empty(); //判断c中是否保存元素

添加/删除元素(不适用于array

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.rbegin(), c.rend() //不能修改元素的逆序迭代器
c.crbegin(), c.crend() //返回const_reverse_iterator

迭代器

容器迭代器都支持解引用、递增、递减(除forward_list外)、相等/不等运算。

只有vectordequearraystring支持加减(加/减上一个整数或一个迭代器)算术运算和不等号比较。

一个迭代器范围由一对迭代器(beginend)表示。该范围包含:从begin开始到end之前的一个元素,即:[begin, end).

while (begin != end) {
    *begin = val;
    ++begin;
}

当不需要写访问时,应该使用cbegincend

容器定义和初始化

当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。不过当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了,新容器内的元素类型只要能转换成初始化容器的元素类型即可

list<string> authors = {"Milton", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};

// 拷贝方式一:直接拷贝整个容器, 要求容器类型和元素类型都必须相同
list<string> list2(authors);    // 正确: 类型匹配
vector<string> words(articles); // 错误: 类型不匹配

// 拷贝方式二:传递迭代器参数来拷贝一个范围
// 正确: 可以将const char* 元素转换为string
forward_list<string> words(articles.begin(), articles.end());

只有顺序容器才支持大小参数,关联容器不支持:

C seq(n)    // seq包含n个元素, 这些元素进行了值初始化; 此构造函数是explicit的(string不适用)
C seq(n,t)  // seq包含n个初始化为t值得元素

定义一个array时,除了制定元素类型,还要指定容器大小:

array<int, 10> ia1;   // 10个默认初始化的int
array<int, 10> ia2 = {42}; // ia3[0]为42, 剩余元素为0
ia2 = {0}; //错误,不允许将花括号列表赋值array(见下)

虽然我们不能对内置数组赋值或拷贝,但是array并无此限制。

赋值和swap

// 将c1中的元素替换为c2中元素的拷贝, c1和c2必须具有相同的类型
c1=c2
// 将c1中元素替换为初始化列表中元素的拷贝(array不适用)
c={a,b,c...}
// 交换c1和c2中的元素, c1和c2必须具有相同的类型, swap操作通常比从c2向c1拷贝元素快得多
swap(c1, c2)
c1.swap(c2)
// assign不适用于关联容器和array
// 将seq中的元素替换为迭代器b和e所表示范围的元素
seq.assign(b,e) 
// 将seq中的元素替换为初始化列表il中的元素
seq.assign(il)
// 将seq中的元素替换为n个值为t的元素
seq.assign(n,t)

顺序容器操作

添加元素

  • c.push_back(t), c.emplace_back(args):在c的尾部创建一个值为t或者由args创建的元素
  • c.push_front(t), c.emplace_front(args):在c的头部创建一个值为t或者由args创建的元素(vectorstring不支持)
  • c.insert(p,t), c.emplace(p, args):**在迭代器p指向的元素之前**创建一个值为t或由args创建的元素。返回指向新添加的元素的迭代器
  • c.insert(p,n,t):在迭代器p指向的元素之前插入n个值为t的元素,返回指向新添加的第一个元素的迭代器
  • c.insert(p,b,e):将迭代器be指定范围内的元素插入到迭代器p指向的元素之前,返回指向新添加的第一个元素的迭代器
  • c.insert(p,il):将花括号包起来的元素值列表插入到迭代器p指向的元素之前,返回新添加的第一个元素的迭代器

当调用push或者insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素

注意:

  • 将元素插入到vectordequestring的任何位置都是合法的,但是会很耗时

  • vectordequestring插入元素会使所有指向容器的迭代器、指针、引用失效

  • 使用insert的返回值:

    list<string> lst;
    auto iter = lst.begin();
    while (cin >> word)
        iter = lst.insert(iter, word); //等价于push_front
    

访问元素

包括array在内的每个顺序容器都有一个front成员函数,而除forward_list之外的所有顺序容器都有一个back成员函数。这两个操作分别返回首元素和尾元素的引用

可以用迭代器解引用访问元素,也可以直接用frontback。注意:end迭代器指向的是尾后元素,要解引用必须先--

在调用frontend之前,必须确保容器非空!!

以下都是返回引用(容器是const对象则返回const的引用):

  • c.back():返回c中尾元素的引用, 如果c为空则函数行为未定义
  • c.front():返回c中首元素的引用, 如果c为空则函数行为未定义
  • c[n]:返回第n个元素的引用, 如果n>=c.size()则函数行为未定义(链表不可随机访问)
  • c.at(n):返回第n个元素(从0开始)的引用, 如果下标越界则抛出out_of_range异常(链表不可随机访问)

使用auto时记得加上&

删除元素

  • c.pop_back():删除c中尾元素, 若c为空则函数行为未定义,返回void
  • c.pop_front():删除c中首元素, 若c为空则函数行为未定义返回voidvectorstring不支持)
  • c.erase(p):删除迭代器p指向的元素, 返回一个指向被删元素之后元素的迭代器
  • c.erase(b,e):删除迭代器be所指定范围内的元素, 返回一个指向最后一个被删除元素之后元素的迭代器
  • c.clear():删除c中所有元素

特殊的forward_list操作

在链表中添加或删除元素(不适用于array),是通过改变给定元素之后的元素来完成的。所以forward_list返回首前迭代器。

  • lst.before_begin(), lst.cbefore_begin():返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用
  • lst.insert_after(p,t),lst.insert_after(p,n,t),lst.insert_after(p,b,e),lst.insert_after(p,il):在迭代器p之后的位置插入元素,返回一个指向最后一个插入元素的迭代器
  • emplace_after(p,args):使用argsp指定的位置之后创建一个元素,返回一个指向这个新元素的迭代器
  • lst.erase_after(p), lst.erase_after(b,e):删除元素,返回一个指向被删元素之后元素的迭代器

改变容器大小

除了array外我们可以使用resize()来增加或者缩小容器(不适用于array)。如果当前大小小于所要求的大小,容器后面的元素会被删除;如果当前大小小于新大小,会将新元素添加到容器后部。

  • c.resize(n)c.resize(n,t)

容器操作可能使迭代器失效

保证每次改变容器的操作后都正确地重新定位迭代器!!(尤其是对于vectordequestring

不要在循环开始前保存end迭代器。

容器大小管理操作

  • c.shrink_to_fit():将capacity()减少为与size()相同大小(只适用于vectorstringdeque
  • c.capacity():不重新分配内存空间的话,c可以保存多少元素(只适用于vectorstring
  • c.reserve(n):分配至少能容纳n个元素的内存空间(永远不会减少空间)(只适用于vectorstring

resize区别:只改变容量,不改变元素个数。

额外的string操作

本节初次阅读可能令人感到心烦,读者只需快速浏览。

构造string的其他方法
  • string s(cp,n)scp指向的数组前n个字符的拷贝,此数组至少应该包含n个字符
  • string s(s2,pos2)sstring s2从下标pos2开始的字符的拷贝
  • string s(s2,pos2,len)ssting s2从下标pos2开始len2个字符的拷贝
  • s.substr(pos,n):返回一个string,包含s中从pos开始的n个字符的拷贝
改变string的其他方法

string类型支持顺序容器的赋值运算以及assigninserterase操作,同时还定义了额外的inserterase版本。

  • s.insert(pos,args):在pos之前插入args指定的字符,pos可以是一个下标或一个迭代器,接受下标的版本返回一个指向s的引用,接受迭代器的版本返回指向第一个插入字符的迭代器
  • s.erase(pos,len):删除从pos位置开始的len个字符,返回指向s的引用
  • s.append(args):将s中的字符替换为args的字符,返回一个指向s的引用
  • s.replace(range,args): 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s的迭代器。返回s的引用

上面提到的args可以是一下形式之一:

  • str:字符串str
  • str,pos,lenstrpos开始最多len个字
  • cp,len:从cp指向的字符数组的前(最多)len个字符
  • cpcp指向的以空字符结尾的字符数组
  • n,cn个字符c
  • b,e:迭代器be指定的范围内的字符
  • 初始化列表:花括号包围的,以逗号分割的字符列表

注意:

  • assign总是替换string中的所有内容
  • append总是将新字符追加string的末尾
  • replace函数提供了两种指定删除元素范围的方式:即可以通过一个位置和一个长度来指定范围,也可以通过一个迭代器范围来指定
  • insert函数允许我们使用两种方式来指定插入点:用一个下标或者一个迭代器,两种方式下新元素都会插入到给定下标之前的位置
string搜索操作

string类提供了6个不同的搜索函数,每个搜索操作返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败则返回一个名为string::nposstatic成员。搜索操作包括:

  • s.find(args):查找sargs第一次出现的位置
  • s.rfind(args):查找sargs最后一次出现的位置
  • 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中的字符

args须是以下形式之一:

  • c,pos:从s中位置pos开始查找字符cpos默认为0
  • s2,pos:从s中位置pos开始查找字符串s2pos默认为0
  • cp,pos:从s位置pos开始查找指针cp指向的以空字符结尾的C风格字符串,pos默认为0
  • cp,pos,n:从s中位置pos开始查找指针cp指向的数组的前n个字符, posn无默认值
compare函数

类似于strcmp函数,根据s是等于、大于还是小于参数指定的字符串,s.compare返回0、正数或者负数。根据我们是要比较两个string还是一个string与一个字符数组,我们可以将compare划分为6个版本:

  • s2:比较ss2
  • pos1.n1,s2:将s中从pos1开始的n1个字符与s2进行比较
  • pos1,n1,s2,pos2,n2:将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符进行比较
  • cp:比较scp指向的以空字符结尾的字符数组
  • pos1,n1,cp:将s中从pos1开始的n1个字符与cp指向的以空字符结尾的字符数组进行比较
  • pos1,n1,cp,n2:将s中从pos1开始的n1个字符与指针cp指向的地址开始的n2个字符进行比较
数值转换

要转换为数值的string中第一个非空白符必须是数值中可能出现的字符,如果string不能转换为一个数值,否则这些函数会抛出一个invalid_argument的异常。

  • to_string(val):一组重载函数,返回数值valstring表示。
  • stoi(s,p,b),stol(s,p,b),stoul(s,p,b),stoll(s,p,n),stoull(s,p,b):返回s的起始子串(表示整数内容)的数值,返回值分别是intlongunsigned longlong longunsigned long longb表示转换所用的基数,默认值为10psize_t指针,用来保存s中第一个非数值字符的下标,默认为0,即不保存下标
  • stof(s,p),stod(s,p),stold(s,p):返回s的起始子串(表示浮点数)的数值,返回值类型分别是floatdouble或者long double。参数p的作用同上

容器适配器

标准库定义了三个顺序容器适配器:stackqueuepriority_queue。例如stack适配器接收一个顺序容器(除arrayforward_list外),并使其操作起来像一个stack一样。

容器适配器都支持的操作和类型
  • size_type:一种类型,足以保存当前类型的最大对象的大小
  • value_type:元素类型
  • container_type:实现适配器的底层容器类型
  • A a:创建一个名为a的空适配器
  • A a(c):创建一个名为a的适配器,带有容器c的一个拷贝
  • 关系运算符:每个适配器都支持所有关系运算符==, !=, <, <=, >, >=
  • a.empty():判断a是否包含元素
  • a.size():返回a的元素数目
  • swap(a,b), a.swap(b):交换ab的内容,ab必须有相同类型,包括底层容器类型也必须相同
定义一个适配器

每个适配器都定义两个构造函数:默认构造函数创建一个空对象,或接受一个容器的构造函数拷贝该容器来初始化适配器。默认情况下,stackqueue是基于deque实现的,priority_queue是基于vector实现的。但我们也可以通过在创建一个适配器时将一个命名的顺序容器作为第二个类型参数来重载默认容器类型。

//在vector上实现的空栈
stack<string, vector<string>> str_stk;
//str_stk2在vector上实现,初始化时保存svec的拷贝
stack<string, vecotr<string>> str_stk2(svec);
栈适配器

栈默认基于deque实现,也可以再list或者vecotr上实现

  • s.pop():删除栈顶元素,但不返回该元素值
  • s.push(item):创建一个新元素压入栈顶,通过拷贝或者移动item而来
  • s.emplace():创建一个新元素压入栈顶,该元素通过args构造
  • s.top():返回栈顶元素,但不将该元素弹出栈
队列适配器

queue默认基于deque实现,也可以用listvecotr实现。priority_queue默认基于vector实现,也可以用deque实现。

  • q.pop():删除queue的首元素或priority_queue的最高优先级元素,但不返回此元素
  • q.front():返回首元素,但不删除此元素
  • q.back():只适用于queue,返回尾元素
  • q.top():返回最高级元素,但不删除该元素,只适用于priority_queue
  • q.push(item), q.emplace(args):在queue末尾或priority_queue中恰当的位置创建一个元素,其值为item,或者由args构造
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值