环境:CLion2021.3;64位macOS Big Sur
文章目录
地表最强C语言系列传送门:
「地表最强」C语言(一)基本数据类型
「地表最强」C语言(二)变量和常量
「地表最强」C语言(三)字符串+转义字符+注释
「地表最强」C语言(四)分支语句
「地表最强」C语言(五)循环语句
「地表最强」C语言(六)函数
「地表最强」C语言(七)数组
「地表最强」C语言(八)操作符
「地表最强」C语言(九)关键字
「地表最强」C语言(十)#define定义常量和宏
「地表最强」C语言(十一)指针
「地表最强」C语言(十二)结构体、枚举和联合体
「地表最强」C语言(十三)动态内存管理,含柔性数组
「地表最强」C语言(十四)文件
「地表最强」C语言(十五)程序的环境和预处理
「地表最强」C语言(十六)一些自定义函数
「地表最强」C语言(十七)阅读程序
五、循环语句
5.1 while
5.1.1 getchar()在循环中的使用
首先明确一点,程序读取键盘键入的数据时,并非与键盘直接相连,在程序与键盘之间存在一个缓冲区,每次键盘键入的数据会放入缓冲区,程序每次会从缓冲区中取数据;若缓冲区为空,则程序会等待键盘键入数据到缓冲区,然后从缓冲区读取数据。回车会以’\n’的形式存在缓冲区中。
getchar()一次只能获取一个字符,与其对应的putchar()一次只能打印一个字符;与scanf()不同的是,scanf()遇到空格、回车、制表符的时候会停止读取数据,即使这三者后边仍然有数据,也无法读取;
而getchar()会将空格、回车、制表符也作为数据读入,直到遇到文件结束标志EOF(end of file:文件结束标志),或者键入ctrl z才停止读取数据。
下面举一个简单的例子,输入密码后要删除一个文件,确定的话输入’Y’,否则删除失败:
char password[20] = { 1 };//第一项为1,其余为0
printf("please input the psw:");
scanf("%s", password);//数组名本身就是地址,不需取地址&
//此时缓冲区有键入的数据和\n,scanf会取走数据,留下\n
printf("are u sure to delete?(Y/N):");
int ch = getchar();//此时缓冲区有\n,直接被getchar读取
if (ch == 'Y')
printf("sunccess\n");
else
printf("fail");
无论输入什么,其实都不会让你有确认的机会(即不会让你输入Y或者其他),会直接返回fail这个结果:
以上边的输入为例,说明一下为什么是这个结果以及怎么解决:
如图,当输入密码后,按下回车键,scanf()就开始从缓冲区中读取数据,此时缓冲区中的数据有 ‘a’ ‘s’ ‘d’ ‘4’ ‘5’ ‘6’ ‘4’ ‘6’ ‘.’ ‘.’ ‘\n’,\n就是按下的回车键,以这个形式存放在缓冲区。而scanf不会读取回车、空格、制表符,因此读取后,缓冲区会留下’\n’。当执行getchar()的时候,getchar()会读取这个’\n’,存入变量ch,显然不等于’Y’,因此执行else语句。
想要解决这个问题,我们需要将缓冲区的空格、回车、制表符都拿出来,以防影响后续输入。
char password[20] = { 1 };//第一项为1,其余为0
printf("please input the psw:");
scanf("%s", password);//数组名本身就是地址,不需取地址& 此时缓冲区有键入的数据和\n,scanf会取走数据,留下\n
//scanf不读取回车,空格,制表符
printf("please confirm the psw(Y/N):");
//清理缓冲区中的多个字符
int tmp = 0;
while ((tmp = getchar()) != '\n')
{
// 只要不是\n,就全部用getchar()接收,接收后什么的不用做。
// 当最后一次遇到\n时,已经被接受了并且传给了tmp,此时说明缓冲区已空;
}
int ch = getchar();//此时缓冲区有\n,直接被getchar读取
if (ch == 'Y')
printf("sunccess\n");
else
printf("fail");
改进后可以看到,测试的字符串中虽然有空格等字符,但仍然不影响结果:
5.1.2 break与continue
break:终止循环;
continue:跳过本次循环中,continue后边的代码,回到判断出判断是否继续循环。
测试:键入字符,若是数字,则打印,否则不打印:
int ch = 0;
while ((ch = getchar()) != EOF)
{
if (ch < '0' || ch > '9')
//break;
continue;
putchar(ch);
}
break结果:
continue结果:
对比上边的结果:当遇到非数字时,break直接跳出了while循环;而continue跳过了本次循环后边的部分,即printf()没有被执行,然后开始下一次循环。
5.2 for
首先两点建议:
- 不在循环体内调整循环变量:(极易产生错误)
- 循环控制变量采用前闭后开的写法
5.2.1 break与continue
前面在while中已经介绍过二者的用法,但是还是不够直观,这里采用一种更为直观的方法:
int i = 0;
for (i = 1; i <= 10; i++)
{
if (i == 5)
//break;
continue;
printf("%d ", i);
}
break结果,到5直接跳出循环:
continue结果,跳过5这一次循环,继续第六次循环:
5.2.2 for循环的其他形式
- 表达式的省略
1.1三个部分都可以省略,但是判断部分的省略会导致死循环,谨慎使用:
for (; ;)
{
printf("123 ");
}
运行结果:
1.2省略初始化:
int i = 0;
int j = 0;
for (; i < 3; i++)
{
for (; j < 3; j++)//j每次都没有初始化为0,而是保存为3
{
printf("12\n");
}
}
运行结果:
12
12
12
内层for循环中的j并没有初始化,在增加到3以后就一直保存为3,因此外层循环的i=1和2时不进入内层循环,故打印三次。
- 有两个循环变量
int i = 0;
int k = 0;
for (i = k = 0; k = 0; i++, k++)//判断语句恒为假,一次也不执行
{
k++;
}
运行结果:
是的,运行结果为空,其原因在于循环判断部分k=0为赋值运算而非==,k被赋值为0即假,因此不执行循环。
5.3 do while
特点:循环体至少执行一次
int i = 1;
do
{
if (i == 5)
//break;
continue;
printf("%d ", i);
i++;
} while (i <= 10);
break运行结果:1 2 3 4
continue运行结果:
结果是个死循环,因为当i == 5时进入if内部执行continue,continue跳过了i++,直接转到while处进行判断,再次进入if’内部…