首页 > 代码库 > 《C++Primer》复习——with C++11 [1]

《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     else7         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,不能留给别人处理,要在析构函数内部处理完成。更多继承类型的异常在复习了继承以后再来讲述。

 

《C++Primer》复习——with C++11 [1]