C++ Primer 5th学习笔记8 顺序容器

顺序容器

1 顺序容器概述

顺序容器类型
名称 描述
vector 可变大小数组,支持快速随机访问。在尾部之外的位置插入元素或删除元素可能很慢
deque 双端队列,支持快速随机访问。从头尾位置插入/删除速度很快
list 双向链表,只支持双向顺序访问。在list中任何位置进行插入和删除操作速度都很快
forward_list 单向链表,只支持单向顺序访问。在任何位置进行插入和删除操作都很快
array 给定大小数组。支持快速随机访问。不能添加或删除元素
string vector相似的容器,随机访问快,在尾部插入/删除速度快

Tips:

  • string和vector均是将元素保存在连续的内存空间中,由于储存空间的连续,因此利用下标计算其地址非常快。但在容器的中间位置添加或删除元素会非常耗时:在一次插入操作后,需要移动插入或删除位置之后的所有元素,而且添加一个元素后可能还需要分配额外的储存空间。
  • list和forward_list优点是在容器如何位置添加和删除操作都很快。但这两类容器不支持随机访问。访问一个元素时,只能遍历整个容器。额外内存开销比其他容器较大。
  • deque支持快速随机访问,在中间位置添加和删除元素的代价搞,但是在两端添加或删除元素都很快

  选择容器的基本原则:

  • 一般情况下,优先选择vector
  • 若程序有很多小的元素,且空间的额外开销很重要,则不要使用listforward_list
  • 若程序要求随机访问元素,应使用vectordeque
  • 若程序要求在容器的中间插入或删除元素,应使用listforward_list
  • 若程序要求在容器的头尾位置插入或删除元素,但不会在中间位置进行插入或删除,则使用deque
  • 若程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素。如果必须在中间位置插入元素,则在输入阶段使用list,输入完成之后,将list的内容拷贝到一个vector中。

2 容器库概览

容器操作
类型别名 描述
iterator 该容器的的迭代器类型
const_iterator 可以读取元素,但不能修改元素的迭代器类型
size_type 无符号整数类型,足够保存此容器类型最大可能容器的大小
difference_type 有符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型,与value_type&含义相同
const_reference 元素的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

大小

名称 描述
c.size() c中元素的数目(不支持forward_list)
c.max_size() c可保存的最大元素数目
c.empty() 若c中储存了元素,返回false,否则返回true

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

名称 描述
c.insert(args) args中的元素拷贝进c
c.emplace(args) 使用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的尾元素和首元素之前位置的迭代器
c.crbegin(), c.crend() 返回const_reverse_iterator

2.1 容器类型成员

示例如下:

//iter是通过list<stirng>定义的一个迭代器类型
list<string>::iterator iter;
//count是通过vector<int>定义的一个difference_type类型
vector<int>::difference_type count;
//

2.2 容器定义和初始化

  将一个新容器创建为另外一个容器的拷贝的方法有两种:可以直接拷贝整个容器,或者(array除外)拷贝由一个迭代器对指定的元素范围。当将一个容器初始化为另外一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。
示例:

//拷贝元素,直到(但不包括)it指向的元素
deque<string> authList(authors.begin(), it);

2.3 赋值和swap

容器赋值运算
操作 描述
c1 = c2 c1中的元素替换为c2中元素的拷贝
c = {a,b,c...} c1中的元素替换为初始化列表中元素的拷贝(array不适用)
swap(c1, c2) 交换c1c2中的元素,两者必须具有相同类型,比拷贝快
c1.swap(c2) 同上

assign操作不适用于关联容器和array

操作 描述
seq.assign(b, e) 将seq中的元素替换为迭代器b和e所表示的范围中的元素,b和e不能指向seq中的元素
seq.assign(il) 将seq中的元素替换为初始化列表il中的元素
seq.assign(n, t) 将seq中的元素替换为n个值为t的元素

