初级C语言之【循环语句】

举报
春人. 发表于 2023/11/27 00:21:57 2023/11/27
【摘要】 一:while循环1.1:while循环的语法结构while(表达式) 循环语句;if单分支的语法结构if(表达式) 语句;可以看出,while循环的语法结构和if语句的单分支语法结构十分相似。但if单分支语句是当表达式的值为真时执行语句,执行完后程序会接着往下走,执行其他语句,若表达式的值为假,则什么也不执行。while和if也十分相似,当表达式的值为真就执行循环语句,否则就什么也不...

一:while循环

1.1:while循环的语法结构

while(表达式)
 循环语句;

if单分支的语法结构

if(表达式)
    语句;

可以看出,while循环的语法结构和if语句的单分支语法结构十分相似。但if单分支语句是当表达式的值为真时执行语句,执行完后程序会接着往下走,执行其他语句,若表达式的值为假,则什么也不执行。while和if也十分相似,当表达式的值为真就执行循环语句,否则就什么也不执行,但whileif不同点在于如果表达式的值为真,执行循环语句,执行完后程序不像if那样接着往下走,而是返回去继续判断表达式的值是否为真,如果为真会继续执行循环语句,直到表达式的值为假停止执行循环语句,程序会继续往下走,执行另外的语句。

在这里插入图片描述

在这里插入图片描述

通过代码的运行结果可以看出,当if后面表达式的值为真的时候,会执行一次if下面的语句,而把if换成while后,由于n的值始终等于0没发生任何改变,导致while后面的表达式始终为真,循环语句就会一直执行。while语句的执行流程:(其中!=0表示真,==0表示假)

在这里插入图片描述

1.2:在屏幕上打印1~10

int main()
{
	int n = 1;
	while (n <= 10)
	{
		printf("%d ", n);
		n++;
	}
	return 0;
}

需要注意的是,和if语句一样,while也只能控制其后面的一条语句,如果当while后面的表达式为真的时候要执行多条语句,需要把这多条语句用大括号括起来,如上面代码的5~8行。

1.3:while循环中的break和continue

1.3.1:break(打断、终止)

还是打印1~10,当n==5的时候执行break

int main()
{
	int n = 1;
	while (n <= 10)
	{
		if (5 == n)
		{
			break;
		}
		printf("%d ", n);
		n++;
	}
	return 0;
}

代码的执行结果:
在这里插入图片描述
可以看出执行结果是只打印了1~4。从5开始就没有打印了。

所以break的作用就是,在循环中只要遇到break,就停止后期的所有循环,直接终止循环,所以while中的break是用于永久终止循环的。某种条件满足的状况下我们希望循环终止,此时就可以再代码中加入一个判断然后break。(这是break的第二种用法,在switch选择语句中我们也用到了break)

1.3.2:continue

把上面代码中break改成continue,代码如下:

int main()
{
	int n = 1;
	while (n <= 10)
	{
		if (5 == n)
		{
			continue;
		}
		printf("%d ", n);
		n++;
	}
	return 0;
}

在这里插入图片描述
通过运行结果可以看出,当打印完4后,光标一直在闪动,此时说明代码死循环了。

continue的作用就是跳过本次循环continue后面的代码程序直接跳到循环的判断部分,以上面的代码为例,当n=5的时候,if后面的表达式的值为真,执行continue,此时程序就跳过了“printf("%d ", n);和n++;”,重新回到while后面的判断表达式,由于此时n=5,小于10,所以循环继续,当程序执行到if后面的判断表达式时,此时n还是等于5表达式为真,执行continue,一直循环往复最终导致程序进入死循环。

总结:

continue在while循环中的作用就是:continue是用于终止本次循环的,也就是本次循环中continue后面的代码不会执行,而是直接跳转到while语句的判断部分,进行下一次循环的入口判断。

将上面代码稍作修改就可以解决死循环的问题了,我们先来找一下上面代码出现死循环的原因,通过上面的分析可以知道,当n=5的时候,执行了continue,程序跳过了“printf("%d ", n);和n++;”,这导致n的值始终为5不会发生任何变化,程序就出现了死循环的结果。那我们把“n++;”这条语句放到continue之前,此时程序每次都会执行n++了,因为continue只会跳过它后边的语句。下面我们上机试试:
在这里插入图片描述
可见此时程序没有产生死循环,打印时跳过了5,但是仔细观察就可以发现此时1没有被打印出来却多打印了一个11,这里主要是因为循环里在printf前进行了n++,此时我们只需要把n的初识值赋为0就可以打印出1,把while后面的判断表达式改成“n<10”就可。

