Day 4
编写代码常见错误:
- 忘写返回值
- 边界值问题
- 命名问题
- 重复代码
- 马虎大意不仔细,写错逻辑。要写注释
语句
表达式语句
我们已经非常清楚:表达式有主要作用和副作用,那么当表达式变了表达式语句,会发生什么变化呢?
很简单,表达式语句一般会舍弃表达式是的主要作用,而利用表达式的副作用。这就是表达式语句存在意义。
比如:
int x = 10;
主要作用是返回10这个值,副作用是改变了x变量的取值。很明显主要作用被丢弃了,起作用的是副作用。
y--;
主要作用是返回y本身的值,副作用是将y的值减1。很明显主要作用被丢弃了,起作用的是副作用。当然一个表达式语句中,可能存在多个表达式,关于其作用还需要具体情况具体分析。
关于控制结构使用的建议(重要)
在上面我们知道所有的控制结构,都可以省略{}
。这种做法虽然会导致只能控制一条语句,但减少了{}
,使得代码简洁。所以很多C程序员出于代码简洁优雅的追求,会选择"能够省略{}
就省略{}
"的编程风格。
但我们要说清楚,这种编程习惯对于现代编程而言,是非常非常不推荐的,甚至是应该禁止的。
switch语句
C语言中的switch
语句是一种多分支选择结构,用于基于变量或表达式的离散的值进行多分支选择。它可以在某些场景下替代if-else多分支
语句,特别是在需要根据同一个表达式的多个离散结果选择不同的执行路径时。
注意事项
switch
的小括号中的表达式的结果必须是整型或字符型。浮点数、字符串等其他类型都不行。(思考一下为什么)- case关键字后面必须紧跟一个常量表达式,不能是变量或者一个运行时才能确定的表达式。
default
分支不是必需的,但推荐使用它来处理意外情况,增强程序的健壮性。和if多分支不同的是,default分支不一定要放到最后。实际上switch语句中每个分支的书写顺序对switch而言是无所谓的。- 如果整个switch中没有分支能匹配,且没有default分支,那么switch不执行,代码会继续执行switch语句后面的部分。
case穿越
我们知道switch结束有两种可能:
- 碰到了break语句结束switch
- 执行完整个switch结束
所以,当某个case分支的末尾没有break
,那么程序就会继续执行下一个case分支,直到遇到break或者执行完整个switch。这种语法现象就是所谓的"case穿越"(case穿刺)。
循环语句
C 语言支持三种循环结构:
- **while 循环:**在每次循环前都会率先检查条件判断语句,如果为真才执行循环体。while循环是最简洁的循环结构。
- for循环:for循环和while循环其实是等价可以互换的。但for循环特别适合在那些已知循环次数或需要递增/递减计数变量的场景,比如数组,某些数据结构等场景中。for循环是日常最常用的循环结构。
- **do…while循环:**总是会先执行一次循环体,然后再检查条件判断语句,决定是否继续执行循环。do…while的最大特点是无论如何都会执行一次循环体,而for和while循环则可能一次都不执行。总得来说,do…while循环比较少用。
for循环
C语言中,for循环的语法是:
for(初始化语句; 条件判断语句; 循环控制语句) {
// 循环体语句
}
执行流程(重要):
- 执行初始化语句,初始化循环控制变量,该语句只会执行一次。
- 执行循环控制表达式进行条件判断,如果结果是:
- true(非0),执行一次循环体语句。
- false(0),循环终止。
- 执行一次循环控制语句,改变循环控制变量的值,用于改变循环的状态。
- 再次执行循环控制表达式进行条件判断,开始循环。直到布尔表达式的结果是false,循环终止。
![for循环流程图](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fhixiaodong123.oss-cn-hangzhou.aliyuncs.com%2Ftypora%2F202312291835577.png%3Falign%3Dcenter%23padding%23400px&pos_id=img-aUBLTdPo-1704002964589%29)
跳转控制语句
在while循环和for循环当中,我们可以在循环开始前就控制循环的结束,而在do…while循环中,我们可以在一次循环后控制循环的结束。但不管是什么循环,我们目前都还缺少在循环过程中控制循环的手段,此时就需要使用跳转控制语句了。
所谓跳转控制语句,就是由三个关键字加";"组成的语句,用于在循环过程中控制循环的流程:
- break语句
- continue语句
- goto语句
下面我们逐一介绍这三个语句。
break语句
break意为"打断,中断",是非常重要的跳转控制关键字。
break语句实际上有两个用处:
- 在switch语句当中使用,break用于结束当前switch。
- 在循环中使用,用于结束当前层次的循环。
一个简单的break代码示例:
#include <stdio.h>
int main(void) {
int num = 777;
int is_prime = 1; // 1表示true,是素数。默认num是素数
int d;
// 判断某个数是不是素数
for (d = 2; d < num; d++) {
if (num % d == 0) {
// 找到了一个能整除的数,num不是素数
is_prime = 0;
break;
}
}
if (is_prime) {
printf("%d是一个素数.\n", num);
}
else {
printf("%d不是一个素数.它可以被%d整除.\n", num, d);
}
return 0;
}
break语句经常在死循环中使用,在满足特定的条件时退出循环,以免出现死循环。如:
#include <stdio.h>
int main() {
int number;
while (1) { // 无限循环
printf("请输入一个整数(输入0表示结束): ");
scanf("%d", &number);
/*
这里解释一下为什么会直接跳出while,而忽略if,我想是因为if是选择语句,
switch也是分支语句,而switch时是有case穿刺情况的
这里的主题是如何跳转控制语句!!!!
*/
if (number == 0) {
break; // 当用户输入0时,退出循环
}
// 否则,继续执行一些操作,这里以打印输入的数字为例
printf("你这一次输入的数字是: %d\n", number);
}
return 0;
}
但是break表示跳出循环时,只能跳出当前层次循环,如果存在嵌套循环,不会一并跳出外层循环。如:
for{
switch{
// 只能跳出switch, 不会跳出外层for
break;
}
}
如果需要跳出外层循环,建议使用goto语句。
break在表示跳转控制时,会跳出整个当前层次的循环(或switch),😒转而执行当前层次循环的下一条语句。
具体用代码来描述是这样的:
switch(){
case:
break; // 如果执行就跳到switch语句的下一条语句
}
// break跳到这里
// 整个switch后第一条语句
while(){
if(){
break; // 如果执行就跳到while语句的下一条语句
}
}
// break跳到这里
// 整个while后第一条语句
while(){
for(){
if(){
break; // 如果执行就跳到for语句的下一条语句
}
}
// break跳到这里
// 整个for后第一条语句,不会跳出外层while,外层while循环继续
}
continue
continue意为"继续,延续",也是常用的跳转控制关键字。
continue语句只能在循环中使用,表示结束当前层次的当前次循环,转而进行下一次循环。
当然continue也无法在嵌套循环中,跳出一次外层循环,如有需求可以使用goto语句。
一个简单的例子如下:
需求:
进行五次键盘录入整数,然后输出这些整数的和。
int count = 0; int sum = 0; int i; while (count < 5) { scanf("%d", &i); if (i == 0) { // 输入0就不必要累加了 continue; } sum += i; count++; } printf("sum = %d\n", sum);
尝试键盘录入数据:
1 1 1 1 1
按回车,结果就会输出sum的值是5。
如果录入数据:
1 0 0 1 1
这五个数,然后回车,结果是什么?
实际上程序不会结束,还会继续等待你做键盘录入。
那么为什么呢?
continue作为跳转关键字,具体跳到哪里呢?
continue在表示跳转控制时,并不会跳出到当前层次的循环外,而是跳到这一次循环语句的末尾。
具体用代码来描述是这样的:
while(){
if(){
continue; // 如果执行就跳到while语句的末尾
}
// while最后一条语句
// continue跳到这里,也就是接下来立刻要进行while的条件判断决定是否进行循环
}
while(){
for(){
if(){
continue; // 如果执行就跳到for语句的末尾
}
// while最后一条语句
// continue跳到这里。
// 但是对于for循环而言,跳到它一次循环的末尾,意味着接下来会执行循环控制语句,再进行条件判断,决定是否继续执行。
}
}
特别需要注意的是:
- while循环中的continue跳过循环,往往意味着会跳过循环控制语句,循环控制变量就不会改变。此时直接做条件判断,循环往往会继续执行。这就是上述输入数据0后,循环无法结束的原因。
- for循环中的continue跳过循环,意味着跳过循环体,但转而会执行循环控制语句,然后才会做条件判断决定循环是否继续执行。
总之,上面的一段代码,采用while-continue实现的程序,实际的功能描述是:
一定会键盘录入5个非零整数,然后求和!!!
思考题,把上面的代码中的while循环改写成for循环,如下:
#include <stdio.h> int main(void) { int sum = 0; int i; for (int count = 0; count < 5; count++) { scanf("%d", &i); if (i == 0) { // 输入0就不必要累加了 continue; } sum += i; } printf("sum = %d\n", sum); return 0; }
goto语句
break 和 continue 语句都是受限制的跳转控制语句:
- break用于跳出当前层次的switch语句或者循环语句,转而执行下一条语句。
- continue 语句则用于跳转到循环体的末尾,转而进行下一次循环。(具体会因while和for循环的不同有所不同)
如果希望能够更灵活的进行跳转,那么goto语句就是你的选择。
goto非常灵活,只要在函数体内部,可以跳转到任何标签的位置。也就是说它的唯一限制,就是它不能跨函数跳转,只能在函数内进行跳转。
那么如何使用goto语句呢?
首先你需要在,跳转的目的地语句定义一个标签(label):
label_name : statement;
标签的语法就相当于给函数体中的某条语句加上一个标志或标记,接下来我们就可以使用goto语句跳转到这个位置,并执行该语句。语法如下:
goto label_name;
一个使用goto语句,跳出外层循环的代码示例如下:
int main(void) {
int i, j;
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
printf("%$ ");
if (j == 3) {
goto end;
}
}
printf("\n");
}
end:
printf("\ngoto结束打印.\n");
return 0;
}
如果没有goto语句,程序实际会打印一个5行5列的"$"图案。但由于goto语句的存在,实际程序的执行结果是:
$ $ $ $
这说明goto语句改变了程序的流程,跳出了整个嵌套循环,直接跳到了end标签所在的位置。
goto的历史渊源以及最好不好使用goto
return语句
return意为"返回",它可以在函数体的任意位置使用,作用有两个:
- 标志着函数执行的结束,控制流程回到函数调用处。
- 如果函数声明了返回值,那么return语句还可以返回一个值给函数的调用者。
一些细节/注意事项:
- 在C语言中,很多编译器实现在函数有返回值类型时,也不会检查函数是否真正使用了
return
语句返回一个值。但这是非常危险的操作,调用一个有返回值但忘记给定返回值的函数将导致未定义行为。 - 函数的返回值类型应与函数定义中声明的返回类型相匹配。如果类型不匹配,可能会发生隐式类型转换,或者在某些情况下,编译器会报错。
- 如果函数的返回类型是
void
,则该函数不需要返回值。但是,你仍然可以在函数中使用不带值的return
语句来提前退出函数。 - 在
main
函数中,return
语句通常用来指示程序的退出状态。按惯例,返回0表示成功,非0值表示发生了错误或特定的退出原因。
- return后边可以是一个数值,也可以是一个表达式,如果是表达式则先执行表达式,在返回表达式的结果。
- return 后边也可以说明都没有,直接写return;这种写法适合返回类型是void的情况。void 可以表示什么数值都不返回。
- return 返回的值和函数返回的类型不一致,系统会自动将返回的值隐式转化为函数的返回类型。
int test()//int类型的函数 { return 3.141;//3.141是浮点型,而函数int(整型) } int main() { int n = test (); printf("%d",n);//若输出则只会输出3 return 0; }
4.return 语句执行后,函数就彻底返回,后边的代码不在执行
void test() { int n =0; scanf("%d",&n); printf("hehe"); if(n == 5)//表示如果输出的数字是5 则函数会直接返回 return; //并不会执行下边的语句 也就是并不会输出haha printf("haha"\n); } int main() { test(); return0; }
return和break看似类似,但区别非常大,break只是跳出距离它最近的一层循环,且跳出循环后,若循环后边还有语句,则会选择继续执行,而return如果遇见,则是直接停下,不会执行后边的语句。
5.如果函数中存在 if 等峰值的语句,则要保证每一种情况下都有return返回,否则会出现编译错误!
if 的一种选项中包含了 return 那么其他选项都要有return 否则会返回值出错。
就如图上图的例子,当n=5的时候会返回数字1,而当n并不等于5的时候呢? 所以当一个选项中出现了return 其他的选项中也要包含return 否则会出错!
6.return 0; 其实表示的是主函数main 执行结束的标志