【C语言】操作符详解

✨内容专栏:【C语言】初阶部分

✨本文概括:继初识C语言,对C语言操作符(运算符)部分进行详细归纳与总结。

✨本文作者:花香碟自来_ 

✨发布时间:2023.2.13

目录

0.操作符分类

1.算术操作符

2.移位操作符

2.1 左移操作符

2.2 右移操作符 

3.位操作符

3.1 & 按位与

3.2 | 按位或

3.3 ^ 按位异或

 练习一下

方法一:

方法二:

4.赋值操作符

4.1复合赋值符

5.单目操作符

6.关系操作符

7.逻辑操作符

 7.1 结论

8.条件操作符

9.逗号表达式

10.下标引用、函数调用和结构成员

10.1  下标引用操作符 [ ]

 10.2  函数调用操作符 ( )

10.3结构体成员

11.表达式求值

11.1隐式类型转换

1.整型提升的意义

2.如何进行整型提升?

3.整型提升的例子

11.2 算术转换

11.3操作符的属性


0.操作符分类

  • 算术操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号表达式
  • 下标引用、函数调用和结构体成员

1.算术操作符

+ (加) - (减)  *(乘)  /(除)  % (取模)

对于这些算术操作符,我们在初识C语言已经有了基本的了解。

这里,我们需要对 / 和% 再着重强调下。

#include<stdio.h>
int main()
{
	//  1./得到的是商
	//  2.%得到的是余数
	printf("%d\n", 10 / 2);// 5
	printf("%d\n", 10 % 2);// 0
	return 0;
}

 

 对于/ 操作符如果两个操作符都为整数,执行整数除法,而只要有一个操作符为浮点数,那么执行的就是浮点数除法。

#include<stdio.h>
int main()
{
	//lf打印double类型
	printf("%lf\n", 10 / 3.0);// 3.333333
	printf("%lf\n", 10.0 / 3.0);// 3.333333
    return 0;
  }

 对于%操作符,两个操作数必须为整数。返回的是整除之后的余数。

2.移位操作符

<< (左移操作符)    >> (右移操作符)

2.1 左移操作符

移位操作符移动的是二进制位数字,什么是二进制数字?

这里就不得不详细牵扯一下整数在内存中存储的相关知识。

我们平常说的“173”、“56”、“1924”,这些都是十进制数字,即由0~9的数字组成。拿“173”的这个十进制数字来说,个位数‘3’的权重为10^0,十位数‘7’的权重为10^1,百位数‘1’的权重为10^2,即 1*10^2+7*10^1+3*10^0 = 173 。

那么由此可以知道二进制就是由0~1的数字组成的,考虑二进制的1010表示的数值(十进制)是多少呢?

 所以,二进制的1010所代表的就是数值10。

有关整数的二进制表示形式

整数的二进制表示形式有三种:原码、反码、补码

原码:我们把一个数按照正负直接翻译成二进制序列就是原码。比如说‘10’ 和‘-10’,这两个整数是存放到整型变量当中的,一个整型变量为4个字节(32bit),上面描述过,10用二进制表示就是1010,但是共有32个bit位,我们将高位补充0,于是得到:0000 0000 0000 0000 0000 0000 0000 1010

那么如何表示“-10”呢?在最高位(符号位)将‘0’置为‘1’即可,即:

1000 0000 0000 0000 0000 0000 0000 1010

 我们需要记住,正整数的原码、反码、补码均相同,而负整数的反码是按照原码的符号位(最高位)不变,其他位按位取反,补码则是在反码基础上加1。

那么“-10”的反码和补码是什么呢?

反码:1111 1111 1111 1111 1111 1111 1111 0101(符号位不变,其他的将0置为1,1置为0)

补码:1111 1111 1111 1111 1111 1111 1111 0110(在最低位+1,即1+1 = 2 ,逢2进1)

整数在内存中的存储形式是补码(二进制序列)

下面我们用编译器调试,查看整数“10”和“-10”在内存中是如何存储的。

测试整数“10”:我们通过调试到“内存”中输入地址:&a,列:4,找到了“0a 00 00 00”。

我们知道“10”的补码是0000 0000 0000 0000 0000 0000 0000 1010,我们把它转换为十六进制位(每4个二进制位对应1个十六进制位),为00 00 00 0a,对应了内存中的“0a 00 00 00”,编译器上的存储为倒序的,即小端存储。

 测试整数“-10”:我们通过调试到“内存”中输入地址:&b,列:4,找到了“f6 ff ff ff”,我们知道‘-10’的补码是:1111 1111 1111 1111 1111 1111 1111 0110 ,将其计算转换为十六进制为ff ff ff f6,对应了内存中的“f6 ff ff ff”。

