我的C++primer长征之路:顺序容器

顺序容器

概述

顺序容器类型

vector可变大小数组,支持快速随机访问,尾部之外位置插入删除元素会很慢。
deque双端队列,支持快速随机访问,在头尾插入删除速度都很快(list和forward_list相当)。
list双向链表,只支持双向顺序访问不支持随机访问,在任何位置插入删除速度都很快。
forward_list双向链表,只支持双向顺序访问不支持随机访问,在任何位置插入删除速度都很快。
array固定大小数组,支持快速随机访问,不能添加删除元素。
string与vector类似,专门用于保存字符。随机访问快,尾部插入删除快。

迭代器

标准容器迭代器运算符
*iter返回迭代器iter所指向元素的引用。
iter->men解引用iter并获取该元素的mem成员,等价于(*iter).mem。
++iter令iter指向容器中下一元素。
- -iter令iter指向容器中上一元素。
iter1 == iter2 iter1 != iter2判断两迭代器是否相等,若指向同一元素或同一容器尾后迭代器,则相等,否则不等。
string s("some string.");
//将字符串转换成大写,遇到空格或结尾停止
for(auto it = s.begin(); it != s.end() && !isspace(*it); ++it){ //begin(),end()为容器首元素迭代器和尾后迭代器,*it为解引用,也就是it指向的元素。
    *it = toupper(*it);
}

C++中,只有string和vector等标准库类型有下标运算符,并非全部类型都有。所有的容器的迭代器都定义了==和!=运算符,除了无序关联容器外都定义了关系运算符(>, >=, <, <=),所以最好养成迭代器和!=一起使用的习惯。

容器的相等运算实际是调用元素的==运算符实现的。
而其他关系运算符是使用元素的< 运算符。如果元素类型不支持该运算符,那就不能进行关系运算。

迭代器范围

迭代器的范围为左闭右开区间[begin,end),其中begin是容器首元素,end是最后一个元素的后一个位置。

string s(10, ' ');//创建10个空格的string
for(auto it = s.begin(); it != s.end(); ++ it){
    *it = 'c'; //为迭代器指向的元素赋值
   // ++it; //指向下一元素
}

容器类型别名

通过类型别名,可以在不了解容器元素类型的情况下使用它。若需要确定元素类型,可以使用容器的value_type,如果需要元素类型的一个引用,可以使用reference或者const_reference。

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

顺序容器定义及初始化

每个容器类型都定义了一个默认构造函数。除array之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接收指定容器大小和初值的参数。

vector<int> v; //默认构造函数,v是空的vector
vector<int>v(10); //定义大小为10int的空vector,除arraystring外都支持
vector<int>v(10,1); //创建10个元素为1的vector,除array外其他顺序容器都支持这种创建方式
array<int, 10> arr; //10个默认初始化的int
array<int, 10> arr1 = {0, 1, 2, 3}; //列表初始化
array<int, 10> arr2 = arr1; //拷贝赋值,类型大小必须都一样, 内置数组不支持这样操作
定义和初始化方式
C c;默认构造函数,构建一个空的顺序容器,除array外,array会按默认方式进行初始化。
C c1(c2); C c1 = c2;c1初始化为c2的拷贝,c1c2类型必须相同,对于array,大小还必须相同。
C c{a,b,c,…}; C c={a,b,c,…}c初始化为列表中元素,元素类型必须相同,对于array,列表元素数目必须小于等于array大小
C c(b,e)c初始化为迭代器be之间的元素,且元素类型必须相容(array不适用)
只有顺序容器(array除外)的构造函数才能接收大小参数
C s(n);创建的c大小为n,进行了值初始化,且此构造函数时显式的。(string不适用)
C s(n, t)创建n个初值为t的顺序容器。

赋值和swap

c1 = c2; //将容器c1的内容替换为c2中的拷贝
c1 = {a, b, c}; //赋值后,c的大小为3

容器为array的情况:

array<int, 10> a1 = {0,1,2,3,4,5,6,7,8,9};
array<int, 10> a2 = {0}; //所有元素初始化为0
a1 = a2; //正确,array支持赋值
a2 = {0}; //错误,不能将花括号列表赋予数组。此外,array也不支持assign。
容器赋值运算
c1 = c2将c2拷贝赋值给c1,必须具有相同类型
c = {a,b,…}列表初始化,array不支持
c1.swap(c2); swap(c1, c2);交换c1c2中的元素,必须具有相同类型。通常比拷贝快很多。
assign不支持关联容器和array
seq.assign(b, e);将seq中的元素替换为迭代器b,e范围内的元素。b,e不能指向seq。会替换所有元素
seq.assign(il);将seq中的元素替换为初始化列表中的元素
seq.assign(n, t);将seq中的元素替换为n个值为t的元素。

赋值运算会导致指向左边容器内部的迭代器、引用、指针失效,而swap则不会。(array和string除外。)
对于除array以外的容器,其大小不一样也可以swap,如:

vector<string> v1 = {"hello"};
vector<string> v2 = {"hi", "jack"};
swap(v1, v2);