1.4:while循环的实际应用

1.4.1:getchar和putchar介绍:

getchar和putchar分别可以用来获取字符和打印字符

1.4.1.1:getchar

在这里插入图片描述

可看出getchar是一个只可以获取字符的函数,它的返回值是int型,其实也就是该字符的ASCII值,读取失败返回EOF

1.4.1.2:putchar

在这里插入图片描述

putchar是一个打印字符的函数,它的参数是一个字符型变量返回值是字符型变量存储的字符。

在这里插入图片描述

1.4.2:实例1:利用while循环清除缓存区的数据

#include <stdio.h>
int main()
{
 int ch = 0;
 while ((ch = getchar()) != EOF)
       putchar(ch);
    return 0;
}

这段代码的意图是,用getchar读取一个字符放到ch里面,如果读取的字符不是EOF,我们就对其进行打印
在这里插入图片描述
这段代码的具体工作原理还是比较复杂的:

getchar并不是直接从键盘上读数据的,在getchar和键盘之间有一个工作缓存区,getchar在工作的时候会先到工作缓存区检查一下里面有没有字符,没有字符就等待,如上图的黑框光标在闪动,此时我们从键盘上点击A和回车,回车就是’\n’,此时缓存区里就有两个字符’A’和’\n’,然后getchar会拿走一个字符,也就是’A’,把A放到ch变量里,然后ch和EOF比较,EOF是-1,ch是’A’对应的ASCII值,所以此时ch的值是65,不等于-1,判断结果为真,程序就会执行putchar打印出A,下一次循环getchar继续从缓存区读取字符,第一次循环我们输入了’A’和’\n’,getchar取走了’A’还剩一个’\n’,所以第二次循环getchar会取走缓存区里的’\n’放到ch里,覆盖掉之前的A,‘\n’的ASCII值是0,判断表达式的值为真,程序会执行putchar打印出’\n’,我们就会看到换行的效果。想让循环停下来我们需要按“Ctrl+z”。

此代码的应用场景:

int main()
{
	char password[20] = { 0 };
	int ch = 0;
	printf("请输入密码:>");
	scanf("%s", password);//123456
	printf("请确认密码(Y/N):>");
	ch = getchar();
	if (ch == 'Y')
	{
		printf("确认成功\n");

	}
	else
	{
		printf("确认失败\n");
	}
	return 0;
}

输入一串密码,然后输入"Y"或者"N"来输出"确认成功"或者"确认失败"
在这里插入图片描述
通过代码的运行结果可以看出,当我们输入密码后,程序直接就输出“确认失败”。说明此代码有问题。接着我们来分析一下问题出现的原因:

我们从键盘上输入的时候,会把数据放在缓存区,然后scanf和getchar都是从缓存区中读取数。我们在输入密码的时候在键盘上点击了"123456\n",此时就会把"123456\n"放入缓存区。然后scanf从缓存区中读取数据,但是scanf只取走了"123456"并赋值给password(这里我们要知道,scanf不会拿’\n’和空格,这时scanf函数的特性,大家记住就可以),缓存区中还剩一个’\n’,当程序执行到getchar的时候,getchar检查缓存区发现有一个’\n’,getchar就会把’\n’拿走赋值给ch,然后执行if后面的判断表达式,‘\n’不等于’Y’,程序就跳转到else输出“确认失败”。

如何解决这个问题呢?

根据上面的分析,我们很容易想出,只要把缓存区里剩下的’\n’消化掉就可以了。具体操作就是:在scanf语句后添加上一个getchar。代码修改如下:

int main()
{
	char password[20] = { 0 };
	int ch = 0;
	printf("请输入密码:>");
	scanf("%s", password);//123456
	getchar();//用来消化缓存区中剩下的'\n'。
	printf("请确认密码(Y/N):>");
	ch = getchar();
	if (ch == 'Y')
	{
		printf("确认成功\n");

	}
	else
	{
		printf("确认失败\n");
	}
	return 0;
}

在这里插入图片描述
可以看出,此时代码已经可以正常运行了。