终于,我们知道了移位操作符移动的是整数的补码二进制形式,我们往下看,直接上代码:

整数‘10’的左移操作符运算

#include<stdio.h>
int main()
{
	int a = 10;
	// 补码 0000 0000 0000 0000 0000 0000 0000 1010
    //a向左移动1位
	int b = a << 1;// 0000 0000 0000 0000 0000 0000 0001 0100
	printf("%d\n", b);// 20
	printf("%d\n", a);// 10
    return 0;
}

解释:我们知道移动的是补码的二进制序列, a << 1表示a的补码向左移动1位,高位移出的0会被丢弃,而低位空余的部分会补充0,计算之后的补码为 0000 0000 0000 0000 0000 0000 0001 0100 ,将补码转化为原码,原反补相同,所以计算后值为20,赋值给b。

a的值参与了运算,但是a本身并不会被改变,就像 b = a + 1,计算的值赋给了b,但是a没有改变。

整数‘-10’的左移操作运算

#include<stdio.h>
int main()
{
	
	int c = -10;
	// 原码 1000 0000 0000 0000 0000 0000 0000 1010
	// 反码 1111 1111 1111 1111 1111 1111 1111 0101
	// 补码 1111 1111 1111 1111 1111 1111 1111 0110
	int d = c << 1; 
    //补码转换为原码
    //方法1(减1取反)
    //补码 1111 1111 1111 1111 1111 1111 1110 1100
	//反码 1111 1111 1111 1111 1111 1111 1110 1011
	//原码 1000 0000 0000 0000 0000 0000 0001 0100 --> -20

    //方法2(取反加1)
    // 1111 1111 1111 1111 1111 1111 1110 1100
    // 1000 0000 0000 0000 0000 0000 0001 0011
    // 1000 0000 0000 0000 0000 0000 0001 0100 --> -20
	printf("%d\n", d); 
	printf("%d\n", c);
	return 0;
}

