写这篇文章的目的
身为C++的零基础初学者,短期内把《C++Primer》啃下来是一个比较笨但是有效的方法,一方面可以掌握比较规范的C++语法(避免被项目中乱七八糟的风格带跑偏),另一方面又可以全面地了解C++语法以及C++11新标准(后续要做的事情就剩下查漏补缺,不断完善自己的知识体系)。
个人感觉从零学习一门新知识比较好的方法是快速了解知识的全貌,然后构建自己的知识地图,后续不断地补充相应的细节。
由于《C++Primer》和大多数的教科书一样废话连篇,因此想要精炼一下每篇文章的内容再打印成pdf,方便温故知新。
空语句
如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句。一种常见的情况是,当循环的全部工作在条件部分就可以完成时,我们通常会使用到空语句。例如我们想读取输入流的内容直到遇到一个特定的值位置,除此之外什么事情都不做:
// 重复读入数据直至达到文件末尾或者某次输入的值等于sought
while (cin >> s && s != sought)
; // 空语句
在
if
或者while
条件后面跟了一个额外的空语句可能表示循环体是空的,可能引发错误。
语句作用域
可以在if
、switch
、while
和for
语句的控制结构内定义变量。定义在控制结构中的变量只在相应语句的内部内可见,一旦语句结束,变量也就超出其作用范围了。
如果其他代码也需要控制访问变量,则变量必须定义在语句的外部。
条件语句
1. if语句
- 复杂
if
语句或者嵌套if
语句时注意使用花括号,否则可能结果会超乎你的预期 - 悬垂
else
:当一个if
语句嵌套在另一个if
语句内部时,很可能if
语句会多于else
语句,C++
对于判断某个给定的else
是与if
匹配提供了方法:它规定else
与离它最近的尚未分配的if
匹配,从而消除了程序的二义性。 - 如果希望
else
分支和最外层的if
语句匹配起来,那么可以在内层if
语句的两端加上花括号,使其成为一个块。
2. switch语句
-
case
标签必须是整型常量表达式 - 任何两个
case
标签的值不能相同,否则会引发错误 - 如果某个
case
标签匹配成功,那么将从该标签开始往后顺序执行所有的case
分支 - 一般不要省略
case
分支最后的break
语句,如果没写的话最好加上注释表示这样写的逻辑 - 如果
switch
结构以一个空的default
标签作为结束,则该default
标签后面必须跟上一条空语句或者一个空块
循环语句
1. while语句
while (condition)
statement
- 定义在
while
条件部分或者while
循环体内的变量每次迭代都经历从创建到销毁的过程 - 当不确定需要迭代多少次时,使用
while
循环比较合适 - 还有一种情况也适合使用
while
循环:当我们想在循环结束后访问循环控制变量
2. 传统的for语句
for (init-statement; contidition; expression)
statement
- 牢记
for
语句中定义的对象只在for
循环体内可见,这也是当我们想在循环结束后访问循环控制变量最好使用while
的原因 -
init-statement
可以定义多个对象,但是只能有一条声明语句,因此所有变量的基础类型必须相同
3. 范围for
语句
C++11
新标准引入了一种更加简单的for
语句,用于遍历容器或者其他序列的所有元素:
for (declaration : expression)
statement
-
expression
表示的必须是一个序列,比如用花括号括起来的初始值列表、数组、vector
或者string
等类型的对象,这些对象的共同特点是可以返回迭代器的begin
和end
成员 -
declaration
定义一个变量,序列中的每个元素都能转换成该变量的类型,确保类型相容最简单的方法是使用auto
类型说明符。如果需要对序列中的元素执行写操作,循环变量必须声明为引用类型 - 不能通过范围
for
语句增加vector
对象或者其他容器的元素,因为在范围for
语句中预存了end()
的值,一旦在序列中添加或者删除元素就会导致end
函数的值变得无效
do while语句
do while
语句和while
语句基本一致,唯一的区别在于不管条件的值如何,do while
都会至少执行一次循环:
do
statement
while (condition);
跳转语句
1. break语句
break
语句只能出现在迭代语句或者switch
语句内部,负责终止离它最近的while
、do while
、for
或switch
语句。
2. continue语句
continue
终止最近的循环中的当前迭代并立即开始下一次迭代。
-
continue
只能出现在for
、while
、do while
循环的内部 - 仅作用于离它最近的循环
3. goto语句
goto
语句的作用是从goto
语句无条件跳转到同一函数内的另一条语句,不推荐在程序中使用goto
语句,因为它使得程序又难理解又难修改。
try语句和异常处理
异常处理机制为程序中异常检测和异常处理这两部分的协作提供支持,在C++
语言中异常处理包括:
-
throw
表达式:异常检测部分使用thrrow
表达式来表示它遇到了无法处理的问题,我们说throw
引发raise
了异常 -
try
语句块:try
以关键字try
开始,并以一个或者多个catch
子句结束。try
语句块中抛出的异常通常会被某个catch
子句处理,它们也被称为异常处理代码 - 异常类:用于在
throw
表达式和相关的catch
子句之间传递异常的具体信息
1. throw表达式
举个例子,下面的程序把两个Sales_item
对象相加,检查读入的记录是否是关于同一种书籍的,如果不是就输出一条信息然后退出:
// 检查两条数据是不是关于同一种书籍的
if (item1.isbn() != item2.isbn())
throw runtime_error("Data must refer to same ISBN");
cout << item1 + item2 << endl;
上述例子抛出了一个
runtime_error
的对象,该异常会终止当前函数,并将控制权交给能处理该异常的代码
2. try语句块
try {
program-statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
}
-
try
语句块内声明的变量在块外部无法访问,特别是在catch
子句内也无法访问 - 可通过
runtime_error
的成员函数what
获取const char*
的C
风格字符串 - 如果一段程序没有
try
语句且发生了异常,那么系统会调用terminate
函数并终止当前程序的运行 - 对于需要处理异常并继续执行的程序,我们必须时刻清楚异常何时发生,异常发生后程序应如何确保对象有效、资源无泄漏和程序有无处于合理状态等
3. 标准异常
<stdexcept>
定义的异常类:
exception 最常见的问题
runtime_error 只有在运行时才能检测出来的问题
range_error 运行时错误:生成的结果超出了有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 程序逻辑错误
domain_error 逻辑错误:参数对应的结果值不存在
invalid_argument 逻辑错误:无效参数
length_error 逻辑错误:试图创建一个超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值
- 我们只能以默认初始化的方式初始化
exception
、bad_alloc
和bad_cast
对象,不允许为这些对象提供初始值 - 其他异常类型的行为恰恰相反,我们应该用
string
或者C
风格字符串初始化这些类型的对象,但是不允许使用默认初始化的方式。当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。 - 异常类型只定义了一个名为
what
的成员函数,返回值是一个指向C
风格字符串的const char*
,用于提供关于异常的一些文本信息。 - 如果异常对象类型有一个字符串初始值,那么
what
会返回该值,对于其他无初始值的异常类型来说,what
返回的内容由编译器决定
网友评论