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


    前言

    通过上期的讲解,相信大家对操作符已经有了一定的理解,其实学习操作符是为了筑基,学习操作符的根本目的是为了表达式求值,本章就以此目的继续探讨表达式求值!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
    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    (4)小试牛刀

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

    #include
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14


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

    #include
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    2、算术转换

    (1)寻常算术转换

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

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


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

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

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

    #include 
    int i;
    int main()
    {
        i--;
        if (i > sizeof(i))
        {
            printf(">\n");
        }
        else
        {
            printf("<\n");
        }
        return 0; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    解析: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 
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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; }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    📝问题表达式3

    #include 
    int main()
    {
     	int i = 1;
     	int ret = (++i) + (++i) + (++i);
     	printf("%d\n", ret);
     	printf("%d\n", i);
     	return 0; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    总结

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

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

    在这里插入图片描述

  • 相关阅读:
    年中上线,工资核算中怎么处理累计工资项?
    拒绝蛮力,高效查看Linux日志文件!
    9 STM32标准库函数 之 独立看门狗(IWDG)所有函数的介绍及使用
    13、Python -- while 循环、嵌套循环
    JS动态加载数据绑定事件 jquery delegate() 方法
    Spring中Bean的作用域
    uniapp中video播放视频上按钮没显示的问题
    利用 SonarScanner 静态扫描 Rainbond 上的 Maven 项目
    关于图片裁剪怎么弄,这里分享几款软件
    CuratorFrameworkFactory.builder()方法可配置属性
  • 原文地址:https://blog.csdn.net/LEE180501/article/details/126331675