Tips:赋值相关运算会导致指向左边容器内部的迭代器,引用和指针失效。而swap操作将容器内容交换不会导致指向容器的迭代器,引用和指针失效(容器类型为array和string的情况除外)

  赋值运算要求左边和右边的运算对象具有相同的类型,assign操作用参数指定的元素(的拷贝)替换左边容器中的所有元素。例如可以将一个vector中的一段char*值赋给一个list中的string:

list<string> names;
vector<const char*> oldstyle;
names.assign(oldstyle.cbegin(),oldstyle.cbegin());

使用swap操作用来交换两个相同类型容器的内容。除array外,交换两个容器内容的操作中,元素本身未交换,swap只是交换了两个容器的内部数据结构。这意味着元素不会被移动,因此除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。而swap两个array会真正交换两者的元素,因此对于array,在swap操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经和另外一个array中对应的元素的值进行了交换。

2.3 容器大小操作

  成员函数size返回容器中元素的数目;empty当size为0时返回布尔值true,否则返回false;max_size返回一个大于或等于改类型容器所能容纳的最大元素的值。

2.4 关系运算符

  关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。比较方式如下:

  • 如果两个容器具有相同的大小且所有的容器都两两对应相等,则这两个容器相等;否则两个容器不等。
  • 如果两个容器大小不同,但较小的容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。
  • 如果两个容器都不是另外一个容器的前缀子序列,则比较的结果取决于第一个不相等的元素的比较结果

3 顺序容器操作

3.1 向顺序容器添加元素

向顺序容器添加元素的操作
操作 描述
c.push_back(t) c.emplace_back(args) 在c的尾部创建一个值为t或由args创建的元素,返回void
c.push_front(t) c.emplace_front(args) 在c的头部创建一个值为t或由args创建的元素,返回void
c.insert(p, t) c.emplace(p, args) 在迭代器p指向的元素之前创建一个值为t或由args创建的元素,返回指向新添加的元素的迭代器
c.insert(p, n, t) 在迭代器p指向的元素之前插入n个值为t的元素,若n为0,则返回p;否则返回指向新添加的第一个元素的迭代器
c.insert(p, b, e) 将迭代器b和e指定范围内的元素插入到迭代器p指向的元素之前,b和e之间不能指向e。若范围为空,则返回p;否则返回指向新添加的第一个元素的迭代器
c.insert(p, il) il是一个花括号包围的元素值列表。

Tip:这些操作会改变容器的大小,array不支持这些操作;forward_list有自己专用版本的insert和emplace;forward_list不支持push_back和emplace_back;vector和string不支持push_front和emplace_front;

使用insert的返回值,可以在容器中一个特定位置反复插入元素,示例:

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

使用emplace操作,是将参数传递给元素类型的构造函数。当调用push或insert成员时,是将元素类型的对象传递给他们,这些对象是拷贝到容器中;当调用emplace成员函数时,则是将参数传递给元素类型的构造函数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素。示例:

class Book
{
    public:
    Book(string n = "",int i = 0)
        :name(n),price(i)
        {   
        }
    private:
    string name;
    int price;
};
vector<Book> c;
c.emplace_back("111",123);
c.push_back(Book("111",123));

3.2 访问元素

在顺序容器访问元素的操作
操作 描述
c.back() 返回c中尾元素的引用。若c为空,函数行为未定义
c.front() 返回c中首元素的引用。若c为空,函数行为未定义
c[n] 返回c中下标为n的元素的引用,若n>c.size(),则函数行为未定义
c.at(n) 返回c中下标为n的元素的引用,若如果下标越界,则抛出异常:out_of_range

Tip:at和下标操作只适用于string、vector、deque、array
back不是适用于forward_list

3.3 删除元素

