重读c++Primer学习笔记-顺序容器篇

一个容器就是一些特定类型对象的集合。**顺序容器(sequential container)**为程序员提供了控制元素存储和访问顺序的能力。
标准库中的容器都提供了快速访问元素的能力,不同的是这些容器在

  • 向容器添加或从容器删除元素的代价
  • 非顺序访问容器元素的代价
    两方面进行了不同的性能折衷

顺序容器类型

顺序容器类型
vector可变数组大小 支持快速随机访问,尾部之外插入和删除数据可能很慢
deque双端队列,支持快速随机访问,头尾插入删除速度快
list双向链表 只支持双向顺序访问 在任何位置插入删除都很快
forward_list单向链表 只支持单向顺序访问 在任何位置插入删除都很快
array固定大小数组,支持快速随机访问,不能添加或删除
string与vector类似的容器,但专门保存字符,随机访问快,尾部删除插入快

容器保存元素的策略有着固有的,有时是重大的影响。在某些情况下,存储策略决定特定策略是否支持特定操作
例如,string和vector将元素顺序存储在连续的内存空间中。连续存储使得利用下标计算其位置是很快的。但是在中间进行元素添加和删除会非常耗时:每次操作后需移动其后的所有元素。
list和forward_list为了支持快速增删,取消了随机访问。访问必须从头遍历。而且与其他容器相比,他们的内存消耗很大。
与内置数组相比,array更加安全,也更容易使用
forward_list没有size操作,而其他容易的size操作是一个快速的常量操作

通常使用vector是最好的选择,除非你有很好的理由选择其他容器

  • 首先,确定是否真的要在容器中间添加元素。当处理输入数据时,可以很容易在vector后添加数据,然后使用sort将元素重排,从而避免在中间添加元素
  • 如果必须在中间插入元素,考虑在输入阶段用list。输入完成后将list的内容拷贝到vector中
    如果程序既需要随机访问,又要在中间增删元素,那么就取决于list或forward_list中访问元素和vector或deque中增删元素的相对性能。

c++Primer 第五版 第295页 容器操作

迭代器范围的概念是标准库的基础
一个迭代器范围指的是一对迭代器表示,两个迭代器通常称为begin和end
元素范围为左闭合区间 即[begin,end),这使得有三个好处

  • 如果begin和end相等,则范围为空
  • 如果begin与end不等,则至少有一个元素,且begin指向第一个元素
  • 我们可以对begin++直至其等于end

c++Primer第五版第297页 容器类型成员

每个容器都定义了多个类型,如size_type,iterator,const_iterator,反向迭代器,
通过类型别名,我们可以在不了解元素类型的情况下使用它。如果需要使用元素类型,可以用value_type。如果需要元素类型的引用,可以使用reference或const_reference。这些在泛型编程中十分有用

list<string> a = {"xys","sod"};
auto it1  = a.begin();// iterator
auto it2 = a.rbegin();// reverse_iterator
auto it3 = a.cbegin();// const_iterator
auto it4 = a.crbegin();//const_reverse_iterator

不需要写访问时,使用cbegin和cend

C++Primer 第五版 第299页 容器定义和初始化

C c;默认构造函数。array类型则执行默认初始化,其他则为空
C c1(c2);
C c1=c2
c1初始化为c2的拷贝,二者必须是相同类型,对于array还必须是相同大小
C c{a,b,c…}
C c={a,b,c…}
c初始化为列表元素的拷贝
C c(b,e)c初始化为迭代器b和e之间元素的拷贝
只有顺序容器(不包括array)的构造函数才接受大小参数
C seq(n)seq中包含n个元素
C seq(n,t)seq中包含n个初始化为t的元素

为了创建容器为另外一个容器的拷贝,两个容器的类型及其元素类型必须匹配。不过,通过传递迭代器来拷贝一个范围时,容器类型就不要求相同了。而且,新容器和元容器的元素类型也可以不同,只要可以相互转换即可。

C++Primer 第五版 第301页 标准库array

标准库array和内置数组一样,除了指定元素类型,还要指定容器大小

