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

时间:2023-03-09 16:08:27
《C++Primer》复习——with C++11    [1]

1.头文件中不应包含using声明,因为头文件的内容会拷贝到所有引用到他的文件中去,如果头文件里有谋个using声明,那么每个使用了该头文件的文件就会有这个声明,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。

2.string对象的拷贝初始化和直接初始化。string str1 = "Hello";是拷贝初始化,编译器吧等号右侧的初始值拷贝到新创建的对象中去。使用getline读取一行, getline(cin, line),函数从给定的输入流中读取内容,知道遇到换行符为止,但是换行符并没有被读入line中去。

 string line;
while (getline(cin, line))
cout << line << endl;

getline的empty和size操作,empty操作返回string对象是否为空,返回一个对应的布尔值,size返回string对象的长度,即string对象中字符的个数。

 while (getline(cin, line))//如果串不空则输出
if (!line.empty())
cout << line << endl;
while (getline(cin, line))//输出字符数量大于30的串
if (line.size > )
cout << line << endl;

string对象的size_type类型是一个无符号整形,用于表示返回自防护大小的类型,允许通过auto来推断他的类型

auto len = line.size(); //len的类型就是size_type,不过使用的auto自动确定了类型而不需要事先了解size返回类型

同时string对象也顶一了比较的操作符 != , == , > , <, <= , >= 。string对象也允许对象间的相加,string1+string2。处理字符串中的每个字符,判断特定的字符,用函数库cctype定义了一组表尊库函数处理这些。例如

 isalunm(c)//是字母或数字为真
isalpha(c)//是字母为真
iscntrl(c)//是控制字符为真
isdigit(c)//是数字为真
isgraph(c) // 不是空格但可以打印
islower(c) //是小写?
isprint(c) //是否是可打印的字符
tolower(c) //如果大写字母,转换小写,否则输出
toupper(c) //如果小写字母,转换成对应的大写,否则原本输出

使用基于范围的for语句,用范围for把string对象中的字符每行一个输出。

 string str("some stringA");
for (auto c : str) //对于str中的每一个字符
cout << c << endl; //输出当前字符

如下代码使用范围for语句和ispunct函数来统计string对象中标点符号的个数。

 string s("hello world!!");
decltype(s.size()) punct_cnt = ;
for (auto c : s)
if (ispunct(c))
++punct_cot;
cout << punct_cnt << "puncts in: " << s << endl;

string对象支持[]下标访问,想要编写一个吧0到15之间的数字转换成对应的十六进制形式,初始化一个字符串令其存放16个十六进制数字。

 const string hexdigits = "0123456789ABCDEF";
string result;
string::size_type n;
 while (cin >> n)
if (n < hexdigits.size())
result += hexdigits[n];
cout << "Your hex number is:" << result << endl;

3.vector是一个类模板,vecotr表示对象的集合,其中所有的对象类型一致。因为引用不是对象,所以不包含存在引用的vector,其他大多数非引用类型和类类型都可以构成vector对象,vector的初始化方法如下:

 vector<T> v1;              //空
vector<T> v2 = v1; //拷贝初始化
vector<T> v2(v1); //默认初始化
vector<T> v3(n, val); //v3含有n个val初始值
vector<T> v4(n); //v4包含了n个重复执行了初始化的对象
vector<T> v5= {a, b, c, ...};//v5包含了初始值个数的原色
vector<T> v5 {a, b, c, ...};//等价于上面
vector<string> v1{"hello", "world", "!!!"};//列表初始化
vector<string> v2("hello", "world", "!!!");//错误

vector对象的添加元素,首先创建一个空对象,然后用push_back方法相其中添加元素。

 vector<int> v2;                 //空vector对象
for (int i = ; i != ; ++i)
v2.push_back(i); //一次把整数值放在v2的尾端
 string word;
vector<string> text; //空对象
while (cin >> word)
text.push_back(word); //把word添加到text后面

通常的情况下都是先定义一个空的vector对象,然后在使用的时候添加值,而不开始时候直接复制,vector对象支持高效率的添加元素。vector的操作很多与string的操作类似或者相同,如empty、size、[]、!= == > < <= >= 等。可以使用范围for语句处理vector对象的所有元素

     vector<int> v{,,,,,,,,};
