跳到主要内容
C++ 控制流:从基本语句到现代 C++ 实践 | 极客日志
C++
C++ 控制流:从基本语句到现代 C++ 实践 C++ 控制流从基本顺序执行、条件分支、循环到跳转与异常处理,全面讲解语法和现代特性如 constexpr if、范围 for、std::ranges 等,结合多线程、状态机、递归等高级应用,并通过案例给出可读性与性能优化的最佳实践。
任何程序都离不开控制流——它就像程序的交通指挥,告诉 CPU 何时直行、何时转弯、何时回头。C++ 的控制流工具足够丰富,从老派的 if、for 到 C++11 带来的范围循环,再到 C++20 的 ranges,总能找到趁手的。但工具多了,坑也多。这篇文章会从最基础的顺序执行讲起,一直聊到异常处理、多线程协调和编译期分支,顺便分享一些我在项目中踩过的坑和惯用写法。
顺序执行:写什么就跑什么
顺序执行是最简单也最常用的控制流。代码一行接一行,没有判断、没有回头,直到函数结束或遇到跳转。很多初始化、线性计算就是这么干的。
#include <iostream>
using namespace std;
int main () {
int a = 5 ;
int b = 10 ;
int sum = a + b;
int diff = b - a;
cout << "Sum: " << sum << endl;
cout << "Difference: " << diff << endl;
return 0 ;
}
顺序执行的好处是直观,调试不费脑。但显然它太死板,一旦需要根据条件分支或者重复处理,就得靠选择控制和循环了。所以实际代码里,顺序执行总是和其他结构混着用:
int n;
cout << "Enter a number: " ;
cin >> n;
if (n < 0 ) {
cout << "Invalid input." << endl;
} else {
int factorial = 1 ;
for (int i = 1 ; i <= n; ++i) factorial *= i;
cout << "Factorial: " << factorial << endl;
}
这段代码就是顺序里嵌了 if-else 和 for,很典型。
选择语句:让代码变得'聪明'
没有选择,程序就是个傻子。C++ 提供了 if、if-else、else-if 和 switch-case,可以根据布尔条件或整型值分岔。
if 和 if-else
最简单的条件判断:
(a > ) {
cout << << endl;
}
if
5
"a > 5"
if (a > 5 ) {
cout << "large" << endl;
} else {
cout << "small" << endl;
}
多条件可以用 else if 链,但如果分支太多,考虑用 switch 或者查表法。
switch-case:专治一堆 if当你要根据一个整数或字符值走不同分支,switch 通常比一串 if-else 更清晰,编译器也更容易优化成跳转表。
char grade = 'B' ;
switch (grade) {
case 'A' : cout << "Excellent!" << endl; break ;
case 'B' : cout << "Good!" << endl; break ;
case 'C' : cout << "Fair!" << endl; break ;
default : cout << "Invalid grade!" << endl;
}
忘了 break 会 fall-through,有时故意这么用,但最好注释说明。
现代 C++ 的选择:constexpr if C++17 带来的 if constexpr 可以在编译期分支,写模板时尤其好用,避免产生无效代码路径。
template <typename T>
void printType (const T& value) {
if constexpr (std::is_integral<T>::value) {
cout << "Integral: " << value << endl;
} else {
cout << "Non-integral: " << value << endl;
}
}
循环:重复的事情机器做 循环是程序员的体力活替身。C++ 有 while、do-while、for、范围 for、以及 C++20 的启用的 ranges。
while 与 do-whilewhile 先判断再执行,do-while 先执行一次再判断,适合至少执行一次的场景,比如输入验证:
int number;
do {
cout << "Enter a positive number: " ;
cin >> number;
} while (number < 0 );
for 循环int sum = 0 ;
for (int i = 1 ; i <= 10 ; i++) sum += i;
cout << "Sum: " << sum << endl;
范围 for:告别下标 C++11 的范围 for 直接拿元素,不用索引,干净利落:
int arr[] = {1 , 2 , 3 , 4 , 5 };
for (int num : arr) cout << num << ' ' ;
如果要用索引,还是得自己写传统 for,或者用 std::size_t 搭一个范围适配器。但通常,用 std::vector 时范围遍历够了。
C++20 ranges 结合 std::ranges 和管道操作符,可以写出强大的函数式循环:
#include <ranges>
#include <vector>
std::vector<int > nums = {1 , 2 , 3 , 4 , 5 };
for (int num : nums | std::ranges::views::reverse)
cout << num << ' ' ;
循环控制:break 与 continue break 直接跳出循环,continue 跳过本次剩余部分,重新进入条件检查。
for (int i = 1 ; i <= 10 ; i++) {
if (i == 5 ) break ;
if (i % 2 == 0 ) continue ;
cout << i << ' ' ;
}
注意 continue 在 for 里会执行迭代表达式(比如 i++),在 while/do-while 里则直接跳到条件判断,小心死循环。
跳转:goto、break/continue 与 throw 除了 break 和 continue,C++ 还有个古老但少用的 goto,以及异常处理中的 throw。
goto:能不用就别用goto 可以跳到标签处,通常用来摆脱多层嵌套循环。但代码逻辑会变得支离破碎,团队里容易挨骂。非要用的话,请限制在极短距离内,而且一定要注释。
for (int i = 0 ; i < 5 ; i++) {
for (int j = 0 ; j < 5 ; j++) {
if (i == 2 && j == 2 ) goto end;
cout << i << ", " << j << endl;
}
}
end:
cout << "Exited loop." << endl;
throw:错误时的跳转throw 会把执行流直接抛给最近的匹配 catch,是一种'非局部跳转'。它用来处理异常情况,不该用于正常控制流。
异常处理:预见错误,优雅兜底 C++ 的异常机制通过 try/throw/catch 工作。发生错误时,抛出异常对象,沿着调用栈向上传递,直到被捕获。能用标准异常类就用标准的,比如 std::runtime_error、std::logic_error、std::bad_alloc。
void divide (int a, int b) {
if (b == 0 ) throw std::runtime_error ("Division by zero." );
cout << a / b << endl;
}
int main () {
try {
divide (10 , 0 );
} catch (const std::runtime_error& e) {
cout << "Error: " << e.what () << endl;
}
}
自定义异常可以继承 std::exception 并重写 what()。
class MyException : public std::exception {
public :
const char * what () const noexcept override {
return "Custom exception occurred!" ;
}
};
异常处理有性能代价,不要用异常控制正常流程,只在真正意外的时候抛。另外,用 RAII 和智能指针管理资源,防止异常导致内存泄漏。C++11 还加了 noexcept,承诺函数不抛异常,编译器可以优化,移动操作往往用它。
高级控制流:设计模式与多线程 控制流不仅仅是 if/for,把它和设计模式、多线程、递归结合起来,能玩出很多花样。
函数指针与策略模式 可以用函数指针或 std::function 运行时决定调用哪个函数,实现策略模式:
void strategyA () { cout << "A" << endl; }
void strategyB () { cout << "B" << endl; }
void execute (void (*strat)()) { strat (); }
int main () {
void (*p)() = condition ? strategyA : strategyB;
execute (p);
}
状态机 枚举加 switch 很适合实现简单状态机,逻辑集中在状态转移里:
enum State { IDLE, RUNNING, STOPPED };
State state = IDLE;
for (int i = 0 ; i < 5 ; ++i) {
switch (state) {
case IDLE: cout << "Starting..." << endl; state = RUNNING; break ;
case RUNNING: cout << "Stopping..." << endl; state = STOPPED; break ;
case STOPPED: cout << "Back to idle." << endl; state = IDLE; break ;
}
}
多线程控制流 多线程的程序里,控制流变得很复杂,需要互斥锁 (std::mutex) 和条件变量 (std::condition_variable) 协调。经典的生产者-消费者:
std::queue<int > buffer;
std::mutex mtx;
std::condition_variable cv;
const int CAP = 5 ;
void producer () {
for (int i = 0 ; i < 10 ; ++i) {
std::unique_lock<std::mutex> lock (mtx) ;
cv.wait (lock, []{ return buffer.size () < CAP; });
buffer.push (i);
cout << "Produced " << i << endl;
cv.notify_all ();
}
}
void consumer () {
for (int i = 0 ; i < 10 ; ++i) {
std::unique_lock<std::mutex> lock (mtx) ;
cv.wait (lock, []{ return !buffer.empty (); });
int item = buffer.front (); buffer.pop ();
cout << "Consumed " << item << endl;
cv.notify_all ();
}
}
递归与回溯 递归是函数自己调用自己,天然的倒序执行;回溯则是尝试-撤销的模式,常用于搜索问题。八皇后是教科书例子:
std::vector<int > board (8 , -1 ) ;
bool isValid (int row, int col) { }
void solve (int row) {
if (row == 8 ) { return ; }
for (int col = 0 ; col < 8 ; ++col) {
if (isValid (row, col)) {
board[row] = col;
solve (row + 1 );
board[row] = -1 ;
}
}
}
常见问题与优化
多层嵌套 if-else:用早返回或合并条件,比如 if (!loggedIn) return;。
循环里重复计算:把不变的函数调用提到外面,或者用缓存。
滥用 goto:用标志变量或函数封装。
忽略异常安全:忘了解锁或释放内存,用 RAII。
忽略 switch 的 break:上 static analysis 工具查漏。
优化方面,优先使用范围 for,利用标准算法如 std::for_each,用 std::optional 表达可能为空的值,编译期能用 constexpr if 就别放到运行时。
案例:几个真实场景的写法 if (user.isLoggedIn ()) {
if (user.isActive ()) {
if (user.isAdmin ()) { }
}
}
if (!user.isLoggedIn ()) { cout << "Please log in." ; return ; }
if (!user.isActive ()) { cout << "Inactive." ; return ; }
cout << (user.isAdmin () ? "Welcome, Admin!" : "Welcome, User!" ) << endl;
循环性能
遍历大数组时,用范围 for 并合并条件,减少检查:
for (int num : data) {
if (num > 0 && num % 2 == 0 ) {
cout << num << " is positive and even." << endl;
}
}
或者用 std::for_each + lambda。
异常正确的文件读取
不用异常时,错误返回码很容易被忽略;用异常,配合 RAII 自动关闭文件:
void readFile (const std::string& filename) {
try {
std::ifstream file (filename) ;
if (!file) throw std::runtime_error ("Cannot open " + filename);
std::string line;
while (std::getline (file, line)) cout << line << endl;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what () << std::endl;
}
}
最后 C++ 的控制流方案足够灵活,但选择了合适的写法,代码可读性和性能都会好很多。从 if 到 switch,从 for 到 std::ranges::views,每一步的选择都反映了你的设计意图。下次写代码时,不妨想想:这个控制流是不是最清晰的表达?能不能用更现代的特性简化?如果读者一眼看不懂,或许就是该重构的信号了。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online