解释:先将‘-10’转换为二进制的补码,左移1位后,再转换为原码,即补码减1得到反码,反码符号位不变,其他位按位取反得到原码(也可尝试补码取反再加1得到的也是原码。

结论:

左移规则:左边丢弃,右边补0。

2.2 右移操作符 

那么对于右移操作符的规则与左移操作符的规则是略有差别的,右移操作符分为算术右移逻辑右移

算术右移:右边丢弃,左边用原来的符号位填充

逻辑右移:右边丢弃,左边直接用0填充。

那么代码运行的时候,到底采用的是算术右移呢?还是逻辑右移呢?其实是根据编译器而定的。

正整数在测试时是看不出区别的,因为正整数的符号位为0,所以依旧我们用整数“-10”来测试,下面上代码:

#include<stdio.h>
int main()
{
	int a = -10;
	//原码 1000 0000 0000 0000 0000 0000 0000 1010
	//反码 1111 1111 1111 1111 1111 1111 1111 0101
	//补码 1111 1111 1111 1111 1111 1111 1111 0110
	int b = a >> 1;  
	printf(" a = %d\n", a);
	printf(" b = %d\n", b);
	return 0;
}

 打印代码,我们发现b的值为-5,是一个负数,由此我们想到补充的是按移位之前‘-10’的补码的符号位来填充的,是否是这样的呢?

我们来计算一下,我们把“-10”的补码1111 1111 1111 1111 1111 1111 1111 0110,向右移动1位,得到1111 1111 1111 1111 1111 1111 1111 1011,计算它的反码为

1111 1111 1111 1111 1111 1111 1111 1010,符号位不变,按位取反得到原码为

1000 0000 0000 0000 0000 0000 0000 0101,转换数值为-5,与打印的值吻合,所以我们的编译器(visual studio 2019)是按照算术右移的方式进行计算的

⚠️注意:对于移位运算符,不要移动负数位,这个是标准未定义的。

以下代码就是错误的。

int main()
{
    int a = 10;
     a >> - 1; //error
    return 0;
}

3.位操作符

& (按位与) | (按位或) ^(按位异或)

这里的位是什么呢?是指二进制位。

语法: 两操作数各对应的补码的二进位进行计算。

⚠️注意:操作数必须是整数。

3.1 & 按位与

运算规则:只有对应的两个二进位均为1时,结果位才为1 ,否则为0

#include<stdio.h>
int main()
{
	int a = -3;
	int b = 5;
	//a 原码 1000 0000 0000 0000 0000 0000 0000 0011
	//  反码 1111 1111 1111 1111 1111 1111 1111 1100
	//  补码 1111 1111 1111 1111 1111 1111 1111 1101

	//b 补码 0000 0000 0000 0000 0000 0000 0000 0101

   //c 按位与0000 0000 0000 0000 0000 0000 0000 0101 --> 5    
	int c = a & b;
	printf("%d", c);
	return 0;
}

解释:a的补码与b的补码的二进制位序列进行一一对照, 两者都为1时,结果才为1,否则为0。计算的结果为补码,发现最高位为0,所以判断为正数,转为数值5。

3.2 | 按位或

运算规则:只要对应的两个二进位有一个为1时,结果位就为1

#include<stdio.h>
int main()
{
	int a = -3;
	int b = 5;
	//a 原码 1000 0000 0000 0000 0000 0000 0000 0011
	//  反码 1111 1111 1111 1111 1111 1111 1111 1100
	//  补码 1111 1111 1111 1111 1111 1111 1111 1101

	//b 补码 0000 0000 0000 0000 0000 0000 0000 0101

	//c按位或1111 1111 1111 1111 1111 1111 1111 1101 (补码)
	//       1111 1111 1111 1111 1111 1111 1111 1100 (反码)
	//       1000 0000 0000 0000 0000 0000 0000 0011 (原码)
	int c = a | b; 
	printf("%d", c);
	return 0;
}

3.3 ^ 按位异或

运算规则:对应的二进制位,相同为0,相异为1。

#include<stdio.h>
int main()
{
	int a = -3;
	int b = 5;
	//a 原码 1000 0000 0000 0000 0000 0000 0000 0011
	//  反码 1111 1111 1111 1111 1111 1111 1111 1100
	//  补码 1111 1111 1111 1111 1111 1111 1111 1101

	//b 补码 0000 0000 0000 0000 0000 0000 0000 0101

  //c按位异或1111 1111 1111 1111 1111 1111 1111 1000 (补码)
	//       1111 1111 1111 1111 1111 1111 1111 0111  (反码)
	//       1000 0000 0000 0000 0000 0000 0000 1000(原码)
	int c = a ^ b;  // -8
	printf("%d", c);
	return 0;
}

 练习一下

不创建临时变量(第三个变量),实现两个数的交换。

方法一:

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a = %d b = %d\n", a, b);
	a = a + b;
	b = a - b;
	a = a - b;
	printf("交换后:a = %d b = %d\n", a, b);
	return 0;
}

我们发现方法一的代码存在一定的缺陷,假设a和b是一个很大值,但是两者没超过int类型的范围,如果它们相加就可能超过了int类型的最大范围,会出现溢出情况,会丢失精度。

方法二:

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a = %d b = %d\n", a, b);
	a = a ^ b; //code1
	b = a ^ b; //code2
	a = a ^ b; //code3
	printf("交换后:a = %d b = %d\n", a, b);
	return 0;
}

解释:我们知道,异或的运算法则是相同为0,相异为1。

那么就有a ^ a = 0,0 ^ a = a的结论,3 ^ 3 ^ 5 = 5 ,那么3 ^ 5 ^ 3的结果呢?我们经过计算也是为5,即3 ^ 3 ^ 5 等价于 3 ^ 5 ^ 3,故而我们可以将其理解为异或的交换律。

观察方法二的代码,code1将a ^ b的值给a,那么code2的代码可以替换为a ^ b ^ b 即为a的值赋值给了b ,code3的代码a ^ b中的a还是code1的a ^ b,b被赋了原来a的值,故可以转换为a ^ b ^ a ,即为b的值,再赋值给a。

方法二的操作并不会导致溢出,可能适宜这个练习的代码,但是这种方法并不比直接根据临时变量交换要好。

理由如下:

  1. 它对于运算的操作数的要求必须是整数;
  2. 代码可读性较差。

4.赋值操作符

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值

#include<stdio.h>
int main()
{
    int weight = 120;//体重
    weight = 89;//不满意就赋值
return 0;
}

4.1复合赋值符

+=    -=    *=     /=   %=    >>=    <<=   &=    |=    ^=

简单举几个例子,操作基本都类似

#include<stdio.h>
int main()
{
	int a = 3;
	a = a << 1; //等价于 a <<= 1;

	int b = 5;
	b = b ^ 5; //等价于 b ^= 5
	return 0;
}

5.单目操作符

