表达式求值——隐式类型转换与操作符属性

前言

通过上期的讲解,相信大家对操作符已经有了一定的理解,其实学习操作符是为了筑基,学习操作符的根本目的是为了表达式求值,本章就以此目的继续探讨表达式求值!lets go!

一、隐式类型转换

在表达式求值时,有些表达式的操作数在求值的过程中可能需要转换为其他类型,其主要体现为整型提升和算术转换。

1、整型提升

(1)什么是整型提升?

C的整型算术运算总是至少以默认整型类型的精度来进行的。为了获得这个精度,表达式中的char-字符型和short-短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

整形提升的条件是:表达式中大小达不到int的charshort才会发生整型提升

(2)为什么会发生整型提升?

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。

(3)如何进行整型提升?

整型提升是按照变量的数据类型的符号位来提升的。对于有符号整型提升高位补符号位;对于无符号整型提升,高位补0

📝例如:计算并打印 a+b

#include<stdio.h>
int main()
{
	char a = 5;//截断
	//00000000000000000000000000000101
	//00000101 - a
	char b = 126;//截断
	//00000000000000000000000001111110
	//01111110 - b
	char c = a + b;//整型提升+截断
	//整型提升
	//00000000000000000000000000000101-a
	//00000000000000000000000001111110-b
	//00000000000000000000000010000011
	//10000011 - c
	printf("%d\n", c);
	//%d 十进制的方式打印有符号整数,对c整型提升
	//11111111111111111111111110000011
	//11111111111111111111111110000010
	//10000000000000000000000001111101
	//-125
	return 0;
	//输出:-125
}

(4)小试牛刀

📝1、下面代码输出的结果是什么?

#include<stdio.h>
int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;
	if (a == 0xb6)
		printf("a");
	if (b == 0xb600)
		printf("b");
	if (c == 0xb6000000)
		printf("c");
	return 0;
}


📝2、下面代码输出结果是什么?

#include<stdio.h>
int main()
{
    //sizeof返回值是无符号整型,可以用u%打印
 	char c = 1;
 	printf("%u\n", sizeof(c));
 	printf("%u\n", sizeof(+c));
 	printf("%u\n", sizeof(-c));
 	return 0; 
}
//输出:1 4 4

为什么程序输出结果存在4 呢?正是因为只要char或short存在于表达式中,即使不计算也会发生整型提升,因此会输出4

2、算术转换

(1)寻常算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。
转换条件:表达式中存在不同类型,并且大于等于int

下面的层次体系称为寻常算术转换:👇


注意:有些转换可能会丢失精度
如:
float f = 3.14;
int num = f;

(2)一道变态的算术转换题

📝下面程序输出结果是什么?

#include <stdio.h>
int i;
int main()
{
    i--;
    if (i > sizeof(i))
    {
        printf(">\n");
    }
    else
    {
        printf("<\n");
    }
    return 0; 
}

解析:C语言中,0为假,非0即为真。全局变量,没有给初始值时,编译其会默认将其初始化为0。i的初始值为0,i- - 结果-1,i为整形,sizeof(i)求i类型大小是4,按照此分析来看,输出结果为<,但是sizeof的返回值类型实际为无符号整形,因此编译器会自动将左侧i自动转换为无符号整形的数据(算术转换),-1对应的无符号整形是一个非常大的数字,超过4或者8,故输出结果为>这道题其实很隐蔽,真是虾仁猪心!!!

二、操作符的属性

1、操作符的三种属性

复杂表达式的求值有三个属性,也就是三个影响的因素:

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序。

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性

2、操作符属性表

注:

  1. 从上往下操作符的优先级越来越低。优先级最高位()最低为
  2. N/A表示不具有结合性;L-R从左向右结合;R-L从右向左结合。
  3. 只有逻辑与-&&、逻辑或-||条件操作符-?:逗号-,操作符控制求值顺序。

补充:控制求值顺序是指,这些操作符在表达式求值时会决定哪些值计算哪些值不计算。

