《C++Primer》复习——with C++11 [1]
1.头文件中不应包含using声明,因为头文件的内容会拷贝到所有引用到他的文件中去,如果头文件里有谋个using声明,那么每个使用了该头文件的文件就会有这个声明,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。
2.string对象的拷贝初始化和直接初始化。string str1 = "Hello";是拷贝初始化,编译器吧等号右侧的初始值拷贝到新创建的对象中去。使用getline读取一行, getline(cin, line),函数从给定的输入流中读取内容,知道遇到换行符为止,但是换行符并没有被读入line中去。
1 string line; 2 while (getline(cin, line)) 3 cout << line << endl;
getline的empty和size操作,empty操作返回string对象是否为空,返回一个对应的布尔值,size返回string对象的长度,即string对象中字符的个数。
1 while (getline(cin, line))//如果串不空则输出 2 if (!line.empty()) 3 cout << line << endl; 4 while (getline(cin, line))//输出字符数量大于30的串 5 if (line.size > 30) 6 cout << line << endl;
string对象的size_type类型是一个无符号整形,用于表示返回自防护大小的类型,允许通过auto来推断他的类型
auto len = line.size(); //len的类型就是size_type,不过使用的auto自动确定了类型而不需要事先了解size返回类型
同时string对象也顶一了比较的操作符 != , == , > , <, <= , >= 。string对象也允许对象间的相加,string1+string2。处理字符串中的每个字符,判断特定的字符,用函数库cctype定义了一组表尊库函数处理这些。例如
1 isalunm(c)//是字母或数字为真 2 isalpha(c)//是字母为真 3 iscntrl(c)//是控制字符为真 4 isdigit(c)//是数字为真 5 isgraph(c) // 不是空格但可以打印 6 islower(c) //是小写? 7 isprint(c) //是否是可打印的字符 8 tolower(c) //如果大写字母,转换小写,否则输出 9 toupper(c) //如果小写字母,转换成对应的大写,否则原本输出
使用基于范围的for语句,用范围for把string对象中的字符每行一个输出。
1 string str("some stringA"); 2 for (auto c : str) //对于str中的每一个字符 3 cout << c << endl; //输出当前字符
如下代码使用范围for语句和ispunct函数来统计string对象中标点符号的个数。
1 string s("hello world!!"); 2 decltype(s.size()) punct_cnt = 0; 3 for (auto c : s) 4 if (ispunct(c)) 5 ++punct_cot; 6 cout << punct_cnt << "puncts in: " << s << endl;
string对象支持[]下标访问,想要编写一个吧0到15之间的数字转换成对应的十六进制形式,初始化一个字符串令其存放16个十六进制数字。
1 const string hexdigits = "0123456789ABCDEF"; 2 string result; 3 string::size_type n;
4 while (cin >> n) 5 if (n < hexdigits.size()) 6 result += hexdigits[n]; 7 cout << "Your hex number is:" << result << endl;
3.vector是一个类模板,vecotr表示对象的集合,其中所有的对象类型一致。因为引用不是对象,所以不包含存在引用的vector,其他大多数非引用类型和类类型都可以构成vector对象,vector的初始化方法如下:
1 vector<T> v1; //空 2 vector<T> v2 = v1; //拷贝初始化 3 vector<T> v2(v1); //默认初始化 4 vector<T> v3(n, val); //v3含有n个val初始值 5 vector<T> v4(n); //v4包含了n个重复执行了初始化的对象 6 vector<T> v5= {a, b, c, ...};//v5包含了初始值个数的原色 7 vector<T> v5 {a, b, c, ...};//等价于上面
vector<string> v1{"hello", "world", "!!!"};//列表初始化 vector<string> v2("hello", "world", "!!!");//错误
vector对象的添加元素,首先创建一个空对象,然后用push_back方法相其中添加元素。
1 vector<int> v2; //空vector对象 2 for (int i = 0; i != 100; ++i) 3 v2.push_back(i); //一次把整数值放在v2的尾端
1 string word; 2 vector<string> text; //空对象 3 while (cin >> word) 4 text.push_back(word); //把word添加到text后面
通常的情况下都是先定义一个空的vector对象,然后在使用的时候添加值,而不开始时候直接复制,vector对象支持高效率的添加元素。vector的操作很多与string的操作类似或者相同,如empty、size、[]、!= == > < <= >= 等。可以使用范围for语句处理vector对象的所有元素
1 vector<int> v{1,2,3,4,5,6,7,8,9}; 2 for (auto &i : v) 3 i *= i; 4 for (auto i: v) 5 cout << i << " "; 6 cout << endl;
1 vector<unsigned> scores(11, 0); 2 unsigned grade; 3 while (cin >> grade) 4 { 5 if (grade <= 100) 6 ++scores[grade/10]; //很好的体现了C++代码的简洁性 7 }
使用下表的时候确认了他的合法性,属于0 到 scores.size() - 1的才是有效的范围。切记不可使用下标的形式添加元素,例如有100个元素,我们使用 v[100]来添加第101个元素,这是错误的,如果要添加元素,应该使用push_back方法。
4.迭代器是一个对象,它同指针一样,可以通过它来访问容器中的元素,也可以指向其他元素。获取迭代器不是通过取地址符,而是通过容器对象的一些方法返回之,比如begin、end分别返回了指向第一个元素和最后一个元素的下一个为止的迭代器,下一个位置的迭代器意味着并不是指向最后一个位置,这个位置的迭代器没有实际意义,仅仅作为一个标记。如果容器为空,则begin和end返回的是同一个迭代器,都是韦后迭代器。通过 == 、!=符号来比较迭代器是否相同。
1 string s("some string"); 2 if (s.begin() != s.end()) 3 { 4 auto it = s.begin(); 5 *it = toupper(*it); 6 }
通过++运算符我们将迭代器从原来的元素移动到下一个元素,因为end返回的迭代器并不实际指向谋个元素,所以不能对它进行递增或者递减以及解引用的操作。
1 for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) 2 *it = toupper(*it);
如果对象是敞亮,begin和end返回的迭代器类型是 const_iterator,如果不是敞亮,返回的是iterator类型。为了专门的带const_iterator类型,C++11引入了两个函数分别是cbegin和cend泳衣返回const_iterator类型的迭代器。书上特别的声明了:凡是使用了迭代器的循环体,都不要想迭代器所属的容器添加元素。迭代器相关的操作也有 >/</!=/==之类。下面是使用迭代器写的二分搜索。
1 auto beg = text.begin(), end = text.end(); 2 auto mid = text.begin() + (end - beg)/ 2; 3 while (mid != end && *mid != sought){ 4 if (sought < *mid) 5 end = mid; 6 else 7 beg = mid + 1; 8 mid = beg + (end - beg) / 2; 9 }
对于数组,使用迭代器的函数begin和end也可以获取指向数组首元素和为元素下以位置的指针。
5.try语句块和异常处理。throw表达式引发一个异常,throw表达式包含关键字和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型,throw表达式后面通常紧跟一个分好,从而构成一条表达式语句。
1 #include <iostream> 2 #include <stdexcept> 3 using namespace std; 4 void fun(int a, int b) 5 { 6 if (a == b) 7 throw runtime_error("A == B"); 8 else 9 throw runtime_error("A != B"); 10 } 11 int main() 12 { 13 int x = 1, y = 1; 14 try{ 15 fun(x, y); 16 }catch(runtime_error err){ 17 cout << err.what(); 18 } 19 return 0; 20 }
这段代码很明白的演示了throw、try、catch的用法,runtime_error在标准函数库头文件stdexcept中定义。当异常抛出时候,首先搜索抛出该异常的函数,如果没有找到匹配的catch字语句,种植该函数,病在调用该函数中继续寻找,如果还是没有找到匹配的catch语句,这个新的函数也将被种植,继续搜索调用它的函数,以此类推,沿着程序的执行路径逐层回退,知道找到适当的类型catch语句为止。如果最终没有找到catch,程序转到名为terminate的标准库函数,该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。
栈展开过程沿着乔涛函数的调用莲不断查找,知道找到了与异常匹配的catch为止,或者没有找到而退出。栈展开过程时,局部对象被销毁,栈展开过程中如果退出了谋个块,编译器将负责确保在这个块中创建的对象能被正确的销毁,如果谋个局部对象是类类型,那么会调用析构函数。如果异常发生在构造函数中,则当前的对象可能只构造了一部分,有的成员已经初始化了,而另外一些成员没有初始化,也保证对象能够正常的销毁。如果析构函数也要抛出异常,那么就在析构函数自身内部try、catch和throw,不能留给别人处理,要在析构函数内部处理完成。更多继承类型的异常在复习了继承以后再来讲述。