C++ Primer第五版之第9章 顺序容器

1. 顺序容器概述

  • 顺序容器类型:
vector             //可变大小数组,支持快速随机访问,在尾部之外的位置插入或删除元素很慢
deque              //双端队列,支持快速随机访问,在头尾位置插入或删除元素很快
list               //双向链表,支持双向顺序访问,在list任何位置插入或删除元素都很快
forward_list       //单向链表,只支持单向顺序访问,在任何位置插入或删除元素都很快
array              //固定大小数组,支持快速随机访问,不能添加或删除元素,是一种更安全易用的数组类型
string             //与vector类似
//list与forward_list不支持元素的随机访问
  • 新标准的容器比旧版本快得多,现代c++ 应当使用标准库容器,而不是更原始的数据结构,如内置数组。
  • 确定使用哪种容器类型:一般来说使用vector(其他情况看程序具体要求)。根据占主导地位的操作(执行访问操作多还是插入删除操作多)决定容器类型选择
    • 随机访问元素:vector、deque;
    • 中间插入元素或删除元素:list、forward_list;
    • 头尾插入元素:deque;

2. 容器库概览

  • 每个容器都定义在一个头文件中,文件名与类型名相同;
  • 容器均定义为模板类;

迭代器

  • 迭代器范围
    • [begin,end),左闭合区间

容器类型成员

  • iterator、const_iterator、size_type、difference_type等等
  • begin和end成员
    • begin和end有多个版本,带r的返回反向迭代器;带c的返回const迭代器
list<string> a = {"M", "S", "A"};
auto it1 = a.rbegin();   //list<string>::reverse_iterator
auto it2 = a.cbegin();   //list<string>::const_iterator
auto it3 = a.crbegin();  //list<string>::const_reverse_iterator
  • auto与begin和end配合使用
auto it = a.begin(); //仅当a是const时,it是const_iterator

容器定义和初始化

  • 将一个容器初始化未另一个容器的拷贝
    • 直接拷贝整个容器
      • 要求容器类型与元素类型必须都相同
    • 拷贝由一个迭代器对指定的元素范围
      • 不要求容器类型相同,而且元素类型也可以不同,只要能够转换为要初始化的容器的元素类型
  • 当定义一个array时,除了指定元素类型,还要指定容器大小
vector<const char*> articles = {"A","S","K"};
forward_list<string> words(articles.begin(), articles.end());//正确:可以将const char*元素转换为string
  • 列表初始化
    • 新标准中,我们可以对一个容器进行列表初始化,同时也隐式指定了容器大小(array除外)。
vector<const char*> articles = {"A","S","K"};
list<string> authors = {"zhangsan", "lisi", "wangwu"};
  • 如果元素是内置类型或者具有默认构造函数的类类型,可以只为顺序容器提供一个容器大小的参数 ;否则还必须指定一个显式的元素初始值。
  • 标准库array具有固定大小
    • 使用array必须同时指定元素类型和大小。
    • array类似于数组,但支持拷贝和对象赋值