操作符描述用法用例结果类型结合性是否控制求值顺序
()聚组(表达式)与表达式相同N/A
()函数调用repx(repx,…,repx)repxL-R
[ ]下标引用repx[repx]lexpL-R
.访问结构成员lexp.member_namelexpL-R
->访问结构指针成员rexp->member_namelexpL-R
++后缀自增lexp++rexpL-R
后缀自减lexp - -rexpL-R
逻辑反!rexprexpR-L
~按位取反~rexprexpR-L
+单目,表示正值+rexprexpR-L
-单目,表示负值-rexprexpR-L
++前缀自增++rexprexpR-L
- -前缀自减- -rexprexpR-L
*间接访问* rexplexpR-L
&取地址& lexprexpR-L
sizeof取其长度,以字节表示sizeof rexp sizeof(类型)rexpR-L
(类型)类型转换(类型)rexprexpR-L
*乘法rexp * rexprexpL-R
/除法rexp / rexprexpL-R
%整数取余rexp % rexprexpL-R
+加法rexp + rexprexpL-R
-减法rexp - rexprexpL-R
<<左移位rexp << rexprexpL-R
>>右移位rexp >> rexprexpL-R
>大于rexp > rexprexpL-R
>=大于等于rexp >= rexprexpL-R
<小于rexp < rexprexpL-R
<=小于等于rexp <= rexprexpL-R
==等于rexp == rexprexpL-R
!=不等于rexp != rexprexpL-R
&位与rexp & rexprexpL-R
^位异或rexp ^rexprexpL-R
|位或rexp | rexprexpL-R
&&逻辑与rexp && rexprexpL-R
||逻辑或rexp ||rexprexpL-R
?:条件操作符rexp?rexp:rexprexpN/A
=赋值lexp=rexprexpR-L
+=以…加lexp += rexprexpR-L
- =以…减lexp - = rexprexpR-L
*=以…乘lexp *= rexprexpR-L
/ =以…除lexp / = rexprexpR-L
%=以…取模lexp %= rexprexpR-L
<< =以…左移lexp << = rexprexpR-L
>> =以…右移lexp >> = rexprexpR-L
& =以…与lexp &= rexprexpR-L
^ =以…异或lexp ^ = rexprexpR-L
| =以…或lexp |= rexprexpR-L
,逗号rexp,rexprexpL-R

当我们知道了一个表达式的优先级和结合性,我们就可以对复杂表达式进行求值运算。

📝例如:

#include <stdio.h>
int main()
{
	int a, b, c;
	a = 5;
	c = ++a;// ++a:加给a+1,结果为6,用加完之后的结果给c赋值,因此:a = 6  c = 6
	b = ++c, c++, ++a, a++;
	// 逗号表达式的优先级,最低,这里先算b=++c, b得到的是++c后的结果,b是7
	// b=++c 和后边的构成逗号表达式,依次从左向右计算的。
	// 表达式结束时,c++和,++a,a++会给a+2,给c加1,此时c:8,a:8,b:7
	b += a++ + c; // a先和c加,结果为16,在加上b的值7,比的结果为23,最后给a加1,a的值为9
	printf("a = %d b = %d c = %d\n:", a, b, c); // a:9, b:23, c:8
	return 0;
}
//输出:a=9 b=23 c=8

3、问题表达式

通过操作符的属性已经可以解决绝大多数的表达式求值,但是如果操作符使用不当很可能造成问题表达式。

📝问题表达式1

a*b + c*d + e*f
注释:代码1在计算的时候,由于*+的优先级高,只能保证,*的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行。

📝问题表达式2

int fun()
{
     static int count = 1;
     return ++count; }
int main()
{
     int answer;
     answer = fun() - fun() * fun();
     printf( "%d\n", answer);//输出多少?
     return 0; }

注释:虽然在大多数的编译器上求得结果都是相同的。但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。

📝问题表达式3

#include <stdio.h>
int main()
{
 	int i = 1;
 	int ret = (++i) + (++i) + (++i);
 	printf("%d\n", ret);
 	printf("%d\n", i);
 	return 0; 
}

注释:这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。因此在不同的编译器上运算结果不同。

总结

本章主要介绍了两种隐式类型转换以及操作符属性,重点在于理解并掌握两种转换方式,以及灵活运用操作符属性进行表达式求值。

特别注意:
我们在写表达式时,要保证在求值时计算路径是唯一的。如果我们写出的表达式不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。所以请不要折磨编译器,杜绝写出以上华而不实的代码!!!

在这里插入图片描述

  • 48
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 32
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不摸鱼的程序员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值