哈哈知道这些还远远不够,如果当我们输入的密码是:123456 xxx呢?此时在123456后面有空格
在这里插入图片描述
可以看出此时程序又出问题了,它又直接输出了“确认失败”

和之前的分析方法类似,此时缓存区中存的是"123456 xxx\n",而scanf只拿走空格前面的东西(123456),缓存区中还剩下" xxx\n",而scanf后面就只有一个getchar,只能消化一个字符(空格),消化后缓存区中还剩下"xxx\n",那怎么办呢?我们可以写一个循环反复的用getchar直到把缓存区里的’\n’也消化掉就完事了,代码修改如下:

int main()
{
	char password[20] = { 0 };
	int ch = 0;
	printf("请输入密码:>");
	scanf("%s", password);//123456
	while (getchar() != '\n')//用循环来消化缓存区的数据
	{
		;
	}
	printf("请确认密码(Y/N):>");
	ch = getchar();
	if (ch == 'Y')
	{
		printf("确认成功\n");

	}
	else
	{
		printf("确认失败\n");
	}
	return 0;
}

1.4.3:实例2:输出所有字符数字

#include <stdio.h>
int main()
{
    char ch = '\0';
 while ((ch = getchar()) != EOF)
 {
     if (ch < '0'|| ch > '9')
        continue;
     putchar(ch);
 }
 return 0;
}

此段代码只会打印数字字符,不会打印其他字符。

二:for循环

2.1:for循环语法结构

for(表达式1; 表达式2; 表达式3)
 循环语句;

注意:表达式之间要用分号隔开。

表达式1
表达式1为初始化部分,用于初始化循环变量的。
表达式2
表达式2为条件判断部分,用于判断循环时候终止。
表达式3
表达式3为调整部分,用于循环条件的调整。

2.2:利用for循环打印1-10

int main()
{
	int i = 0;
	for (i = 1; i <= 10; i++)
	{
		printf("%d ", i);
	}
	return 0;
}

for循环的执行流程:

首先创建了一个变量i,然后赋值成1,接着判断i<=10是否成立,如果成立程序到循环体执行printf打印出1,接着执行i++,i变成2,接着再一次判断i<=10是否成立,如果成立程序到循环体执行printf打印出2,然后再一次自行i++,i变成3,接着再判断……i变成9,i<=10成立,程序执行printf打印出9,然后执行i++,i变成10,i<=10成立,程序执行printf打印出0,然后继续执行i++,i变成11,此时i<=10不成立,for循环结束。

在这里插入图片描述

2.3:for循环中的break和continue

2.3.1:break

用来终止循环,当满足某一条件时,我们希望循环结束可以用break

当i=5的时候终止循环,代码如下:

int main()
{
	int i = 0;
	for (i = 1; i <= 10; i++)
	{
		if (5 == i)
		{
			break;
		}
		printf("%d ", i);
	}
	return 0;
}

在这里插入图片描述

可以看出,此时程序打印出了1-4就结束了。

2.3.2:continue

把上面代码中的break换成continue

continue只会跳过本次循环后面的代码,当i=5的时候,程序执行continue,跳过后面的printf,此时就不会打印出5了,然后执行i++,i变成6,接着程序会依次打印出6 7 8 9 10。和while中continue不同的是,在while循环中如果执行了continue,程序会直接跳到while后面的判断表达式,而在for循环中执行了continue,程序会直接跳到表达式3也就是调整部分。

int main()
{
	int i = 0;
	for (i = 1; i <= 10; i++)
	{
		if (5 == i)
		{
			continue;
		}
		printf("%d ", i);
	}
	return 0;
}

2.4:for语句的循环控制变量

如上面代码中的 i 就叫循环控制变量。对于循环控制变量这里有两点建议:

  1. 不可在for 循环体内修改循环变量,防止 for 循环失去控制。
  2. 建议for语句的循环控制变量的取值采用“前闭后开区间”写法。

小tips:C99中支持在for后面的括号里定义循环控制变量,如下:

for(int i = 1;i <= 10;i++)

但我们一般情况下都会把int定义到前面去。

对循环控制变量的取值采用“前闭后开区间”写法的解释:

int i = 0;
//前闭后开的写法
for(i=0; i<10; i++)
{}
//两边都是闭区间
for(i=0; i<=9; i++)
{}