顺序容器的删除操作
操作 描述
c.pop_back() 删除c中尾元素,若c为空,则函数行为未定义。函数返回void
c.pop_front() 删除c中首元素,若c为空,则函数行为未定义。函数返回void
c.erase(p) 删除迭代器p所指向的元素,返回一个指向被删除元素之后元素的迭代器
c.erase(b,e) 删除迭代器b和e所指向范围内的元素。返回一个指向最后一个被删除元素之后元素的迭代器
c.clear() 删除c中的所有元素,返回void

Tip:

  • forward_list有特殊版本的erase
  • forward_list不支持pop_back;vector和string不支持pop_front。
  • 删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用、指针失效
  • 指向vector或string中删除点之后位置的迭代器、引用、指针失效

3.4 特殊的forward_list操作

  forward_list是单向链表,由于在单向链表中,没有简单的方法来获取一个元素的前驱,因此在一个forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。与其他容器的操作不同,forward_list定义了名为insert_after、emplace_after和erase_after的操作。

在forward_list中插入或删除元素操作
操作 描述
lst.before_begin() 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用
lst.cbefore_begin() cbefore_begin()返回一个const_iterator
lst.insert_after(p, t) 在迭代器p之后的位置插入元素,t是一个对象
lst.insert_after(p, n, t) n表示数量
lst.insert_after(p, b, e) b和e表示范围的一对迭代器
lst.insert_after(p, il) il表示一个花括号列表
emplace_insert(p, args) 使用agrs在p指定的位置之后创建一个元素。返回一个指向这个新元素的迭代器
lst.erase_after(p) 删除p指向的位置之后的元素
lst.erase_after(b, e) 删除b之后直到e之间的元素,返回一个指向被删除元素之后元素的迭代器

3.5 改变容器大小

顺序容器大小操作
操作 描述
c.resize(n) 调整c的大小为n个元素
c.resize(n, t) 调整c的大小为n个元素。任何新添加的元素都初始化为值t

resize不适用与array,如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;
对vector、string或deque进行resize操作可能导致迭代器、指针和引用失效。

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

向容器添加元素后:

  • 如果容器是vector或string,且储存空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果空间未出现分配,指向插入位置之前的元素的迭代器、指针和引用仍然有效,但在该位置之后的元素的迭代器、指针和引用将会失效
  • 对于deque,插入到除首尾之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效
  • 对于list和forward_list,指向容器的迭代器、指针和引用仍然有效。

在容器中删除元素后:

  • 对于list和forward_list,指向容器其他位置的迭代器、引用指针仍然有效
  • 对于deque,若在首尾之外的任何位置删除元素,那么指向被删除元素之外其他元素的迭代器、指针或引用会失效;若删除首元素,则不受影响;若删除尾元素,则尾后迭代器失效,其他不受影响
  • 对于vector或string,指向被删除元素之前元素的迭代器、引用和指针仍然有效

4 vector对象是如何增长的

4.1 管理容量的成员函数

容器大小管理操作
操作 描述
c.shrink_to_fit() capacity()减少为与size()相同大小
c.capacity() 不重新分配内存空间的话,c可以保存多少元素
c.reserve(n) 分配至少能容纳n个元素的内存空间

reserve并不改变容器中元素的数量,仅影响vector预先分配多大的内存空间。
只要没有操作需求超出vector的容量,vector就不能重新分配内存空间。只有当在执行insert操作时,size与capacity相等,或者调用resize或reserve 时给定的大小超过当前capacity,vector才可能重新分配内存空间,分配多少取决于具体实现。==如在需要分配新内存空间时将当前容量翻倍,此时可以调用shrink_to_fit来要求vector将超出当前大小的多余内存退回给系统。==详细说明将另外一篇文章vector对象增长说明

5 额外的string操作

5.1 构造string的其他方法

构造string的其他方法
操作 描述
string s(cp, n) s是cp指向的数组前n个字符的拷贝
string s(s2, pos2) s是string s2从下标pos2开始的字符的拷贝,若pos2>s2.size(),构造函数的行为未定义
string s(s2, pos2, len2) s是string s2从下标pos2开始len2个字符的拷贝,若pos2>s2.size(),构造函数的行为未定义

