目录
表达式求值
操作符的属性
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
复杂表达式的求值有三个影响的因素。
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序。//&& || , ?:这四个可以控制
C语言的运算符包括单目运算符、双目运算符、三目运算符,优先级如下:
第1优先级:各种括号,如()、[]等、成员运算符 . ;
第2优先级:所有单目运算符,如++、--、!、~等;
第3优先级:乘法运算符*、除法运算符/、求余运算符%;
第4优先级:加法运算符+、减法运算符-;
第5优先级:移位运算符<<、>>;
第6优先级:大于运算符>、大于等于运算符>=、小于运算符<、小于等于运算符<=;
第7优先级:等于运算符==、不等于运算符!=;
第8优先级:按位与运算符&;
第9优先级:按位异或运算符^;
第10优先级:按位或运算符|;
第11优先级:逻辑与运算符&&;
第12优先级:逻辑或运算符||;
第13优先级:三目条件运算符 ?: ;
第14优先级:各种赋值运算符,如=、+=、-=、*=、/= 等;
第15优先级:逗号运算, 。
操作符号表:
操作符 | 描述 | 用法示例 | 结果类型 | 结合性 | 控制求值顺序 |
---|---|---|---|---|---|
( ) | 聚组 | (表达式) | 与表达式同 | N/A | 否 |
( ) | 函数调用 | rexp(rexp,……) | rexp | L-R | 否 |
[ ] | 下标引用 | rexp[rexp] | lexp | L-R | 否 |
. | 访问结构成员 | lexp.mem_name | lexp | L-R | 否 |
-> | 访问结构体指针 | lexp->mem_name | lexp | L-R | 否 |
++ | 后缀自增 | lexp++ | rexp | L-R | 否 |
-- | 后缀自减 | lexp-- | rexp | L-R | 否 |
! | 逻辑反 | !lexp | rexp | R-L | 否 |
~ | 按位取反 | ~lexp | rexp | R-L | 否 |
+ | 单目表示正值 | +rexp | rexp | R-L | 否 |
- | 单目表示负值 | -rexp | rexp | R-L | 否 |
++ | 前缀自增 | ++rexp | rexp | R-L | 否 |
-- | 前缀自减 | --rexp | rexp | R-L | 否 |
* | 间接访问 | *rexp | lexp | R-L | 否 |
& | 取地址 | &lexp | rexp | R-L | 否 |
sizeof | 取长度,字节表示 | sizeof rexp/(类型) | rexp | R-L | 否 |
(类型) | 类型转换 | (类型)rexp | rexp | R-L | 否 |
* | 乘法 | rexp*rexp | rexp | L-R | 否 |
/ | 乘法 | rexp/rexp | rexp | L-R | 否 |
% | 整数取余 | rexp%rexp | rexp | L-R | 否 |
+ | 加法 | rexp+rexp | rexp | L-R | 否 |
- | 减法 | rexp-rexp | rexp | L-R | 否 |
<< | 左位移 | rexp<<rexp | rexp | L-R | 否 |
>> | 右位移 | rexp>>rexp | rexp | L-R | 否 |
> | 大于 | rexp>rexp | rexp | L-R | 否 |
>= | 大于等于 | rexp>=rexp | rexp | L-R | 否 |
< | 小于 | rexp<rexp | rexp | L-R | 否 |
<= | 小于等于 | rexp<=rexp | rexp | L-R | 否 |
== | 等于 | rexp==rexp | rexp | L-R | 否 |
!= | 不等于 | rexp!=rexp | rexp | L-R | 否 |
& | 位与 | rexp&rexp | rexp | L-R | 否 |
^ | 位异或 | rexp^rexp | rexp | L-R | 否 |
| | 位或 | rexp|rexp | rexp | L-R | 否 |
&& | 逻辑与 | rexp&&rexp | rexp | L-R | 是 |
|| | 逻辑或 | rexp||rexp | rexp | L-R | 是 |
?: | 条件操作符 | rexp?rexp:rexp | rexp | N/A | 是 |
= | 赋值 | lexp=rexp | rexp | R-L | 否 |
+= | 以…加 | lexp+=rexp | rexp | R-L | 否 |
-= | 以…减 | lexp-=rexp | rexp | R-L | 否 |
*= | 以…乘 | lexp*=rexp | rexp | R-L | 否 |
/= | 以…除 | lexp/=rexp | rexp | R-L | 否 |
%= | 以…取余 | lexp%=rexp | rexp | R-L | 否 |
<<= | 以…左移 | lexp<<=rexp | rexp | R-L | 否 |
>>= | 以…右移 | lexp>>=rexp | rexp | R-L | 否 |
&= | 以…与 | lexp&=rexp | rexp | R-L | 否 |
^= | 以…异或 | lexp^=rexp | rexp | R-L | 否 |
|= | 以…或 | lexp|=rexp | rexp | R-L | 否 |
, | 逗号 | rexp,rexp | rexp | L-r | 是 |
L-R从左向右结合
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
有些表达式仍然不能完全判断计算:a * b + b * c + c * d
判断1
c + --c = 2c - 1/2c
非法表达式判断2
int main()
{
int i = 10;
i = i-- - --i * ( i = -3) * i++ + ++i;
printf("i = %d",i);
return 0;
}//不同的编译器测出的答案值不一样
vscode答案:36
判断3
#include<stdio.h>
int fun()
{
static int count = 1;//因为有static,所以不会重置count
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();//2 - 3 * 4
printf("%d\n",answer);//输出多少
return 0;
}
-10
在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。
判断4
#include<stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) +(++i);
printf("%d\n",i);
return 0;
}
VS - 12//eax = i+1+1+1 =4; 括号里面的都过一遍以后寄存器存储到4,然后三个一样的合起来变成12 vscode - 10//第一个和第二个先计算3+3,最后一个再算为4,合起来为10 Linux gcc - 10
寄存器:eax ebx ecx edx ebp esp
寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成。
隐式类型转换
C的整型算术运算总是至少以缺少整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。
#include<stdio.h>
int main()
{
char a,b,c;
a = b + c;
return 0;
}
//b和c的值被提升为普通整型,然后再执行加法运算。
//加法运算完成之后,结果将被截断,然后再存储于中。
整型占4个字节,字符型占1个字节
整型如何提升:
#include<stdio.h>
int main()
{
char a = 3;
//00000000000000000000000000000011
char b = 127;
//00000000000000000000000001111111
char c = a + b;
//00000000000000000000000010000010
//10000010-补码 10000001-反码 11111110-原码-126
//发现a和b都是char类型的,都没有达到一个int的大小
//这里就会发生整型提升
printf("%d\n",c);
return 0;
}
-126
//负数的整形提升 char c1 =-1; 变量c1的二进制位(补码)中只有8个比特位: 11111111 因为char为有符号的char 所以整型提升的时候,高位补充符号位,即为1 提升之后的结果是: 11111111111111111111111111111111 //正数的整形提升 char c2 1; 变量c2的二进制位(补码)中只有8个比特位: 00000001 因为char为有符号的char 所以整形提升的时候,高位补充符号位,即为0 提升之后的结果是: 00000000000000000000000000000001
实例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;
}
c
实例2
#include<stdio.h>
int main()
{
char c = 1;
printf("%u\n",sizeof c);//1
printf("%u\n",sizeof -c);//参与运算就会发生整型提升
printf("%u\n",sizeof +c);
printf("%u",sizeof !c);//编译器处理的问题 gcc环境下应该也是4
return 0;
}
1
4
4
1
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
short s = 5;
a + b;//值属性,类型属性 int
return 0;
}
算数转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。 警告:但是算术转换要合理,要不然会有一些潜在的问题。
#include<stdio.h> int main() { int a = 4; float b = 4.5; printf("%f",a+b);//a进行算数转换成float类型 return 0; }