c语言修炼秘籍【第一章】分支与循环语句
【心法】
【第零章】c语言概述
【第一章】分支与循环语句
【第二章】函数
【第三章】数组
【第四章】操作符
【第五章】指针
【第六章】结构体
【第七章】const与c语言中一些错误代码
【禁忌秘术】
【第一式】数据的存储
【第二式】指针
【第三式】字符函数和字符串函数
【第四式】自定义类型详解(结构体、枚举、联合)
【第五式】动态内存管理
【第六式】文件操作
【第七式】程序的编译
文章目录
前言
在处理现实中的问题时,我们总会遇到需要作出选择或是需要重复多次的完成一种事件。
你去图书馆学习,需要日复一日的坚持,每多学一天,你的代码功力就能+1,当你的代码功力,达到了某一程度,你就能够迎娶白富美,走上人生巅峰。
为了实现这些目的,c语言提供了分支语句,用以实现选择;提供循环语句,用以实现重复多次的机械活动;
一、什么是语句?
c语句可以分为以下五类:
- 表达式语句
- 函数调用语句
- 控制语句
- 复合语句
- 空语句
分支、循环语句属于控制语句。
控制语句用于控制程序的执行流程,以实现程序的各种结构方式,它们由各种特定的语句定义符构成;c语言中有九种控制语句:
- 条件判断语句,也叫分支语句:if语句,switch语句;
- 循环执行语句:for语句,while语句,do…while语句;
- 转向语句:break语句,continue诗句,return语句,goto语句;
二、分支语句
周末时,原计划你打算去图书馆学习,但是你室友却叫你一起游戏,这时你就需要进行选择;是去图书馆学习,毕业后,年薪300k+,还是和室友打游戏,毕业后回家种地。这就是选择。
1.if语句
if语句的语法结构:
表达式为真时,执行语句;
0为假,非0为真;
if(表达式)
语句;
if(表达式)
语句;
else
语句;
// 多分支
if(表达式1)
语句;
else if(表达式2)
语句;
else
语句;
上述的语句可以为多条,使用代码块{}
示例代码
int main()
{
int input = 0;
printf("请输入评分:>");
scanf("%d", &input);
if (input < 3)
{
printf("town in go!\n");
}
return 0;
}
int main()
{
int input = 0;
printf("请输入评分:>");
scanf("%d", &input);
if (input < 3)
{
printf("town in go!\n");
}
else
{
printf("正常人\n");
}
return 0;
}
int main()
{
int input = 0;
printf("请输入评分:>");
scanf("%d", &input);
if (input < 3)
{
printf("town in go!\n");
}
else if(input > 13)
{
printf("MVP\n");
}
else
{
printf("正常人\n");
}
return 0;
}
1.1悬空else
下面代码会输出什么呢?
int main()
{
int a = 0;
int b = 1;
if(a == 1)
if(b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}
运行结果:
可以看到,该程序没有任何输出;
从代码的对齐可以算出,该代码想要的逻辑是,当a==1
为真时,继续判断b==2
,若为真,输出hehe
,若为假,则不输出;当a==
为假时,输出haha
,所以该程序的目标输出应该是haha
。
但事实上,程序什么也没输出,所以上述的逻辑是错误的,这里就是遇到了悬空else的问题。
c语言中,如果没有{}
来对if
else
进行匹配的话,else
会与它上面的最近的if进行匹配。上面代码中的else实际上是与if(b==2)
进行的匹配。
实际代码逻辑如下
int main()
{
int a = 0;
int b = 1;
if(a == 1)
if(b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}
那么要如何修改,才能实现上面的逻辑呢?
int main()
{
int a = 0;
int b = 1;
if(a == 1)
{
if (b == 2)
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
}
运行结果:
1.2if语句的书写形式
对比以下代码
// 代码1
if (condition)
{
return x;
}
return y;
// 代码2
if (condition)
{
return x;
}
else
{
return y;
}
int num = 3;
// 代码3
if (num == 5) // 常见错误,num = 5;将5赋值给num,导致该表达式恒为真,编译器不会提示
{
printf("hehe\n");
}
// 代码4
if (5 == num) // 当写成5 = num时,会出现编译错误,5不能作为左值,编译器会提示
{
printf("hehe\n");
}
代码2和代码4更好。
1和2、3和4两组代码的逻辑与实现的功能是相同的,但代码2和代码4的可读性更高,逻辑更清晰,更不容易出错。
练习
判断一个数是否为奇数
// 返回1 -- num是奇数;返回0 -- num是偶数
int idOdd(int num)
{
if (num % 2) // 奇数模2运算,结果为1
{
return 1;
}
else
{
return 0;
}
}
2.switch语句
输入一个1-7之间的数字,输出它代表星期几,
输入1,输出星期一
…
要实现这个功能虽然可以使用if
else if
…else
语句,但形式太复杂了,所以就有了switch
语句。
switch语句的语法结构
switch(整型表达式)
{
语句项;
}
// 什么是语句项?
// 是一些case语句
case 整型常量表达式:
语句;
示例代码
int main()
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
case 4:
printf("Thursday\n");
break;
case 5:
printf("Friday\n");
break;
case 6:
printf("Saturday\n");
break;
case 7:
printf("Sunday\n");
break;
default:
printf("day error\n");
break;
}
return 0;
}
2.1switch中的break
事实上,switch语句并不能直接实现分支,它需要借助break
语句
示例代码,当我们输入2时,switch语句,会从case 2:开始顺序执行剩余的语句。
int main()
{
int day = 0;
printf("请输入:>");
scanf("%d", &day);
switch (day)
{
case 1:
printf("Monday\n");
case 2:
printf("Tuesday\n");
case 3:
printf("Wednesday\n");
case 4:
printf("Thursday\n");
case 5:
printf("Friday\n");
case 6:
printf("Saturday\n");
case 7:
printf("Sunday\n");
default:
printf("day error\n");
}
return 0;
}
运行结果:
代码中遇到break
语句时,会跳出当前的代码块。在switch中实现将语句列表分成不同的分支部分。
利用break的特性,修改上述的代码,当输入1-5时,输出Workday;输入6-7时,输出Weekday;
int main()
{
int day = 0;
printf("请输入:>");
scanf("%d", &day);
switch (day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("Workday\n");
break;
case 6:
case 7:
printf("Weekday\n");
break;
default:
printf("day error\n");
break;
}
return 0;
}
运行结果:
2.2default语句
在switch选择分支的过程中,可能出现表达式的值与所有分支的case标签都不匹配的情况,出现这种情况时会发生什么呢?
并不会发生什么,仅仅是所有的语句都被跳过,程序并不会中止,也不会报错。
但一般我们不应该忽略这些不匹配的情况,应该在语句列表中加入一条default
子句,
标签default:
可以出现在任何case:
能出现的地方。表示,当所有的case:
标签都不匹配时,default
子句将会执行。
注:一个switch语句中只能出现一条default子句,但它可以出现在语句列表的任何位置。
上一个代码输入数字不在1-7之间时
练习
下面的代码会输出什么?
int main()
{
int m = 2;
int n = 1;
switch (n)
{
case 1:
m++;
case 2:
n++;
case 3:
switch (n) // switch语句允许嵌套使用
{
case 1:
n++;
case 2:
m++;
n++;
break;
}
case 4:
m++;
break;
default:
break;
}
printf("m == %d, n == %d\n", m, n);
return 0;
}
m初始化为2,n初始化为1
进入第一个switch语句,与case 1:匹配
执行m++,继续顺序执行 – m == 3
执行n++,继续顺序执行 – n == 2
进入第二个switch语句,与case 2:匹配
执行m++;n++,遇到break;跳出当前switch语句,继续顺序执行 – m == 4, n == 3
m++,遇到break;跳出当前switch语句 – m == 5
输出结果应为 m == 5, n == 3
运行结果:
三、循环语句
1.while循环
对于if语句:
if(条件)
语句;
当条件满足时,执行一次。但生活中有许多事情是需要我们重复做多次的。
c语言引入了while
语句,用于执行循环。
while 的语法结构
while(条件)
循环语句;
while语句的执行流程:
比如,现在需要输出1-10之间的数字
int main()
{
int i = 0;
while (i++ < 10)
{
printf("%d ", i);
}
return 0;
}
1.1while语句中的break
示例代码,会输出什么?
int main()
{
int i = 0;
while (i++ < 10)
{
printf("%d ", i);
if (5 == i)
{
break;
}
}
return 0;
}
运行结果:
可以看到,在while语句中遇到break
时,就会停止后续的所有循环,直接终止循环。跳出当前while语句的作用域。
所以,while中的break
是用来永久终止循环的。
1.2while语句中的continue
示例代码,下面的代码会输出什么?
int main()
{
int i = 0;
while (i++ < 10)
{
if (5 == i)
{
continue;
}
printf("%d ", i);
}
return 0;
}
运行结果:
可以看到,在while语句中遇到continue
时,就会跳过当前的这次循环,本次循环的后续代码不会执行,直接跳转到while语句的判断部分。进行下一次循环的入口判断
练习
下面的代码是什么意思呢?
// 代码1
int main()
{
char ch = 0;
while ((ch = getchar()) != EOF)
{
;
}
return 0;
}
// 代码2
int main()
{
char ch = 0;
while ((ch = getchar()) != EOF)
{
if (ch < '0' || ch > '9')
{
continue;
}
putchar(ch);
}
return 0;
}
代码1可用来清空缓冲区中的数据
代码2是要只打印数字字符,跳过其他字符
2.for循环
有了while循环,为什么还要for循环呢?
我们先来看看for循环的语法
for(exp1; exp2; exp3)
循环语句;
exp1是初始化部分,用于初始化循环变量
exp2是条件判断部分,用于判断循环什么时候终止
exp3是调整部分,用于循环条件调整
打印1-10之间的数字
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
printf("%d ", i);
}
return 0;
}
for循环的执行流程:
下面对比while循环和for循环
int main()
{
int i = 0;
// while循环
i = 1; // 初始化循环变量
while (i <= 10) // 循环判断
{
printf("%d ", i);
i++; // 调整部分
}
// for循环
for (i = 1; i <= 10; i++)
{
printf("%d ", i);
}
return 0;
}
可以看到,while循环中,依旧有循环变量初始化、条件判断、变量调整这三部分。但因为循环的结构形式不同,三个部分之间间隔较远,查找、修改时不够集中。
所以,for循环的风格更优,使用更频繁。
2.1for循环中的break和continue
for循环中使用的break和continue的功能和while循环中使用的功能是一样的。
示例代码
代码1的输出,仍是1 2 3 4
代码2的输出,仍是1 2 3 4 6 7 8 9 10
int main()
{
int i = 0;
// 代码1
for (i = 0; i < 10; i++)
{
if (5 == i)
{
break;
}
printf("%d ", i);
}
// 代码2
for (i = 0; i < 10; i++)
{
if (5 == i)
{
continue;
}
printf("%d ", i);
}
return 0;
}
2.2for循环的控制变量
对于for循环中的控制变量的使用,有两点建议
- 不要在for循环内部修改控制变量,防止for循环失去控制
- 循环控制变量的取值范围采用
前闭后开
的写法
重复执行十次的for循环
int i = 0;
// 前闭后开
for(i = 0; i < 10; i++)
{
;
}
// 前闭后闭
for(i = 0; i <= 9; i++)
{
;
}
可以看出,使用前闭后开的写法的可读性更高,能够一眼看出该循环需要执行十次
2.3一些for循环的变种
变种示例,它们分别会执行多少次呢?
int main()
{
int i = 0;
int count1 = 0;
// 代码1 -- 将初始化、判断、调整部分都省略
for (; ; )
{
//printf("hehe\n");
count1++;
}
// 代码2 -- for循环进行嵌套
i = 0;
int j = 0;
int count2 = 0;
for (i = 0; i < 10; i++)
{
for(j = 0; j < 10; j++)
{
//printf("hehe\n");
count2++;
}
}
// 代码3 -- for循环进行嵌套,只省略初始化部分
i = 0;
j = 0;
int count3 = 0;
for (; i < 10; i++)
{
for (; j < 10; j++)
{
//printf("hehe\n");
count3++;
}
}
// 代码4 -- 用多余一个的变量来控制for循环
int x = 0;
int y = 0;
int count4 = 0;
for (x = 0, y = 0; x < 2 && y < 5; ++x, y++)
{
//printf("hehe\n");
count4++;
}
printf("count2 == %d\ncount3 == %d\ncount4 == %d\n", count2, count3, count4);
return 0;
}
代码1中,没有判断条件,也就是会无条件执行,是死循环;
代码2中,对for循环进行了嵌套,内部循环会执行10次,外部循环会执行10次,总共执行100次循环语句;
代码3中,没有对控制循环变量进行初始化,内部循环在第一次执行10次之后,变量j
一直是10,不会执行,所以总共执行了10次循环语句;
代码4中,条件判断部分由x < 2
和y < 5
组成,且它们之间的逻辑是与&&
,需要两者都满足才执行循环语句,当执行完第二次时,x == 2,不满足条件,跳出循环,此时,仅执行了2次循环语句;
运行结果:
练习
下面的循环,循环了多少次?
int main()
{
int i = 0;
int k = 0;
int count = 0;
for (i = 0, k = 0; k = 0; i++)
{
k++;
count++;
}
printf("count == %d\n", count);
return 0;
}
0次
注意,该循环的判断部分为赋值语句k=0
,始终为假,并不会进入循环体,而是会直接跳出。
运行结果:
3.do…while循环
do…while循环的语法
do
循环语句;
while(exp);
do…while循环的执行流程:
注:do…while循环至少都会执行一次
其中使用的break
和continue
的作用和另两个循环的用法相同
4.循环语句的练习
1.计算n的阶乘
// 计算阶乘
int factorial(int num)
{
int ret = 1;
// 0的阶乘为1
if (0 == num)
{
return 1;
}
else
{
for (; num >= 1; num--)
{
ret *= num;
}
return ret;
}
}
#define PRINT(num) printf("%d的阶乘 == %d\n", num, factorial(num))
int main()
{
PRINT(5);
return 0;
}
2.计算1! + 2! + ··· + 10!
//计算阶乘和 -- 计算 1! + 2! + ··· + n!
//在计算n!的过程中,实际上能够提到,它前面所有数字的阶乘的值
int factorial_sum(int n)
{
int sum = 0;
int factorial = 1;
int i = 0;
for (i = 1; i <= n; i++)
{
factorial *= i; // factorial为i!
sum += factorial;
}
return sum;
}
#define PRINT(n) printf("%d的阶乘和 == %d\n", n, factorial_sum(n))
int main()
{
PRINT(10);
return 0;
}
3.在一个有序数组中查找具体的某个数字n(二分查找)
// 二分查找
// arr是保存数据的数组,num是要查找的数字,sz为数组的元素个数
// 找到返回下标,未找到返回-1
int binary_search(int* arr, int num, int sz)
{
int left = 0;
int right = sz - 1;
int mid = (left + right) / 2;
while (left < right)
{
if (arr[mid] < num) // num只能出现在arr[mid]的右边
{
left = mid + 1;
mid = (left + right) / 2;
}
else if (arr[mid] > num) // num只能出现在arr[mid]的左边
{
right = mid - 1;
mid = (left + right) / 2;
}
else // 找到了
{
return mid;
}
}
// 未找到
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(int);
int input = 0;
printf("请输入要查找的数字:>");
scanf("%d", &input);
int ret = binary_search(arr, input, sz);
if (ret == -1)
{
printf("数组中没有%d这个数字\n", input);
}
else
{
printf("已找到,%d在数组中的下标为%d\n", input, ret);
}
return 0;
}
当要查找的n == 5时,查找过程如下
此时arr[mid] == 5,所以找到了目标数字,结束查找。
当要查找的n == 100时,查找过程如下
第五次查找时,left > right,查找失败,数组中没有这个数字
4.演示从两头向中间汇聚,显示字符串。如”
hello world
“,首先显示"***********
“,再显示"h*********d
”,以此类推
#include <stdlib.h>
#include <string.h>
// 效果演示 -- 要输出字符串 "hello world"
// 第一次 -- "***********"
// 第二次 -- "h*********d"
// 第三次 -- "he*******ld"
// 第四次 -- "hel*****rld"
// 第五次 -- "hell***orld"
// 第六次 -- "hello*world"
// 第七次 -- "hello world"
// arr为要输出的字符串,sz为该字符串的长度
void print_str(const char* arr, int sz)
{
char* tmp = (char*)calloc(sz + 1, sizeof(char)); // sz为字符串的长度,不包括字符串结尾的\0
if (tmp == NULL)
{
perror("calloc");
return;
}
memset(tmp, '*', sz);
printf("%s\n", tmp);
int left = 0;
int right = sz - 1;
while (left <= right)
{
tmp[left] = arr[left];
tmp[right] = arr[right];
printf("%s\n", tmp);
left++;
right--;
}
free(tmp);
tmp = NULL;
}
int main()
{
char arr[] = "hello world";
int sz = strlen(arr);
print_str(arr, sz);
return 0;
}
可以print_str进行修改,使得每次输出之前,都先休眠0.5s,并清空屏幕。
while (left <= right)
{
Sleep(500); // 休眠0.5s
system("cls"); // 清屏
tmp[left] = arr[left];
tmp[right] = arr[right];
printf("%s\n", tmp);
left++;
right--;
}
5.模拟用户登录情景,只能登录三次。(只能输入三次密码,如果密码正确,则提示登录成功;如果三次,均密码错误,退出程序)
int main()
{
char psw[] = "123456";
char input[30] = { 0 };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("请输入密码:>");
scanf("%s", input);
if (!strcmp(input, psw)) // 库函数strcmp(),用于比较两个字符串是否相同,相同返回0
{
printf("密码正确,登录成功\n");
break;
}
else
{
if(i == 2)
printf("退出程序\n");
else
printf("密码错误(你还有%d次机会)\n", 3 - i - 1);
}
}
return 0;
}
5.猜数字
猜一个1-100之间的数字,猜大或猜小,程序会给出提示
#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 input = 0;
int num = (rand() % 100) + 1; // 生成随机数
//printf("%d\n", num);
while (1)
{
printf("请猜数字:>");
scanf("%d", &input);
if (input < num)
{
printf("猜小了\n");
}
else if (input > num)
{
printf("猜大了\n");
}
else
{
printf("猜对了,数字是%d\n", num);
break;
}
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); // 设置随机数种子
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
//printf("猜数字\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误\n");
break;
}
} while(input);
return 0;
}
四、goto语句
c语言中提供了能够随意跳转的goto语句和标记跳转位置的符号。
理论上,不使用goto语句,也能很容易写出代码。并且,不推荐使用goto语句。因为,它可能会导致代码可读性变差,容易出现逻辑错误。
但某些情况下,goto语句也能用到。
比如,需要跳出多重循环时。
for(..)
{
for(..)
{
for(..)
{
for(..)
{
for(..)
{
if(disastar)
goto error;
}
}
}
}
}
error:
if(disastar)
// 处理错误情况
在上面这段代码中,如果不用goto
语句,则需要多个break
才能实现。
一个有趣的小玩意
一个关机程序
#include <stdio.h>
#include <windows.h>
int main()
{
char input[100] = { 0 };
// -s 关闭计算机 -t 设置时间 60 秒数
system("shutdown -s -t 60"); // 60秒后自动关机
printf("电脑将在60s后自动关机\n");
again:
printf("只有输入\"我是town in go\",才能取消关机\n");
printf("输入:>");
gets(input);
if (!strcmp(input, "我是town in go"))
{
system("shutdown -a");
}
else
goto again;
return 0;
}
总结
本章介绍了分支和循环语句,以及使用时可能会遇到的一些问题,并给出了一些使用示例。