一、 什么是语句?
C语句可分为以下五类:
- 表达式语句
- 函数调用语句
- 控制语句
- 复合语句
- 空语句
本章后面介绍的是控制语句。
控制语句用于控制程序的执行流程,以实现程序的各种结构方式(C语言支持三种结构:顺序结构、选 择结构、循环结构),它们由特定的语句定义符组成,C语言有九种控制语句。
可分成以下三类:
- 条件判断语句也叫分支语句:if语句、switch语句;
- 循环执行语句:do while语句、while语句、for语句;
- 转向语句:break语句、goto语句、continue语句、return语句。
二、 分支语句(选择结构)
如果你好好学习,校招时拿一个好offer,走上人生巅峰。
如果你不学习,毕业等于失业,回家卖红薯。
这就是选择!
1. if语句
语法结构:
(1) if
if(表达式) 语句;
解释一下:
表达式成⽴(为真),则语句执⾏,
表达式不成⽴(为假),则语句不执⾏
在C语言中如何表示真假?
0表示假,非0表示真。
例⼦:输⼊⼀个年龄,判断是否成年:
#include <stdio.h> int main() { int age = 0; scanf("%d", &age); if(age<18) { printf("未成年\n"); } }
(2) if.....else
如果⼀个人不是未成年,那就是成年了,如果任意⼀个年龄,我们要清楚的判断是成年还是未成年怎么表⽰呢?
if(表达式) 语句1; else 语句2;
例⼦:输⼊⼀个整数,判断是否为奇数,如果是奇数打印是奇数,否则打印偶数。
#include <stdio.h> int main() { int age = 0; scanf("%d", &age); if(age<18) { printf("未成年\n"); } else { printf("成年\n"); } }
(3) 嵌套if
//多分支 if(表达式1) 语句1; else if(表达式2) 语句2; else 语句3;
在
if else
语句中,else
可以与另⼀个if
语句连⽤,构成多重判断。⽐如:判断一个是少年(< 10)、青年(>= 18 && <30 )、中年(>= 30 && < 50)、老年(>= 50 && < 80)、老寿星(>= 80)。请看如下代码:
#include <stdio.h> int main() { int age = 0; scanf("%d", &age); if(age<18) { printf("少年\n"); } else if(age>=18 && age<30) { printf("青年\n"); } else if(age>=30 && age<50) { printf("中年\n"); } else if(age>=50 && age<80) { printf("老年\n"); } else { printf("老寿星\n"); } }
如果条件成立,要执行多条语句,怎应该使用代码块。
#include <stdio.h> int main() { if(表达式) { 语句列表1; } else { 语句列表2; } return 0; }
这里的一对 { }就是一个代码块。
(4) 悬空else
就近原则
规则:
else
总是跟最接近的,并且没有和else
匹配的if 匹配。错误案例;
#include <stdio.h> int main() { int a = 0; int b = 2; if(a == 1) if(b == 2) printf("hehe\n"); else printf("haha\n"); return 0; }
在多个 if 和 else 存在时,会出现悬空 else 问题,即 else 总是与最接近的 if 匹配。
如上述代码,其排版易让人误以为 else 与第一个 if 匹配,当第一个 if 条件不成立时,按常理应执行 else 子句打印 “haha”,但实际 else 与第二个 if 匹配,导致整个嵌套的 if...else 语句在第一个 if 不成立时根本不会执行,最终无任何输出。
若将代码修改为如下形式,则逻辑会更加清晰易懂:
#include <stdio.h> int main() { int a = 0; int b = 2; if(a == 1) { if(b == 2) { printf("hehe\n"); } } else { printf("haha\n"); } return 0; }
(5) if书写形式的对比
//代码1 if (condition) { return x; } return y; //代码2 if(condition) { return x; } else { return y; } //代码3 int num = 1; if(num == 5) { printf("hehe\n"); } //代码4 int num = 1; if(5 == num) { printf("hehe\n"); }
代码2和代码4更好,逻辑更加清晰,不容易出错。
2.switch语句
switch语句也是一种分支语句。
常常用于多分支的情况。 比如:
- 输入1,输出星期一
- 输入2,输出星期二
- 输入3,输出星期三
- 输入4,输出星期四
- 输入5,输出星期五
- 输入6,输出星期六
- 输入7,输出星期日
那我没写成 if...else if ...else if 的形式太复杂,那我们就得有不一样的语法形式。
这就是switch 语句。
switch(整型表达式) { 语句项; }
而语句项是什么呢?
//是一些case语句: //如下: case 整形常量表达式: 语句;
(1) if语句和switch语句对比
练习:输⼊任意⼀个整数值,计算除3之后的余数
使⽤if语句如下:
#include <stdio.h> int main() { int n = 0; scanf("%d", &n); if(n%3 == 0) printf("整除,余数为0\n"); else if(n%3 == 1) printf("余数是1\n"); else printf("余数是2\n"); return 0; }
使⽤
switch
语句改写:#include <stdio.h> int main() { int n = 0; scanf("%d", &n); switch(n%3) { case 0: printf("整除,余数为0\n"); break; case 1: printf("余数是1\n"); break; case 2: printf("余数是2\n"); break; } return 0; }
(2) 在switch语句中的 break
break语句 的实际效果是把语句列表划分为不同的分支部分。
上述的代码中,我们要注意的点有:
case
和后边的数字之间必须有空格- 每⼀个
case
语句中的代码执⾏完成后,需要加上break
,才能跳出这个switch
语句。如果我们去掉case语句中的break,会出现什么情况呢?
以 0 除以 3 为例,结果应余数为 0,
但程序却多打印“余数是 1”和 “余数是 2”。
原因是 switch 语句的分支特性依赖 break。
若无 break,代码会从当前 case 持续向下执行,直至遇到 break 或 switch 结束。
如上述代码,因 case 0 后无 break,便执行了 case 1,又因为同样原因执行了case 2。
不过,break 并非每个 case 必备,需依实际情况决定,有时为使多个 case 执行相同代码可不加 break,但多数情况合理使用 break 对确保程序逻辑准确清晰至关重要。
练习:输⼊⼀个1~7的数字,打印对应的星期⼏
例如:
- 输⼊:1输出:星期⼀
- 输⼊:2输出:星期⼆
- 输⼊:3输出:星期三
- 输⼊:4输出:星期四
- 输⼊:5输出:星期五
- 输⼊:6输出:星期六
- 输⼊:7输出:星期天
#include <stdio.h> int main() { int day = 0; scanf("%d", &day); switch(day) { case 1: printf("星期⼀\n"); break; case 2: printf("星期⼆\n"); break; case 3: printf("星期三\n"); break; case 4: printf("星期四\n"); break; case 5: printf("星期五\n"); break; case 6: printf("星期六\n"); break; case 7: printf("星期天\n"); break; } return 0; }
有时候我们的需求变了:
- 输入1-5,输出的是“weekday”;
- 输入6-7,输出“weekend”
#include <stdio.h> int main() { int day = 0; scanf("%d", &day); switch(day) { case 1: case 2: case 3: case 4: case 5: printf("weekday\n"); break; case 6: case 7: printf("weekend\n"); break; } return 0; }
编程好习惯
在最后一个 case 语句的后面加上一条 break语句。
(之所以这么写是可以避免出现在以前的最后一个 case 语句后面忘了添加 break语句)。
(3) default子句
当 switch 表达式与所有 case 标签值均不匹配时,程序不会终止或报错,只是对应语句被跳过,因这在 C 中不算错误。
若不想忽略该不匹配值,可在语句列表中添加一条 default 子句(标签为 “default:”),它能置于任何 case 标签可出现之处。
一旦 switch 表达式不匹配所有 case 标签值,default 子句后的语句便会执行,且其在语句列表位置任意,执行时与普通 case 标签无异,且每个 switch 语句仅能有一条 default 子句。
举例:
#include <stdio.h> int main() { int day; printf("请输入一个数字(1 - 7):"); scanf("%d", &day); switch (day) { case 1: printf("星期一\n"); break; case 2: printf("星期二\n"); break; case 3: printf("星期三\n"); break; case 4: printf("星期四\n"); break; case 5: printf("星期五\n"); break; case 6: printf("星期六\n"); break; case 7: printf("星期日\n"); break; default: printf("输入错误,请输入 1 - 7 之间的数字\n"); break; } return 0; }
编程好习惯
在每个 switch 语句中都放一条default子句是个好习惯,甚至可以在后边再加一个 break 。
三、循环语句
- while
- for
- do while
1.while循环
我们已经掌握了,if语句:
if(条件) 语句;
当条件满足的情况下,if语句后的语句执行,否则不执行。
但是这个语句只会执行一次。
由于我们发现生活中很多的实际的例子是:同一件事情我们需要完成很多次。
那我们怎么做呢?
C语言中给我们引入了: while 语句,可以实现循环。
//while 语法结构 while(表达式) 循环语句;
while语句执行的流程:
⾸先上来就是执⾏判断表达式:
- 表达式的值为0,循环直接结束;
- 表达式的值不为0,则执⾏循环语句,语句执⾏完后再继续判断,是否进⾏下⼀次判断。
先判断,后循环,当不满足条件时跳出循环(当型循环)
练习:在屏幕上打印1~10的值
#include <stdio.h> int main() { int i = 1; while(i<=10) { printf("%d ", i); i = i+1; } return 0; }
2.while语句中的break和continue
(1) break
break在while循环中的作用:
其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。
所以:while中的break是用于永久终止循环的。
举例:
#include <stdio.h> int main() { int i = 1; while(i<=10) { if(i == 5) break;//当i等于5后,就执⾏break,循环就终⽌了 printf("%d ", i); i = i+1; } return 0; }
这里代码输出的结果是什么?
1 2 3 4
打印了1,2,3,4后,当i等于5的时候,循环在 break 的地⽅终⽌,不再打印,不再循环
所以 break 的作⽤就是永久的终⽌循环,只要 break 被执⾏, break 外的第⼀层循环就终⽌了。
(2) continue
continue在while循环中的作用就是:
continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,
而是直接跳转到while语句的判断部分。进行下一次循环的入口判断。
举例:
#include <stdio.h> int main() { int i = 1; while(i<=10) { if(i == 5) continue; //当i等于5后,就执⾏continue,直接跳过continue的代码,去循环的判断的地⽅ //因为这⾥跳过了i = i+1,所以i⼀直为5,程序陷⼊和死循环 printf("%d ", i); i = i+1; } return 0; }
到这⾥我们就能分析出来,
continue
可以帮助我们跳过某⼀次循环continue
后边的代码,直接到循环的判断部分,进⾏下⼀次循环的判断.如果循环的调整是在
continue
后边的话,可能会造成死循环3.for循环
(1) for的基本语法
首先来看看for循环的语法:
for(表达式1; 表达式2; 表达式3) 循环语句;//如果循环体想包含更多的语句,可以加上⼤括号
表达式1
表达式1为初始化部分,用于初始化循环变量的。
表达式2
表达式2为条件判断部分,用于判断循环时候终止。
表达式3
表达式3为调整部分,用于循环条件的调整。
练习:使用for循环 在屏幕上打印1-10的数字。
#include <stdio.h> int main() { int i = 0; for(i=1; i<=10; i++) { printf("%d ", i); } return 0; }
现在我们对比一下for循环和while循环。
int i = 0; //实现相同的功能,使用while i=1;//初始化部分 while(i<=10)//判断部分 { printf("hehe\n"); i = i+1;//调整部分 } //实现相同的功能,使用while for(i=1; i<=10; i++) { printf("hehe\n"); }
可以发现在while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较远,这样 查找修改就不够集中和方便。
所以,for循环的风格更胜一筹;for循环使用的频率也最高。
(2) break和continue在for循环中
- break
举例:
#include <stdio.h> int main() { int i = 1; for(i=1; i<=10; i++) { if(i == 5) break; printf("%d ", i); } return 0; }
和
while
循环中的 break ⼀样,for
循环中的break
也是⽤于终⽌循环的,不管循环还需要循环多少次,只要执⾏到了break
,循环就彻底终⽌
- continue
举例:
#include <stdio.h> int main() { int i = 1; for(i=1; i<=10; i++) { if(i == 5) continue;//这⾥continue跳过了后边的打印,来到了i++的调整部分 printf("%d ", i); } return 0; }
在相关 for 循环中,因 continue 会跳过本次循环 continue 后的代码并进入调整部分,如 i 为 5 时跳过打印,所以不会死循环,而是打印 1 2 3 4 6 7 8 9 10。
这表明 for 循环里 continue 可跳过后续代码至调整阶段,适用于特定条件下无需执行本次循环部分操作的场景。
在这⾥我们也可以对⽐⼀下
while
循环和for
循环中continue
的区别:
(3) for语句的循环控制变量
建议:
- 不可在for 循环体内修改循环变量,防止 for 循环失去控制。
- 建议for语句的循环控制变量的取值采用“前闭后开区间”写法。
int i = 0; //前闭后开的写法 for(i=0; i<10; i++) {} //两边都是闭区间 for(i=0; i<=9; i++) {}
(4) 一些for循环的变种
无限循环
#include <stdio.h> int main() { //代码1 for(;;) { printf("hehe\n"); } }
这里,for 循环的初始化部分、判断部分和调整部分都被省略了,只剩下两个分号。
这种写法会导致循环条件始终为真,从而形成一个无限循环,程序会不停地输出 “hehe”。
- 省略初始化部分的嵌套循环
#include <stdio.h> int main() { //代码2 int i = 0; int j = 0; //这里打印多少个hehe? for(i = 0; i < 10; i++) { for(j = 0; j < 10; j++) { printf("hehe\n"); } } return 0; } #include <stdio.h> int main() { //代码3 int i = 0; int j = 0; //如果省略掉初始化部分,这里打印多少个hehe? for(; i < 10; i++) { for(; j < 10; j++) { printf("hehe\n"); } } return 0; }
这里,外层 for 循环省略了初始化部分(不过我们在循环前已经初始化了
i = 0
),内层 for 循环不仅省略了初始化部分(同样,循环前j = 0
),而且没有在每次外层循环开始时重新初始化j
。这会导致内层循环在第一次执行完后,由于
j
已经达到 10,后续外层循环中内层循环不会再执行。所以,最终输出 “hehe” 的次数为 10 次,而不是我们预期的 100 次(如果内层循环每次都能完整执行 10 次的话)。
多个变量控制循环
#include <stdio.h> int main() { //代码4 - 使用多余一个变量控制循环 int x, y; for (x = 0, y = 0; x < 2 && y < 5; ++x, y++) { printf("hehe\n"); } return 0; }
在这个 for 循环中,我们使用了两个变量
x
和y
来共同控制循环的条件。初始化部分同时初始化了
x
和y
为 0,循环条件是x < 2
且y < 5
,每次循环后x
自增 1,y
也自增 1。循环会在x
达到 2 或者y
达到 5 时停止。在这个过程中,
x
和y
的变化相互影响,共同决定了循环的执行次数。(5) 一道面试题
//请问循环要循环多少次? #include <stdio.h> int main() { int i = 0; int k = 0; for(i =0,k=0; k=0; i++,k++) k++; return 0; }
在这个
for
循环中,我们需要重点关注它的循环判断条件,也就是for
循环括号里中间的那个表达式k = 0
。在 C 语言里,赋值表达式本身是有值的,它的值就是所赋的值,在这里也就是
0
。而在
for
循环里,判断条件部分会把这个表达式的值当作真假来判断,0
代表假,非0
代表真。由于
k = 0
这个赋值表达式的值始终为0
(也就是假),所以第一次进行循环条件判断的时候,就判定为不满足循环条件了,循环体里的代码根本就不会执行,直接就跳出了循环。因此,这道题里的
for
循环执行次数是0
次。4.do...while()循环
(1) do...while() 基本语法
do 循环语句; while(表达式);//注意有分号
while 和 for 这两种循环都是先判断,条件如果满⾜就进⼊循环,执⾏循环语句,如果不满⾜就跳出循环;
⽽ do while 循环则是先直接进⼊循环体,执⾏循环语句,然后再执⾏ while 后的判断表达式,表达式为真,就会进⾏下⼀次,表达式为假,则不再继续循环。
特点:先循环,后判断,直到条件不满足跳出循环(直到型循环)
练习:在屏幕上打印1~10的值:
#include <stdio.h> int main() { int i = 1; do { printf("%d ", i); i = i + 1; }while(i<=10); return 0; }
(2) break和continue在 do...while() 循环中
和
while
循环中一模一样
四、实战练习
- 计算 n的阶乘。
- 计算 1!+2!+3!+……+10!
- 在一个有序数组中查找具体的某个数字n。(讲解二分查找)
- 编写代码,演示多个字符从两端移动,向中间汇聚。
模拟用户登录情景
- 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则 提示登录成,如果三次均输入错误,则退出程序。
1.计算 n 的阶乘
代码实现思路是利用循环让从 1 到 n 的每个数依次相乘。示例代码如下:
#include <stdio.h> int main() { int n, i; long long factorial = 1; // 用 long long 类型来存放阶乘结果,防止溢出 printf("请输入一个整数 n: "); scanf("%d", &n); for(i = 1; i <= n; i++) { factorial *= i; } printf("%d 的阶乘是 %lld\n", n, factorial); return 0; }
2.计算 1! + 2! + 3! + …… + 10!
这个练习在计算阶乘的基础上,增加了求和的要求。需要两层循环,外层循环控制从 1 到 10 的数字,内层循环用来计算每个数字对应的阶乘,然后再将这些阶乘值累加起来。
示例代码如下(这里简略示意核心逻辑):
#include <stdio.h> int main() { int i, j; long long sum = 0, factorial; for(i = 1; i <= 10; i++) { factorial = 1; for(j = 1; j <= i; j++) { factorial *= j; } sum += factorial; } printf("1! + 2! + 3! + …… + 10! 的结果是 %lld\n", sum); return 0; }
3.二分查找算法
以下是在主函数内实现查找一个具体数字 n 在有序数组中的代码示例:
#include <stdio.h> int main() { int arr[] = {1,2,3,4,5,6,7,8,9,10}; int left = 0; int right = sizeof(arr)/sizeof(arr[0]) - 1; int key = 7; // 这里假设要查找的数字是 7,可根据实际情况修改 int mid = 0; while(left <= right) { mid = (left + right) / 2; if(arr[mid] > key) { right = mid - 1; } else if(arr[mid] < key) { left = mid + 1; } else break; } if(left <= right) printf("找到了,下标是 %d\n", mid); else printf("找不到\n"); return 0; }
在循环中,不断更新查找区间,通过比较中间元素和要查找的 key 值,缩小查找范围,直到找到目标数字或者确定不存在为止。
实现二分查找函数
如果把二分查找封装成一个函数,代码如下:
int bin_search(int arr[], int left, int right, int key) { int mid = 0; while(left <= right) { mid = (left + right) >> 1; // 右移一位相当于除以 2,效率更高些 if(arr[mid] > key) { right = mid - 1; } else if(arr[mid] < key) { left = mid + 1; } else return mid; // 找到了,返回下标 } return -1; // 找不到 }
4.字符汇聚效果演示
编写代码演示多个字符从两端移动,向中间汇聚,这个练习可以让我们更好地掌握循环以及字符数组的操作。以下是代码示例:
#include <stdio.h> #include <windows.h> // 引入 Sleep 函数所需头文件 int main() { char arr1[] = "welcome to bit..."; char arr2[] = "#################"; int left = 0; int right = strlen(arr1) - 1; printf("%s\n", arr2); // while 循环实现 while(left <= right) { Sleep(1000); // 暂停 1 秒,以便观察效果 arr2[left] = arr1[left]; arr2[right] = arr1[right]; left++; right--; printf("%s\n", arr2); } // for 循环实现(原代码这里变量名有小错误,已修正示意正确逻辑) for (left = 0, right = strlen(arr1) - 1; left <= right; left++, right--) { Sleep(1000); arr2[left] = arr1[left]; arr2[right] = arr1[right]; printf("%s\n", arr2); } return 0; }
通过循环逐步替换字符数组
arr2
中的字符,并且利用Sleep
函数让我们可以清晰看到字符从两端向中间汇聚的动态效果,同时展示了while
和for
循环都可以完成这个任务。5.模拟用户登录情景
模拟用户登录情景,且只允许输入三次密码,如果密码正确则提示登录成功,如果三次均输入错误,则退出程序。代码实现如下:
#include <stdio.h> #include <string.h> int main() { char psw[10] = ""; int i; for (i = 0; i < 3; ++i) { printf("please input:"); scanf("%s", psw); if (strcmp(psw, "password") == 0) // 这里假设密码是 "password",可按需修改 break; } if (i == 3) printf("exit\n"); else printf("log in\n"); return 0; }
利用
for
循环控制输入次数,通过strcmp
函数比较输入的密码和正确密码是否一致,根据循环结束后的情况判断是登录成功还是失败。6.猜数字游戏实现
#include <stdio.h> #include <stdlib.h> #include <time.h> void menu() { printf("**********************************\n"); printf("*********** 1.play **********\n"); printf("*********** 0.exit **********\n"); printf("**********************************\n"); } void game() { int random_num = rand() % 100 + 1; int input = 0; while(1) { printf("请输入猜的数字>:"); scanf("%d", &input); if (input > random_num) { printf("猜大了\n"); } else if (input < random_num) { printf("猜小了\n"); } else { printf("恭喜你,猜对了\n"); break; } } } int main() { int input = 0; srand((unsigned)time(NULL)); // 用时间作为随机数种子,保证每次运行结果不同 do { menu(); printf("请选择>:"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: break; default: printf("选择错误,请重新输入!\n"); break; } } while (input); return 0; }
这里通过
rand
函数生成随机数作为要猜的数字,利用循环让玩家不断输入猜测,根据比较结果提示猜大还是猜小,直到猜对为止,整体通过do-while
循环和switch
语句来控制游戏的流程选择。
五、goto语句
C语⾔提供了⼀种⾮常特别的语法,就是
goto
语句和跳转标号,goto
语句可以实现在同⼀个函数内跳转到设置好的标号处。例如:一次跳出两层或多层循环。
多层循环这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。
goto语言真正适合的场景如下:
for(...) { for(...) { for(...) { if(disaster) goto error; } } } error: // 处理错误情况
下面是使用goto语句的一个例子,然后使用循环的实现方式替换goto语句:
一个关机程序
#include <stdio.h> int main() { char input[10] = {0}; system("shutdown -s -t 60"); again: printf("电脑将在1分钟内关机,如果输入:我是猪,就取消关机!\n请输入:>"); scanf("%s", input); if(0 == strcmp(input, "我是猪")) { system("shutdown -a"); } else { goto again; } return 0; }
而如果不适用goto语句,则可以使用循环:
#include <stdio.h> #include <stdlib.h> int main() { char input[10] = {0}; system("shutdown -s -t 60"); while(1) { printf("电脑将在1分钟内关机,如果输入:我是猪,就取消关机!\n请输入:>"); scanf("%s", input); if(0 == strcmp(input, "我是猪")) { system("shutdown -a"); break; } } return 0; }