array<int,42>
array<string,10>
//为使用array类型,我们必须同时指定类型和大小
array<int,10>::size_type i;//正确 必须指定array类型和大小
array<int>::size_type i;//错误,未指定大小

值得注意的是,虽然我们对内置数组类型无法执行拷贝或者对象赋值操作,但array并无此限制

C++Primer 第五版 第302页 容器赋值运算
容器赋值运算(未指定必须是顺序容器)
c1=c2将c1元素替换为c2元素的拷贝,二者必须具有相同的类型
c={a,b,…}
swap(c1,c2)
c1.swap(c2)
交换C1,c2,swap通常比从c2拷贝要快
assign不适用于关联容器和arrayassign替换左边容器中所有元素
seq.assign(b,e)将seq中的元素替换为b和e范围内的元素,但b和e不能指向seq中的元素
seq.assign(il)将seq中的元素替换为初始化列表il中的元素
seq.assign(n,t)将seq中的元素替换为n个值为t的元素

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

list<string> names;
vector<const char*> oldstyle;
names = oldstyle;//错误,容器类型不匹配
//正确,可以将const char*转换为string
names.assign(oldstyle.cbegin(),oldstyle.cend());

由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器
assign的第二个版本接受一个整型值和一个元素值。它用指定数目且相同的元素替换容器中原有的元素

//等价于slist1.clear();
//后跟slist1.insert(slist1.begin(),10,"Hiya");
list<string> slist1(1);//1个元素 为空
slist1.assign(10,"Hiya");//10个元素

与其他容器不同,swap会真正交换两个array中的元素;与其他容器不同,swap会导致两个string的迭代器、引用、指针失效
在新标准库中,容器既能提供成员函数版本的swap,也能提供非成员函数版本的swap。在泛型编程中,非成员函数版本非常重要。统一使用非成员版本的swap是一个好习惯
forward_list支持的大小操作有max_size和empty,不支持size
每个容器都支持==和!=,除了无序关联容器,都支持关系运算符(>,>=,<,<=),容器的比较实际上是进行元素的逐个比较;注意只有当元素类型也定义了比较运算符时才能够比较两个容器

c++Primer 第五版 第305页 顺序容器操作

容器添加元素

c.push_back(t)
c.emplace_back(args)
在c的尾部创建一个值为t或由args创建的元素,返回void;forward_list不支持
c.push_front(t)
c.emplace_front(args)
在c的头部…,vector和string不支持
c.insert(p,t)
c.emplace(p,args)
在迭代器p指向的元素前创建,返回指向新添加元素的迭代器,forward_list有自己专有版本
c.insert(p,n,t)在p前创建n个t,若n为0,返回p
c.insert(p,b,e)
c.insert(p,il)

除array和forward_list之外,每个顺序容器(包括string和list)都支持push_back
新标准引入的三个新成员emplace,emplace_front,emplace_back允许构造而不是拷贝元素

//在c的末尾构造一个Sale_data
//使用三个参数的Sales_data构造函数
c.emplace_back("0000",25,15.99);
//错误:没有接受三个参数的push_back
c.push_back("0000",25,15.99);
//正确,创建临时Sales_data
c.push_back(Sales_data("0000",25,15.99));
c++Primer 第五版第309页 容器访问

包括array在内的每个顺序容器都有一个front,除了forward_list之外都有一个back

//解引用迭代器或调用front back前要先检查是否有元素
if(!c.empty()){
    auto val  = *c.begin();val2 = c.front();
    auto last = c.end();
    auto val3 = *(--last);//forward_list不能递减
    auto val4 = c.back();
}

此程序用两种不同方式获得c的首尾元素
值得注意的有两点,一是end指向尾元素之后的元素,因此需要–才能获得尾元素;二是调用front ,back前必须检查容器非空,否则操作未定义
访问成员函数返回的是引用

if(!c.empty()){
    c.front() = 42;//将42赋给第一个元素
    auto &v  = c.back();//获得引用
    v = 1024;
    auto v2 = c.back();//v2不是引用 而是一个拷贝
    v2 = 0;
}
//和往常一样 如果使用auto 记得将变量定义为引用类型

