c语言修炼秘籍【第一章】分支与循环语句

c语言修炼秘籍【第一章】分支与循环语句

【心法】
【第零章】c语言概述
【第一章】分支与循环语句
【第二章】函数
【第三章】数组
【第四章】操作符
【第五章】指针
【第六章】结构体
【第七章】const与c语言中一些错误代码
【禁忌秘术】
【第一式】数据的存储
【第二式】指针
【第三式】字符函数和字符串函数
【第四式】自定义类型详解(结构体、枚举、联合)
【第五式】动态内存管理
【第六式】文件操作
【第七式】程序的编译



前言

在处理现实中的问题时,我们总会遇到需要作出选择或是需要重复多次的完成一种事件。
你去图书馆学习,需要日复一日的坚持,每多学一天,你的代码功力就能+1,当你的代码功力,达到了某一程度,你就能够迎娶白富美,走上人生巅峰。
为了实现这些目的,c语言提供了分支语句,用以实现选择;提供循环语句,用以实现重复多次的机械活动;


一、什么是语句?

c语句可以分为以下五类:

  • 表达式语句
  • 函数调用语句
  • 控制语句
  • 复合语句
  • 空语句

分支、循环语句属于控制语句。
控制语句用于控制程序的执行流程,以实现程序的各种结构方式,它们由各种特定的语句定义符构成;c语言中有九种控制语句:

  1. 条件判断语句,也叫分支语句:if语句,switch语句;
  2. 循环执行语句:for语句,while语句,do…while语句;
  3. 转向语句: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;
}

运行结果:
悬空else
可以看到,该程序没有任何输出;
从代码的对齐可以算出,该代码想要的逻辑是,当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;
}

运行结果:
if

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 ifelse语句,但形式太复杂了,所以就有了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

代码中遇到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之间时

default
练习

下面的代码会输出什么?

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语句的执行流程:
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;
}

运行结果:
break

可以看到,在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;
}

运行结果:
在这里插入图片描述continue
可以看到,在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 < 2y < 5组成,且它们之间的逻辑是与&&,需要两者都满足才执行循环语句,当执行完第二次时,x == 2,不满足条件,跳出循环,此时,仅执行了2次循环语句;

运行结果:
for
练习

下面的循环,循环了多少次?

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
注:do…while循环至少都会执行一次
其中使用的breakcontinue的作用和另两个循环的用法相同

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时,查找过程如下
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;
}

总结

本章介绍了分支和循环语句,以及使用时可能会遇到的一些问题,并给出了一些使用示例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值