循环语句
一、while循环
二、for循环
三、do...while循环
四、循环语句练习
getchar 与 putchar
总结
if 语句:当条件满足的情况下,if语句后的语句执行,否则不执行。但是这个语句只会执行一次。
生活中很多的实际的例子是:同一件事情我们需要完成很多次。
C语言中给我们引入了循环语句。
C语言中有三种循环语句:
- while循环
- for循环
- do...while循环
1、语法结构
while(表达式)
{
循环语句;
}
2、while循环的执行流程
3、while循环的使用
打印1~100之间的整数
#include
int main()
{
//打印1~100
int i = 1;
while (i <= 100)
{
printf("%d ", i);
//每打印10个数换行
if (i % 10 == 0)
{
printf("\n");
}
i++;
}
return 0;
}
运行结果:
4、while循环中的break和continue
(1)break
break在循环中的作用:
在循环中只要遇到break,就终止循环(跳出循环)。
while循环遇到break
#include
int main()
{
int i = 1;
while (i <= 100)
{
if (5 == i)
break;
printf("%d ", i);
i++;
}
return 0;
}
运行结果:
代码分析:
当 i 等于5时程序执行break语句,while循环中遇到break,就会跳出循环不会再执行break后的语句。所以最后只打印了 1 2 3 4
(2)continue
continue在循环中的作用就是:
continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接跳转到循环语句的判断部分。进行下一次循环的入口判断。
while循环遇到continue
#include
int main()
{
int i = 1;
while (i <= 100)
{
if (5 == i)
continue;
printf("%d ", i);
i++;
}
return 0;
}
运行结果:
代码分析:
当 i 等于5时程序执行continue语句,while中遇到continue,本次循环中continue后边的代码不会再执行,直接跳转到while语句的判断部分。但是i++;这条语句在continue后面,所以 i 的值不会再发生变化,i 的值一直都是5。所以程序进入了死循环,最终打印 1 2 3 4....(4的循环)一直循环。
1、语法结构
for(表达式1;表达式2;表达式3)
循环语句;
每个表达式的作用:
- 表达式1:表达式1为初始化部分,用于初始化循环变量的。
- 表达式2:表达式2为条件判断部分,用于判断循环时候终止。
- 表达式3:表达式3为调整部分,用于循环条件的调整。
2、for循环的执行流程
3、for循环的使用
打印1~10之间的整数
#include
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
printf("%d ", i);
}
return 0;
}
运行结果:
4、for循环中的break和continue
(1)break
#include
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (5 == i)
{
break;
}
printf("%d ", i);
}
//打印完以后换行
printf("\n");
return 0;
}
运行结果:
代码分析:
当 i 等于5时程序执行break语句,for循环中遇到break,就会跳出循环不会再执行break后的语句。所以最后只打印了 1 2 3 4
(2)continue
#include
int main()
{
int i = 0;
for (i = 1; i <= 10; i++)
{
if (5 == i)
{
continue;
}
printf("%d ", i);
}
//打印完以后换行
printf("\n");
return 0;
}
运行结果:
代码分析:
当 i 等于5时程序执行continue语句,for循环中遇到continue,本次循环中continue后边的语句不会再执行,直接跳转到for循环中的表达式3(i++)再执行表达式2进行判断(满足条件进入循环)。执行表达式3以后 i 的值变成了6(6不等于5)循环中 if 的条件不成立,程序往下执行输出6,再执行表达式3(i++)再执行表达式2直到表达式2的条件不成立,程序跳出循环。最后输出 1 2 3 4 6 7 8 9 10。
5、for语句的循环控制变量
建议:
- 不可在for 循环体内修改循环变量,防止 for 循环失去控制。
- 建议for语句的循环控制变量的取值采用“前闭后开区间”写法。
(1)如果在for循环内修改循环变量,会造成死循环
#include
int main()
{
int i = 0; //不好的习惯
for (i = 1; i <= 10; i++)
{
i = 0;
printf("hello\n");
}
return 0;
}
运行结果
(2)for语句的循环控制变量的取值采用“前闭后开区间”
#include
int main()
{
int i = 0;
//代码1
printf("代码1:\n");
for (i = 0; i < 10; i++)
{
printf("hello\n");
}
printf("-------------------\n");
//代码2
printf("代码2:\n");
for (i = 0; i <= 9; i++)
{
printf("hello\n");
}
return 0;
}
运行结果:
代码1采用了“前闭后开区间”写法,代码2前后都是闭区间,代码1与代码2的执行结果相同,但是代码1的可读性更好一些。
这里只是建议写成将循环的控制变量的取值写成“前闭后开区间”,根据个人习惯使用。
6、for循环的变种
#include
int main()
{
for (;;)
{
printf("hello\n");
}
return 0;
}
- for循环的初始化(表达式1)、判断(表达式2)、调整(表达式3)三个部分可以省略。
- 判断部分(表达式2)如果省略,意味着判断恒为真,就构成了死循环
如果条件允许,不建议省略for循环的3个表达式。
如果省略了for循环的表达式,得到的可能不是预期的结果
#include
int main()
{
int i = 0;
int j = 0;
for (i=0;i<3;i++)
{
for (j = 0; j < 3; j++)
{
printf("hello\n");
}
}
return 0;
}
运行结果:
代码分析:
for循环是可以嵌套的。i = 0时执行里面的for循环输出3个hello;i = 1时执行里面的for循环输出3个hello;i = 2时执行里面的for循环输出3个hello,然后跳出循环。3*3=9,最后打印了9个hello。
如果省略两个for循环中的表达式1,结果会是怎样的呢?
#include
int main()
{
int i = 0;
int j = 0;
for (;i<3;i++)
{
for (; j < 3; j++)
{
printf("hello\n");
}
}
return 0;
}
运行结果:
代码分析:
- i = 0 时进入循环执行里面的for循环(j=0 输出一个hello,再执行j++,再判断,直到 j 为3时跳出循环)输出3个hello。
- 当i = 1时进入循环,此时里面的for循环 j 没有初始化(此时值为3),j 值为3跳出里面的循环。程序再执行i++。当 i = 2时进入循环,此时里面的for循环 j 没有初始化(此时值为3),j 值为3跳出里面的循环。最后程序只输出了3个hello。
for循环中,表达式1、表达式2、表达式3,可以同时写多个变量。
#include
int main()
{
int i = 0, j = 0;
for (i = 0, j = 0; i < 3 && j < 5; ++i, j++)
{
printf("hello\n");
}
return 0;
}
了解了for循环以后,可以发现for循环与while循环中都存在三个必须条件(三个表达式)。但由于语法结构,使得while循环中的三个表达式太过于分离,这样在修改代码的时候就不太方便,而for循环中的三个表达式是放在一起的。所以for循环的语法结构更好一些,使用频率也是最高的。
做一道题,看看是否真的理解了for循环
下面这段代码循环了几次?
#include
int main()
{
int i = 0;
int k = 0;
for (i = 0, k = 0; k = 0; i++, k++)
{
k++;
printf("hello\n");
}
return 0;
}
因为表达式2(判断条件)是k赋值为0,所以循环的条件为假不会进入到循环里,所以循环次数为0。
1、语法结构
do
循环语句;
while(表达式);
2、do...while循环的执行流程
3、do...while循环的使用
打印1~10之间的整数
#include
int main()
{
int i = 1;
do
{
printf("%d ", i);
i++;
} while (i <= 10);
return 0;
}
运行结果:
4、do...while循环中的break和continue
(1)break
#include
int main()
{
int i = 1;
do
{
if (i == 5)
break;
printf("%d ", i);
i++;
} while (i <= 10);
printf("\n");
return 0;
}
运行结果:
代码分析:
当 i 等于5时程序执行break语句,do...while循环中遇到break,就会跳出循环不会再执行break后的语句。所以最后只打印了 1 2 3 4
(2)continue
#include
int main()
{
int i = 1;
do
{
if (i == 5)
continue;
printf("%d ", i);
i++;
} while (i <= 10);
printf("\n");
return 0;
}
运行结果:
代码分析:
当 i 等于5时程序执行continue语句,do...while中遇到continue,本次循环中continue后边的代码不会再执行,直接跳转到do...while语句的判断部分。但是i++;这条语句在continue后面,所以i 的值不会再发生变化,i 的值一直都是5。所以程序进入了死循环,最终打印 1 2 3 4.....(4的循环)一直循环。
5、do...while循环的特点
循环至少执行一次,使用的场景有限,所以不是经常使用。
1、计算n的阶乘
- 输入 n
- 产生1~n的数字,累积乘在一起
- 打印出来
#include
int main()
{
int n = 0;
int i = 0;
int ret = 1;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
ret *= i;
}
printf("%d\n", ret);
return 0;
}
这里的n不要输入的太大,int类型存储的数据是有限的。
2、计算1~10的阶乘和
#include
int main()
{
int i = 0;
int j = 0;
int t = 1;
int sum = 0;
for (i = 1; i <= 10; i++)
{
t = 1;
for (j = 1; j <= i; j++)
{
t = t * i;
}
sum += t;
}
printf("%d\n", sum);
return 0;
}
这种算法程序执行效率太低
代码优化:
#include
int main()
{
int i = 0;
int t = 1;
int sum = 0;
for (i = 1; i <= 10; i++)
{
t *= i;
sum += t;
}
printf("%d\n", sum);
return 0;
}
3、在一个有序数组中查找具体的某个数字n。
方法一(顺序查找):
#include
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 7;
int i = 0;
for (i = 0; i < 10; i++)
{
if (arr[i] == k)
{
printf("找到了,下标是%d\n", i);
break;
}
}
if (10 == i)
{
printf("找不到\n");
}
return 0;
}
这种方法是将k的值与数组中的元素一个一个的进行比较。这种方法虽然能解决这个问题,但是效率太低。
方法二(二分查找):
简单说一下二分查找:
- 在一个有序的数组中,先查找中间元素。数组中有10个数,数组下标为:0~9。
- 如果待查找的元素比中间元素小,就将查找的范围缩小一半在中间元素的左边查找(此时查找的数组左下标为0,右下标为中间元素的下标-1)。
- 如果待查找的元素比中间元素大,就将查找的范围缩小一半在中间元素的右边查找(此时查找的数组左下标为中间元素的下标+1,右下标为9)。
这只是进行了一次二分查找,如果要在数组中找到待查找的数,就要进行多次二分查找。
循环的结束条件为 左下标>右下标(因为当左下标>右下标时表示整个数组已经查找完)。
- 如果找到了就输出:找到了,并输出该元素的下标。
- 如果没找到就输出:没找到。
放一张图帮助理解
代码:
#include
int main()
{
int arr[] = { 10,20,30,40,50,60,70,80,90,100 };
int k = 70;
int sz = sizeof(arr) / sizeof(arr[0]);
int left = 0;
int right = sz - 1;
int mid = 0;
while (left <= right)
{
mid = (left + right) / 2;
if (k > arr[mid])
{
left = mid + 1;
}
else if (k < arr[mid])
{
right = mid - 1;
}
else
{
printf("找到了,下标为%d\n", mid);
break;
}
}
if (left > right)
{
printf("找不到\n");
}
return 0;
}
int sz = sizeof(arr) / sizeof(arr[0]); 计算数组元素个数
- sizeof(数组名)计算的是数组的总大小,单位是字节。
- arr数组中总共有10个元素,每个元素都是整型占4个字节空间,sizeof(arr)计算出来就是40。
- sizeof(arr[0])计算的是第一元素的大小(4)。
- sz = sizeof(arr) / sizeof(arr[0]) = 40 / 4 = 10
4、编写代码,演示多个字符从两端移动,向中间汇聚。
要实现的过程:两个字符串
arr1: Hello World!
arr2: ############
- H##########!
- He########d!
- Hel######ld!
- ……
- Hello World!
代码:
#include
#include
int main()
{
char arr1[] = "Hello World!";
char arr2[] = "############";
int left = 0;
int right = strlen(arr1) - 1; //strlen - 求字符串长度
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
left++;
right--;
}
return 0;
}
可以将程序运行的结果以动态的形式展示
#include
#include
#include
int main()
{
char arr1[] = "Hello World!";
char arr2[] = "############";
int left = 0;
int right = strlen(arr1) - 1; //strlen - 求字符串长度
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
//windows 提供的函数
Sleep(1000);//睡眠函数 - 单位是毫秒
system("cls");//执行系统命令 - cls:清空屏幕
left++;
right--;
}
printf("%s\n", arr2);
return 0;
}
感兴趣的朋友可以运行一下看看代码执行的结果
输入可以使用scanf 函数,如果想要输入字符也可以使用getchar函数(getchar函数只能用来输入字符)。
getchar - 获取(输入)一个字符
从键盘中获取一个字符,通过返回的方式带回一个值(这个值就是获取的字符),返回值类型为 int 。
getchar 函数的使用
#include
int main()
{
int ch = 0;
//scanf("%d",&ch);//输入a的ASCII码值(97)
ch = getchar();
printf("%c\n", ch);//%c - 打印字符
return 0;
}
运行结果:
getchar是获取一个字符,为什么要使用 int 类型的变量来接收这个字符呢?
两个原因:
- getchar返回的是一个字符,而字符在内存中的存储是以ASCII码值的形式存储的,比如小写a在内存中存放的是97(a的ASCII码值为97),97是一个整数,所以将 getchar 函数的返回值放到一个整型变量中是没有任何问题的。
- getchar函数读取失败的时候返回EOF(-1),而 -1 是一个整数占4个字节的空间,char 类型是存放字符的(一个字符只有1个字节的空间)。如果将 -1 放到char类型变量中是放不下的,所以综合考虑将 getchar函数 的返回值放到 int 类型的变量中。
#include
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
putchar(ch);
return 0;
}
运行结果:
代码分析:
getchar一次只能读取一个字符,但是在while循环中可以读取多个字符。
putchar一次只能输出一个字符,但是在while循环中可以打印多个字符。
- -1指的是getchar读取字符失败返回的值,但是当你输入 -1的时候,getchar读取-1并没有失败,所以输入-1循环不会终止。
- 如果输入EOF,getchar每次只能获取一个字符,虽然输入了三个字符,但每次的返回值都是一个字符的ASCII码值,会返回3次。这3次中的getchar读取字符都没有失败,所以不会返回-1,所以循环不会终止。
- 当输入Ctrl+z时,getchar读取失败然后返回-1,所以才会终止循环。
输出可以使用 printf 函数,如果想要输出字符也可以使用putchar函数(putchar函数只能用来输出字符)。
putcahr - 输出一个字符:将获取到的字符输出到屏幕上。
putchar 函数的使用
#include
int main()
{
int ch = 0;
//scanf("%d",&ch);//输入a的ASCII码值(97)
ch = getchar();
//printf("%c\n", ch);//%c - 打印字符
putchar(ch);
return 0;
}
运行结果
#include
int main()
{
char input[20] = { 0 };
printf("请输入密码:>");
scanf("%s", input);
printf("请确认密码(Y/N):>");
int ch = getchar();
if ('Y' == ch)
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
运行结果:
程序的本意是:输入密码,再输入Y 或 N 确认密码,输入Y屏幕输出确认成功,输入N屏幕输出确认失。可是当输完密码后按回车键,程序直接输出确认失败。
程序执行的结果偏离了我们的本意,为什么会这样呢?
原因:
- 程序运行以后,在屏幕上输出:请输入密码:> 这个时候程序在等待着我们去输入密码。而scanf 读取的数据是从键盘中输入的数据。
- scanf 在读取数据的时候并不是直接从键盘中获取数据,从键盘输入数据的时候有一个输入缓冲区。当scanf 在工作的时候会到输入缓冲区中获取数据(如果输入缓冲区中有数据,scanf就会直接拿走缓冲区的数据;如果输入缓冲区中没有数据,scanf 就会一直等待键盘输入数据,然后再读取数据)。
- 程序中输入密码:abcdef,为了让scanf获取到密码(abcdef)还要在键盘上敲回车键(相当于\n),敲回车是为了让input获取密码(abcdef)。此时输入缓冲区中存放的数据为:abcdef\n,当敲完回车键后 abcdef 就被scanf拿走。
- abcdef被拿走后输入缓冲区还剩下一个字符 '\n',此时程序会输出:请确认密码(Y/N):> 。然后getchar开始工作,getchar也是从输入缓冲区中获取数据(如果缓冲区中有数据getchar就会直接拿走数据,如果缓冲区没有数据getchar就会等待键盘输入数据,然后再读取数据)。
- 此时缓冲区里还有一个字符'\n',getchar就会直接拿走'\n' 将'\n'放到变量ch里。此时ch再进行判断:ch不等于Y,程序就走到else语句中,然后输出"确认失败"。
- 得出:getchar在拿取缓冲区的数据时,因为缓冲区里有数据('\n')所以程序不会等待着让我们输入 (Y 或 N ),如果缓冲区中没有数据,那么getchar就会等待键盘输入信息放到缓冲区里,然后再从缓冲区中读取数据,再进行判断。
解决办法:在scanf后面再写一个getchar,让第一个getchar先将缓冲区中的'\n'拿走(此时的getchar不需要返回值),再执行第二个getchar的时候缓冲区里就没有数据了,这个时候程序就会等待我们输入数据。
修改后的代码:
#include
int main()
{
char input[20] = { 0 };
printf("请输入密码:>");
scanf("%s", input);
getchar();//拿走'\n'
printf("请确认密码(Y/N):>");
int ch = getchar();
if ('Y' == ch)
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
运行结果:
但是此时程序还是有问题,如果有人在输入密码的时候中间加了空格,程序执行的结果依旧不是我们想要的结果:
补充:scanf("%s",....); 读取字符串的时候,遇到空格或换行('\n')就结束了。如果在字符串中想要读取空格可以使用gets函数。这里只是提一下gets函数,后期会详细说这个函数。
代码分析:
- scanf在输入缓冲区中读取数据时,当读取到abcdef后面的空格时就不会再读取了,此时缓冲区中还剩下:空格abc\n。
- 程序往下执行到第一个getchar时只能拿走缓冲区里的空格字符,拿走以后缓冲区中还有:abc\n。
- 程序再往下执行到第二个getchar时发现缓冲区中有数据就直接将a拿走,a不等于 Y 。所以程序输出:确认失败。
解决办法:
使第一个 getchar 将缓冲区里的所有数据都拿走(最后一个数据是'\n')。此时就要使用循环来读取字符,直到将'\n'也拿去。
修改后的代码:
#include
int main()
{
char input[20] = { 0 };
printf("请输入密码:>");
scanf("%s", input);
int tmp = 0;
//清理缓冲区
while ((tmp = getchar()) != '\n'); //只循环
//也可以写成
//while ((tmp = getchar()) != '\n')
//{
// ;//空语句
//}
printf("请确认密码(Y/N):>");
int ch = getchar();
if ('Y' == ch)
{
printf("确认成功\n");
}
else
{
printf("确认失败\n");
}
return 0;
}
运行结果:
scanf 函数的功能比getchar函数的功能更全, printf 函数的功能比putchar函数的功能更全。
getchar 和 putchar 只能操作字符(一次只能操作一个字符)。
详细介绍了C语言中的循环语句,可能有些地方写的不是很清晰(请谅解)。
还介绍了getchar函数与putchar函数。getchar与putchar是使用文字描述的,可能会太抽象,不容易理解。
学习是循序渐进的过程,每天学一点就会有收获!