“愿你有前进一寸的勇气,亦有后退一尺的从容”
前言
本文详细介绍了C语言中的关系操作符、条件操作符、逻辑操作符以及分支和循环结构的使用方法。关系操作符用于比较表达式,返回真或假;条件操作符(三目操作符)根据条件选择执行不同的表达式;逻辑操作符用于构建复杂的逻辑表达式,包括逻辑取反、与、或操作符,并介绍了短路求值的概念。分支结构包括if语句和switch语句,用于根据不同条件执行不同代码块。循环结构包括for、while和do-while循环,用于重复执行代码块。此外,还介绍了break和continue语句在循环中的使用,以及goto语句的无条件跳转功能。文章强调了正确使用这些操作符和结构的重要性,以避免逻辑错误和代码维护问题。
目录
一、关系操作符
用于比较的表达式,称为关系表达式,所使用的操作符为关系操作符,关系操作符需要两个操作数
- >:大于
- <:小于
- >=:大于等于
- <=:小于等于
- ==:相等
- !=:不相等
关系表达式通常返回1或0,表示真假
在C语言中,0为假,非0为真
注:
1.相等操作符“==”与赋值操作符“=”,因为就相差一个“=”很容易用错
变量在左,常量在右
a == 5//判断a是否等于5
a = 5//将5赋值给a
在上述代码中本来是要判断a是否等于5,要是用错了操作符,就会将5赋值给a,此时表达式恒为真,赋值表达式最后返回左边的操作数,真假取决于a的值,0为假,非0为真,本来的逻辑就会出错,而且不易发现
为避免用错后还发现不了,在判断变量变量和常量时,常量放在左边,变量放在右边,若用错了操作符,编译器就会报错,变量是无法赋值给常量的
5 == a//还是判断a是否等于5
5 = a//将a赋值给5,出错
2.多个关系操作符不能连用,比如判断a是否是在0~10范围内
0 <= a <= 10;//无法判断
只能将多个关系表达式连用来进行多条件判断,用“&&”(并且)或“||”(或者)连用,根据具体逻辑选择使用,下文有具体介绍
二、条件操作符
条件操作符也叫做三目操作符,顾名思义,需要三个操作数
exp1 ? exp2 : exp3
条件操作符的计算逻辑:
如果exp1为真,exp2计算,计算的结果是整个表达式的结果
如果exp1为假,exp3计算,计算的结果是整个表达式的结果
其中exp1、exp2、exp3都是表达式
#include <stfio.h>
int main()
{
int a = 3;
//用ret来接收表达式的结果
int ret = 0 == a ? 1 : -1;//0==a不成立,为假结果为-1
printf("%d\n", ret);
ret = 0 != a ? 1 : -1;//0!=a成立,为真结果为1
printf("%d\n", ret);
return 0;
}
三、逻辑操作符
逻辑操作符提供逻辑判断功能,用于构建更复杂的表达式,主要由下面三个操作符:
1.逻辑取反操作符:!
改变单个表达式的真假,真变假,假变真
a !a 非0 0 0 1
#include <stdio.h>
int main()
{
int a = 3;
int ret = !(0 == a);//0==a为假,!(0==a)为真,非0值
printf("%d\n", ret);
ret = !(0 != a);//a!=0为真,!(a!=0)为假,0
printf("%d\n", ret);
return 0;
}
2.与操作符:&&
与操作符,并且的意思,是一个双目操作符,使用方式是a&&b,&&两边的表达式都是真的时候整个表达式才为真,只要有一个是假,则整个表达式为假,一假则假
a b a&&b 非0 非0 1 非0 0 0 0 非0 0 0 0 0
#include <stdio.h>
int main()
{
int a = 3;
int ret = a > 0 && a < 5;//都为真则真
printf("%d\n", ret);//1
ret = a > 0 && a < 2;//一假则假
printf("%d\n", ret);//0
ret = a < 2 && 0 == a % 2;//都为假则假
printf("%d\n", ret);//0
return 0;
}
3.或操作符:||
或操作符,或者的意思,是一个双目操作符,使用方式是a||b,||两边的表达式只要有一个是真则整个表达式为真,两个都为假,则整个表达式才为假,一真则真
a b a||b 非0 非0 1 非0 0 1 0 非0 1 0 0 0
#include <stdio.h>
int main()
{
int a = 3;
int ret = a > 0 || a < 5;//都为真则真
printf("%d\n", ret);//1
ret = a > 0 || a < 2;//一真则真
printf("%d\n", ret);//1
ret = a < 2 || 0 == a % 2;//都为假则假
printf("%d\n", ret);//0
return 0;
}
4.短路
C语言逻辑运算符还有一个特点,它总是先对左边的表达式求值,再对右边的表达式求值,这个顺序是保证的。 如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值,这种情况称为“短路”。
对于&&操作符来说,左边的操作数为0时,右边的操作数就不再执行
对于||操作符来说,左边的操作数为1时,右边的操作数就不再执行
仅仅根据左边操作数的结果就能知道整个表达式的结果,不再对右边的操作数进行计算的运算称为短路求值
#include <stdio.h>
int main()
{
int flag = 0;
int b = 0;
flag && (b = 9);//flag为0,&&不会再进行右边的b=9
printf("%d\n", b);//结果为0
flag = 1;
flag || (b = 9);//flag为1,&&不会再进行右边的b=9
printf("%d\n", b);//结果为0
return 0;
}
四、分支结构
C语言的分支结构通过条件判断控制程序执行路径,主要分为if语句和switch语句两类,用于在不同条件下选择不同的代码块执行
1.if语句
(1)if
单分支机构,语法形式:
if(exp)
statement;//具体语句
表达式exp成立(为真),则statement执行,表达式不成立(为假),则不执行
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
//判断是否为偶数
if(a % 2 == 0)
printf("偶数\n");
return 0;
}
(2)else
双分支结构,使用if-else处理两种可能性,语法形式:
if(exp)
statement1;
else
statement2;
表达式exp成立(为真),则statement1执行,表达式不成立(为假),则statement2执行
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
//判断奇偶数
if(a % 2 == 0)
printf("偶数\n");
else
printf("奇数\n");
return 0;
}
(3)分支中包含多条语句
如果在一次条件判断下要执行多条语句
#include <stdio.h>
int main()
{
int score = 0;
scanf("%d", &score);
//判断成绩是否及格,若及格显示“及格了”和“太棒了!”
if(score >= 60)
printf("及格了\n");
printf("太棒了!\n");
return 0;
}
若直接按上述写,无论score是否>=60,第二条printf语句都会执行
因为if/else默认都只能控制一条语句,若要控制多条语句(代码块),将代码块放在{}内
#include <stdio.h>
int main()
{
int score = 0;
scanf("%d", &score);
//判断成绩score是否及格,若及格显示“及格了”和“太棒了!”
if(score >= 60)
{
printf("及格了\n");
printf("太棒了!\n");
}
return 0;
}
(4)嵌套if
在if-else语句中,else语句中,else可以与另一个if语句连用,构成多重判断
#include <stdio.h>
int main()
{
int score = 0;
scanf("%d", &score);
//判断成绩等级
if (score >= 90)
printf("A\n");
else if (score >= 80)
printf("B\n");
else
printf("C\n");
return 0;
}
也可层层嵌套
#include <stdio.h>
int main()
{
int score = 0;
scanf("%d", &score);
//判断成绩是否及格
if (score < 60)
printf("不及格\n");
else
{
printf("及格\n");
//及格判断成绩等级
if (score >= 90)
printf("A\n");
else if (score >= 80)
printf("B\n");
else
printf("C\n");
}
return 0;
}
(5)悬空else问题
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 0)
if (b == 2)
printf("hello!\n");
else
printf("byebye!\n");
return 0;
}
上述代码的预期逻辑:希望当
a != 0
时执行else语句输出"byebye!"但代码实际运行起来无任何输出,这就是else悬空问题
以为else是和对齐的if(a == 0)匹配的,其实并不是,因为C语言规定else总是与最近未匹配的if配对,此处else是和if(b == 2)所匹配
上述代码写成以下形式更好理解
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 0)
{
if (b == 2)
printf("hello!\n");
else
printf("byebye!\n");
}
return 0;
}
当外层if(a == 0)不满足时便会跳过内层的if-else,所以没有输出
实现原来的逻辑的解决方案:使用大括号{}明确作用域
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 0)
{
if (b == 2)
printf("hello!\n");
}
else
printf("byebye!\n");
return 0;
}
通过这个例子可以清晰看出,未正确使用代码块括号会导致逻辑与预期不符
建议在编写嵌套条件语句时,始终用{}明确作用域,避免此类问题
(6)判断闰年
判断年份year是否为闰年
判断闰年的规则:
①能被4整除且不能被100整除的是闰年
②能被400整除的是闰年
#include <stdio.h>
int main()
{
int year = 0;
printf("请输入年份:");
scanf("%d", &year);
if (year % 4 == 0 && year % 100 != 0)
printf("是闰年\n");
else if (year % 400 == 0)
printf("是闰年\n");
else
printf("不是闰年\n");
return 0;
}
可以把是闰年的部分放在一起判断,(能被4整除且不能被100整除)或(能被400整除)
#include <stdio.h>
int main()
{
int year = 0;
printf("请输入年份:");
scanf("%d", &year);
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
printf("是闰年\n");
else
printf("不是闰年\n");
return 0;
}
2.switch语句
switch是一种多分支结构,是一种特殊形式的if-else结构,用于判断有多个结果的情况
switch(exp)
{
case value1:
statement1;
break;
case value2:
statement2;
break;
//...
default:
statement;
break;
}
根据表达式exp不同的值,执行相应的case分支,如果找不到对应的case分支,就执行default分支,default子句处理未匹配的情况,通常置于末尾但非必须,case和default无顺序要求,视情况而定,满足情况即可
注:switch后的exp必须是整型表达式,case后的value必须是整型表达式,case和后边的value必须有空格
判断月份为哪一季度:
#include <stdio.h>
int main()
{
int month = 0;
printf("请输入月份:");
scanf("%d", &month);
switch (month)
{
case 1:
printf("第一季度\n");
break;
case 2:
printf("第一季度\n");
break;
case 3:
printf("第一季度\n");
break;
case 4:
printf("第二季度\n");
break;
case 5:
printf("第二季度\n");
break;
case 6:
printf("第二季度\n");
break;
//此处省略三四季度
default:
printf("输入错误\n");
break;
}
return 0;
}
若将上述代码中的break去掉,此时会发现,不仅会执行对应case分支,还会执行后续所有的分支
#include <stdio.h>
int main()
{
int month = 0;
printf("请输入月份:");
scanf("%d", &month);
switch (month)
{
case 1:
printf("第一季度\n");
case 2:
printf("第一季度\n");
case 3:
printf("第一季度\n");
case 4:
printf("第二季度\n");
case 5:
printf("第二季度\n");
case 6:
printf("第二季度\n");
//此处省略三四季度
default:
printf("输入错误\n");
}
return 0;
}
break用于防止case穿透,若无则会顺序执行后续代码块,case是决定入口,若不加break,则会进入下一个case,每一个case语句中的代码块执行完后,需要加上break,才能跳出swtich语句,加不加break视情况而定
因此,利用这个特点可以将代码改写的更简洁
#include <stdio.h>
int main()
{
int month = 0;
printf("请输入月份:");
scanf("%d", &month);
switch (month)
{
case 1:
case 2:
case 3:
printf("第一季度\n");
break;
case 4:
case 5:
case 6:
printf("第二季度\n");
break;
//此处省略三四季度
default:
printf("输入错误\n");
break;
}
return 0;
}
五、循环结构
循环结构用于重复执行某段代码,C语言支持for、while、do-while三种形式,核心在于循环变量初始化、条件判断和变量更新
1.for循环
for循环语法结构:
for(初始化; 条件; 更新)
循环语句;
初始化:循环变量的初始化,只执行一次(所以可以在此处创建循环变量)
条件:判断循环结束的判断
更新:循环变量的调整
和if一样,若要控制多条语句(代码块),要加上{}
特点:初始化、条件、更新可省略,但分号必须保留,例如for(;;)表示无限循环
计算1~100之和:
#include <stdio.h>
int main()
{
int sum = 0;
for (int i = 1; i <= 100; i++)
{
sum += i;
}
printf("sum = %d\n", sum);
return 0;
}
2.while循环
while循环语法结构:
while(表达式)
循环语句;
表达式为真,执行循环语句,再进行判断,若为真则再一次执行,直至表达式为假,终止循环
若控制多条语句需放在{}内
特点:先判断条件再执行循环体,可能一次都不执行
例如读取输入直到用户输入0:
#include <stdio.h>
int main()
{
int num;
while (scanf("%d", &num) && num != 0)
{
// 处理num
}
return 0;
}
while循环中也有对应for循环中的3个部分:初始化、条件、更新,只是比较分散,不利于代码的维护,for循环将这三部分集中在一起,解决了这一问题
#include <stdio.h>
//计算1~100之和
int main()
{
int sum = 0;
int i = 1;//初始化
while (i <= 100)//条件
{
sum += i;
i++;//更新
}
printf("sum = %d\n", sum);
return 0;
}
3.do-while循环
do-while循环语法结构:
do
循环语句;
while(表达式);
先执行循环语句,再进行判断,若为真则再一次执行,直至表达式为假,终止循环
若控制多条语句需放在{}内
注:最后的while()后有一个分号,很多人容易忘记导致出现语法错误
特点:至少执行一次循环体,适合需要先执行再判断的场景
例如输入验证:
#include <stdio.h>
int main()
{
int input = 0;
do {
printf("请输入1-10的数字:");
scanf("%d", &input);
} while (input < 1 || input > 10);
return 0;
}
六、break和continue
break和continue是循环控制语句
break:永久的终止循环,break语句执行,就会直接跳出循环,继续往后执行
#include <stdio.h>
int main()
{
for (int i = 0; i < 10; i++)
{
if (i == 5)
break; // i=5时终止循环
printf("%d ", i);//打印0 1 2 3 4
}
return 0;
}
break仅作用于最近的封闭结构,在嵌套场景中:
循环中嵌套的switch语句,switch中的break只能跳出switch,不能跳出外层的循环
双层循环嵌套,内层循环的break只能跳出内层循环,不会跳出外层的循环
continue:跳过本次循环continue后边的代码
例如for循环中跳过偶数:
#include <stdio.h>
int main()
{
for (int i = 1; i <= 10; i++)
{
if (i % 2 == 0)
continue;
printf("%d ", i); // 输出1 3 5 7 9
}
return 0;
}
在for循环和while循环中有所差异,在for循环中continue不会影响到循环变量的更新,在while循环中循环变量的更新在循环体内部,所以continue的位置会影响到循环变量的更新,如果循环变量的更新在continue之后可能造成死循环
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 5)
continue;//触发后跳过后续代码
printf("%d ", i);//打印1 2 3 4
i++;//被跳过的代码
}
return 0;
}
上述代码会陷入死循环,因为当i==5时,continue跳过i++,此时i永远保持为5,循环条件i<=10始终成立,导致无限循环
避免死循环:
- 调整变量位置:将循环变量的更新放在continue之前
- 使用for循环替代:当需要频繁跳过循环体时,for循环更安全
- 添加冗余条件:在循环体中额外检查终止条件
使用for循环代替:
#include <stdio.h>
int main()
{
for (int i = 1; i <= 10; i++)
{
if (i == 5)
continue;
printf("%d ", i);
}
return 0;
}
七、goto语句
goto是C语言中的无条件跳转语句,可以实现在同一个函数内通过标签(Label)将程序执行流转移到指定位置
goto的语法结构:
goto label; // 跳转到标签
...
label: // 标签定义(以冒号结尾)
目标代码;
例如可以直接跳转:
#include <stdio.h>
int main()
{
printf("开始执行\n");
goto skip; // 跳转到标签 skip
printf("这段代码不会执行\n");
skip:
printf("跳转到这里\n");
return 0;
}
跳出多层循环:
#include <stdio.h>
int main()
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == 1 && j == 1)
goto exit; // 直接跳出所有循环
printf("i=%d, j=%d\n", i, j);
}
}
exit:
printf("循环已终止\n");
return 0;
}
注:goto容易出现bug,尽量避免使用
在多层循环的代码中,如果想快速跳出循环使用goto就非常方便