substr操作
示例如下:

string s("hello world");
string s2 = s.substr(0, 5);        //s2 = hello
string s3 = s.substr(6);        //s2 = world
string s4 = s.substr(6, 11);        //s2 = world
string s5 = s.substr(12);        //抛出一个out_of_range异常

string类还定义了两个额外的成员函数:append和replace。append操作是在string末尾进行插入操作的一种简写形式。

s2.apppend("4th ed");        //等价方法:将"4th ed"追加到s2

5.2 string搜索操作

string搜索操作
操作 描述
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中的字符

args必须是以下形式之一

形式 描述
c, pos 从s中位置pos开始查找字符c。pos默认为0
s2, pos 从s中位置pos开始查找字符串s2pos默认为0
cp, pos 从s中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串。pos默认为0
cp, pos, n 从s中位置pos开始查找指针cp指向的数组的前n个字符。pos和n无默认值

5.3 compare函数和数值转换

compare的几种参数形式
参数形式 描述
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和数值之间的转换
操作 描述
to_string(val) 返回数值val的string表示,val可以是任何算术类型
stoi(s, p, b) stol(s, p, b) stoul(s, p, b) 返回s的起始子串(表示整数内容)的数值,返回类型为int、long、
stoll(s, p, b) stoull(s, p, b) unsigned long、long long、unsigned long long。b表示转换用的基数,默认为10,p是size_t指针,用来保存s中第一个非数值字符的下标
stof(s, p) stod(s, p) stold(s, p) 返回s的起始子串(表示浮点数内容)的数值,返回类型分别是float、double、long double

6 容器适配器

  标准库定义了三个顺序容器适配器:stack、queue和priority_queue。

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

定义一个适配器:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝改容器来初始化适配器。假定deq是一个deque<int>示例如下:

stack<int> stk(deq);       //从deq拷贝元素到stk

在构造适配器时,均要求容器具有添加、删除以及访问尾元素的能力。同时要注意以下几点:

  • stack只要求push_back,pop_back和back,因此可以使用除array和forward_list之外的任何容器类型来构造stack
  • queue要求back、push_back,front和push_front,因此可以构造与list或deque之上,但不能基于vector构造。
  • priority_queue除了front、push_back和pop_back操作之外,还要求随机访问能力,因此可以构造于vector或deque之上,但不能基于list构造。

栈适配器
  stack类型定义在stack头文件中,下面的示例展示了如何使用stack:

stack<int> intStack;        //空栈
//填满栈
for (size_t ix = 0;ix != 10; ++ix )
{
    intStack.push(ix);        //intStack保存0到9十个数
}
while ( !intStack.empty())    //intStack中有值就继续循环
{
    int value = intStack.top()//使用栈顶值的代码
    intStack.pop();        //弹出栈顶元素,继续循环
    
}
未列出的栈操作
操作 描述
s.pop() 删除栈顶元素,但不返回该元素之
s.push(item) 创建一个新元素压入栈顶,该元素通过拷贝或移动item而来
s.emplace(args) 或者由args构造
s.top() 返回栈顶元素,但不将元素弹出栈

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

队列适配器

queue和priority_queue适配器定义在queue,以下是其支持的操作

未列出的queue和priority_queue操作
操作 描述
q.pop() 返回queue的首元素或priority_queue的最高优先级的元素
q.front() 返回首元素或尾元素,但不删除此元素
q.back() 只适用于queue
q.top() 返回最高优先级元素,但不删除该元素(只适用于priority_queue
q.push(item) 在queue末尾或priority_queue中恰当的位置创建一个元素,
q.emplace(agrs) 其值为item,或者由agrs构造

queue默认基于deque实现,priority_queue默认基于vector实现;
queue也可以用list或vector实现,priority_queue也可以用deque实现;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章