一、迭代器
在string对象或vector对象中我们可以使用下标运算符来访问其中的字符,除此外,还有一种更通用的机制也可以实现这样的目的,即迭代器(iterator)。
1.1 使用迭代器
与指针不同,迭代器有迭代器的类型,并且拥有返回迭代器的成员,如这些类型都拥有begin成员负责返回指向第一个元素,以及end成员返回指向容器尾元素的下一个位置的迭代器。
1.1.1 迭代器运算符
和指针类型,可以通过解引用迭代器来获取它所指的元素,执行解引用的迭代器必须合法并确实指示这某个元素:
string s("some string");
auto it = s.begin();
while (it != s.end()){ //确保s非空
*it = toupper(*it); //将当前字符改成大写形式
++it;
}
1.1.2 迭代器类型
一般地,与不知道string和vector的size_type成员是什么类型一样,我们也不知道也无须知道迭代器的精确类型,实际上拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型:
vector<int>::iterator it; //it能读写vector<int>的元素
string::iterator it2; //it2能读写string对象中的字符
vector<int>::const_iterator it3; //it3只能读元素,不能写元素
string::const_iterator it4; //it4只能读字符,不能写字符
begin和end返回的具体类型由对象是否是常量决定,如对象是常量,那么begin和end返货const_iterator,否则返回iterator。如果对象只需读操作而无需写操作的话(无论对象本身是否是常量),为了便于专门得到const_iterator类型的返回值,可以使用cbegin和cend。
vector<int> v1;
const vector<int> v2;
auto it1 = v1.begin(); //it1的类型是vector<int>::iterator
auto it2 = v2.begin(); //it2的类型是vector<int>::const_iterator
auto it3 = v1.cbegin(); //it3的类型是vector<int>::const_iterator
1.1.3 结合解引用和成员访问
解引用迭代器可获得迭代器所指的对象,如果该对象的类型是类,那么久可能希望进一步访问其成员,例如一个字符串组成的vector对象来说,要检查其元素是否为空,令it为该vector对象的迭代器,然后检查it所指字符串是否为空即可:
(*it).empty()
在C++中定义了箭头运算符(->),箭头运算符把解引用和成员访问两个操作结合在一起,即it->men和(*it).men表达的意思相同。
需要注意的是,虽然vector对象可以动态地增长,但是不能再for循环中向vector对象中添加元素,以及任何一种可能改变vector对象容量的操作都会使得vector对象的迭代器失效,所以凡是使用迭代器的循环体,都不要向迭代器所属的容器添加元素。
1.2 迭代运算
迭代器的递增运算令迭代器每次移动一个元素,同时也能用==和!=对任意标准库类型的两个有效迭代器进行比较,所有这些运算称之为迭代器运算。
1.2.1 迭代器的算术运算
可以令迭代器和一个整数值相加或相减,其返回值是向前或向后移动了若干个位置的迭代器,如下如果vi有20个元素,vi.size()/2得到10,那么vi.begin()+10表示迭代器所指的元素是vi[10]。
//计算一个得到最接近vi中间元素的迭代器
auto mid = vi.begin() + vi.size()/2;
二、数组
数组是一种类似于标准库类型vector的数据结构,但是数组的大小是确定不变的,不能随意想数组中增加元素,这对程序的运行性能有利,但也会损失一些灵活性。
2.1 定义和初始化内置数组
数组是一种复合类型,数组中的元素个数属于数组类型的一部分,编译时维度是已知的,即维度必须是一个常量表达式。需要注意的是和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。
unsigned v1 = 42; //不是常量表达式
constexpr unsigned v2 = 42; //是常量表达式
int arr[10]; //含有10个整数的数组
int *parr[v2]; //含有42个整型指针的数组
int bad[v1]; //错误,v1不是常量表达式
2.1.1 初始化数组元素
const unsigned sz = 3;
int a1[sz] = {0,1,2};
int a2[] = {0,1,2}; //维度是3的数组
int a3[5] = {0,1,2}; //等价于a3[]={0,1,2,0,0}
int a4[3] = {"hi","bye"}; //等价于a4[]={"hi","bye",""}
int a5[2] = {0,1,2}; //错误,初始值过多
需要注意的是可以用字符串字面值初始化字符数组,,但是需要注意字符串字面值的结尾还有一个空字符,以及不能讲数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值:
const char a6[6] = "daniel" //错误,没有足够空间放空字符
int a[] = {0,1,2};
int a7[] = a; //错误,不允许使用一个数组初始化另一个数组
a7 = a; //错误,不能把一个数组直接赋值给另外一个数组
2.1.2 复杂的数组声明
与vector一样,数组能存放大多数类型的对象,例如可以存放指针的数组,数组本身也是对象,那么可以定义数组的指针及数组的引用:
int *ptrs[10]; //ptrs是含有10个整型指针的数组
int &refs[10] = /* ? */ //错误,不存在引用的数组
int (*parray)[10] = &arr; //parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef引用一个含有10个整数的数组
int *(&arry)[10] ptrs; //arry是数组的引用,该数组含有10个指针
2.1.3 访问数组元素
与标准库类型vector和string一样,数组的元素也能使用范围for语句或下标运算符来访问,数组的索引从0开始,使用数组下标的时候,通常将其定义为size_t类型,size_t是一种跟机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。
unsigned scores[11] = {}; //初始化为0
unsigned grade;
while (cin >> grade){
if (grade <= 100)
++scores[grade/10]; //将当前分数段计数加1
}
for (auto j : scores)
cout << j << " "; //输出当前的计数值
cout << endl;
2.2 指针和数组
C++语言中,指针和数组有非常紧密的联系,使用数组的时候编译器一般会把它转换成指针。通常情况使用取址符来获取某个对象的指针:
int nums[] = {1,2,3,4};
int *p = &nums[0]; //p指向nums的第一个元素
int *p2 = nums; //等价于p2=&nums[0]
auto p3(nums); //p3是一个整型指针,指向nums第一个元素
p3 = 42; //错误,p3是一个指针,不能用int值给指针赋值
需要注意的是使用decltype关键字时,即decltype(nums)时返回的类型是由10个整数构成的数组:
decltype(nums) a = {0,1,2,3,4,5};
a = p; //错误,不能用整型指针给数组赋值
a[4] = j; //正确,把j的值赋给a的一个元素
2.2.1 指针也是迭代器
vector和string的迭代器支持的运算,数组的指针全部支持,并且可以使用指针遍历数组中的元素,前提是获得指向数组第一个元素的以及尾元素的下一位置的指针。
int arr[] = {0,1,2,3,4};
int *p = arr; //p指向arr的第一个元素
++p; //现在p指向arr[1]
给一个指针加上或减去某整数值,结果仍是指针,新指针指向的元素是与原来的指针相比前进或后退了该整数值个位置,并且可以解引用该结果指针。
constexpr size_t sz = 5;
int arr[sz] = {1,2,3,4,5};
int *p = arr;
int *p2 = p+4; //p2指向arr[5]
int *last = *(p+4); //解引用,把last初始化为5
和迭代器一样,两个指针相减的结果是它们之间的距离,参与运算的两个指针必须指向同一数组中的元素。两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,与size_t一样,ptrdiff_t也是一种定义在cstdeff头文件中的机器相关的类型,其实一种带符号类型,这一点与vector和string不同。
int *p = &arr[2]; //p指向索引为2的元素
int k = p[-2]; //p[-2]是arr[0]指向的那个元素
2.2.2 标准库函数begin和end
这里的begin和end这两个函数与容器中的两个同名成员功能类似,不过数组不是类类型,因此这里的两个函数不是成员函数,它们定义在iterator头文件中。
int a[] = {0,1,2,3,4,5,6};
int *beg = begin(a); //指向a首元素的指针
int *last = end(a); //指向a尾元素的下一位置的指针
参考文献:
①C++ Primer 第五版。