🌔第5章 语句
🌗5.1 简单语句
表达式语句
- 一个表达式,末尾加上分号就变成了
表达式语句
- 表达式语句的作用是
执行表达式并丢弃掉求值结果
ival + 3; //一条没有实际用处的表达式语句
cout << ival;//一条有用的表达式语句
空语句
- 空语句只含单独的一个分号
用法
:
- 用在
语法上需要一条语句但逻辑上不需要的地方
,此时用空语句 - 当循环的全部工作条件部分就可以完成时
使用空语句应该加上注释。
; //空语句
while (cin >> s && s != sought)
;//空语句
复合语句(块)
- 复合语句是指用
{}
(花括号)括起来的语句和声明的序列,复合语句也被称作块
- 一个块就是一个作用域
- 空块的作用等价于空语句
用法
- 语法上需要一条语句,但是逻辑上需要多条语句
while (val <= 10)
{
sum += val;
++ val;
}
🌗5.2 语句作用域
- 可以在 if、switch、while、for 语句的控制结构内定义变量。。但是定义在控制结构当中的变量
只能在内部使用一旦语句结束,变量不可以使用
- 如果其他代码也需要访问控制变量,则变量必须定义在语句的外部。
🌗5.3 条件语句
条件语句分为俩种 if
语句和 switch
语句。
🌕5.3.1 if 语句
- if 语句有两种形式,一种有 else 分支,另一种没有。
使用 if 语句最好在所有的 if 和 else 之后都用花括号
。- 悬垂else(dangling else):用来描述在嵌套的
if else
语句中,如果if
比else
多时如何处理的问题。C++使用的方法是else
匹配最近没有配对的if
。
🌕5.3.2 switch 语句
- switch 语句计算一个
整型表达式
的值,然后根据这个值从几条执行路径中选择一条。 - case 关键字和它对应的值一起被称为 case 标签,
case 标签必须为整型常量表达式
。 - default 也是一种特殊的 case 标签。
switch(ch){
case 'a': ++aCnt; break;
case 'b': ++bCnt; break;
}
int ival = 42;
switch(ch){
case 3.14; //错误
case ival; //错误
}
执行问题
:
- 如果 某个 case 标签匹配成功后,程序将
从该 case 标签开始顺序执行所有case分支或者遇到break结尾
- c++ 程序的形式比较自由,case 标签之后不一定要换行。
一条 case 后可以跟多条语句
,不必用花括号括起来。一般在每个 case标签后都有一条 break 语句
。如果需要两个或多个值共享同一组操作,可以故意省略掉 break 语句。
unsigned vowlCnt = 0;
switch(ch){
case 'a':
case 'b':
case 'c':
++ vowelCnt;
break;
}
switch(ch){
case 'a': case 'b':
++Cnt;
break;
}
注意
:
一般不要省略 case 分支最后的 break 语句
。如果没有 break 语句,最好注释一下。- 如果没有任何一个 case 标签匹配 switch 表达式的值,就执行 default 分支。
- 即使不准备在 default 下做任何工作,最好也定义一个 default 标签。
如果要在 case 分支定义并初始化变量,应该定义在块内以约束其作用域
。
🌗5.4 迭代语句
迭代语句有三种:while语句、for语句(范围for语句)、do while语句
🌕5.4.1 while 语句
- while 的条件不能为空。条件部分可以是一个
表达式
或者是一个带初始化的变量声明
。 - 定义在 while 条件部分或while循环体内的变量每次迭代都经历从创建到销毁的过程。
- while 适合不确定到底迭代多少次的情况。
while (condition)
statement
🌕5.4.2 传统的for语句
for(init-statement; condition; expression)
statement;
- init-statement 可以是
声明语句
、表达式语句
或空语句
。 - init-statement 可以定义多个对象,但是
只能有一条声明语句
,因此所有的变量基础类型必须相同。 - expression 在每次循环之后执行。
- for 语句头能省略掉三者中的任意一个或全部。
- 省略 condition 等于条件恒为 true。
for (int i = 0, j = 0; i < n ;i++)
statement
for (int i = 0, char j = '0'; i < n ;i++) //错误
🌕5.4.3 范围for语句
for(declaration : expression)
statement
- 范围 for 语句用来
遍历容器或其他序列的所有元素。
- expression 表示的
必须是一个序列
,可以是花括号括起来的初始值列表
。这些序列的共同的是都可以返回迭代器的 begin 和 end 成员。
- 如果需要对容器中的元素执行
写操作
,必须将循环变量声明成引用类型
。 - declaration 定义一个能从序列中元素转换过来的
变量
(不是迭代器)。最简单的方法是使用auto 类型说明符
。 - 范围 for 语句
不能
改变序列的元素数量(增加、删除)。因为范围for预存了 end()
的值,改变元素数量后end() 的值就可能失效了
。 每次迭代都会重新定义循环控制变量,并将其初始化为序列的下一个值。
🌕5.4.4 do while 语句
do
statement
while (condition);
do while
语句和while
语句的唯一区别是do while语句先执行循环体后检查条件。即至少执行一次循环。
- do while 后
不要忘了加分号。
- 因为 condition 在循环体后面,所以 condition 使用的变量应该定义在循环体外面。
🌗5.5 跳转语句
- 四种跳转语句:
break
、continue
、return
、go to
。
🌕5.5.1 break 语句
- break 语句负责终止离他最近的
while
、do while
、for
或者switch
语句,并从这些语句之后的第一条语句开始执行。 - break 语句只能出现在迭代语句或者 switch 语句内部(包括嵌套在此类循环里的语句或块的内部)
🌕5.5.2 continue 语句
- continue 语句终止最近的循环中的当前迭代并立即开始下一次迭代。
- continue 只能出现在
for
、while
、do while
中,比break少了一个switch
。
🌕5.5.3 goto语句
- goto 语句的作用是从
goto 语句无条件跳转到同一函数内的另一条语句
。
label : int a = 1;
goto label;
- label 是用于
标识一条语句的标示符
- 标签标示符独立于变量或其他标示符的名字,标签标示符可以和程序中其他实体的标示符使用同一个名字而不会相互干扰。
如果 goto 语句跳回了一条变量的定义之前意味着系统将会销毁该变量,然后重新创建它。
不要使用 goto,它会令程序又难理解又难修改。
🌗5.6 try 语句块和异常处理
- 异常是指存在于运行时的反常行为,典型的异常有失去数据库连接和遇到意外输入等。
- 当程序的某部分检测到一个它无法处理的问题时,需要用到异常处理。此时检测到问题的部分应该发出检测信号。
(它不需要处理异常,只需要用于检测异常)
- 如果程序里有可能引发异常的代码,通常需要有专门的代码处理问题。
- c++中异常处理机制一般包括
异常检测
和异常处理
俩部分协同完成。
throw 表达式:异常检测部分使用 throw 表达式来表示遇到了无法处理的问题。称为 throw 引发了异常。
try 语句块:异常处理部分使用 try 语句块处理异常。try 语句块以关键字 try 开始,并以一个或多个 catch 子句结束。
一套异常类:用于在 throw 表达式和相关的 catch 子句间传递异常的具体信息。
🌕5.6.1 throw 表达式
- throw 表达式包含关键字
throw
和紧随其后的一个表达式
,表达式的类型就是抛出的异常类型。 - throw 表达式后面紧跟一个分号。从而构成一条表达式语句。
throw只是用来检测异常,并不处理,处理交给try和catch语句。
- throw 后面跟一个异常类型的对象(必须同时使用 string 或 C 风格字符串对其初始化)。
if (item1.isbn() != item2.isbn())
throw runtime_error("Data must refer to same ISBN");
cout << item1 + item2 << endl;
🌕5.6.2 try 语句块
try
语句块的一开始是关键字try
,随后紧跟一个块。- 跟在
try
块后面的是一个或多个 catch 子句
。catch 子句包括三部分:关键字 catch
、括号内一个(可能未命名)的对象的声明(叫做异常声明)
、一个块
。 - try语句
块内
声明的变量在块外部无法访问
,特别是在catch子句也无法访问
。 当 try 语句块中抛出了一个异常,如果该异常类型与 catch 子句括号中的异常类型相同,就会执行该 catch 子句
。- catch 一旦完成,程序就跳转到 try 语句块最后一个 catch 子句之后的那条语句继续执行。
- 在 catch 后面的括号里使用省略号(...)可以让 catch 捕获所有类型的异常。
每个标准库异常类都有一个 what 成员函数,它没有参数,返回初始化该对象时所用的 C 风格字符串。
while (cin >> item1 >> item2){}
try{
throw runtime_error("Data must be same as size");//try 语句块抛出了一个异常
}
catch(runtime_error err){//在 catch 后面的括号中声明了一个“runtime_error”类型的对象,与 try 抛出的异常类型相同,接下来执行此子句。
cout << err.what()
<< "\nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c == 'n')
{
break; //跳出while循环
}
}
}
异常运行结果
err.what()就是为了输出上面错误的信息"Data must be same as size"
Data must refer to same ISBN
Try Again? Enter y or n
函数在寻找处理代码的过程中退出
- throw 语句可能出现在
嵌套的 try 语句块中或在 try 语句块中调用的某个函数内
。当异常被抛出,首先程序会从内到外一层层寻找相应类型的 catch 子句
。如果最后还是没找到,系统会调用terminate 函数
并终止当前程序的执行。 如果对于 throw 语句外就没有 try 语句块,也会执行 terminate 函数
。
编写异常代码非常困难
- 异常中断了程序的正常流程。异常发生时,
有的程序执行完成有的程序执行到一半就中断了
,c此时会导致资源没有正常释放
,对象处于无效状态
等情况。异常安全的代码要求在异常发生时能正确执行清理工作。这个非常困难。
🌕5.6.3 标准异常
C++ 标准库定义了一组异常类
,用于报告标准库函数遇到的问题。他们定义在 4 个头文件中
。
- 定义在
stdexcept 头文件
中的类型必须使用 string 对象或 C 风格字符串来初始化他们。不允许使用默认初始化方式
。 - 其他 3 个头文件中的 3 中类型则
只能默认初始化,不能提供初始值。
- 异常类型
只有一个 what 成员函数
,该函数没有参数
,返回是一个C 风格字符串的指针
,目的是提供关于异常的文本信息。 - 对于无初始值的异常类型,what 返回的内容由编译器决定,有初始值的返回初始值。
exception | 异常类 exception 是最通用的异常类。它只报告异常 的发生,不提供额外信息。 |
---|---|
bad_alloc | 异常类 bad_alloc。在使用 new 分配动态内存失败时抛出 |
bad_cast | 异常类型 bad_cast。经常发生在使用 dynamic_cast 时发生 |
exception | |
runtime_error | 只有在运行时才能检测出的问题 |
range_error | 运行时错误:生成的结果超出了有意义的值域范围 |
overflow_error | 运行时错误:计算上溢 |
underflow_error | 运行时错误:计算下溢 |
logic_error | 程序逻辑错误 |
domain_error | 逻辑错误:参数对象的结果值不存在 |
invalid_argument | 逻辑错误:无效参数 |
length_error | 逻辑错误:试图创建一个超出该类型最大长度的对象 |
out_of_range | 逻辑错误:使用一个超出有效范围的值 |
exception头文件
定义了exception
。new头文件
定义了bad_alloc
。type_info头文件
定义了bad_cast
。- 其余的都定义在
stdexcept头文件
。
上面的异常类之间存在继承关系,其中 exception 是所有其他类的基类,总的继承关系如下图
void StrBlob::check(size_type i, const string& msg)
{
if (i >= data->size())
throw out_of_range(msg);
}