前言
本文章详细地和各位小伙伴展示了我对C语言操作符的理解,欢迎大家一起讨论!
一、操作符分类
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员调用
二、算术操作符
+ - * / %
1.除了%操作符之外,其他的几个操作符可以作用于整数和浮点数。
2.对于/操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行就是浮点数除法。
#include<stdio.h> void main() { //下面两个表达式都是以整数为操作数 int a = 10 / 3; double b = 10 / 3; //一个操作数为小数形式 double c = 10.0 / 3; //两个操作数都为小数形式 double d = 10.0 / 3.0; printf("a=%d,b=%f,c=%f,d=%f", a, b, c, d); }
注意:不管最终接收的数据是int型,还是float型,计算结果的值都是由操作数本身的数据类型来控制的。
3.%操作符的两个操作数必须为整数。返回的是整数之后的余数。
int main() { int a = 7 % 3;//余1 printf("%d\n", a); return 0; }
三、移位操作符
<< 左移操作符
>> 右移操作符
注意:移位操作符的操作数只能是整数(因为是转化成二进制进行移位操作)
1.左移操作符
#include<stdio.h> void main() { int a = 2; int b = a << 1; printf("a=%d,b=%d", a, b); }
想要了解此操作符的意义,那我们就必须将十进制2转化为对应的int的二进制数
①十进制数2对应的二进制数:00000000000000000000000000000010(32位)
②a<<1表示将此二进制数的所有位都向移动一位,左边丢弃,右边补0。也就得到
00000000000000000000000000000100(32位)也就是提升了此数的一个权位,来到了2^2部分,所以左移操作符相当于该数乘以2。
注意:虽然说a是进行了左移,但不意味着a的值有变化,只是将a进行左移后的值赋值给b,他本身没有发生变化。
③还有如下代码证明
2.右移操作符
移位规则:首先右移运算分为两种
①逻辑移位:左边用0填充,右边丢弃。
②算术移位:左边用原该值的符号位填充,右边丢弃。
例子1:以正整数为例
首先由于这个4是正整数,所以无论是进行逻辑移位,还是算术移位,都是在往左边补0
①十进制正整数4的二进制数为:00000000000000000000000000000100(32位)
②右移之后就变成了:00000000000000000000000000000010(32位)
③从上面的例子不难总结右移操作符就是在原来的数除以2。
例子2:以负整数为例
首先我们得知道负数是以补码的形式进行存储的,那么怎么将一个数从原码转化成补码呢?转换顺序为:原码->反码->补码,如下所示:
(1)原码:直接根据数值写出的二进制序列就是原码。
(2)反码:原码的符号位不变,其他位按位取反就是反码。
(3)补码:反码+1,就是补码。
注意:正整数的补码就是他的原码
我们知道了原码怎么转变为补码后,就来推推这个列子
①十进制数的-1的二进制原码是:10000000000000000000000000000001(第一位的数值表示正负号,0表示正,1表示负)。
②对应的反码:11111111111111111111111111111110。
③对应的补码:11111111111111111111111111111111。
④对11111111111111111111111111111111进行右移一位(算术右移)得到的的二进制数为:11111111111111111111111111111111,那么该值也是为-1的补码。
注意:内存里是以补码形式存储,但是结果是以原码形式展现的。
警告: 对于移位运算符,不要移动负数位,这个是标准为定义的,例如:
四、位操作符
位操作符有:
& 按位与
| 按位或
^ 按位异或
注意:他们的操作数都必须是整数
1.&按位与
按位与规则:只有两个对应位置的数都为1,结果才为1;如果有是0对1或者0对0,结果都为0,包括符号位也要变化。(有点像逻辑操作符的&&)
例子1:以正整数和正整数为例
①我们得知道 3,5所对应的补码,如上所示,
②然后按照对应的规则进行按位与&
例子2:以负整数和正整数为例
#include<stdio.h> void main() { int a = -2; int b = 3; int c = a & b; //-2原码:10000000000000000000000000000010 //-2反码:11111111111111111111111111111101 //-2补码 11111111111111111111111111111110 //3的补码 00000000000000000000000000000011 //-2 & 3 为:00000000000000000000000000000010(结果为2) printf("c=%d", c); }
①还是一样得知道-2的补码,3的补码。
②然后按照对应的规则进行按位与&。
例子3:以负整数和负整数为例
#include<stdio.h> void main() { int a = -1; int b = -2; int c = a & b; //-1的补码:11111111111111111111111111111111 //-2的补码:11111111111111111111111111111110 //-1&-2:11111111111111111111111111111110(记为x) //x的反码:11111111111111111111111111111101 //x的原码:10000000000000000000000000000010(为-2) printf("c=%d", c); }
①首先得知道-2,-1的补码
② 按照对应的规则进行按位与&,结果为-2
2. | 按位或
按位或规则:只要对应的位置有一个1,此位置生成的数也是1,如0对1或者1对1,结果都为1;如果对应的是0对0,那么结果就是0,包括符号位也要变化。(有点像逻辑操作符的||)
例子1:以正整数和正整数为例
①知道5,3的补码
②按照按位或的规则进行运算,得出结果为7
例子2:以负整数和正整数为例
#include<stdio.h> void main() { int a = -2; int b = 3; int c = a | b; //-2原码:10000000000000000000000000000010 //-2反码:11111111111111111111111111111101 //-2补码 11111111111111111111111111111110 //3的补码 00000000000000000000000000000011 //-2 | 3 为:11111111111111111111111111111111(记为x) //x的原码为: //11111111111111111111111111111110(反码) //10000000000000000000000000000001(原码)结果为-1 printf("c=%d", c); }
①首先得知道-2的补码和3的补码
②然后按照按位或的规则进行运算,得出结果为-1
例子3:以负整数和负整数例子
#include<stdio.h> void main() { int a = -1; int b = -2; int c = a | b; //-1的补码:11111111111111111111111111111111 //-2的补码:11111111111111111111111111111110 //-1|-2:11111111111111111111111111111111(记为x) //x的反码:11111111111111111111111111111110 //x的原码:10000000000000000000000000000001(为-1) printf("c=%d", c); }
①的指导-1和-2的补码
②然后按照按位或的规则进行运算,得出结果为-1
3.^按位异或
按位异或规则:按(2进制)位异或,对应的二进制位进行异或,相同为0,相异为1,包括符号位也要变化。
例子1:以正整数和正整数为例
例子2:以正整数和负整数为例
例子3:以负整数和负整数为例
4.位操作符的练习题
例1:不能创建临时变量(第三个变量),实现两个数的交换
#include<stdio.h> void main() { int a = 5; int b = 3; a = a ^ b; b = a ^ b; a = a ^ b; printf("a=%d,b=%d", a, b); }
其实我们还可以从另一个角度去理解:首先我们的指导0^任何数的值都为该数的值;一个数去^自己本身的值为0,例子如下:
那么a ^ b ^ b可以理解为:b ^ b == 0,a ^ 0 = a,那么b = a(此时b的值就为a的值了,完成了交换)
解决两个数值交换的方式还有很多,如下:
int main() { int a = 3; int b = 5; //交换 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); int c = 0;//空瓶 printf("a = %d b = %d\n", a, b); c = a; a = b; b = c; printf("a = %d b = %d\n", a, b); return 0; }
例2:编写代码实现求一个整数存储在内存中的二进制中1的个数
方法一:运用了位操作符和移位操作符
思路如下图:
代码如下:
void main() { int a = 5; int count = 0; int i; for (i = 0;i < 32;i++) { if (a & (1 << i)) { count++; } } printf("%d", count);//结果为2 }
方法二:利用十进制转二进制的思路,用余数来记录1的个数
void main() { int a = 5; int count = 0; int i; while (a) { if (a % 2 == 1) { count++; } a /= 2; } printf("%d", count);//结果为2 }
方法三:采用相邻的两个数据进行按位与运算
#include<stdio.h> void main() { int a = 5; int count = 0; while (a) { a = a & (a - 1); count++; } printf("%d", count);//结果为2 }
五、赋值操作符
赋值操作符是一个很好用的操作符,他可以让你重新对一个变量进行赋值。(赋值操作符为“=”)
void main() { int weight = 120;//体重 weight = 100;//使用赋值操作符重新赋值 double salary = 10000.0;//money salary = 200000.0;//使用赋值操作符重新赋值 }
int a = 10; int x = 0; int y = 20; //连续赋值,一般不会这么用,影响了可读性 a = x = y + 1;
1.复合赋值符
- +=:a += b,a与b的和赋值给a
- -=:a -= b,a与b的差赋值给a
- *=:a *= b,a与b的积赋值给a
- /=:a /= b,a与b的商赋值给a
- %=:a %= b,a与b的余数赋值给a
- >>=:a >>= b,a右移b个位后的值赋值给a
- <<=:a <<= b,a左移b个位后的值赋值给a
- &=:a &= b,a与b按位与后的值赋值给a
- |=:a |= b,a与b按位或后的值赋值给a
- ^=:a ^= b,a与b按位异或后的值赋值给a
这些操作符都可以达到复合的效果!
int a = 10; a = a + 10; a += 10;//复合赋值 //其他运算符一样的道理,这样写更加简洁
六、单目操作符
1.单目操作符介绍
- ! 逻辑反操作
- - 负值
- + 正值
- & 取地址
- sizeof 操作数的类型长度(以字节为单位)
- ~ 对一个数的二进制按位取反
- -- 前置、后置--
- ++ 前置、后置++
- * 间接访问操作符(解引用操作符)
- (类型) 气质类型转换
2.!逻辑反操作符
int main() { int flag = 0; printf("%d\n", !flag); //flag为真,打印hehe if (flag) { printf("hehe\n"); } //flag为假,打印haha if (!flag) { printf("haha\n"); } return 0; }
3.sizeof操作符
3.1 sizeof操作符的注意事项
sizeof可以用来求变量(类型)所占的空间大小
#include<stdio.h> void main() { short s = 5; int a = 10; printf("%d\n", sizeof(s = a + 2)); printf("%d\n", s); /* sizeof的执行是发生在编译时期,而a + 2的运算是发生在运行时期。 所以这里的sizeof是发生在a + 2之前的,所以不会有类型提升的说法, 同时不管你类型是否提升,最终都是放到short型里,所以都是short型。 sizeof在编译期就结束了,sizeof只想知道你的类型是什么,它不管你 是否有进行了运算,就直接结束了。 */ int arr[10] = { 0 }; printf("%d\n", sizeof(arr));//单位是字节 printf("%d\n", sizeof(int[10]));//40 - int [10]是arr数组的类型 printf("%d\n", sizeof(a));//计算a所占空间的大小,单位是字节 printf("%d\n", sizeof(int)); printf("%d\n", sizeof a);//说明了sizeof是一个操作符而不是一个函数 }
编译结果如下:
3.2 sizeof操作符和数组
#include <stdio.h> void test1(int arr[]) { printf("%d\n", sizeof(arr));//(2) } void test2(char ch[]) { printf("%d\n", sizeof(ch));//(4) } int main() { int arr[10] = { 0 }; char ch[10] = { 0 }; printf("%d\n", sizeof arr);//(1) printf("%d\n", sizeof(ch));//(3) test1(arr); test2(ch); return 0; }
问:(1)(2)(3)(4)分别是输出什么?答案如下:
位置(1):sizeof arr就是输出int arr[10]的数组长度,也就是40个字节。
位置(2):sizeof(ch)就是输出char ch[10]的数组长度,也就是10个字节。
位置(3):首先我们得知道,传到函数test1()里面的实参虽然是arr,但是它的本质其实是一个指针,也就是int* arr,所以test1()函数里面的sizeof(arr)就是输出int的大小,也就是4个字节。
位置(4):同理于位置(3),也就是输出指针char* ch的大小,也就是4个字节。
注意:无论是什么类型的,他的地址长度是看操作位数决定的32位就是4个字节,64位就是8个字节。
4.~按位取反
int main() { int a = -1; //10000000000000000000000000000001 - 原码 //11111111111111111111111111111110 - 反码 //11111111111111111111111111111111 - 补码 //~ 按位取反 //11111111111111111111111111111111 //00000000000000000000000000000000 // int b = ~a; printf("%d\n", a); printf("%d\n", b); return 0; }
按位取反操作符是连同符号位也会进行取反。
5.自加自减举例
#include <stdio.h> void main() { int a = 10; printf("%d\n", a--);//10 printf("%d\n", a);//9 }
注意:后置++或者后置--,都是等到整条语句结束的时候才进行自加或自减。
6.&取地址操作符和*解引用操作符
int main() { int a = 10; printf("%p\n", &a);//& - 取地址操作符 int * pa = &a;//pa是用来存放地址的 - pa就是一个指针变量 *pa = 20;//* - 解引用操作符 - 间接访问操作符 printf("%d\n", a);//20 return 0; }
七、关系操作符
- >
- >=
- <
- <=
- != 用于测试“不相等”
- == 用于测试“相等”
这些关系运算符比较简单,没什么可细述,注意别在编程中把==和=混淆就行。
八、逻辑操作符
逻辑操作符有哪些:
- && 逻辑与
- || 逻辑或
注意:区分逻辑与和按位与;区分逻辑或和按位或。
1&2----->0
1&&2---->1
1|2----->3
1||2---->1
例题:360笔试题
#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 = 1
b = 2
c = 3
d = 4
九、条件操作符
条件操作符也称为三目运算符
结构:exp1 ? exp2 : exp3
int main() { int a = 3; int b = 0; if (a > 5) b = 1; else b = -1; //三目操作符 b = (a > 5 ? 1 : -1); return 0; }
十、逗号表达式
结构:exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
int main() { int a = 3; int b = 5; int c = 0; //逗号表达式 - 要从做向右依次计算,但是整个表达式的结果是最后一个表达式的结果 int d = (c = 1, a = c + 3, b = a - 4, c += b); //c=10 a=8 b=4 printf("%d\n", d);//结果为10,也就是c的值 return 0; }
十一、下标引用、函数调用和结构成员
1.[ ]下标引用操作符
操作数:一个数组名+一个索引值
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; // 0 1 4 printf("%d\n", arr[4]);//[] - 就是下标引用操作符 //[] 的操作数是2个:arr , 4 return 0; }
2.( )函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的实参。
//函数的定义 int Add(int x, int y) { return x + y; } void test() {} int main() { int a = 10; int b = 20; //函数调用 int ret = Add(a, b);//() - 函数调用操作符 test(); return 0; }
3.访问一个结构体成员
. 结构体.成员名
-> 结构体指针->成员名
struct Book { //结构体的成员(变量) char name[20]; char id[20]; int price; }; int main() { //int num = 10; //结构体变量名.成员名 struct Book b = {"C语言", "C20210509", 55}; struct Book * pb = &b; //结构体指针->成员名 printf("书名:%s\n", pb->name); printf("书号:%s\n", pb->id); printf("定价:%d\n", pb->price); //利用*解引用知道结构体变量b,在用点操作符去访问结构体成员 printf("书名:%s\n", (*pb).name); printf("书号:%s\n", (*pb).id); printf("定价:%d\n", (*pb).price); //直接用结构体变量b加点操作符去访问结构体成员 printf("书名:%s\n", b.name); printf("书号:%s\n", b.id); printf("定价:%d\n", b.price); return 0; }
十二、表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样有些表达式操作符在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
c的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要自CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特位直接相加运算(虽然机器指令中可能有这种字节的相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行。
例子1:
int main() { char a = 3; //00000000000000000000000000000011 //00000011 - a char b = 127; //00000000000000000000000001111111 //01111111 - b char c = a + b; //00000000000000000000000000000011 //00000000000000000000000001111111 //00000000000000000000000010000010 //10000010 - c,此数就是将原来的值截断 //%d是按int类型输出,所以这里又是类型提升 printf("%d\n", c); //-126 //11111111111111111111111110000010 - 补码 //11111111111111111111111110000001 - 反码 //10000000000000000000000001111110 - 原码 //-126 //发现a和b都是char类型的,都没有达到一个int的大小 //这里就会发生整形提升 return 0; }
这里因为%d要按照int类型输出,所以就发生了隐式类型提升,符号位是什么就往左补符号位相对应的数。也就是整型提升是按照变量的数据类型的符号位来提升。
例子2:
//负数的整形提升 char c1 = -1; 变量c1的二进制位(补码)中只有8个比特位: 1111111 因为 char 为有符号的 char 所以整形提升的时候,高位补充符号位,即为1 提升之后的结果是: 11111111111111111111111111111111 //正数的整形提升 char c2 = 1; 变量c2的二进制位(补码)中只有8个比特位: 00000001 因为 char 为有符号的 char 所以整形提升的时候,高位补充符号位,即为0 提升之后的结果是: 00000000000000000000000000000001 //无符号整形提升,高位补0
例子3:
//实例1 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; }
例子3中的a,b要进行整型提升,但是c不需要整型提升
a,b整型提升之后,变成了负数,所以表达式a==0xb6,b==0x600的结果为假,但是c不发生整型提升,则表达式c==0x6000000的结果为正
结果输出c
例子4:
(一)
(二)
首先我们得知道一个运算表达式他是具有两个属性,分别是值属性和类型属性。
值属性:是通过运算后得到的值,可以理解为是在运行时期发生。
类型属性:是通过推断后得到的,可以理解为在编译时期就发生了。
(一)c本身没进行运算,所以结果为1;第2、第3和第4条输出语句中都有运算表达式,所以发生了类型属性推断,也就是隐式类型转换。(在gcc环境中,第四条语句的结果为4,vs的编译环境不太对!所以为1)
(二)首先我们知道了sizeof的类型判断和运算表达式的类型推断都是发生在编译期,所以我们就可以分为两部分来看
①先看s = a + 3,不管它在运算时怎么类型转换,最终都是short型,那么此运算表达式的类型推断为short型。
②sizeof得知了s = a + 3的类型推断为short型,那么它的值也就是2。
2.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系成为寻常算术转换。
- long double
- double
- float
- unsigned long int
- long int
- unsigned int
- int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。但是算术转换要合理,要不然会有一些潜在的问题。
3.操作符的属性
复杂表达式的求值有三个影响的因素
(1)操作符的优先级
(2)操作符的结合性
(3)是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符的优先级如下:
操作符的优先级
操作符 | 描述 | 结合性 | 是否控制求值顺序 |
() | 聚组 | N/A | 否 |
() | 函数调用 | L-R | 否 |
[ ] | 下标引用 | L-R | 否 |
. | 访问结构体成员 | L-R | 否 |
-> | 访问结构指针成员 | L-R | 否 |
++ | 后缀自增 | L-R | 否 |
-- | 后缀自减 | L-R | 否 |
! | 逻辑反 | L-R | 否 |
~ | 按位取反 | R-L | 否 |
+ | 单目,表示正值 | R-L | 否 |
- | 单目,表示负值 | R-L | 否 |
++ | 前缀自增 | R-L | 否 |
-- | 前缀自减 | R-L | 否 |
* | 间接访问(解引用) | R-L | 否 |
& | 取地址 | R-L | 否 |
sizeof | 取其长度,以字节表示 | R-L | 否 |
(类型) | 类型转换 | R-L | 否 |
* | 乘法 | L-R | 否 |
/ | 除法 | L-R | 否 |
% | 整数取余 | L-R | 否 |
+ | 加法 | L-R | 否 |
- | 减法 | L-R | 否 |
<< | 左移位 | L-R |
否 |
>> | 右移位 | L-R | 否 |
> | 大于 | L-R | 否 |
>= | 大于等于 | L-R | 否 |
< | 小于 | L-R | 否 |
<= | 小于等于 | L-R | 否 |
== | 等于 | L-R | 否 |
!= | 不等于 | L-R |
否 |
& | 按位与 | L-R | 否 |
^ | 按位异或 | L-R | 否 |
| | 按位或 | L-R | 否 |
&& | 逻辑与 | L-R | 是 |
|| | 逻辑或 | L-R | 是 |
?: | 条件操作符 | N/A |
否 |
= | 赋值 | R-L | 否 |
+= | 加后赋值 | R-L | 否 |
-= | 减后赋值 | R-L | 否 |
*= | 乘后赋值 | R-L | 否 |
/= | 除后赋值 | R-L | 否 |
%= | 取余后赋值 | R-L | 否 |
<<= | 左移后赋值 | R-L | 否 |
>>= | 右移后赋值 | R-L | 否 |
&= | 按位与后赋值 | R-L | 否 |
^= | 按位异或后赋值 | R-L | 否 |
|= | 按位或后赋值 | R-L | 否 |
, | 逗号 | L-R | 是 |
总结:
(1)单(目)>算(术)>关(系)>逻(辑)>赋(值)
(2)单目运算符和赋值运算符:自右至左结合
(3)其它的:自左到右
4.一些问题表达式
例子1:
//表达式的求值部分由操作符的优先级决定。 //表达式1 a*b + c*d + e*f
代码在计算的时候,由于*比+优先级高,只能保证*的计算比+早,但是优先级并不能决定第三个*比第一个+早执行
所以表达式在计算机顺序就可能是:
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:
//表达式2 c + --c;
同上的道理,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的做操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是由歧义的。
例子3:
//代码3-非法表达式 int main() { int i = 10; i = i-- - --i * ( i = -3 ) * i++ + ++i; printf("i = %d\n", i); return 0; }
例子3在不同的编译器中测试结果:非法表达式程序的结果
值 | 编译器 |
-128 | Tandy 6000 Xenix3.2 |
-95 | Think C 5.02(Macintosh) |
-86 | IBM PowerPC AIX 3.2.5 |
-85 | Sun Sparc cc(K&C编译器) |
-63 | gcc |
4 | Sun Sparc acc(K&C编译器) |
21 | Turbo C/C++ 4.5 |
42 | Microsoft C 5.1 |
例子4:
//代码4 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();中我们只能通过操作符的优先级得知:先算乘法,再算减法。
其实也可以换个思路来理解:函数调用操作符()的优先级高于算术操作符(也叫算术运算符),并且他的操作顺序是从左往右的,等函数调用操作符运算完成之后,才开始算术运算符的操作。显然这并不是我们想要的执行顺序,我们的思路和计算机的运算规律产生了歧义。
例子5:
//代码5 #include <stdio.h> int main() { int i = 1; int ret = (++i) + (++i) + (++i); printf("%d\n", ret); printf("%d\n", i); return 0; } //尝试在linux 环境gcc编译器,VS2019环境下都执行,看结果。
VS2013的环境下:
有的小伙伴就感觉奇怪了,为什么不是2+3+4=9呢?而是4+4+4=12呢?
(1)这就需要我们去了解一下汇编语言,首先得知道计算机有许多寄存器(读取效率:寄存器>内存>磁盘)(寄存器有:eax、ebx、ecx、edx、ebp、esp等等)。
(2)用vs2019进行代码调试,在ret变量那里打开反汇编,就可以看到此例子第2张图片的界面,我们可以发现i的值是寄存到ecx寄存器中的,由寄存器去帮我们进行运算,最终得到一个值,也就是说他是执行了3次++i后才进行的算术运算,所以结果就为12。
(3)可以理解为变量i是从寄存器ecx中取值的步骤,而ecx只有一个且是统一执行到结束,里面存放的结果就是4,所以所有的i都为4。
在调试的状态下,对变量ret右键,就可以看到有转到反汇编的指令,点击就行了。
呼!12340个字,终于写完了,重新对操作符进行学习,加深了我对C语言操作符的认识,感觉还挺有趣的!
点个赞呗!