9.2 容器库概览
容器库的操作分为通用的,顺序容器(大部分)通用的,关联容器(大部分)通用的,以及容器特有的操作。
对于容器的通用操作,如表所示:
可以看到容器的通用操作大部分是和迭代器有关的。
有几个可能会被忘记的点是:
1.可以使用迭代器表示一个范围来初始化容器
2.可以使用swap来交换两个容器的元素
同样,我觉得这些东西不需要死记,在需要用到的时候再查也不迟。
练习
9.2
list<deque<int>> data;
9.2.1 迭代器
迭代器的以下操作基本是通用的。
iter++;
iter--;//forward_list不支持,顾名思义嘛,forward_list是单向的
iter1==iter2;
iter1!=iter2;
*iter;
iter->data;
而这些操作是string ,vector,deque,array才能用的
iter+n;
iter-n;
iter1>iter2;
iter1<iter2;
iter1<=iter2;
iter1>=iter2;
iter1-iter2;
迭代器范围
迭代器返回是一个编程的规范,编译器是不会检查这个的。
范围由两个迭代器决定,两个迭代器通常为begin和end。
begin指向容器的第一个元素,而end指向元素的最后一个元素之后的位置。
容器的end指向最后一个元素的下一元素的位置的好处。
我们可以通过begin==end来判断容器是否为空。
需要满足以下的条件,才能称之为一个迭代器范围
1.两个迭代器指向同一个容器中的元素或者最后一个元素之后的位置
2.begin要大于等于end,即,begin可以递增达到end。
一个迭代器范围包含了begin但是不包含end。
练习
9.3
1.两个迭代器都指向同一个容器的元素或者指向容器的最后一个元素的之后的位置
2.“begin”要大于“end”,即“begin”可以通过递增的方式达到“end”;
9.4
bool get_value_index(const vector<int> &vec, int value) {
bool can_find = false;
for (vector<int>::const_iterator begin = vec.begin(); begin != vec.end(); begin++) {
if (*begin == value) {
can_find = true;
break;
}
}
return can_find;
}
测试用例
vector<int> vec = {3,2,3,4,4,6,7,6,4,4,5,43,3,1};
int i = 4;
cout << get_value_index(vec,i) << endl;
vector<int> vec = {1,2,3,5,6,7,8,9,0};
int i = 4;
cout << get_value_index(vec,i) << endl;
9.5
vector<int>::const_iterator get_value_index(const vector<int> &vec,int value) {
//bool can_find = false;
vector<int>::const_iterator iter=vec.end();
for (vector<int>::const_iterator begin = vec.begin(); begin != vec.end();begin++) {
if (*begin==value) {
iter = begin;
//can_find = true;
break;
}
}
return iter;
}
vector<int> vec = {3,2,3,4,4,6,7,6,4,4,5,43,3,1};
int i = 4;
if (get_value_index(vec, i)!=vec.end()) {
cout<<"find!"<<endl;
}
else {
cout<<"can't find"<<endl;
}
我在函数中使用的是const_iterator,因为在函数中,不需要改变其值。
9.6
list不支持迭代器比较大小。
可以修改为iter1!=iter2
9.2.2 容器类型成员
每个容器中定义了很多类型,比如size_type,iterator,const_iterator等类型。
初次之外还有value_type,reference,const_reference等类型别名,让我们无需关注容器中具体的元素。
注意reference是容器中元素的引用,而不是容器本身的引用。
访问这些类型我们都需要作用域运算符。
9.7
vector<int>::size_type
9.8
list<string>::const_iterator;//读取
list<string>::iterator;//读取和写入都可以
9.3 begin和end成员
begin和end两个迭代器表示的迭代器范围是容器的所有元素
begin和end有多个版本
begin()
rbegin()
cbegin()
cbegin()返回的类型是const iterator
begin则根据声明的类型来返回iterator或者const iterator
rbegin也是
end和rend也是根据声明的类型来返回iterator是普通类型还是常量类型
为什么begin()就可以范围const iterator,还需要cbegin()呢,其实就是为了减少代码量,使得我们可以直接使用auto来声明变量。
练习
9.0
begin根据变量声明的类型,来决定返回的是普通话iterator还是常量iterator。
cbegin直接返回const iterator
9.10
it1 vector<int>::iterator;
it2 vector<int>::const_iterator
it3 vector<int>::const_iterator
it4 vector<int>::const_iterator
9.2.4 容器定义和初始化
容器的定义和初始化有很多种方式。
1.空构造,因为array在创建具体的类型时,需要指定元素的大小,使用这种方式进行初始化时,array内部的元素执行默认初始化,而其余的容器都是空容器
2.使用拷贝的方式初始化容器,array要求大小要相同。原因很简单,因为array的大小是固定。在执行拷贝初始化时,要求容器的类型和元素的类型必须是一样的。
3.初始化列表,对于其他的容器,列表初始化中元素的个数,就表示容器的元素的个数,但是array除外,初始化array容器时,初始化列表中的元素小于或者等于array的大小。如果小于容器的大小,则先按照顺序初始化,容器后面剩余的元素执行值初始化。
4.使用一个迭代器范围初始化。使用这种方式初始化容器不要求容器的类型一样,也不要求容器的元素类型一样,只要元素类型可以转化为需要初始化的容器的元素的类型即可。array不可以使用这种初始化方式。
//5和6是顺序容器独有的初始化方式
5.指定容器大小进行初始化,这种方法的构造函数时explicit的,即,不可以隐式的转化,该种方式对于没有默认构造函数的类型不适用。对于string不适用
6.指定容器大小,并指定初始值的元素。
注意,array只支持默认初始化和列表初始化和拷贝初始化。
练习
9.11
//1.空的定义
vector<int> vec1;
//2.使用另一个容器初始化
vector<int> vec2(vec1);
vector<int> vec3 = vec1;
//3.使用一个容器的迭代器范围初始化
vector<int> vec4(vec2.begin(),vec2.end());
//4.指定容器内元素的个数
vector<int> vec5(10);
//5.指定个数并赋予初始值
vector<int> vec6(10, 1);
//6.列表初始化
vector<int> vec7{1,2,3,4,5};
9.12
使用一个容器来初始化另外一个容器,要求容器的类型和容器内存储的元素都相同。
而接手两个迭代器创建拷贝的构造函数,不要求容器类型一致,对于容器内的元素,只要可以转换,类型不同也可以。
比如 C风格字符串可以转换为string字符串
那么
deque<const char *> d = {"hello","world"};
vector<string> vec_str(d.begin(),d.end());
上述的代码是成立的。
9.13
list<int> number_list = {1,3,5,7,9,10};
vector<double> number_vec(number_list.begin(),number_list.end());
for (const auto & item:number_vec) {
cout<<item<<endl;
}
9.2.5 赋值和swap
标准库的array允许进行赋值,要求运算符左右两边必须为相同的类型。
容器的赋值操作有以下几种
1.二者的类型必须一致,这和初始化时拷贝初始化一致
2.将列表的值赋值给左侧的容器,容器原来的值就没了,但是array不一样,对于array={123,321},会按照顺序赋值,剩余的部分执行值初始化。
3.swap可以交换两个容器,对于大部分的容器,其实只是两个变量名指向的容器变了,并没有进行拷贝。但是string和array不一样,对string执行swap,回导致之前的迭代器、引用和指针失效,而array的swap是进行了拷贝的交换。其余的容器的迭代器,引用和指针都没有失效。swap在容器类和全局中都有实现,C++ Primer推荐使用全局的swap函数。
4.assign,使用迭代器进行赋值,和初始化时使用迭代器进行赋值时一样的,类型可以不同,容器内部存储的类型可以转换**。需要注意是赋值的迭代器不能是目标容器的迭代器**
5.可以为assign传入列表初始化进行赋值
6.可以传入元素大小,以及值进行初始化。
练习
list<const char*> str_list = {"123","321","456"};
vector<string> str_vec;
str_vec.assign(str_list.cbegin(),str_list.cend());
for (const auto item:str_vec) {
cout << item << endl;
}
9.2.6 容器大小操作
几个和容器大小相关的操作,
size()//当前存了多少个元素
empty()//如果size=0,则empty为true,否则为false
max_size()//容器所能容纳的最大数目
forward_list只有max_size()和empty()两个数据成员。
9.2.7关系运算符
所有的容器都有==和!=运算符
除了无序关联容器外,所有的容器都支持>,<,>=,<=.
无需关联容器容器具体指的是什么,还需要等到后面才知道。
比较的方式和比较字符串一样
1.如果两个容器长度相同,且逐一元素相同则容器相等。
2。如果一个容器的size()小于另外一个容器,但是对应元素都相同,则size()小的那个小于size()大的那个
3.如果容器的大小不一样,则按顺序,逐一比较元素,结果取决于第一个不相等的元素的比较结果。
容器的关系比较,使用的是容器内部的关系运算符。想要实现==和!=。容器内部的元素就需要实现==。
想要实现>,<,>=,<=容器内部的元素就需要实现<和==。
只有类型一样,且容器内元素一样的容器才能进行比较。
9.2.7
9.15
vector<int> vec1 = {1,2,3,4,5,6,7,8,9};
vector<int> vec2 = {1,2,3,4,5,6,7,8,0};
if (vec1==vec2){
cout << "equal" << endl;
}
else {
cout << "not equal" << endl;
}
9.16
两个容器的类型不一样,无法直接比较。
但是我们可以按照之前定义的比较方式自己写代码进行比较。
vector<int> vec2 = {1,2,3,4,5,6,7,8};
list<int> list1 = {1,2,3,4,5,6,7,8,9};
bool equal = true;
if (vec2.size()==list1.size()) {
auto vec_iter = vec2.cbegin();
auto list_iter = list1.cbegin();
while (vec_iter!=vec2.cend()) {
if ((*vec_iter)!=(*list_iter)) {
equal = false;
break;
}
++vec_iter;
++list_iter;
}
}
else {
equal = false;
}
cout << equal << endl;