int digs[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int cpy[10] = digs; //错误,内置数组不支持拷贝

array<int, 10> digts = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
array<int, 10> cpy1= digts; //正确

赋值和swap

//赋值运算
swap(c1, c2);         //交换c1和c2中的元素,c1和c2的类型必须相同       
c1.swap(c2);

//assign操作不适用于关联容器和array
seq.assign(b, e);        //将seq中的元素替换为迭代器b和e所表示范围中的元素
seq.assign(il);          //将seq中的元素替换为初始化列表il中的元素
seq.assign(n,t);         //将seq中的元素替换为n个值为t的元素
//赋值相关运算会导致指向容器内部的迭代器、引用和指针失效;swap不会(容器类型为array和string的除外)
  • 使用assign(仅顺序容器)
list<string> names;
vector<const char*> oldstyle;
names = style; //错误,容器类型不匹配

names.assign(oldstyle.begin(), oldstyle.end()); //正确。const char*可以转化为string
  •  使用swap
    • 对于除array外的所有容器使用swap,都不会使两个容器的元素交换,仅仅交换两个容器的内部数据结构,除string外,指向容器的迭代器、指针、引用在swap操作后仍然指向原来的那些元素,只不过不属于原来的容器了。
    • 对于array使用swap,会真正交换它们的元素,但指针、引用和迭代器所绑定的元素不变,但元素值已经进行了交换。
    • 新标准中容器既提供了成员函数版本的swap也提供了非成员版本的swap。统一使用非成员版本是个好习惯。
vector<string> svec1(10);
vector<string> svec2(24);
swap(svec1, svec2); //交换后svec1(24),svec2(10)
  • 容器大小操作
    • 只有当其元素类型定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。

3. 顺序容器操作

向顺序容器添加元素:

/*forward_list有自己专有版本的insert和emplace
forward_list不支持push_back和emplace_back
vector和string不支持push_front和emplace_front
*/
c.push_back(t)                 //在c的尾部创建一个值为t或由args创建的元素
c.emplace_back(args)

c.push_front(t)                //在c的头部创建一个值为t或由args创建的元素
c.emplace_front(args)

c.insert(p,t)                  //在迭代器p指向的元素之前创建一个值为t或由args创建的元素
c.emplace(p,args)
c.insert(p,n,t)                //在迭代器p指向的元素之前插入n个值为t的元素
c.insert(p,b,e)                //在迭代器p指向的元素之前插入迭代器b和e指定的范围内的元素
c.insert(p,il)                 //在迭代器p指向的元素之前元素值列表il中的值

//向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效
//注意:当向容器中插入元素时,注意迭代器可能失效,所有需要重新指定迭代器
//1.当我们调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中
//2.而当我们调用emplace时,则是将参数传递给元素类型的构造函数
  • list、forward_list和deque支持push_front操作;
  • 有些容器不支持push_front操作,但可以通过insert实现 
string word("Hello"); //string为字符容器
word.push_back('!');

vector<string> svec;
list<string> slist;
slist.insert(slist.begin(), "Hello!");
svec.insert(svec.begin(), "Hello!");

list<string> lst;
auto iter = lst.begin();
while(cin>>word)
	iter = lst.insert(iter, word); //insert返回值为新插入元素的迭代器
//使用emplace
c.emplace_back("432-432432432", 25, 15.99); //emplace_back使用这些成员在容器管理的内存空间中直接构造元素

访问元素:

//at和下标操作只适用于string、vector、deque和array
//back不适用于forward_list
//back、front、at、[]均返回引用
c.back() <==> *(--c.end())       //返回c中尾元素的引用,若c空,则行为未定义
c.front() <==>*c.begin()       //返回c中首元素的引用,若c空,则行为未定义
c[n]                            //返回c中下标为n的元素的引用,若n>c.size(),则行为未定义
c.at(n)                         //返回下标为n的元素的引用,若越界,则跑出out_of_range异常

if (!c.empty())
{
    c.front() = 42;
    auto &v = c.back();      //获得指向最后一个元素的引用
    v = 1024;                //改变c中的元素
    auto v2 = c.back();      //v2不是一个引用,它是c中最后一个元素的拷贝
    v2 = 0;                  //c中元素不改变
}

删除元素:

/*删除元素改变容器大小,不适用于array
forward_list有特殊版本的erase
forward_list不支持pop_back;vector和string不支持pop_front
*/

c.pop_back()             //删除c中尾元素
c.pop_front()            //删除c中首元素
c.erase(p)               //删除迭代器p所指向的元素,返回一个指向被删元素之后元素的迭代器
c.erase(b,e)             //删除迭代器b和e所指定范围内的元素,返回一个指向最后一个被删元素之后元素的迭代器
c.clear()                //删除c中所有元素

//1.删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效
//2.指向vector或string中删除点之后位置的迭代器、引用和指针都会失效

 特殊的forward_list操作:

//在forward_list中插入或删除元素的操作
lst.before_begin()         //返回指向链表首元素之前不存在的元素的迭代器
lst.cbefore_begin()        //.......cbefore_begin()返回一个const_iterator

lst.insert_after(p,t)      //在迭代器p之后的位置插入元素,t是一个对象,n是数量,b和e是
lst.insert_after(p,n,t)    //表示范围的一对迭代器,il是一个元素值列表
lst.insert_after(p,b,e)
lst.insert_after(p,il)

emplace_after(p,args)      //使用args在p指定的位置之后创建一个元素,返回一个指向这个新元素的迭代器

lst.erase_after(p)         //删除p指向的位置之后的元素,或删除从b之后到e之间的元素
lst.erase_after(b,e)       //返回一个指向被删元素之后元素的迭代器

改变容器大小

//resize不适用于array
c.resize(n)         //调整c的大小为n个元素,若n<c.size(),则多出的元素被丢弃;否则新元素被初始化
c.resize(n,t)       //调整c的大小为n,任何新添加的元素被初始化为t

//1.如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效
//2.对vector、string或deque进行resize可能导致迭代器、引用和指针失效

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

 添加元素删除元素
vector若空间未重新分配,则指向插入元素之前的迭代器、指针、引用都将有效,之后的失效;若空间重新分配,则所有的都会失效同前
string若空间未重新分配,则指向插入元素之前的迭代器、指针、引用都将有效,之后的失效;若空间重新分配,则所有的都会失效同前
list都有效(包括尾后迭代器和首前迭代器)除了删除元素的失效,其他的都有效
forward_list都有效(包括尾后迭代器和首前迭代器)除了删除元素的失效,其他的都有效
deque如果插入到首尾之外的其他位置,则都会失效;如果插入到首尾,则除首尾外都不会失效同前
  • 建议:管理迭代器
    • 向迭代器添加元素和删除元素都可能导致迭代器失效,因此必须每次操作之后维护迭代器,尤其是vector、string、deque。
//删除偶数元素,复制每个奇数元素
vector<int> vi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto iter = vi.begin();
while(iter!=vi.end())
{
	if(*iter%2)
	{
		iter = vi.insert(iter, *iter); //在给定位置之前插入元素
		iter+=2;
	}
	else
		iter = vi.erase(iter); //删除元素后返回下一位置
}

4. vector对象是如何增长的

vector和string管理容量的成员函数

//shrink_to_fit只适用于vector、string和deque
//capacity和reserve只适用于vector和string
c.shrink_to_fit()    //请将capacity()减少为与size()相同大小
c.capacity()         //不重新分配内存空间的话,c可以保存多少元素
c.reserve(n)          //分配至少能容纳n个元素的内存空间
  • reserve并不改变容器中元素的数量,也不影响容器原来的内容,它仅影响vector预先分配多大的内存空间,也就是reserve只影响capacity的大小;
  • resize只改变容器中元素的数目,也就是只影响size的大小。
  • shrink_to_fit()将capacity调整至size大小,退回不必要的内存空间。
  • 实际上,只要没有操作需求超出vector的容量,vector就不会重新分配内存空间。

5.额外的string操作

  • 构造string:
//
string s(cp,n)             //s是cp指向的数组中前n个字符的拷贝
string s(s2,pos2)          //s是string s2从下标pos2开始的字符的拷贝
string s(s2,pos2,len2)     //s是string s2从下标pos2开始len2个字符的拷贝

//substr操作
s.substr(pos,n)            //返回一个string,包含s中从pos开始的n个字符的拷贝;pos默认值为0
  • 改变string:
s.insert(pos,args)           //在pos之前插入args指定的字符;接受下标的版本返回一个指向s的引用
                             //接受迭代器版本返回一个指向第一个插入字符的迭代器
s.erase(pos,len)             //删除从位置pos开始的len个字符,返回一个指向s的引用

s.assign(args)               //将s中的字符替换为args指定的字符,返回一个指向s的引用

s.append(args)               //将args追加到s,返回一个指向s的引用

s.replace(range,args)        //删除s中范围range内的字符,替换为args指定的字符
  • string搜索操作:
//搜索操作返回制定字符出现的下标,若未找到则返回npos
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中的字符
  • 数值转换:
to_string(val)       //返回数值val的string表示,val可以是任何算术类型

stoi(s,p,b)          //返回值类型分别是int、long、unsigned long、long long、unsigned long long, b表示转换所用的基数,
                       默认为10,p是size_t指针,用来保存s中第一个非数值字符的下标,默认为0
stol(s,p,b) 
stoul(s,p,b)
stoll(s,p,b)
stoull(s,p,b)

stof(s,p)             //返回值类型分别是float、double、long double
stod(s,p)
stold(s,p)
int i = 42;
string s = to_string(i); //整数i转化为string
double d = stod(s); //s转换为浮点数

6.容器适配器(stack、queue、priority_queue):

适配器:一个适配器是一种机制,能使某种事物的行为看起来向另外一种事物一样

  • 栈适配器:
s.pop()                   //删除栈顶元素,但不返回该元素值
s.push(item)              //创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,或由args构造
s.emplace(args)
s.top()                   //返回栈顶元素,但不将元素弹出栈
  • 队列适配器:
//queue默认基于deque实现,priority_queue默认基于vector实现
q.pop()                    //删除queue中首元素或priority_queue中最高优先级元素
q.front()                  //返回首元素或尾元素,其中back只适用于queue
q.back()
q.top()                    //返回最高优先级元素,只适用于priority_queue
q.push(item)               //在queue末尾或priority_queue中恰当位置创建一个元素
q.emplace(args)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值