目录
这个是补上面的内容(关系操作符和条件操作符)。
关系操作符
C语言中用于比较的表达式,称为关系表达式,里面使用的运算符就称为关系运算符主要有下面6个。
• > 大于运算符
• < 小于运算符
• >= 大于等于运算符
• <= 小于等于运算符
• == 相等运算符
• != 不相等运算符
关系表达式的比较结果通常返回 0 或 1 ,表示真假。 C 语言中, 0 表示假,所有非零值表示真。比如, a = 20 > 12 ,这个表达式的结果为真,返回 1,即a = 1。 b = 12 > 20 ,这个表达式的结果为假,返回 0 ,即b = 0。
关系表达式常用于 if 或 while 结构
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
//如果a>b,那么这个表达式的结果就返回1(为真),就打印hehe
//如果a<b,那么这个表达式的结果就返回0(为假),就执行while循环,死循环打印haha
if (a > b)
{
printf("hehe\n");
}
while (a < b)
{
printf("haha\n");
}
return 0;
}
注意:相等运算符 (==) 与赋值运算符 (=) 是两个不一样的运算符,不要混淆。有时候,可能会不小心写出下面的代码,它可以运行,但很容易出现意料之外的结果。
#include <stdio.h>
int main()
{
int a = 5;
if (a == 5)
{
printf("hehe\n");
}
if (a = 5)
{
printf("haha\n");
}
return 0;
}
虽然这个代码运行的结果达到了我们的预期(打印hehe和haha),但是这两个表达式的意义完全不一样。a == 5,是判断a 是否等于5(如果是5,那么这个表达式的结果就为真);而a = 5,则是将5赋值给a。(这个表达式的结果一定为真)
为了防止出现这种错误,可以将变量写在等号的右边。
这样即使我们写错了,编译器也会报错。
另一个需要避免的错误是:多个关系运算符不宜连用。
i < j < k
连续使用两个小于运算符,这是合法表达式,不会报错,但是通常达不到想要的结果。 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执行的是下面的表达式。
(i < j) < k
上面式子中, i < j 返回 0 或 1 ,所以最终是 0 或 1 与变量 k 进行比较。如果想要判断变量 j 的值是否在 i 和 k 之间,应该使用下面的写法。
(i < j) && (j < k)//括号可不写,因为&&的优先级比较低
优先级这篇文章后面有。
条件操作符
条件操作符也叫三目操作符,需要接受三个操作数的,形式如下:
exp1 ? exp2 : exp3
条件操作符的计算逻辑是:如果 exp1 为真, exp2 计算,计算的结果是整个表达式的结果;如果 exp1 为假, exp3 计算,计算的结果是整个表达式的结果。
练习:使用条件表达式实现找两个数中较大值。
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
int max = a > b ? a : b;
printf("%d\n", max);
return 0;
}
逻辑操作符:&& , || , !
逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。
• ! : 逻辑取反运算符(改变单个表达式的真假,即真变假,假变真)。
• && :与运算符,就是并且的意思(两侧的表达式都为真,则为真,否则为假)。
• || : 或运算符,就是或者的意思(两侧至少有⼀个表达式为真,则为真,否则为假)。
!
#include <stdio.h>
int main()
{
int a = 0;
if (!a)//!0,就代表是真
{
printf("hehe\n");
}
return 0;
}
&&
其实这个在之前的关系操作符中已经举例了。
||
#include <stdio.h>
int main()
{
int month = 0;
scanf("%d", &month);
if (month == 12 || month == 1 || month == 2)//满足其中一个条件就可以打印hehe
{
printf("hehe\n");
}
return 0;
}
短路
C语言逻辑运算符还有⼀个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的。如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为短路。
对于&&操作符来说,左边操作数的结果是0的时候,右边操作数就不再执行。
|| 操作符的左操作数的结果不为0时,就无需执行右操作数。
像这种仅仅根据左操作数的结果就能知道整个表达式的结果,不再对右操作数进行计算的运算称为短路求值。
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
//当a为0时,右侧的就不再执行
i = a++ && ++b && d++;
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
return 0;
}
因此,这个代码的运行结果是1,2,3,4
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
//当a为0时,才开始执行;当b为0时才开始执行
i = a++||++b||d++;
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
return 0;
}
因此,这个代码的最终结果是1,3,3,4
下标访问[ ]、函数调用()
[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
如下图:
结构成员访问操作符
要想弄明白这个,就得知道结构体是什么?C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设我想描述学生,描述一本书,这时单一的内置类型是不行的。描述一个学生需要名字、年龄、学号、身高、体重等;描述一本书需要作者、出版社、定价等。C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型。 结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚至是其他结构体。数组与其定义是比较相似的,数组是一组相同元素的集合。
结构体的关键字是:struct。
下面的图就是结构体创建的语法。
既然已经知道创建结构体的语法了,那我们不妨就来创建一个结构体变量。
例如:我们要描述一个学生,我们就可以用姓名,年龄,学号,身高,体重等来描述。
代码演示:
全局是在括号外定义的;局部是在括号内定义的。
定义完后,我们也可以再初始化变量。
如果不加 f 后缀,那么编译器会把它当成double类型的值。
如果是结构体中包含结构体,那么我们在初始化时,就要将那个被包含的结构体也是按照结构体的方法来初始化的(用{ }),那这个初始化模型就是:{ { ...} ,...}。
代码演示:
结构体成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的。点操作符有两个操作数。一个是结构体变量,一个是结构体成员变量。
代码演示:
结构体成员的间接访问
这个在结构体中细讲。
操作符的属性:优先级、结合性
C语言的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。
优先级
优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级是不一样的。
举例: 2 * 4 + 6 我们想要计算出这个表达式的结果,就得先知道这个表达式的运算符优先性,这个表达式的运算符优先性是 * 大于 + 那么这个表达式的运算顺序是先算2 * 4,再算+ 6的结果。
结合性
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符( = )。
举例:2 + 4 - 6 我们想要计算出这个表达式的结果,就先得知道这个表达式中运算符的优先性,因为 + 和 - 号的优先级是相同的,所以我们就得看它们的结合性,它们都是左结合运算符,所以从左到右执行,先计算2 + 4,再算- 6的结果。
运算符的优先级顺序很多,下面是部分运算符的优先级顺序(按照优先级从高到低排列),建议大概记住这些操作符的优先级就行,其他操作符在使用的时候查看下面表格就可以了。
• 圆括号( () )
• 自增运算符( ++ ),自减运算符( -- )
• 单目运算符( + 和 - )
• 乘法( * ),除法( / )
• 加法( + ),减法( - )
• 关系运算符( < 、 > 等)
• 赋值运算符( = )
由于圆括号的优先级最高,所以可以使用它改变其他运算符的优先级。
下面是完整的。
参考:https://zh.cppreference.com/w/c/language/operator_precedence
整型提升
C语言中整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义: 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特(1字节)直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为 int或unsigned int,然后才能送入CPU去执行运算。
上面说了怎么多,其实就是在说:如果在运算时,有小于一个整形数据长度的(4个字节,也就是32个比特位),就要提升为一个整形数据该有的长度,再去运算。并且运算之后,如果存储在一个小于整形的数据中那么那个数据就要按照它的类型对应的字节来截断这个提升之后的整形。
举例:
如何进行整体提升呢?
1. 有符号整数提升是按照变量的数据类型的符号位来提升的。
2. 无符号整数提升,高位补0。
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另⼀个操作数的类型,否则操作就无法进行。
下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外一个操作数的类型后执行运算。
例如:int类型和double类型计算,会先将int转换为double类型 。