除array外,swap只是交换两个容器内部的数据结构,并没有进行元素拷贝移动或者插入,所以速度很快。对于string,swap会导致其迭代器、引用、指针失效。

小练习:编写程序,将一个list中的char*指针元素赋值给一个vector中的string

list<char*> c = {"abc"};
vector<string> v;
v.assign(c.cbegin(), c.cend());

容器大小操作

forward_list支持max_size,empty。
其余容器都支持size,max_size,empty三种操作。

顺序容器添加元素

forward_list有自己专属的insert和emplace
forward_list不支持push_back和emplace_back
vector和string不支持push_front和emplace_front
向一个vector、string和deque插入元素会使指向其的迭代器、引用和指针失效。
向一个vector、string和deque的任何位置插入元素都是合法的,但是会很慢。

vector<string> v;
list<string> l;
//等价于调用l.push_front("Hello!");
l.insert(l.begin(), "Hello!");
//vector不支持push_front,但可以插入到begin之前,会很慢
v.insert(v.begin(), "Hello!");

将vector中的字符串赋值给list。

vector<string> v = {"tom", "jack", "rose"};
list<string> l = {"downey"};
l.insert(l.begin(), v.begin(), v.end()); //在l开头插入
l.insert(l.end(), v.begin(), v.end()); //在l末尾插入
//对于insert,返回指向其插入的第一个元素的迭代器

emplace
新标准引入emplace_front,emplace,emplace_back对应push_front,insert,push_back。
不同之处是,调用push或insert成员函数时,是将元素类型的对象传递给他们,这些对象被拷贝到容器中。而调用emplace成员函数时,是将参数传递给元素类型的构造函数,在内存空间中直接构造元素。

也就是说传递给emplace的参数必须与元素类型的构造函数参数像匹配。

访问元素

所有顺序容器都有front成员,除forward_list外都有back成员函数。

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

对于顺序容器,at和下标操作只适用于string,vector,deque,array。back不适用于forward_list。
容器中访问元素的成员函数(front,back,下标和at)都返回元素的引用。如容器是const对象,则返回的也是const引用

提供快速随机访问的容器(string,vector,array,deque)都提供下标运算。

删除元素

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

删除deque中首尾元素之外的任何元素都会使迭代器、引用、指针失效。指向vector、string中删除点之后的位置的迭代器、引用、指针也都会失效。

特殊的forward_list插入删除操作

单向链表,无法获取一个元素的前驱节点,所以只能改变一个元素的后一个元素来实现插入删除。

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之后插入元素。t是一个元素,n是数量。be表示一对迭代器。il是花括号列表,返回一个指向最后一个插入元素的迭代器。若范围空,则返回p。若p尾后迭代器则函数行为未定义。
lst.emplace_after(p.args);使用参数在p元素后创建一个新元素。返回指向这个新元素的迭代器。若p为尾后迭代器,函数行为未定义。
lst.erase_after§; lst.erase_after(b,e);删除p指向元素之后的元素。或者删除b到e前1个元素范围内的元素。若不存在这样的元素,返回尾后迭代器。若p指向lst的尾元素或尾后迭代器,则函数行为未定义。

改变容器大小

resize扩大或缩小容器。若当前大小>要求的大小,则容器后面的元素会被删除,如果当前大小<要求大小,会将新元素添加到容器后面。

list<int> ilist(10, 42); //10个int,每个都是42
ilist.resize(15); //5个值为0的元素添加到容器尾部。
ilist.resize(25, -1); //将10个值为-1的元素添加到ilist尾部
ilist.resize(5); //删除后面20个元素,保留前5个元素,分别都是42

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

不要保存end返回的迭代器
因为在删除或插入vector、string或deque之外任意位置的元素后,end迭代器总会失效。

管理容量的成员函数
c.shrink_to_fit()将capacity()减小为size()大小。只适用于vector、string、deque。
c.capacity()不重新分配内存空间的情况下,c可以保存多少元素。只适用于vector和string
c.recerve(n)分配至少能容纳n个元素的空间(通常会更大)

调用resize永远不会减少容器占用空间,resize只改变容器中元素个数,不改变容器容量。如果要减小,用shrink_to_fit()(不保证一定退回)。

vector<int> ivec;
//size为0,capacity依赖于具体实现
cout<< "ivec size:" << ivec.size() << endl;
cout<< "ivec capacity:" <<ivec.capacity() <<endl;

//添加24个元素
for (vector<int>::size_type ix = 0; ix != 24; ++ix){
    ivec.push_back(ix);
}
//size=24,capacity=32
cout<< "ivec size:" << ivec.size() << endl;
cout<< "ivec capacity:" <<ivec.capacity() <<endl;

vector的一个内存分配策略是,每次需要分配新的内存空间时,将当前容量翻倍。(不同实现不一样)

string的特殊操作

