C++ Primer学习纪录(三)
STL
之vector
容器简介
模板
先介绍下模板,C++
语言中既有类模板
,也有函数模板
,STL
中大量使用了类模板编程,这次学习的vector
容器也自然是类模板
之一。
模板的编写等学到了再做详细介绍。模板本身不是类或函数,而是可以看作一份类或函数的编写说明书,编译器通过这份说明书在编译时正确生成相关的类或函数,这一过程称为实例化。
定义和初始化vector
对象
vector
使用之前,需要包含vector
的头文件,include <vector>
, 并且可以使用std::vector<T>
来使用,当然也可以使用using namespace std::vector
来简化代码。
由于vector
使用了类模板编程,因此vector
可以容纳绝大多数的类型对象作为其元素,但引用除外(引用不是对象)。
C++11
之前,若用vector
作为vector
的对象,则需要再最后的尖括号之间留出一个空格, 例如vector<vector<int> >
, 但C++11
标准以后不需要流出哪一个空格。
具体如何初始化见下图,T
为任意类型对象。
列表初始化
列表初始化是C++11
标准所引入的对象初始化方法,用花括号括起来的初试元素值来初始化对象。
vector<int> number = {0, 10, 50};
vector<int> number{0, 10, 50};
上述代码中
第一句话number
包含了3个元素,为0
, 10
, 50
, 该操作为拷贝初始化;
第二句话与第一句同样的效果,但其初始化方式为直接初始化。
在C++11中,若使用2个元素的列表初始化,当元素与容器内数据类型不同时,会执行列表初始化失败,但程序不会报错,而是尝试进行默认初始化
vector<int> v1{10, 1};
vector<string> v2{10, 1};
vector<string> v3{10, 1, 20};
上述代码
第一句为列表初始化,v1
中含有10
, 1
两个元素;
第二句话10
和1
不是string
类型,因此进行列表初始化失败,编译器尝试默认初始化,因此与vector<string> v2(10, 1)
效果相同;
第三句话同样列表初始化失败,但与2不同的时,当编译器进行默认初始化时也失败,因此出错。
push_back
操作
vector
容器又称为动态数组,动态性体现在可以通过push_back
操作向尾部压入一个元素。
vector<int> v;
for(int i = 0; i < 100; ++i){
v.push_back(i);
}
上述代码像一个空vector
容器中添加了0~99
共100
个数。
请注意若循环体内有
push_back
操作,不要使用范围for
语句,因为push_back
会使当前的迭代器失效。
例如
vector<int> v{0, 1, 2, 3, 4};
for(auto it: v){
v.push_back(10);
}
这样的代码是完全错误的。
vector
的其他操作
其中比较操作符与string
类的比较操作符相同,也是通过字典序来进行比较。
字典序比较:
对于序列< a 1 a_{1} a1, a 2 a_{2} a2, a 3 a_{3} a3… a n a_{n} an…> 和 < b 1 b_{1} b1, b 2 b_{2} b2, b 3 b_{3} b3… b n b_{n} bn…>而言,< a 1 a_{1} a1, a 2 a_{2} a2, a 3 a_{3} a3… a n a_{n} an…>小于< b 1 b_{1} b1, b 2 b_{2} b2, b 3 b_{3} b3… b n b_{n} bn…>是指:
存在下标m, 使得 a 1 a_{1} a1 = b 1 b_{1} b1, a 2 a_{2} a2 = b 2 b_{2} b2… a m a_{m} am = b m b_{m} bm, a m + 1 a_{m+1} am+1 < b m + 1 b_{m + 1} bm+1… (后续的随意)
唯一一点的区别是必须要保证vector
中的元素可以进行比较(有一些自己定义的类/结构体没有定义过比较操作,无法进行比较)
size()返回的依旧是size_type类型,因此建议使用
auto len = v.size()
索引操作
vector
的索引与数组类似,索引范围均为[0, length)
的左闭右开区间。只有一点要注意,就是不要通过索引操作为vector
添加元素。
vector<int> v;
for(int i = 0; i < 100; i++){
v[i] = i;
}
v
是空的,通过下标添加会报错,正确的做法是通过push_back
添加元素。
当然索引操作是可以修改元素的~ 除非定义为const vector<T>
类型
迭代器操作
STL
标准库的容器/对象均拥有名为begin
和end
的成员。begin
方法返回第一个元素的迭代器,end
方法返回最后一个元素后面一个位置的迭代器。基于这个特性,可以使用v.begin() == v.end()
来判断一个容器/对象是否为空。
迭代器运算
迭代器可以通过一些运算符来进行运算,具体如下
这儿解释*
解引用符和->
成员访问操作符
*
类似于指针中的解引用,可以解引用迭代器,获取迭代器所指向的对象。若该对象是一个类,可以使用.
访问其中的成员。
->
箭头运算符将解引用和成员访问两个操作结合在一起。it->empty()
与(*it).empty()
是一个效果。
注意
(*it)
要加括号,因为*it.empty()
是指对it.empty()
进行解引用,而不是对it
迭代器进行解引用。
vector
和string
特有的迭代器操作
这些操作是在set
、map
等其他容器的迭代器中所没有的。
因此在使用迭代器操作时,建议使用
!=
和==
操作
例如for(auto it = v.begin(); it != v.end(); it++){...}
;
iter1 - iter2
返回的类型是difference_type
类型,是带符号整数。引入目的与size_type
相同,因此建议使用auto diff = iter1 - iter2;
const_iterator
和iterator
const_iterartor
与常量指针差不多,能读但不能修改指向对象的值。iterator
与指针类似,能读能写。若定义const vector<T> name
常量对象,则name.begin()
返回一个const_iterator
; 若定义为非常量对象, 则返回一个iterator
。
在非常量对象的vector中若希望使用
const_iterator
, 可以使用name.cbegin()
或者name.cend()
来进行获取。但常量对象的容器不能使用iterator
。