09. 语句

语句概述

这里是 C++ 语句的形式化定义:

语句:

声明
表达式可选;
{ 语句列表可选 }
try { 语句列表可选 } 处理模块列表
case 常量表达式 : 语句
default : 语句
break;
continue;
return 表达式可选;
goto 标识符;
标识符 : 语句
选择语句
循环语句

选择语句:

if( 条件 ) 语句
if( 条件 ) 语句 else 语句
switch( 条件 ) 语句

循环语句:

while( 条件 ) 语句
do 语句 while ( 条件 );
for( for 初始化语句可选; 条件可选; 表达式可选) 语句
for( for 初始化声明 : 表达式) 语句

语句列表:

语句 语句列表可选

条件:

表达式
类型修饰符 声明符 = 表达式
类型修饰符 声明符{表达式}

处理模块列表:

处理模块 处理模块列表可选

处理模块:

catch( 表达式声明 ){ 语句列表可选 }

分号本身也是一条语句,即空语句(empty statement)。

“花括号”({ }​)括起来的一个可能为空的语句序列称为块(block)或者复合语句(compound statement)。块中声明的名字的作用域到块的末尾就结束了。

声明(declaration)是一条语句,没有赋值语句或过程调用的语句;赋值和函数调用不是语句,它们是表达式。

for 初始化语句(for-init-statement)要么是声明,要么是一条表达式语句(expression-statement),它们都以分号结束。

for 初始化声明(for-init-declaration)必须是一个未初始化变量的声明。

try 语句块(try-block)的作用是处理异常。

声明作为语句

一个声明就是一条语句。除非变量被声明成 static​,否则在控制线程传递给当前声明语句的同时执行初始化器。允许把声明当成一条语句使用(当然还能用在其他一些场合)的目的是尽量减少由未初始化变量造成的程序错误,并且让代码的局部性更好。在绝大多数情况下,如果没有为变量找到一个合适的值,暂时不要声明它。

选择语句

if 语句和 switch 语句都需要首先检测一个值:

if( 条件 ) 语句
if( 条件 ) 语句 else 语句
switch( 条件 ) 语句

条件(condition)可能是一个表达式,也可能是一个声明。

if 语句

if​ 语句中,如果条件为真,则执行第一条(或者唯一的一条)语句;否则,执行第二条语句(如果有的话)。

if (condition) {
    // 第一条语句
} else {
    // 第二条语句
}

即使条件的求值结果不是布尔值,也能尽量隐式地转换成 bool​ 类型。因此,算术类型及指针类型的表达式都能作为条件。

“普通的”enum ​可以先隐式地转换成整数,然后再转换成 bool ​类型,但是 enum class ​不能。例如:

enum Color { Red, Green, Blue };
Color c = Red;
if (c) { /* ... */ }
enum class Shape { Circle, Square, Triangle };
Shape s = Shape::Circle;
if (s) { /* ... */ } // 错误:枚举类不能隐式转换为bool
if (s == Shape::Circle) { /* ... */ } // 正确:必须显式比较

逻辑运算符 &&||!​ 经常在条件中出现。对于运算符 &&​ 和 ||​ 来说,除非必需,否则运算符右侧的运算对象不会被求值(短路)。

一个名字只能在声明它的作用域中使用。在 if​ 语句中,一个分支声明的名字不能在另一个分支中直接使用。例如:

if (condition) {
    int x = 10;
    // x 在这里是有效的
} else {
    // x 在这里是无效的
}

switch 语句

switch ​语句在一组候选项(case ​标签)中进行选择。case ​标签中出现的表达式必须是整型枚举类型常量表达式。在同一个 switch ​语句中,一个值最多被 case ​标签使用一次。

switch (expression) {
    case constant1:
        // 语句
        break;
    case constant2:
        // 语句
        break;
    default:
        // 语句
}

谨记 switch​ 语句的每一个分支都应该有一条结束语句(break ​语句),否则程序将会继续执行下一个分支的内容。

switch ​语句中 default ​分支的使用:

  • default ​分支可以用于处理最常出现的情况。
  • default ​分支也可以用于处理取值错误的情况,此时所有有效的取值都包含在 case ​分支中。

不使用 default ​分支的情况:

  • switch ​语句的每个分支对应枚举类型中的一个枚举值时,最好不使用 default ​分支。
  • 这样做可以让编译器负责发现并报告 case ​分支与枚举值未能完全匹配的问题。
enum class A { A1, A2, A3 };

void foo1(A a) {
    switch (a) {
    case A::A1: break;
    case A::A2: break;
    }
}

void foo2(A a) {
    switch (a) {
    case A::A1: break;
    case A::A2: break;
    default: break;
    }
}

我在 Visual Studio 的 MSVC 上,使用默认的 Debug 和 Release 模式都没有观察到,但实际上,应该是存在的,详看在线编译器