上面这两段代码执行的结果是一样的,最终采用哪一种方法要结合具体的场景来判断,总之我们要让代码达到通俗易懂的目的,在别人看到我们的代码的时候,很容易就能直到我们这段代码想干什么。

2.5:for循环的一些变种

2.5.1:变种1:表达式的省略

int main()
{
	for (; ;)
	{
		printf("hehe\n");
	}
	return 0;
}

大家可以猜一下这段代码的执行结果是什么。
在这里插入图片描述
可以看出,这段代码进入了死循环。为什么呢?

原因如下:

for循环的初始化,判断和调整三个部分都可以省略不写,但省略掉判断部分,判断就恒为真,循环就是死循环

大家看下面这段代码:

int main()
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("hehe\n");
		}
	}
	return 0;

}

这段代码的执行结果是打印出9个hehe,原因是:i=0的时候内层for循环执行3次,i=1的时候内层for循环执行3次,i=2的时候内层for循环还是执行3次,i=3的时候,循环结束,加起来内层for循环一共执行了9次,所以打印出了9个hehe。

那当我们把第二个for中的 j=0 去掉代码此时会执行几次呢?
在这里插入图片描述
可以看出只打印了3个hehe。原因是:i=0的时候,第一次进入内层for循环时,j=0,执行完printf后执行 j++,j变成1,再执行printf,执行完后再执行 j++,此时j变成2 ,再一次执行printf,执行完后再次执行 j++,j变成3,此时j<3不成立,内层for循环结束,然后程序执行到 i++,i变成1,i<3成立,程序又走到内层for循环,还记得嘛?上一次执行完内层for循环的时候,j已经变成3,由于这里已经去掉了表达式1初始化部分(j=0),所以 j 保持3不变,由于j<3不成立,所以这时不会再执行内层for循环,然后程序走到 i++,i变成2,程序又到了内层for循环,但 j还是等于3,不满足j<3,还是不会执行内层for循环,然后程序走到i++,此时 i 变成3,i<3不成立,这时,整个for循环就都结束了,最终就导致了只打印出了3个hehe。

这个例子告诉我们一个什么道理呢?不要在for循环中随意省略这三个表达式。

2.5.2:变种2:使用多余一个变量控制循环

 for (x = 0, y = 0; x<2 && y<5; ++x, y++)
   {
        printf("hehe\n");
   }

这里我们用了两个循环变量 x和y 一起来控制for循环。这段代码会打印两个hehe,当 x = 0,y = 0的时候打印第一次,当 x = 1,y = 1的时候打印第二次,当 x = 2,y = 2的时候for循环不再执行,因为此时x<2已经不再成了,而&&是当两边都为真的时候整个表达式的值才为真,所以此时判断表达式的值为假,for循环不再执行,就只打印了两个hehe。

三:do…while()循环

3.1:do语句的语法结构:

do
 循环语句;
while(表达式);

程序首先就执行循环语句,然后判断表达式,如果为真,继续执行循环语句,否则不再执行循环语句。所以do…while()循环的特点也十分明显,不管怎样,循环语句至少执行一次。

在这里插入图片描述

3.2:用do…while()打印1-10

int main()
{
	int i = 1;
	do
	{
		printf("%d ", i);
		i++;
	} while (i <= 10);
	return 0;
}

注意:如果循环体超过一条语句,要用大括号把循环体括起来。
do…while()和while的唯一区别就是前者先执行后判断,而后者是先判断后执行。

3.3: do…while()中的break和continue

这里break和continue的作用跟while循环和for循环中的作用一样,break用来终止跳出循环,continue用来跳过本次循环。

3.3.1:break

int main()
{
	int i = 1;
	do
	{
		printf("%d ", i);
		if (i == 5)
		{
			break;
		}
		i++;
	} while (i <= 10);
	return 0;
}

在这里插入图片描述
注意这里打印出了5,因为break在printf后,打印完5才执行的break

3.3.2:continue

int main()
{
	int i = 1;
	do
	{
		printf("%d ", i);
		if (i == 5)
		{
			continue;
		}
		i++;
	} while (i <= 10);
	return 0;
}

大家猜猜此时代码的执行结果是什么呢?
在这里插入图片描述
可见此时程序进入了死循环。原因是i=5的时候,程序执行了continue跳过了 i++,这就导致 i 的值没有任何变化还是5,等下一次循环由于i是5,打印出5,然后程序继续执行continue,就这样一直循环往复下去,程序一直在打印5。