for (auto &i : v)
i *= i;
for (auto i: v)
cout << i << " ";
cout << endl;
 vector<unsigned> scores(, );
unsigned grade;
while (cin >> grade)
{
if (grade <= )
++scores[grade/];  //很好的体现了C++代码的简洁性
}

使用下表的时候确认了他的合法性,属于0 到 scores.size() - 1的才是有效的范围。切记不可使用下标的形式添加元素,例如有100个元素,我们使用 v[100]来添加第101个元素,这是错误的,如果要添加元素,应该使用push_back方法。

4.迭代器是一个对象,它同指针一样,可以通过它来访问容器中的元素,也可以指向其他元素。获取迭代器不是通过取地址符,而是通过容器对象的一些方法返回之,比如begin、end分别返回了指向第一个元素和最后一个元素的下一个为止的迭代器,下一个位置的迭代器意味着并不是指向最后一个位置,这个位置的迭代器没有实际意义,仅仅作为一个标记。如果容器为空,则begin和end返回的是同一个迭代器,都是韦后迭代器。通过 ==  、!=符号来比较迭代器是否相同。

 string s("some string");
if (s.begin() != s.end())
{
auto it = s.begin();
*it = toupper(*it);
}

通过++运算符我们将迭代器从原来的元素移动到下一个元素,因为end返回的迭代器并不实际指向谋个元素,所以不能对它进行递增或者递减以及解引用的操作。

 for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
*it = toupper(*it);

如果对象是敞亮,begin和end返回的迭代器类型是 const_iterator,如果不是敞亮,返回的是iterator类型。为了专门的带const_iterator类型,C++11引入了两个函数分别是cbegin和cend泳衣返回const_iterator类型的迭代器。书上特别的声明了:凡是使用了迭代器的循环体,都不要想迭代器所属的容器添加元素。迭代器相关的操作也有 >/</!=/==之类。下面是使用迭代器写的二分搜索。

 auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg)/ ;
while (mid != end && *mid != sought){
if (sought < *mid)
end = mid;
else
beg = mid + ;
mid = beg + (end - beg) / ;
}

对于数组,使用迭代器的函数begin和end也可以获取指向数组首元素和为元素下以位置的指针。

5.try语句块和异常处理。throw表达式引发一个异常,throw表达式包含关键字和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型,throw表达式后面通常紧跟一个分好,从而构成一条表达式语句。

 #include <iostream>
#include <stdexcept>
using namespace std;
void fun(int a, int b)
{
if (a == b)
throw runtime_error("A == B");
else
throw runtime_error("A != B");
}
int main()
{
int x = , y = ;
try{
fun(x, y);
}catch(runtime_error err){
cout << err.what();
}
return ;
}

这段代码很明白的演示了throw、try、catch的用法,runtime_error在标准函数库头文件stdexcept中定义。当异常抛出时候,首先搜索抛出该异常的函数,如果没有找到匹配的catch字语句,种植该函数,病在调用该函数中继续寻找,如果还是没有找到匹配的catch语句,这个新的函数也将被种植,继续搜索调用它的函数,以此类推,沿着程序的执行路径逐层回退,知道找到适当的类型catch语句为止。如果最终没有找到catch,程序转到名为terminate的标准库函数,该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。

栈展开过程沿着乔涛函数的调用莲不断查找,知道找到了与异常匹配的catch为止,或者没有找到而退出。栈展开过程时,局部对象被销毁,栈展开过程中如果退出了谋个块,编译器将负责确保在这个块中创建的对象能被正确的销毁,如果谋个局部对象是类类型,那么会调用析构函数。如果异常发生在构造函数中,则当前的对象可能只构造了一部分,有的成员已经初始化了,而另外一些成员没有初始化,也保证对象能够正常的销毁。如果析构函数也要抛出异常,那么就在析构函数自身内部try、catch和throw,不能留给别人处理,要在析构函数内部处理完成。更多继承类型的异常在复习了继承以后再来讲述。