我的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

猜你喜欢

转载自blog.csdn.net/weixin_40313940/article/details/105740035