6.关系操作符
> >= < <= != ==
这些关系操作符我们在初识C语言了解过了,没什么有扩充的部分
需要注意一点:== 不要和 = 混淆,以导致程序的错误。
7.逻辑操作符
&& (逻辑与) || (逻辑或)
&&和 || 表达式结果为真返回1,为假返回0,结果的类型为int。
#include<stdio.h> int main() { int a = 3 && 5; //结果为真返回1 printf("%d\n", a); int b = 3 && 0; //结果为假返回0 printf("%d\n", b); int c = 3 || 5; //结果为真返回1 printf("%d\n", c); int d = 0 || 0; //结果为假返回0 printf("%d\n", d); return 0; }
接下来 ,来看一道大厂的笔试题 :
#include <stdio.h> int main() { int i = 0,a=0,b=2,c =3,d=4; i = a++ && ++b && d++; //i = a++||++b||d++; printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d); return 0; } //程序输出的结果是什么?
解释:a++是后置自增,先拿a进行计算,0 &&上任何值,判断结果都为0,因为左操作数已经判断为0,右边不论判断对错,结果都为0。
#include <stdio.h> int main() { int i = 0,a=0,b=2,c =3,d=4; //i = a++ && ++b && d++; i = a++||++b||d++; printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d); return 0; } //程序输出的结果是什么?
解释:a++为后置自增,先拿a进行运算,逻辑或上++b ,b先自增, b为3,0 || 3判断结果为1, 后面就不需要进行判断了,不论右操作数判断对错,结果都为1。 最后,a的值为1 ,b的值为3,c的值为3,d的值为4。
⚠️注意:&&运算符在左操作数的判断结果为0时不对右操作数进行判断。
|| 运算符在左操作数的判断结果不为0时不会对右操作数进行判断。
以上这种情况称为短路求值
8.条件操作符
exp1 ? exp2 : exp3
规则:exp1先进行判断,判断正确执行exp2,表达式整体返回的是exp2的结果,否则执行exp3,表达式整体返回的是exp3的结果。
if (a > 5)
b = 3;
else
b = -3;
转换成条件表达式,是什么样?
#include<stdio.h> int main() { int a = 0,b = 0; scanf("%d", &a); b = ((a > 5) ? 3 : -3); printf("%d", b); return 0; }
9.逗号表达式
exp1, exp2, exp3, …expN
规则:逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
#include<stdio.h> int main() { int a = 1; int b = 2; int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式 printf("%d", c);//13 return 0; }
10.下标引用、函数调用和结构成员
操作数: 一个数组名 + 一个索引值
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("%d", arr[3]);//4 }
10.2 函数调用操作符 ( )
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include<stdio.h> void test() { printf("Hello World!\n"); } int Add(int x, int y) { return x + y; } int main() { test(); //使用( )作为函数调用操作符 int ret = Add(4, 5); //使用( ) 作为函数调用操作符 return 0; }
10.3结构体成员
. 结构体.成员名
-> 结构体指针->成员名
#include <stdio.h> struct S { int num; char ch; }; int main() { struct S s = { 10,'c' }; //结构体的初始化 printf("%d\n", s.num);//10 printf("%c\n", s.ch);//c return 0; }
如果我们只知道结构体s的地址呢?
#include <stdio.h> struct S { int num; char ch; }; void test(struct S* ps) { //printf("%d\n", (*ps).num);//解引用的方式 //printf("%c\n", (*ps).ch);// //结构体指针->结构体成员 printf("%d\n", ps->num); printf("%c\n", ps->ch); } int main() { struct S s = { 10,'c' }; //结构体的初始化 // 结构体名.成员 //printf("%d\n", s.num);//10 //printf("%c\n", s.ch);//c test(&s); return 0; }
11.表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。(说简单点,C语言中的优先级就像数学中的 3 + 4×5,根据符号的优先级先进性运算乘法,后运算加法)
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型,比如说以下的隐式类型转换。
11.1隐式类型转换
C的整型算术运算总是至少以缺省整型类型(int)的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
简单举一个例子:
//实例1
char a,b,c;
...
a = b + c;
解释:b和c为char类型,在执行加法运算前,b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果将被截断,然后再存储于a中。
1.整型提升的意义
整型提升的意义是什么呢?
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
2.如何进行整型提升?
规则:整形提升是按照变量的数据类型的符号位来提升的
//1.有符号类型(signed)整型提升
//1.1负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//1.2正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//2.无符号类型(unsigned)整形提升,高位补0即可
#include<stdio.h> int main() { char a = 3; //0000 0000 0000 0000 0000 0000 0000 0011 //0000 0011 -截断 char b = 127; //0000 0000 0000 0000 0000 0000 0111 1111 //0111 1111 -截断 char c = a + b; //char c为 signed的char,按符号位补充 //0000 0011 --> 0000 0000 0000 0000 0000 0000 0000 0011(整型提升) //0111 1111 --> 0000 0000 0000 0000 0000 0000 0111 1111(整型提升) //0000 0000 0000 0000 0000 0000 1000 0010 //1000 0010 -- c(截断) printf("%d\n", c); //char c为 signed的char,按符号位补充 //1111 1111 1111 1111 1111 1111 1000 0010(补码) //1111 1111 1111 1111 1111 1111 1000 0001(反码) //1000 0000 0000 0000 0000 0000 0111 1110(原码) // -126 return 0; }
解释:将3先用32位二进制序列表示为0000 0000 0000 0000 0000 0000 0000 0011,因为char a类型的空间大小为1个字节,而我们计算的大小为4个字节,就好像把一根长为4m的杆子放到长度为1m的空间里面,放不下,需要进行截断,截断后的结果为0000 0011;同理,把127计算后截断为0111 1111,接下来就要计算a + b ,a 和 b的类型为char ,没有达到整型的大小,所以需要进行整型提升,高位按符号位补充。整型提升后再相加,需要进行截断,因为放在char c当中,然后将c打印前,格式化字符串要求打印%d的形式,于是再根据有符号类型提升,高位补充1,得到他的补码,将补码转换为原码,再输出。
3.整型提升的例子
//实例1
#include<stdio.h> int main() { char a = 0xb6; //转为十进制 1011 0110 short b = 0xb600;//转为十进制 1011 0110 0000 0000 int c = 0xb6000000; if (a == 0xb6) printf("a"); if (b == 0xb600) printf("b"); if (c == 0xb6000000) printf("c"); return 0; }
解释: a,b要进行整形提升,但是c不需要整形提升a,b整形提升之后,变成了负数,所以表达式 a==0xb6 ,b==0xb600 的结果是假,但是c不发生整形提升,则表达式c==0xb6000000 的结果是真。
//实例2
int main() { char c = 1; printf("%u\n", sizeof(c)); printf("%u\n", sizeof(+c)); printf("%u\n", sizeof(-c)); return 0; }
解释:c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节。表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节。
11.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
11.3操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级
一些问题表达式
//表达式的求值部分由操作符的优先级决定。
//表达式1
a*b + c*d + e*f
⚠️注意:代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行。
所以表达式的计算机顺序就可能是:
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
或者:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f
//表达式2
c + --c
⚠️注意:操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得
知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
所以,总的来说
表达式求值先看是否存在整形提升或算术转换,再进行计算
表达式真正计算的时候先看相邻操作符的优先级决定先算谁
相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序
最后,创作不易,还请读者多多关注、点赞+收藏哦