!逻辑反操作
-负值
+正值
&取地址
sizeof操作数的类型长度(以字节为单位)
~对一个数的二进制按位取反
--前置、后置--
++前置、后置++
*间接访问操作符(解引用操作符)
(类型)  (类型) 强制类型转换
* 操作符和 & 操作符
int main()
{
	int a = 10;
	int* p = &a;//&取地址操作符
	*p = 20;//*为解引用操作符(间接引用操作符)

	//int arr[10];
	//&arr;//取出数组的地址
	return 0;
}
sizeof操作符: 返回给定类型的变量所占用的字节数
#include<stdio.h>
int main()
{
	int a = 10;
	printf("%d\n", sizeof(a));//4
    printf("%d\n", sizeof a);
	printf("%d\n", sizeof(int));//4
    printf("%d\n", sizeof int)
	int arr[10] = { 0 };
	printf("%d\n", sizeof(arr));//40
	return 0;
}

sizeof和数组

观察以下代码:

#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//code 1
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//code 2
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));//code 3
printf("%d\n", sizeof(ch));//code 4
test1(arr);
test2(ch);
return 0;
}
//问:
//code 1、code 2两个地方分别输出多少?
//code 3、code 4两个地方分别输出多少?

解释:test1()和test2()传入的实参是数组名,实际传入的是数组首元素的地址,形参在接收时,形式上是数组的表现形式,但本质上是一个指针,所以code1和code2在计算时,计算的是指针变量的大小,所以在x86的环境下,两者计算的均为4个字节,在x64环境下,两者计算的均为8个字节。

那么对于code3和code4呢?我们在数组部分就知道,sizeof(数组名)计算的是整个数组的大小,所以code3计算的值为40,code4计算的值为10

 ~操作符 :对一个数的二进制按位取反
#include<stdio.h>
int main()
{
	int a = 0;
	// 0000 0000 0000 0000 0000 0000 0000 0000 (补码)
	// 1111 1111 1111 1111 1111 1111 1111 1111 (按位取反)
	// 1111 1111 1111 1111 1111 1111 1111 1110 (反码)
	// 1000 0000 0000 0000 0000 0000 0000 0001 (原码)
	printf("%d\n", ~a);
		return 0;
}
~ 操作符与位操作符 & 、 | 的灵活运用
#include<stdio.h>
int main()
{
	int a = 3;
	// 0000 0000 0000 0000 0000 0000 0011
	// 如果要将二进制的第四位(从右开始)的0改为1,其他都不变
	// 0000 0000 0000 0000 0000 0000 1000 //将1向左移动3位
	// 那么我们只需要按位或以上二进制序列 就能得到
	// 0000 0000 0000 0000 0000 0000 1011 --> 11
	a |= (1 << 3);
	printf("%d\n", a); //11

	// 0000 0000 0000 0000 0000 0000 1011 
	//如果要将二进制的第四位(从右开始)的1改回0,其他不变
	// 1111 1111 1111 1111 1111 1111 0111 //将1向左移动3位,再使用~操作符,按位取反
	//那么我们只需要按位与以上二进制序列,就能得到
	// 0000 0000 0000 0000 0000 0000 0011 --> 3
	a &= (~(1 << 3));
	printf("%d\n", a); //3

	return 0;
}
while( ~ scanf(“%d”,&n)) 中的 ~如何理解?我们知道scanf读取失败的时候返回的是 EOF,C语言对于EOF定义的值为 -1,
-1的原码为 1000 0000 0000 0000 0000 0000 0000 0001 ,
反码为  1111 1111 1111 1111 1111 1111 1111 1110
补码为  1111 1111 1111 1111 1111 1111 1111 1111
对补码按位取反 即为全0,跳出while循环。

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。

 7.1 结论

逻辑与运算符a && b如果a和b都不为0时,则表达式的结果为1,否则结果为0(结果的类型为int)
逻辑或运算符||  b如果a和b中有一个不为0,则表达式的结果为1,否则结果为0(结果的类型为int)

⚠️注意:&&运算符在左操作数的判断结果为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.下标引用、函数调用和结构成员

10.1  下标引用操作符 [ ]

操作数: 一个数组名 + 一个索引值 

#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 算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换

数据类型大小(字节)
long double8
double8
float4
unsigned long int4
long int4
unsigned int4
int4

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

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

⚠️注意:操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得
知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

所以,总的来说

  1.  表达式求值先看是否存在整形提升或算术转换,再进行计算
  2.  表达式真正计算的时候先看相邻操作符的优先级决定先算谁
  3.  相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序

最后,创作不易,还请读者多多关注、点赞+收藏哦 

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿四啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值