C++基础(四)——流程控制语句(超详细)
家人们好呀前几篇文章里我们先让计算机喊出了“Hello World”又教会了它“记事情”变量和数据类型最后让它学会了“算算术”运算符和表达式。但到目前为止我们写的程序都像一个死板的机器人从上到下一行一行老老实实地执行从不问“为什么”也从来不会“再来一遍”。然而真正的程序远没有这么听话。比如你要写一个登录系统得判断用户输入的密码是否正确——对了就放行错了就提示重试。比如你要计算全班同学的平均分得把每个同学的成绩挨个加一遍——这就是重复做同一件事。再比如你要在一个数组里找一个特定的数字——找到了就停下来别傻乎乎地继续找。这些“根据条件做出选择”、“反复执行某段代码”、“中途跳出循环”的能力在编程里统称为流程控制。如果说变量和运算符是C的“词汇”那么流程控制语句就是C的“语法”——它们决定了程序的执行顺序让计算机真正有了“智能”。这篇文章我们就来系统地学习C的流程控制语句条件判断、循环结构、跳转语句以及一些现代C带来的新玩法。坐稳了我们要让程序开始“思考”了一、流程控制是什么在没有流程控制的情况下程序只会一条路走到黑——从main函数的第一行开始一行一行往下执行直到最后一行然后结束。这种执行方式被称为顺序结构它是最基本的程序执行方式就像你顺着一条笔直的马路走没有任何岔路口也没有回头路。但现实世界的程序需求远比这复杂。你需要· 根据不同的条件走不同的分支“如果密码正确就登录否则提示错误”· 让某段代码反复执行“把全班50个人的成绩累加起来”· 在特定情况下提前跳出“找到了目标数字不用继续找了”于是C提供了三大类流程控制语句1. 选择结构条件判断 if-else、switch2. 循环结构for、while、do-while3. 跳转语句break、continue、goto、return这三种结构组合起来就能让程序像有了大脑一样根据不同的情况做出不同的反应。二、选择结构选择结构的核心作用就是如果某个条件成立就做A否则就做B。这是程序产生“智能感”的第一步。2.1 if-else语句最灵活的条件判断if-else是C中最常用的条件判断结构支持单分支、双分支和多分支嵌套可以实现从简单到复杂的各种条件决策。2.1.1 单分支if最简单的if语句条件成立就执行不成立就跳过。int score 85; if (score 60) { cout 恭喜你及格了 endl; } // 如果score小于60上面那行根本不会执行关键点· 条件表达式的结果只要非0就认为是“真”会执行if后面的代码。· if和else后面不能加分号加了分号意味着语句结束了if就变成了一个空操作。· 如果要执行多条语句必须用花括号{}包起来。2.1.2 if-else双分支int age 17; if (age 18) { cout 成年了可以去网吧了 endl; } else { cout 未成年回家写作业吧 endl; }2.1.3 if-else if-else多分支当需要判断多个条件时可以用else if串联int score 85; if (score 90) { cout 优秀 endl; } else if (score 80) { cout 良好 endl; } else if (score 60) { cout 及格 endl; } else { cout 不及格 endl; }这种结构就像你妈问你考了多少分——90分以上是“我儿子真棒”80分以上是“还行下次努力”60分以上是“你给老子滚过来”60分以下是“”因为二话不说直接拿衣架开始暴揍了。2.1.4 经典陷阱1和傻傻分不清这是新手最高频的错误而且编译器不会报错最多给个警告int a 3; if (a 5) { // 本意是比较却写成了赋值 cout a等于5 endl; // 这句话永远会执行 }为什么永远执行因为a 5是一个赋值表达式它的值是5非0即为真。于是条件永远成立而且a还被意外改成了5。正确写法是if (a 5) { // 两个等号才是比较 cout a等于5 endl; }一个防御性编程技巧把常量写在左边——if (5 a)。如果你不小心写成5 a编译器会直接报错帮你发现问题。2.1.5 经典陷阱2悬垂elseif (a 1) if (b 2) cout hehe; else cout haha; // 这个else匹配的是哪个ifC规定else匹配离它最近的、尚未匹配的if。所以上面的else属于内层的if (b 2)而不是外层的if (a 1)。当a ! 1时整个内层if包括else都不会执行什么也不输出。解决办法用花括号明确作用域if (a 1) { if (b 2) { cout hehe; } } else { cout haha; // 现在明确属于外层if }别依赖缩进缩进只是给人看的编译器根本不理你。花括号才是逻辑清晰的“第一道防线”。2.1.6 C17新特性带初始化的if语句C17允许在if语句中直接声明并初始化一个变量这个变量的作用域仅限于if语句内部// 传统写法 int result compute(); if (result 0) { // 使用result } // C17写法 if (int result compute(); result 0) { // result只在这个if块内有效 }这个特性可以限制临时变量的作用域让代码更干净。2.2 switch语句高效的多分支选择当你需要根据一个整型变量或能转成整型的变量的多个可能值执行不同操作时switch语句会比一串else if更清晰、更高效。int day 3; switch (day) { case 1: cout 星期一痛苦的开始 endl; break; case 2: cout 星期二继续煎熬 endl; break; case 3: cout 星期三看到希望了 endl; break; case 4: cout 星期四再忍忍 endl; break; case 5: cout 星期五准备解放 endl; break; case 6: case 7: cout 周末睡到自然醒 endl; break; default: cout 一周只有7天你输入的是啥 endl; }switch的要点1. switch后面的表达式必须是整型、字符型或枚举类型不能是浮点数或字符串。2. case后面必须是常量表达式。3. 每个case结尾一般都要加break否则会发生“穿透”——继续执行下一个case的代码。4. default是可选的用来处理所有未匹配到的情况。2.2.1 穿透fall-through的妙用虽然大多数时候break是必须的但有时“故意不写break”可以实现多个case共享同一段代码switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: cout 这个月有31天 endl; break; case 4: case 6: case 9: case 11: cout 这个月有30天 endl; break; case 2: cout 这个月有28或29天 endl; break; }如果刻意使用穿透建议加上注释说明免得别人或三个月后的你自己以为你忘了写break而“好心”帮你加上结果逻辑全乱。2.2.2 switch vs if-else什么时候用哪个判断一个变量是否等于几个离散的常量switch更清晰、可读性更好判断范围条件如大于、小于if-else条件复杂涉及多个变量if-else性能敏感的场景分支很多时switch某些编译器会生成跳转表更快switch语句在底层实现中编译器可能会生成一张“跳转表”jump table使得无论有多少个case查找时间复杂度都是O(1)而if-else链的最坏情况是O(n)。三、循环结构让程序学会“重复劳动”计算机最擅长的事情就是不厌其烦地重复做同一件事。人类算100个数的和会觉得无聊但计算机会毫不犹豫地帮你算完——这就是循环的价值。C提供了三种循环结构for、while和do-while。三者的核心区别在于循环条件的初始化方式和判断时机。3.1 for循环已知次数的重复当你明确知道要循环多少次时for是最佳选择。比如“遍历数组的前10个元素”、“计算1到100的和”。for (初始化; 条件; 更新) { // 循环体 }执行流程初始化只做一次→ 判断条件 → 条件为真则执行循环体 → 执行更新 → 判断条件 → … → 条件为假则退出。经典示例计算1到100的和int sum 0; for (int i 1; i 100; i) { sum i; } cout 1到100的和是 sum endl; // 输出5050灵活用法· 三个表达式都可以省略但分号必须保留int i 0; for (; i 10; i) { /* 省略初始化 */ } for (int i 0; i 10;) { i; /* 省略更新在循环体内做 */ } for (;;) { /* 无限循环需用break退出 */ } // 传说中的“死循环”· 可以同时控制多个变量for (int i 1, j 5; i 5; i, --j) { cout i j i j endl; }3.1.1 C11范围for循环遍历容器的神器C11引入的范围for循环range-based for loop让遍历数组、vector等容器变得极其简洁int arr[] {10, 20, 30, 40, 50}; for (int x : arr) { cout x ; } // 输出10 20 30 40 50如果你需要修改容器中的元素可以使用引用vectorint nums {1, 2, 3, 4, 5}; for (int x : nums) { x * 2; // 每个元素翻倍 }能用范围for的地方尽量用范围for。它不仅代码更短还能避免“下标越界”这种经典Bug。3.2 while循环依赖条件的重复当你不知道要循环多少次只知道“只要某个条件成立就一直循环”时用while最合适。while (条件) { // 循环体 }执行流程先判断条件 → 条件为真则执行循环体 → 执行完再判断条件 → … → 条件为假则退出。如果条件一开始就是假循环体一次都不会执行。经典示例猜数字游戏int secret 42; int guess 0; while (guess ! secret) { cout 猜一个数字; //输入一个数并保存在guess中下一章会讲到 cin guess; if (guess secret) { cout 太大了 endl; } else if (guess secret) { cout 太小了 endl; } } cout 恭喜你猜对了 endl;3.3 do-while循环至少做一次再说do-while和while几乎一样只有一个关键区别它先执行循环体再判断条件。这意味着无论条件是否成立循环体至少会执行一次。do { // 循环体 } while (条件); // 注意这里有分号典型场景菜单选择界面int choice; do { cout 菜单 endl; cout 1. 开始游戏 endl; cout 2. 设置 endl; cout 3. 退出 endl; cout 请选择; cin choice; switch (choice) { case 1: cout 游戏开始 endl; break; case 2: cout 进入设置... endl; break; case 3: cout 再见 endl; break; default: cout 无效选择请重新输入 endl; } } while (choice ! 3);3.4 三种循环怎么选知道具体要循环多少次for或范围for循环次数未知依赖条件可能一次都不执行while循环次数未知但至少需要执行一次do-while遍历数组/容器范围for最推荐3.5 循环的嵌套循环里面还可以套循环这叫做嵌套循环。最经典的例子就是打印乘法口诀表for (int i 1; i 9; i) { for (int j 1; j i; j) { cout j × i i * j \t; } cout endl; }注意事项· 内层循环的变量名不要和外层循环的变量名冲突比如都用i。· 嵌套层数不宜过多一般不超过3层否则代码可读性会急剧下降。四、跳转语句不走寻常路有时候正常循环流程无法满足需求——比如你想在找到目标后立即退出循环或者想跳过某次循环的剩余部分直接开始下一次。这时候跳转语句就派上用场了。C提供了四种跳转语句break、continue、goto和return。4.1 break立即退出break用于立即退出所在的循环或switch语句继续执行循环/switch之后的代码。// 在一个数组中查找目标值找到就退出 vectorint nums {4, 7, 2, 9, 5, 1}; int target 9; int index -1; for (size_t i 0; i nums.size(); i) { if (nums[i] target) { index i; break; // 找到了不用继续找了 } }注意break只能跳出最内层的循环或switch。如果有多层嵌套需要逐层判断或者用其他方法。4.2 continue跳过本次继续下一次continue用于跳过当前循环迭代的剩余部分立即开始下一次迭代。// 只输出奇数 for (int i 1; i 10; i) { if (i % 2 0) { continue; // 偶数就跳过不输出 } cout i ; } // 输出1 3 5 7 9注意continue不能直接用在switch中除非switch在循环内部。4.3 goto禁忌之术goto可以无条件跳转到同一函数内的带标签位置。它的名声很差被很多编程教材列为“禁止使用”的语句因为它会破坏结构化编程让代码变成一团乱麻——“意大利面条式代码”。但goto并非完全没有用武之地。在跳出多重嵌套循环时goto有时是最简洁的解决方案for (int i 0; i 10; i) { for (int j 0; j 10; j) { if (i * j 50) { goto end; // 直接跳出两层循环 } } } end: cout 跳出循环 endl;goto就像“核武器”——威力巨大但大多数时候你最好假装它不存在。除非你确定没有更好的办法并且准备好在代码审查时接受同事的“灵魂拷问”。重要提醒goto不能跳过变量的初始化否则会编译错误goto label; int x 10; // 错误跳过了x的初始化 label: cout x;4.4 return函数到此为止return用于从当前函数返回并将控制权交还给调用者。· 在void函数中return可以不带值或者省略函数末尾会自动返回。· 在非void函数中必须返回一个与函数返回类型兼容的值。int max(int a, int b) { if (a b) { return a; } else { return b; } // 这里永远不会执行到 }在main函数中return 0表示程序正常退出。如果你忘记写C会自动帮你加上但养成写的习惯更好。五、现代C流程控制新特性C17到C26C并不是一门故步自封的语言它的流程控制机制也在不断进化。以下几个新特性虽然新手暂时不一定用得上但了解它们能让你对C的未来充满期待。5.1 C17if constexpr——编译期if普通if的条件在运行时判断而if constexpr的条件在编译期判断。这意味着不满足条件的分支根本不会被编译从而避免了模板编程中的很多编译错误。templatetypename T void print_value(T t) { if constexpr (std::is_pointer_vT) { // 如果T是指针类型才编译这个分支 std::cout *t \n; } else { // 如果T不是指针只编译这个分支 std::cout t \n; } }if constexpr让模板元编程从“魔法”变成了正常的代码逻辑是C17最受欢迎的特性之一。5.2 C20概念ConceptsC20引入的概念Concepts虽然不是直接的流程控制语句但它配合if constexpr可以实现更强大的编译期条件判断。5.3 C26契约编程ContractsC26引入了pre前置条件、post后置条件和contract_assert让函数的前置和后置条件检查成为语言的一部分。虽然在调试模式下影响控制流但它们是保证代码正确性的重要工具。六、最佳实践总结1. if/else/for/while后面永远加花括号{}——即使只有一条语句。这能避免无数低级Bug。2. 比较时把常量放左边——if (5 a)防赋值。3. 能用范围for就别用普通for——更简洁更安全。4. switch的default不要省略——处理意外情况。5. 尽量避免goto——除非跳出多重嵌套循环且没有更好的办法。6. 循环变量不要在循环体内修改——除了正常的更新表达式不要在循环体内随意修改循环变量否则容易导致逻辑混乱。七、动手实践打开你的Visual Studio把下面的代码复制进去运行看看输出是什么#include iostream #include vector using namespace std; int main() { cout 条件判断演示 endl; int score 85; if (score 90) { cout 优秀 endl; } else if (score 80) { cout 良好 endl; } else { cout 继续努力 endl; } cout \n switch演示 endl; int day 3; switch (day) { case 1: cout 星期一 endl; break; case 2: cout 星期二 endl; break; case 3: cout 星期三 endl; break; default: cout 其他 endl; } cout \n for循环演示 endl; int sum 0; for (int i 1; i 10; i) { sum i; } cout 1到10的和 sum endl; cout \n 范围for演示 endl; vectorint nums {10, 20, 30, 40}; for (int x : nums) { cout x ; } cout endl; cout \n while循环演示 endl; int count 0; while (count 5) { cout count ; count; } cout endl; cout \n break演示 endl; for (int i 1; i 10; i) { if (i 5) break; cout i ; } cout (在5处停止了) endl; cout \n continue演示 endl; for (int i 1; i 10; i) { if (i % 2 0) continue; cout i ; } cout (只输出奇数) endl; system(pause); return 0; }改改里面的数字和条件试试不同的组合观察输出变化。编程这件事光看不练是永远学不会的。八、总结恭喜你现在你已经掌握了让C思考的核心技能。快速回顾· 选择结构if-else最灵活、switch多分支整型判断· 循环结构for已知次数、while依赖条件、do-while至少一次、范围for遍历容器· 跳转语句break退出循环/switch、continue跳过本次、goto少用为妙、return返回函数· 现代特性if constexprC17编译期if、if带初始化C17思考题看看你学会了没1. 以下代码会输出什么int x 10; if (x 5) { cout A; } else { cout B; }2. switch语句中的break有什么作用如果忘记写会发生什么3. while循环和do-while循环的根本区别是什么4. 什么时候应该用continue而不是break下一篇文章我们将系统地学习C的输入输出。谢谢大家—— 一个曾因为少写一个break而debug到凌晨三点的C学习者
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2547943.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!