提供快速随机访问的容器支持下标,如果希望确保下标是合法的,可以使用at成员函数

vector<string> svec;//空vector
cout<<svec[0];//运行时错误
cout << svec.at(0);//错误会抛出out_of_range

顺序容器删除操作
c.pop_back()forward_list不支持
c.pop_front()vector和string不支持
c.erase§删除后返回p后的迭代器,如果p指向尾元素,则返回尾后迭代器
c.erase(b,e)
c.clear()
c++ Primer 第五版 第313页 特殊的forward_list

由于链表删除或者添加元素时,删除或者添加元素之前的元素的后继会发生改变,因此我们需要访问其前驱。但是forward_list作为单向链表,无法简单的访问前驱。因此在forward_list中添加或者删除元素,总是通过改变指定元素之后的元素来完成

forward_list中插入或者删除元素
lst.before_begin()
lst.cbefore_begin()
返回首元素之前的不存在的元素的迭代器,不能解引用,cbefore返回const
lst.insert_after(p,t)
lst.insert_after(p,n,t)
lst.insert_after(p,b,e)
lst.insert_after(p,il)
emplace_after(p,args)
lst.erase_after(p )
lst.erase_after(b,e)

改变容器大小,不适用array

c.resize(n)调整c的大小为n,若n<c.size(),则抛弃多出的
c…resize(n,t)调整c的带下为n,且全部为t
c++Primer 第五版 第315页 迭代器失效问题

添加、删除vector、string、deque元素的循环程序必须考虑迭代器、引用、指针可能失效的问题

//傻瓜循环,删除偶数,复制奇数
vector<int> vi = {0,1,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指向删除后的元素
}
c++Primer 第五版 第318页 vector对象大小

管理容量

c.shrink_to_fit()将capacity()减小到size相同大小,只适用vector,string和deque
c.capacity()不重新分配内存的话,c可以保存多少元素
c.reserve(n)分配至少能容纳n个元素的空间
C++Primer 第五版 第321页 string的其他构造方法
string s(cp,n)s是cp指向的数组前n个字符的拷贝,此数组应至少包含n个字符
stirng s(s2,pos2)s是s2从pos2开始的字符的拷贝,若pos2>s2.size() 行为未定义
string s(s2,pos2,len2)

这些构造函数接受一个string 或const char*参数

s.substr(pos,n)//返回s中从pos开始的n个字符的拷贝
//string类定义了两个额外的成员函数 append和replace
s.insert(s.size(),"AAA")
s.append("AAA")
//replace则可以看做是调用erase和insert
s.replace(11,3,"fifth");//删除3个,插入5个
c++Primer第五版 第325页
string搜索
s.find(args)查找s中args第一次出现的位置
s.rfind(args)查找s中args最后一次出现的位置
s.find_first_of(args)在s中查找args中任意一个字符 第一次出现的位置
s.find_last_of(args)在… 最后一次出现位置
s.find_first_not_of(args)在s中查找第一个不在args中的字符
s.find_last_not_of(args)在s中查找最后一个…
args必须是以下形式之一
c,pos从s中位置pos查找c,pos默认0
s2,pos
cp,pos
cp,pos,n从s中位置pos开始查找指针cp指向的数组前n个字符
c++Primer第五版 第328页
string和数值之间的转换
to_string(val)
stoi(s,p,b)
stol(s,p,b)
stoul(s,p,b)
stoll(s,p,b)
stoull(s,p,b)
返回s的起始子串(表示整数内容)的数值,返回类型由函数名决定,b为基数默认为10,p默认为0
stof(s,p)
stod(s,p)
stold(s,p)
返回转为浮点数
c++Primer 第五版 第329页 容器适配器

除顺序容器外,标准库还定义了三个顺序容器适配器
stack、queue、priority_queue
适配器(adapter)是标准库的一个通用概念。容器、迭代器、函数都有适配器。本质上,适配器是一种机制,使得某种事物的行为看起来像另一种一样。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值