这章的内容比较简单,但是非常重要。高中时候,我们就学过顺序结构,条件结构以及循环结构。然后在这章,又在原来的基础上进行了深化和补充。
先来看看这章的知识体系。
1 内容概览
2.1 空语句
空语句非常简单,仅仅只有一个分号,类似于Python中的pass。空语句并不是完全没有作用,常见以下两种情况。
- 不想有任何表达,但是客观上必须要有语句。比方说 ,在嵌入式开发中,最开始的延时就是通过while循环,循环体内一个空语句实现的。
- 暂时不想写东西,为了内容的完整性,补充一个空语句。以后可以补充。比方说,有的人喜欢先写一个基本的逻辑框架,再填写具体的语句。
2.2 表达式语句
2.3 代码块
代码块在此前的代码块作用域已经讲过了。再来举个例子。
int main() { int res = 10; printf("%d",res); { int res = 5; printf("%d",res); } system("pause"); }
比方说,在同一个函数中,有时候想用相同的变量名称,这个时候就可以采用代码块。这个与命名空间有异曲同工之妙。
2.4 if语句
if语句是最常见的条件判断语句,但是C语言中独特的switch语句,使得在某些特殊场合switch语句比传统的if语句更加美观(虽然时间复杂度基本一致)。if语句唯一需要注意的就是if和else的匹配问题:
在C语言中,当出现if和else得匹配出现歧义的时候,else子句从属于靠近它的不完整的if语句。
比方说:
int main() { int score = 66; if (score > 60) if (score > 70) printf("良好"); else printf("不及格"); system("pause"); }
在上面的例子中,就出现了else和if的匹配问题,所以按照所谓的就近原则,会输出不及格,而不是无输出。
2.5 循环语句
2.5.1 while语句
while语句与do语句非常类似,不过while语句是先判断循环条件,再执行循环体,而do语句恰好相反,是先执行循环体,然后再判断循环条件。
while语句相比for语句,当循环的判断条件或者执行情况比较复杂的时候,往往while语句比较占优势。但是时间复杂度基本一致。比方下面这个问题(出自leetcode455.分发饼干):
- 分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
如果采用while循环:
int cmp(int* a, int* b) { return *a - *b; } int findContentChildren(int* g, int gSize, int* s, int sSize){ qsort(g, gSize, sizeof(int), cmp); qsort(s, sSize, sizeof(int), cmp); int res = 0; int g_left = 0; int s_left = 0; while(g_left < gSize && s_left < sSize) { if(g[g_left] <= s[s_left]) { g_left++; s_left++; res++; } else { s_left++; } } return res; }
for循环的具体代码如下:
int cmp(int* a, int* b) { return *a - *b; } int findContentChildren(int* g, int gSize, int* s, int sSize){ qsort(g, gSize, sizeof(int), cmp); qsort(s, sSize, sizeof(int), cmp); int res = 0; int g_left = 0; int s_left = 0; for(;g_left < gSize && s_left < sSize;s_left++) { if(g[g_left] <= s[s_left]) { g_left++; res++; } } return res; }
其中的qsort是快速排序语句,用来给两个数组排序。
从上面的例子可以看出,循环语句中while和for并没有根本上的优劣之分,只是个人使用习惯上的区别,当循环的情况比较复杂的时候,优先考虑while语句。
2.5.2 for语句
for语句一般是在循环步骤比较明确的时候,比较常用,比如需要求一个数组各个元素的和:
int main() { int a[10]; int res = 0; //数组初始化 for (int i = 0; i < 10; i++) { a[i] = i+1; } //求和 for (int j= 0; j < 10; j++) { res += a[j]; } printf("a数组各元素之和为%d\n",res); system("pause"); }
而此时如果用while语句实现,将会是这样:
int main() { int a[10]; int res = 0; int i = 0, j = 0; //数组初始化 while (i < 10) { a[i] = i + 1; i++; } //求和 while (j < 10) { res += a[j]; j++; } printf("a数组各元素之和为%d\n",res); system("pause"); }
从这个例子就可以看出,for循环可以直接将循环体放在花括号里,增加了代码的可读性。
2.5.3 do语句
do语句一般适用于先执行循环体,再进行条件判断,比如输入一个数,判断是奇数还是偶数:
int main() { int a; do { int b; /*要继续吗?*/ printf("请输入一个整数:"); scanf("%d", &b); if (b % 2) puts("这个整数是奇数。"); else puts("这个整数是偶数。"); printf("要重复一次吗?[yes……0/No……9]:"); scanf("%d", &a); } while (a == 0); return(0); }
可以看出,在这个情况下,我们必须先输入数据,执行一次,由用户决定是否继续执行数据处理,这个时候do语句就有了得天独厚的优势。
最后就是break和continue语句,这两条语句在以上的三种循环语句中都适用,break用于跳出本层循环,而continue用于跳过此次循环体的后续语句,直接进行下一轮循环。
三种循环语句运行的流程图如下所示(Y表示满足循环条件,N表示不满足循环条件):
2.6 switch语句
在有的教材中,switch语句又叫开关语句,这样的称呼也行,应该是属于直译,***从逻辑上说,Switch语句和多个if语句并无二致。***当然,在很多同行中称,多个if语句更好维护,因为以后若是有人在此基础上进行开发,增加条件的时候只需要增加if语句就行,在Switch中,不仅仅是增加case这么简单,因为要求判断的表达式必须为整型值。
当然Switch有其独特的优势,就是形式上更加美观整齐。比方说在我们以前的教材中,成绩的评级问题,就可以用Switch语句很好地解决。
int score = 88; switch (score / 10) { case 10: case 9: case 8: printf("优秀\n"); break; case 7: printf("良好\n"); break; case 6: printf("及格\n"); break; default: printf("不及格\n"); break; }
如果我们用if语句,就写成了下面这样:
int score = 88; int stage = score / 10; if (stage == 10 || stage == 9 || stage == 8) { printf("优秀\n"); } else if (stage == 7) { printf("良好\n"); } else if (stage == 6) { printf("及格\n"); } else { printf("不及格\n"); }
显然在这个时候,Switch显得十分优雅,但是Switch使用的时候有个问题,就是要谨慎地使用break语句。如果不使用,就会在执行完该case之后,直接顺延到下一个case的语句中,直到运行完所有语句或者中途遇到break。
- 还有一点,就是如果所以的情况都不符合,则会运行default子句,所以一般情况下,都需要默认加上default子句,类似于if语句中的else。
2.7 goto语句
应该说,goto语句是最不常用的语句,所以在其他很多语言中,是没有goto语句的,原因有二:
3. goto语句破坏了原本程序的空间连续性。
4. 无论在什么情况下,goto语句总是可以找到替代方案。尽管有时候替代方案并不完美,但是无伤大雅。
书中提到了一种看起来需要goto语句的情况,就是在多层的循环中,需要break的时候,并不是非常方便。例如这样的情况,我需要检查一个数组中的数是否有序,可以采用冒泡排序的思想,只要前面的数大于后面的数,直接跳出,但是在冒泡算法中,有两层循环,程序是这样的:
int main() { int rank[10] = {4,0,5,3,6,2,5,1,15,17}; bool res = true; for (int i = 0; i < 10; i++) { for (int j = i + 1; j < 10; j++) { if (rank[i] > rank[j]) { res = false; goto print_res; } } } print_res: if (res == true) { printf("该数组是有序的!"); } else { printf("该数组是无序的!"); } system("pause"); }
替代方案有两种:
- 在每层循环中都加上条件判断。
- 将所有循环放在一个函数中,借用return有语句终止循环。
方案一实现的代码如下:
int main() { int rank[10] = {4,0,5,3,6,2,5,1,15,17}; bool res = true; for (int i = 0; i < 10 && res == true; i++) { for (int j = i + 1; j < 10 && res == true; j++) { if (rank[i] > rank[j]) { res = false; break; } } } if (res == true) { printf("该数组是有序的!"); } else { printf("该数组是无序的!"); } system("pause"); }
显然会打印输出:该数组是无序的!
当然,在上面的例子中,break语句不是必须的,因为当res = false的时候,已经不可能继续执行下次循环,所以循环会终止,将继续执行后面的语句。
方案二实现的代码如下:
bool rank_fun(int *num, int numsize) { for (int i = 0; i < 10; i++) { for (int j = i + 1; j < 10; j++) { if (num[i] > num[j]) { return false; } } } return true; } int main() { int rank[10] = {4,0,5,3,6,2,5,1,15,17}; bool res = rank_fun(rank,10); if (res == true) { printf("该数组是有序的!"); } else { printf("该数组是无序的!"); } system("pause"); }
同样会打印输出:该数组是无序的!
本章的内容到这里就结束了,一起加油,有什么想法可以在下方留言,大家一起讨论!
------------------------------------------------------------END----------------------------------------------------------------