目录
一、操作符的属性:
✨操作符有2个属性:🐸优先级🐸结合性,这2个属性决定了表达式求值的顺序
👉优先级:
指的是在一个表达式中,当含有多个操作符,哪一个运算符先进行计算
比如:在一个有多个运算符的数学式中 我们一般都是先算乘除再算加减
👉在这里 * 的优先级高于 + 所以先算* 再算+
👉结合性:
在一个表达式中,当运算符的优先级相同,此时无法通过优先级判断谁先进行,就需要根据结合性来判断 ,结合性2种:左结合(从左向右)和右结合(从右向左),但是大多数运算符是 左结合 ,少数是右结合(= 运算符)
比如:数学式子乘法除法一起出现时,是不是从左往右边计算,先算乘法再算除法
👉在这里c语言表达式种也一样的,* 和 / 是一个级别的,根据左结合性的原则,先计算 * 再计算 /
c 运算符的优先级:👉点击
二、 表达式求值:
1、整形提升:
✨C语⾔中整型算术运算(算术表达式)总是⾄少以缺省整型类型的精度来进⾏的。为了获得这个精度,表达式中的字符(char)和短整型操作数(shrot)在使⽤之前被转换为普通整型(int),这种转换称为整型提升。
整形提升的原则:
1. 有符号整数提升是按照变量的数据类型的符号位来提升的 signed
✨ 最高位是 1 补1 最高位是 0 则补 0
2. ⽆符号整数提升 unsigned
✨⾼位补0
整形提升的意义:
✨表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀般就是int的字节⻓度 同时也是CPU的通⽤寄存器的⻓度。
因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓度(int型长度)
通⽤CPU(general-purposeCPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执⾏运算。
🐸康康下面的式子 char型再vs中 或者说再绝大部分编译器中 是 有符号的char型
先将a和b存好
🧑🎓表达式c=a+b就是一个算术运算表达式,需要进行先进行整形提升 再进行算术运算
提升完之后,存入c里面当然是要先截断滴,因为char型在内存就只占了一个字节的内存这个就是 c 的 值
👉最后因为是以%d的形式打印c,说明要以int型的形式打印c,又要进行对c整形提升🧑🎓
✨✨内存里面存的是补码 打印的是原码✨✨
2、算术转化:
如果某个操作符的各个操作数不是相同的类型,此时除⾮其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就⽆法进⾏;下⾯的层次体系称为寻常算术转换 👉
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算(排名后的转换为排名靠前的)
👉理解下:大哥和小弟的关系,大哥的大哥也是你的大哥,那么也就是说小弟要跟着大哥走,大哥是什么类型,小弟就是什么类型,作小弟的要有小弟的觉悟✨
看下面:第一眼你觉得打印那个,是不是else分支处的printf
🐸但是不好意思。错了打印的是if语句下的printf 👨
👉👉d为什么呢????算术转换的思想就来了,你看看表达式,运算符两边的操作数不是同一个类型,int 型是有符号整形 sizieof计算的值式 size_t类型,可以理解为无符号的整形
所以这里发生了类型转换,int 型式最小的小弟 ,要将 -1转换成无符号整形
-1的无符号整形是一个非常大的数
3、问题表达式分析;
//表达式的求值部分由操作符的优先级决定。
//表达式1
a*b + c*d + e*f
在这里分析一下,在这里
看出来了吗??有2种可能,虽然计算的结果是一样的,注意!!!!!和计算机对话与我们和人交流时不同的,他的逻辑性很强,你的计算路径有2种 无法确定的具体是哪一个,会出错的
如果a、b、c、d、e、f 都是表达式的话,表达式也这样计算,此时就会出现错误
✨✨改进:加括号、或者拆开写(不要写这么复杂的表达式)
//表达式2
c + --c
如果c=5了,前置-- 的优先级高于 + ;在计算时,要先将变量放入寄存器
无法确定c以哪个状态放进去,下面是在VS中,但是在其他编译器就不得而知了
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
在不同编译器中测试结果:⾮法表达式程序的结果都是不同的,可以看《c和指针》里面测试的,看到的第一眼就头疼
//表达式4
#include <sdtio.h>
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);//输出多少?
return 0;
}
👉在这里无法确定先调用那个函数,优先级是决定了先算那个操作符,和什么时候调用fun是无关的 ✨函数的调⽤先后顺序⽆法通过操作符的优先级确定
//表达式5
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
gcc中的结果
![]()
vs中的结果
看反汇编也能发现:先增加的i,最后才相加
这段代码中的第⼀个 + 在执⾏的时候,第三个++是否执⾏,这个是不确定的,因为依靠操作符的优先级和结合性是⽆法决定第⼀个 + 和第三个前置 ++ 的先后顺序
总结:即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别复杂的表达式,👉👉表达式要保证计算路径唯一
好了,就到这里结束了!