3.4迭代器
迭代器:我们知道可以使用下标运算符来访问string对象的字符或vector对象的元素,迭代器就是一种更通用的机制来实现同样的目的。
所有的标准库容器都可以使用迭代器,迭代器有有效与无效之分,其与指针类似。有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一个位置;其他情况都属于无效。
与指针不一样的是,获取迭代器不是使用取地址符。有迭代器的类型同时拥有返回迭代器的成员,比如:这些类型都有名为begin与end的成员。
begin成员:负责返回指向的第一个元素的迭代器
end成员:负责返回指向容器的“尾元素的下一位置”,表示我们已经处理完了容器中的所有元素。
//b表示v的第一个元素,e表示v尾元素的下一位置
auto b = v.begin();
auto e = v.end();
我们一般不关心迭代器的准确类型到底是什么,所以使用auto关键字定义变量b和e。
*iter; //返回迭代器所指元素的引用
iter->mem; //等价于(*iter).mem解引用iter并获取该元素名为mem的成员
++iter; //令iter指示容器中的下一个元素
--iter; //令iter指示容器中的上一个元素
iter1 == iter2; //判断是否相等
iter1 != iter2; //判断是否不等
eg:把string对象中的第一个单词改为大写形式
//依次处理s的字符直到我们处理完毕或者遇到空白
for(auto it = s.begin(); it != s.end() && !isspace(it*); ++it)
*it = toupper(*it); //将it指向的字符改为大写
泛型编程:所有标准库容器的迭代器都定义了==和!=,但是大多数没有定义<,所以只要我们养成使用迭代器和在for循环中使用!=的习惯,就不用在在意到底是哪种容器类型。
begin和end运算符
begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); //it1的类型是vector<int>::iterator
auto it2 = cv.begin(); //vector<int>::const_iterator
有时这种行为并非我们所要,如果对象值需读操作而无需写操作的话,最好使用常量类型,c++11新标准引入了两个新函数,分别是cbegin和cend
auto it3 = v.cbegin(); //it3的类型是vector<int>::const_iterator
注意:vector以及string对象无论本身是否有常量,返回值都是const_iterator
结合解引用和成员访问操作
解引用迭代器可以获得迭代器所指的对象,如果该对象的类型恰好是类,就有可能进一步访问他的成员。
例如:对于一个由字符串组成的vector的对象,想要检查其元素是否为空,令it为该vector的迭代器,只需检查it所指字符串是否为空即可。
(*it).empty(); //正确,解引用it,然后调用结果对象的empty()成员
it->empty(); //正确,等价于上
*it.empty(); //错误,试图访问it的名为empty的成员,但it是个迭代器没有成员
注意(*it)一定要有小括号
eg:用一个名为text的字符串向量存放文本文件中的数据,如果要输出text中第一段的内容,可利用迭代器写一个循环令其遍历text,直到遇到空字符串的元素为止:
//依次输出text的每一行直到遇到第一个空白行为止
for(auto it = text.cbegin();
it != text.cend() && !it->empty(); ++it)
cout << *it << endl;
3.5数组
数组的维度必须是一个常量表达式,并且定义数组时必须指定类型,不允许使用auto关键字,例如
constexpr unsigned sz = 42; //常量表达式
int arr[10]; //可以
string strs[sz]; //可以
一些复杂数组的声明,就数组而言,由内向外阅读比从右向左好多了。例如:*Parray意味着Parray是个指针,接下来观察右边,可知道Parray是个指向大小为10的数组的指针,最后观察左边,知道数组中的元素是int,所以,Parray是一个指针,他指向一个int数组,数组中包含10个元素。
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个整型指针
数组还有一个特性:在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针
string *p2 = nums; //等价于p2 = &nums[0]
C++11新标准中引入了两个名为begin和end的函数,数组不是类类型,因此这两个函数不是成员函数,正确的使用形式是将数组作为他们的参数
int arr[] = {0,1,2,3,4,5};
int *beg = begin(arr); //指向arr首元素的指针
int *last = end(arr); //指向arr尾元素的下一位置指针
//寻找第一个负值元素,如果已经检查完全部元素则结束循环
while(beg != last && *beg >= 0)
++beg;
C风格字符串
尽管C++支持C风格字符串,但是最好还是不要使用它们。
C++风格字符串是使用的string对象,允许使用字符串字面值来对其初始化
string s("Hello World");
C风格字符串将字符串存放在字符数组中,并且以空字符('\0')结束
strlen(p); //返回p的长度,空字符不计算在内
strcmp(p1,p2); //比较p1与p2是否相等,如果相等,返回0,如果p1大于p2返回正值,否则为负值
strcat(p1,p2); //将p2附加到p1之后,返回p1
strcpy((p1,p2); //将p2拷贝给p1,返回p1
传入此类函数的指针,必须指向以空字符作为结束的数组
与旧代码的接口
任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:
1、允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值
2、在string对象的加法运算中允许使用以空字符结尾的字符数组作为其中一个运算对象(不能两个都是);在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。
上述性质反过来不成立,如果某处需要一个C风格字符串,string专门提供了一个名为c_str的成员函数:
char *str = s; //错误,不能用string对象初始化char*
const char *str = s.c_str(); //正确
c_str()函数的返回值是一个C风格字符串,也就是说,返回的结果是一个指针,该指针指向一个以空字符结束的字符数组,而这个数组所存的数组恰好与string对象一致,结果指针的类型是const char*,从而确保我们不会改变字符数组的内容。
所以如果执行玩c_str()后程序一直都能使用其返回的数组,最好将该数组重新拷贝一份