文章目录
- 一、语句基础
- 二、分支语句
- 1.分支语句--if
- 2.分支语句--switch
- 三、循环语句
- 1.循环语句--while
- 2.循环语句--do-while
- 3.循环语句--for
- 4.循环语句--基于范围的for循环
- 5.break / continue语句
- 四、语句的综合应用--达夫设备
一、语句基础
语句的常见类别:
- 表达式语句:表达式后加分号,对表达式求值后丢弃,可能产生副作用
- 空语句:仅包含一个分号的语句,可能与循环一起工作
- 复合语句(语句体):由大括号组成,无需在结尾加分号(分号标识语句的结束),形成独立的域(语句域)。语句域可以更加精确地控制对象的生命周期,在复合语句结束后对象消亡。复合语句可以包含多条子语句。
if
语句
顺序语句与非顺序语句:
-
顺序语句
-
从语义上按照先后顺序执行
-
实际的执行顺序可能产生变化(编译器优化、硬件乱序执行)
编译器优化–在不改变语义的情况下,对执行顺序执行调整
-
顺序语句执行时,通过编译器优化、硬件乱序执行,与硬件流水线紧密结合,执行效率较高
-
-
非顺序语句
- 非顺序语句可分为分支语句与循环语句
- 在执行过程中引入跳转,从而产生复杂的变化
- 分支预测错误可能导致执行性能降低
最基本的非顺序语句: goto
- 通过标签指定跳转到的位置
- 具有若干限制
- 不能跨函数跳转
- 向前跳转时不能越过对象初始化语句
- 向后跳转可能会导致对象销毁与重新初始化
- goto 本质上对应了汇编语言中的跳转指令:如
jne
,但有如下缺陷- 缺乏结构性的含义
- 容易造成逻辑混乱
- 除特殊情况外,应避免使用
#include <iostream> int main() { int x = 3; if(x) goto label; x = x + 1; label: return 0; }
二、分支语句
1.分支语句–if
if
语句的语法可参考if 语句。当需要在分支语句中引入复杂的逻辑,需要使用语句块表示复杂的分支逻辑。if
语句基于条件的真假来执行不同的分支。
基本形式:
if (condition) {
// 当condition为真时执行的代码
}
if-else结构:二分支
if (condition) {
// 当condition为真时执行的代码
} else {
// 当condition为假时执行的代码
}
if-else if-else结构:三分支以及多分支
if (condition1) {
// 当condition1为真时执行的代码
} else if (condition2) {
// 当condition1不为真且condition2为真时执行的代码
} else {
// 当所有条件都不为真时执行的代码
}
使用逻辑运算符:
C++中的逻辑运算符包括:
&&
(逻辑与)||
(逻辑或)!
(逻辑非)
if (condition1 && condition2) {
// 当condition1和condition2都为真时执行的代码
} else if (condition1 || condition2) {
// 当condition1或condition2为真时执行的代码
} else {
// 当condition1和condition2都不为真时执行的代码
}
示例:
#include <iostream>
int main() {
int score = 75;
//根据score的值,程序会输出对应的评价等级
if (score >= 90) {
std::cout << "优秀" << std::endl;
} else if (score >= 80) {
std::cout << "良好" << std::endl;
} else if (score >= 70) {
std::cout << "中等" << std::endl;
} else if (score >= 60) {
std::cout << "及格" << std::endl;
} else {
std::cout << "不及格" << std::endl;
}
return 0;
}
注意事项:
-
条件表达式必须用圆括号
()
括起来。 -
条件表达式的结果必须是布尔值(
true
或false
)。 -
虽然C++允许省略圆括号,但为了提高可读性,建议总是使用圆括号。
-
实现多重分支–在分支语句中,再继续使用
if-else
分支语句 -
else
会与最近的if
匹配(如果没有使用大括号的话)#include <iostream> int main() { int grade = 65; if (grade > 60) if (grade > 80) std::cout << "Excellent\n"; else std::cout << "Bad\n"; } //输出结果为Bad,但想要的是什么都不输出
使用大括号改变匹配规则后,不能与大括号外面的else语句形成匹配
#include <iostream> int main() { int grade = 65; if (grade > 60) { if (grade > 80) std::cout << "Excellent\n"; } else std::cout << "Bad\n"; } //不输出任何东西
-
使用大括号改变匹配规则
if V.S. constexpr if–C++17引入constexpr if
constexpr if
的条件是常量表达式constexpr
是在编译期确定的,因此,在编译期就可以确定执行哪个分支,屏蔽掉其他分支(为编译器优化引入更多的可能性)
#include <iostream>
int main()
{
constexpr int grade = 65;
//在编译期就可以优化成如下
// if constexpr (grade > 70) {
// if (grade > 80)
// std::cout << "Excellent\n";
// }
// else
std::cout << "Bad\n";
}
带初始化语句的 if:(从C++17开始)
从C++17开始可以将初始化语句放入括号里面。如果希望变量只是为了if
分支语句而引入的,将可以使用带初始化的if
语句。
#include <iostream>
int main()
{
int x = 3;
if(int y = x +3; y > 100)
{
} else {
}
int y = 4;
}
2.分支语句–switch
switch语句的语法可参考switch语句。
switch语句的基本语法:
switch (expression) {
case constant-expression1:
// 当expression的值与constant-expression1相等时执行的代码
break;
case constant-expression2:
// 当expression的值与constant-expression2相等时执行的代码
break;
// ...
default:
// 如果expression的值与所有case都不匹配时执行的代码
break;
}
-
expression
:可以是任意表达式,但表达式的值必须具有整型或枚举类型,或者可隐式转换到整型或枚举类型的类类型。可以包含初始化的语句(C++17起)switch ( 初始化语句 (可选) 条件 ) 语句 等价于 { 初始化语句 switch ( 条件 ) 语句 }
-
constant-expression
:是与case
标签相关联的常量表达式,它必须与switch
表达式的类型兼容。 -
break
:用于终止switch
语句中当前case
的执行,防止代码继续执行到下一个case
。如果省略break
,程序将执行当前case
之后的所有case
,直到遇到一个break
或switch
语句结束,这称为“fall through”。 -
case/default
标签-
case 后面跟常量表达式 , 用于匹配 switch 中的条件,匹配时执行后续的代码
-
可以使用 break 跳出当前的 switch 执行
-
default 用于定义缺省情况下的逻辑
-
在 case/default 中定义对象要加大括号
在标签下不能直接定义对象,会有编译错误。在标签下定义对象默认对象的作用域是整个switch语句,如果该标签后面还有下一个标签,则在执行下一个标签时就会跳过对象的初始化。举例如下:
#include <iostream> int main() { int x = 1; switch (x) { case 1: int x = 0; // 初始化 std::cout << x << '\n'; break; default: // 编译错误:跳到 default: 会在尚未初始化 'x' 的情况下进入它的作用域 std::cout << "default\n"; break; } }
定义对象要加大括号,将对象的生命周期由switch语句变为由大括号定义的域
#include <iostream> int main() { int x = 1; switch (1) { case 1: { int x = 0; std::cout << x << '\n'; break; } // 'x' 的作用域在此结束 default: std::cout << "default\n"; // 无错误 break; } }
-
-
C++17标准引入了
[[fallthrough]];
属性,允许程序员明确指出两个case
之间的意图是连续的#include <iostream> int main() { const int i = 2; switch (i) { case 1: std::cout << "1"; case 2: // 从这个 case 标号开始执行 std::cout << "2"; case 3: std::cout << "3"; [[fallthrough]]; C++17 属性,用以关闭对直落的警告 case 5: std::cout << "45"; break; // 语句的顺序执行到此终止 case 6: std::cout << "6"; default: break; } }
switch语句的简单示例:
#include <iostream>
int main() {
int day = 4;
switch (day) {
case 1:
std::cout << "Monday" << std::endl;
break;
case 2:
std::cout << "Tuesday" << std::endl;
break;
case 3:
std::cout << "Wednesday" << std::endl;
break;
case 4:
std::cout << "Thursday" << std::endl;
break;
case 5:
std::cout << "Friday" << std::endl;
break;
default:
std::cout << "Invalid day" << std::endl;
}
return 0;
}
switch与if比较:
-
分支描述能力较弱
-
在一些情况下编译期能引入更好的优化(运行期节省时间)
从数据结构算法来讲,if接近于线性,而switch可利用跳表或二分查找
三、循环语句
1.循环语句–while
while
语句的语法可查看while语句。while
循环是一种基本的循环结构,它允许代码在给定的布尔条件为true
时重复执行。
while循环的基本语法:
while (condition) {
// 循环体:只要条件为真,就执行这里的代码
}
-
condition
是任何能转换为bool的表达式,或带花括号或等号初始化式的单个变量的声明。如果条件是
T t = x
这样的声明,那么被声明的变量仅在循环体内处于作用域中,而且在每次重复中销毁并重新创建#include <iostream> int main() { // 带单语句的 while 循环 int i = 0; while (i < 10) i++; std::cout << i << '\n'; // 带复合语句的 while 循环 int j = 2; while (j < 9) { std::cout << j << ' '; j += 2; } std::cout << '\n'; // 带声明条件的 while 循环 char cstr[] = "Hello"; int k = 0; while (char c = cstr[k++]) std::cout << c; std::cout << '\n'; }
运行结果为:
10 2 4 6 8 Hello
-
语句:任何语句,是循环体
-
处理逻辑:
- 判断条件是否满足(是否为true),如果不满足则跳出循环
- 如果条件满足则执行循环体
- 执行完循环体后转向步骤 1
-
注意:在 while 的条件部分不包含额外的初始化内容
下面是一个使用while
循环的示例,该示例展示了如何使用while
循环来计算从1到10的整数之和:
#include <iostream>
int main() {
int sum = 0;
int i = 1;
while (i <= 10) {
sum += i; // 将i加到sum上
i++; // 增加i的值
}
std::cout << "The sum of numbers from 1 to 10 is: " << sum << std::endl;
return 0;
}
2.循环语句–do-while
do-while
循环语句的语法可查看do-while循环。C++中的do-while
语句是一种后测试循环,这意味着循环体内的代码至少会执行一次,之后才会判断循环条件。如果条件为真,则再次执行循环体,这个过程会一直重复,直到条件为假。
do-while循环的基本语法如下:
do {
// 循环体:这段代码至少执行一次
} while (condition);
-
condition
是任意能转换成bool的表达式。如果condition
为true,则循环体再次执行。这个过程会一直重复,直到condition
为false。 -
处理逻辑:
- 执行循环体
- 判断条件是否满足,如果不满足则跳出循环
- 如果条件满足则转向步骤 1
-
do-while
循环常用于至少需要执行一次的场合,例如用户输入验证、至少一次的尝试等。 -
注意:结尾处要有分号,表示一条语句的结束
do-while
循环的一个特点是循环体后面的while
部分有一个分号;
,这与while
循环不同,while
循环的条件后面不应该有分号。
下面是一个使用do-while
循环的示例,该示例展示了如何使用do-while
循环来提示用户输入一个正数:
#include <iostream>
int main() {
int number;
do {
std::cout << "Please enter a positive number: ";
std::cin >> number;
if (number <= 0) {
std::cout << "The number is not positive. Try again." << std::endl;
}
} while (number <= 0); // 循环继续,直到输入的number大于0
std::cout << "Thank you for entering a positive number: " << number << std::endl;
return 0;
}
3.循环语句–for
for
循环语句的语法可查看for循环。C++中的for
循环是一种基本的迭代结构,它允许代码在给定的条件下重复执行。for
循环通常用于当你知道循环需要执行的确切次数时。
for循环的基本语法:
for (initialization; condition; increment/decrement) {
// 循环体:只要条件为真,就执行这里的代码
}
-
初始化语句
initialization
:循环开始前的初始化步骤,通常用于声明和初始化循环控制变量。 -
条件
condition
:在每次循环迭代开始前判断的布尔表达式。如果条件为真,则执行循环体。如果条件为假,则循环结束。 -
迭代表达式
increment/decrement
:循环体执行完毕后执行的更新步骤,通常用于更新循环控制变量。这一步可以是增加(如i++
)或减少(如i--
)循环变量。 -
处理逻辑
- 初始化语句会被首先执行(初始化语句声明与定义的变量的作用域是在for循环中)
- 条件部分会被执行,执行结果如果为 false ,则终止循环
- 否则执行循环体
- 循环体执行完后会对迭代表达式进行求值,之后转向 2
-
在初始化语句中声明多个名字,只要它们可以使用相同的声明说明符序列(相同的基础类型)
在变量生命与定义时,语法上允许声明多个变量,但是不建议这么做,因为容易引起歧义。如:
int* p, q;
,p是int指针,q是int型。#include <iostream> int main() { //初始化语句可以声明多个名字,只要它们可以使用相同的声明说明符序列 for (int i = 0, *p = &i; i < 9; i += 2) { std::cout << i << ':' << *p << ' '; } }
-
初始化语句、条件、迭代表达式可以为空,可以省略初始化、条件或更新步骤中的任何一个
#include <iostream> int main() { for (; i < 10; ) { // 循环体 i++; // 循环控制变量的更新在循环体内部进行 } for ( ; ; ) ; }
下面是一个使用for
循环的示例,该示例展示了如何使用for
循环来计算从1到10的整数之和:
#include <iostream>
int main() {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i; // 将i加到sum上
}
std::cout << "The sum of numbers from 1 to 10 is: " << sum << std::endl;
return 0;
}
for更多示例:
#include <iostream>
#include <vector>
int main()
{
std::cout << "1) 典型的以单语句作为循环体的循环:\n";
for (int i = 0; i < 10; ++i)
std::cout << i << ' ';
std::cout << "\n\n" "2) 初始化语句可以声明多个名字,\n"
"只要它们可以使用相同的声明说明符序列:\n";
for (int i = 0, *p = &i; i < 9; i += 2)
std::cout << i << ':' << *p << ' ';
std::cout << "\n\n" "3) (循环)条件可以是声明:\n";
char cstr[] = "Hello";
for (int n = 0; char c = cstr[n]; ++n)
std::cout << c;
std::cout << "\n\n" "4) 初始化语句可以使用 auto 类型说明符:\n";
std::vector<int> v = {3, 1, 4, 1, 5, 9};
for (auto iter = v.begin(); iter != v.end(); ++iter)
std::cout << *iter << ' ';
//方便理解for循环的执行逻辑
std::cout << "\n\n" "5) 初始化语句可以是表达式:\n";
int n = 0;
for (std::cout << "循环开始\n";
std::cout << "循环测试\n"; //<<运算符的结果为std::cout,隐式转换为true
std::cout << "迭代 " << ++n << '\n')
{
if (n > 1)
break;
}
std::cout << "\n" "6) 每次迭代时均会调用循环体中创建的对象的构造函数和析构函数:\n";
struct S
{
S(int x, int y) { std::cout << "S::S(" << x << ", " << y << "); "; }
~S() { std::cout << "S::~S()\n"; }
};
for (int i{0}, j{5}; i < j; ++i, --j)
S s{i, j};
std::cout << "\n" "7) 初始化语句可以使用结构化绑定:\n";
long arr[]{1, 3, 7};
for (auto [i, j, k] = arr; i + j < k; ++i)
std::cout << i + j << ' ';
std::cout << '\n';
}
运行结果:
1) 典型的以单语句作为循环体的循环:
0 1 2 3 4 5 6 7 8 9
2) 初始化语句可以声明多个名字,
只要它们可以使用相同的声明说明符序列:
0:0 2:2 4:4 6:6 8:8
3) (循环)条件可以是声明:
Hello
4) 初始化语句可以使用 auto 类型说明符:
3 1 4 1 5 9
5) 初始化语句可以是表达式:
循环开始
循环测试
迭代 1
循环测试
迭代 2
循环测试
6) 每次迭代时均会调用循环体中创建的对象的构造函数和析构函数:
S::S(0, 5); S::~S()
S::S(1, 4); S::~S()
S::S(2, 3); S::~S()
7) 初始化语句可以使用结构化绑定:
4 5 6
4.循环语句–基于范围的for循环
基于范围的for循环的语法可查看基于范围的for循环。for
循环的一个变体是范围基for
循环(C++11及以后版本引入),它允许你直接迭代容器(如数组、向量、字符串等)中的元素,而无需显式管理索引变量。
范围基for循环的语法:
for (const auto& element : container) {
// 循环体:对container中的每个元素执行操作
}
-
container
可以是任何支持迭代的容器 -
element
是容器中当前迭代到的元素 -
本质:语法糖,编译器会自动转换为 for 循环的调用方式
-
转换形式的衍化: C++11 / C++17 / C++20(编译器对范围表达式的推导)
-
使用常量左值引用读元素;使用 万能引用修改元素
常量左值引用读元素:
#include <iostream> #include <vector> int main() { std::vector<std::string> strs{"h","e","l","l","o"}; for (const auto& str : strs) //不加const为非常量左值引用,可以修改元素,但并不安全 std::cout << str << "\n"; }
万能引用修改元素:
#include <iostream> #include <vector> int main() { std::vector<std::string> strs{"h","e","l","l","o"}; //万能引用修改元素 for (auto&& str : strs) str += "-"; for (const auto& str : strs) std::cout << str << "\n"; }
5.break / continue语句
在C++中,break
和continue
是控制循环流程的两个关键字,它们在循环结构(如for
、while
和do-while
循环)中起到关键作用。
break:
break
关键字用于立即终止其所在的循环体。当break
被执行时,它会跳出最内层的循环,并继续执行循环后面的代码。这适用于任何类型的循环结构。
for (int i = 0; i < 10; ++i) {
if (i == 5) {
break; // 当i等于5时,立即退出循环
}
std::cout << i << " ";
}
// 输出: 0 1 2 3 4
continue:
continue
关键字则用于跳过当前循环的剩余部分,并立即开始下一次循环迭代。当continue
被执行时,它会跳过循环体中剩余的代码,并根据循环条件继续执行下一次迭代。
for (int i = 0; i < 10; ++i) {
if (i % 2 == 0) {
continue; // 跳过偶数,只打印奇数
}
std::cout << i << " ";
}
// 输出: 1 3 5 7 9
break
和continue
都只影响最内层的循环。如果你在嵌套循环中使用它们,它们只控制最内层的循环结构。- 在使用
break
和continue
时,应当小心谨慎,因为过度使用或不当使用可能会使代码逻辑变得难以理解和维护。 - 在使用
switch
语句时,break
也用于终止switch
中的一个case
,防止代码继续执行到下一个case
(除非下一个case
被明确地指定)。
四、语句的综合应用–达夫设备
达夫设备(Duff’s Device)是一种在C/C++编程中提高循环效率的技巧,它利用了C语言中switch
语句的“fal through”特性来实现循环展开,从而减少循环控制的开销。这种方法最早由Tom Duff在1983年提出,目的是为了优化循环,尤其是在循环体内执行的操作非常快速时,循环的测试条件会占用相当一部分时间。
达夫设备的核心思想是减少循环迭代次数,通过预先计算循环的迭代次数并将循环体的一部分操作放入switch
语句的case
分支中,从而减少循环控制的开销。当循环次数不能被展开的固定次数整除时,达夫设备使用switch
来处理剩余的迭代次数。
- 达夫设备使用循环展开提升系统性能
- 处理无法整除的情形
- 额外增加一个循环语句
- 将 switch 与循环结合
以下是达夫设备的一个典型示例,该示例展示了如何将数据从from
数组复制到to
数组:
register short *to, *from;
register count; //count不能整除
{
register n = (count + 7) / 8; /* 假设count > 0 */
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
在这个例子中,count
表示需要复制的元素数量。循环被展开为8次迭代,因为这样可以减少循环控制的开销。switch
语句根据count
除以8的余数选择从哪个case
开始执行,而case
分支中的代码会“跌落”到下一个case
,直到遇到do-while
循环的结束条件。
达夫设备虽然可以提高效率,但它牺牲了代码的可读性和可维护性。现代编译器通常能够自动进行循环展开优化,因此达夫设备在现代编程中较少使用。然而,了解这种技术对于理解编译器优化和程序性能优化仍然有其价值。