四:练习

4.1:计算n的阶乘

5!=12345
思考:产生1-5的数字累积乘在一起。
n!=123*…*n

int main()
{
	int n = 0;//n的阶乘
	scanf("%d", &n);
	int i = 0;
	int ret = 1;//注意不能从0开始,0*任何数结果都是0
	for (i = 1; i <= n; i++)//通过循环找到1到n的所有数字
	{
		ret = ret * i;//把1-n的所有数字累积乘到一起
	}
	printf("%d", ret);
	return 0;
}

4.2:计算1!+2!+3!+…+10!

int main()
{
	int n = 0;
	int i = 0;
	int ret = 1;
	int sum = 0;
	for (n = 1; n <= 10; n++)
	{
		ret = 1;
		for (i = 1; i <= n; i++)
		{
			ret = ret * i;
		}
		sum = sum + ret;
	}
	printf("%d", sum);
	return 0;
}

注意:一定要在每次执行内层for循环前把ret重新赋值为1,否则ret的值是前一个数字的阶乘值,本次求n的阶乘会从这个值为基础进行累乘,而阶乘是从1开始累乘的。

可以对这个问题进行简化,首先:
1!=1
2!=1* 2
3!=1* 2* 3
4!=1* 2* 3* 4
通过分析不难发现:
1!=1
2!=1!* 2
3!=2!* 3
4!=3!* 4

可见n!=(n-1)!*n;

我们可以对上面的代码稍作修改:

int main()
{
	int i = 0;
	int ret = 1;
	int sum = 0;
	for (i = 1; i <= 3; i++)
	{
		ret = ret * i;
		sum = sum + ret;
	}
	
	printf("%d", sum);
	return 0;
}

大家可以看看这段代码如果没有第9行的“sum = sum + ret;”,那不就是求3!嘛?而3!=1* 2* 3,我们通过for循环把“1 * 2 * 3”进行分布求解,当i=1的时候,ret=1 * 1,这不就是1的阶乘嘛?,此时我们把它存到sum里,sum就是1!,当i=2的时候,ret=1 * 2,这不就是2的阶乘嘛?我们让sum=sum+ret,此时等号右边的sum为1的阶乘,ret为2的阶乘,把他俩加在一起赋值给新的sum,此时的sum就是1!+2!,而当i=3的时候,ret=2 * 3,这不就是3的阶乘嘛?,我们再让sum=sum+ret,此时的新sum就是1!+2!+3!。代码改成这样效率会高很多。

4.3:在一个有序数组中查找具体的某一个数字(折半查找法)

普通查找方法:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 7;//查找7
	int i = 0;
	int flag = 0;
	for (i = 0; i < 10; i++)
	{
		if (k == arr[i])
		{
			printf("找到了,下表是%d\n", i);
			flag = 1;
			break;
		}
	}
	//break或者找不到程序都会走到这里
	if (flag == 0)
	{
		printf("没找到\n");
	}
	return 0;
}

假如数组有n个数字,这种方法在最坏的情况下要找n次(该数字不存在或者是最后一个)

折半查找:每次都从最中间的那个数开始比对,如果要查找的数字大于中间的那个数字,因为是有序数组,要查找的数字肯定不可能在中间数字的左边,接下来就只用在中间这个数字的右边找,省去左边那一部分。同理,如果要查找的数字小于中间那个数字,我们接下来就只用在中间数字的左边进行查找。这样一来,就会大大提高我们的查找效率。

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
	int k = 7;//查找7
	int flag = 0;
	int left = 0;
	int right = 9;
	int mid = (left + right) / 2;//可以把这一句放到循环里面,此时第20行和第25行代码可以省略
	while (left <= right)
	{
		if (arr[mid] == k)
		{
			printf("找到了,下标是%d\n", mid);
			flag = 1;
			break;
		}
		else if (arr[mid] < k)
		{
			left = left + 1;
			mid = (left + right) / 2;
		}
		else
		{
			right = right - 1;
			mid = (left + right) / 2;
		}
	}
	if (flag == 0)
	{
		printf("没找到\n");
	}
	
	return 0;
}

今天就分享到这里啦,喜欢的话可以点赞、评论和收藏哟!

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。