<source>: In function 'void foo1(A)':
<source>:4:12: warning: enumeration value 'A3' not handled in switch [-Wswitch]
    4 |     switch (a) {
      |            ^
Compiler returned: 0

case 分支中的声明

C++ 允许在 switch 语句的块内声明变量,但是不能不初始化。

#include <string>
void f(int i) {
	switch (i)
	{
	case 0:
		int x; // 未初始化
		int y = 3; // 错误:程序有可能跳过该声明(显式初始化)
		std::string s; // 错误:程序有可能跳过该声明(隐式初始化)
	case 1:
		++x; // 错误:试图使用未初始化的对象
		++y;
		s = "nasty!";
	}
}

如果我们确实需要在 switch 语句中使用变量,最好把该变量的声明和使用限定在一个块中。

条件中的声明

if (double d = prim(true)) {
	left /= d;
}

首先声明 d ​并给它赋了初值,然后把初始化后的 d ​的值作为条件的值进行检查。d​ 的作用域从声明处开始,到条件控制的语句结束为止。假设还有一个 else​ 分支与上面的 if​ 分支对应,则 d​ 在两个分支中都有效。

条件中的声明语句只能声明并初始化一个变量或 const​。

循环语句

范围 for 语句

for( for 初始化声明 : 表达式) 语句

命名元素的变量的作用域是整个 for​ 语句。
冒号之后的表达式必须是一个序列(一个范围),换句话说,如果我们对它调用
v.begin()​ 和 v.end()​ 或者 begin(v)​ 和 end(v)​,得到的应该是迭代器:

  1. 编译器首先尝试寻找并使用成员 begin​ 和 end​。如果找到了 begin​ 和 end​,但是
    它们不能表示一个范围(比如,begin​ 有可能是变量而非函数),则当前的范围 for
    是错误的。
  2. 如果没有找到,则编译器继续在外层作用域寻找 begin​/end​ 成员。如果找不到
    或者找到的不能用(比如 begin​ 不接受当前序列类型的实参),则范围 for​ 是错
    误的。

如果想在范围 for 循环中修改元素的值,则应该使用元素的引用。

for 语句

for (int i = 0; i != max; ++i) {
    v[i] = i * i;
}

等价于

int i = 0; // 引入循环变量
while (i != max) { // 检验终止条件
    v[i] = i * i; // 执行循环体
    ++i; // 递增循环变量
}

while 语句

for ​语句相比,while ​语句更适合处理以下两种情况:一是没有一个明显的循环变量,二是程序员觉得把负责更新循环变量的语句置于循环体内更自然。for ​语句很容易改写成等价的 while ​语句,反之亦然。

for (初始化; 条件; 更新) {
    语句;
}

可以改写为:

初始化;
while (条件) {
    语句;
    更新;
}

反之,while ​语句:

while (条件) {
    语句;
}

也可以改写为:

for (; 条件;) {
    语句;
}

do 语句

do-while ​循环是一种后测试循环结构,这意味着它会先执行循环体内的代码,然后再判断条件是否满足。如果条件为真,则继续执行循环体;如果条件为假,则退出循环。

do-while ​循环至少会执行一次循环体。

do {
    // 循环体
} while (条件表达式);

退出循环

常见的退出循环的方法:

  • break​ 语句:​​break​ 语句用于立即终止最内层的 switch​ 语句或 while​、do-while​、for​ 循环,并跳出该结构,继续执行循环后面的代码。​
  • return​ 语句:​return​ 语句用于从当前函数返回,并可以带一个返回值。如果 return​ 在循环内部,它将终止循环并结束包含该循环的函数。​
  • continue​ 语句:​continue​ 语句用于跳过当前循环的剩余部分,并立即开始下一次迭代。它不是用来跳出循环的,而是用来跳过当前迭代。
  • 抛出异常(throw​):可以在循环内部抛出一个异常,然后在函数的外部捕获它,从而退出循环。这是一种不常见的做法,因为它涉及到异常处理的开销,并且可能会使代码的流程控制变得复杂。

goto 语句

goto 标识符;
标识符 : 语句

标签的作用域是标签所处的函数。这意味着你能用 goto​ 从块的范围跳进跳出,唯一的限制是不能跳过初始化器或者跳入到异常处理程序

注释

关于注释的建议:

  • 在针对每个源文件的注释中指明:该文件中的声明有何共同点、对应的参考手册条目、程序员的名字以及维护该文件所需的其他信息。
  • 为每个类、模板和名字空间分别编写注释。
  • 为每个非平凡的函数分别编写注释并指明:函数的目的、用到的算法(如果很明显的话可以不用提),以及该函数对其应用环境所做的某些设定。
  • 为全局和名字空间内的每个变量及常量分别编写注释。
  • 为某些不太明显或不可移植的代码编写注释。
  • 其他情况,则几乎不需要注释了。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