string的特殊的构造方法
string s(cp, n)s是cp数组的前n个元素的拷贝。cp至少有n个元素。
string s(s2, pos)s是s2的pos下标开始的字符的拷贝。若pos>s2.size(),构造函数未定义。
string s(s2, pos2, len)s是s2从下标pos2开始的len个字符的拷贝,不管len是多少,之多拷贝s2.size()-pos2个字符。
const char* cp = "Hello World!!";//以空字符结束的数组
char noNull[] = {'H', 'i'}; //不是以空字符结束
string s(cp); //拷贝cp中的字符,直到遇到空字符结尾。
string s1(noNull); //未定义,noNull不是以空字符结尾

改变string的其他方法
s.substr(pos, n)
返回一个string,包含从pos开始的n个字符的拷贝。如果开始位置pos超出string大小,则抛出out_of_range异常。

string除了接收迭代器版本的insert和erase之外,还提供下标版本。

s.insert(s.size(), 5, '!'); //s末尾插入5个感叹号
s.erase(s.size() - 5, 5); //从s删除最后5个字符

还有C风格字符串数组的insert和assign。

const char* cp = "Stately, plump Buck";
s.assign(cp, 7); //赋予s指针cp开始的7个字符
s.insert(s.size(), cp + 1); //s="Stately, plump Buck"

此外还有两个额外的成员函数,append和replace。
append在字符串末尾添加字符串。replace在指定位置替换字符串。

string s("hello world!");
s.append("good."); //s="hello world!good."
a.repalce(6, 5, "jack"); //s="hello jack!good." 也就是说替换字符串的长度与被替换字符串的长度不需要一样。

string的搜索操作
每个搜索操作都返回一个string::size_type值,表示匹配位置的下标。如果搜索失败,则返回名为string::npos的static成员。npos是一个unsigned类型的。

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。
s2, pos从s中位置pos开始查找字符串s2。
cp, pos在s中pos开始查找cp指向的以空字符结尾的C风格字符串
cp, pos, n在s中pos开始查找cp指向的以空字符结尾的C风格字符串的前n个字符

字符串比较函数

s.compare()的集中参数形式
s2比较s与s2
pos1, n1, s2将s中从pos1开始的n1个字符与s2比较
pos1, n1n 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个字符与指针指向的地址开始的n2个字符串进行比较

string与数值之间的转换

int i = 50;
string s = to_string(i); //将数值型转换成string
string与数值之间的转换
stoi(s, p, b),stol(s, p, b), stoul(s, p, b),stoll(s, p, b), stoull(s, p, b)返回s的起始字串的数值返回值类型分别是int,long,unsigned long, long long,unsigned long long,b表示转换所用基数,默认10进制。p是size_t指针,用来保存第一个非数值字符下标,默认为0
stof(s, p), stod(s, p), stold(s, p)返回s的起始字串(表示浮点数内容的)数值。

容器适配器

一个容器适配器接受一种已有类型,使其行为看起来像另一种不同的类型。
stack, queue, priorty_queue

所有容器适配器都支持的操作和类型
size_type一种类型,足以保存当前类型的最大对象大小
value_type元素类型
container_type实现该适配器的底层容器类型
A a;创建一个空的适配器
A a©;创建一个适配器a,带有容器c的一个拷贝。
==、!=、<、<=、>、>=每个适配器都支持所有关系运算符
a.empty()返回是否为空
a.size()返回a中元素数目
swap(a, b),a.swap()交换a,b内容,必须具有相同类型且底层容器类型也必须相同
deque<int>deq = {1, 2, 3};
stack<int> s(dep);//用容器deque对象来初始化stack对象。

默认情况下,stack和queue是基于deque实现的,priorty_queue是基于vector实现的。
stack只要求push_back,pop_back,back操作,所以可以使用除array和forward_list之外的任意顺序容器实现。queue要求back,push_back,front,push_front,因此可以基于deque和list,但不能基于vector。priority_queue除了要求front,back,push_back之外还要求随机访问,所以可以基于vector和deuqe,但不能基于list。

这是默认情况,我们也可以在创建一个适配器时,指定一个命名的顺序容器,来重载默认的容器类型。

	//vector上实现栈
	stack<int, vector<int>> int_stk;
	deque<int> dq = { 1,2,3,4,5,5 }; //默认的stack是基于deque的
	stack<int> s0(dq);
	cout << s0.size() << endl; //size=6
	vector<int> v = { 1, 2, 3, 4 };
	stack<int, vector<int>> s2(v); //将stack重载为基于vector的
	cout << s2.size() << endl; //size=4

在这里插入图片描述
在这里插入图片描述
可以看到,stack s0和s2是基于不同容器构造的。

stack特有的操作
s.pop()删除栈顶元素,但不返回元素。
s.push(item)压栈
s.emplace(args)将args压栈
s.top()返回栈顶元素,但不将元素弹出栈。

每个容器适配器都定义了自己的特殊操作,只可以使用适配器操而不能使用底层容器的操作。

队列适配器特有操作
q.pop()弹出queue的首元素或者priority_queue的最高优先级元素,但不返回此元素。
q.front()返回首元素或者尾元素,但不删除元素
q.back()只适用于queue
q.top()返回最高优先级元素,但不删除元素
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、付费专栏及课程。

余额充值