目录
[6.1 逻辑反操作](#6.1 逻辑反操作)
[6.2 负值与正值](#6.2 负值与正值)
[6.3 取地址](#6.3 取地址)
[6.4 sizeof](#6.4 sizeof)
[6.5 取反操作符](#6.5 取反操作符)
[6.6 --和++操作符](#6.6 --和++操作符)
[6.7 间接访问操作符(解引用操作符)](#6.7 间接访问操作符(解引用操作符))
[6.8 强制类型转换](#6.8 强制类型转换)
[11.1 [] 下标引用操作符](#11.1 [] 下标引用操作符)
[11.2 () 函数调用操作符](#11.2 () 函数调用操作符)
[11.3 访问结构成员操作符](#11.3 访问结构成员操作符)
[12.1 隐式类型转换](#12.1 隐式类型转换)
[12.2 算数转换](#12.2 算数转换)
[12.3 操作符的属性](#12.3 操作符的属性)
前言
这篇文章将对C语言的操作符相关知识点,进行详细的讲解,读完本篇文章,您对C语言的了解将更进一步。
一、操作符分类
C语言中对于操作符一共分为十类:(进行简单介绍)
- 算数操作符:加减乘除取余
- 移位操作符:左移(<<),右移(>>)
- 位操作符:按位与(&),按为或(|),按位异或(^)
- 赋值操作符:简单赋值:=;复合赋值:与前三类结合
- 单目操作符:sizeof、取反~、取非!、+、-、++、--、*、强制类型转换()
- 关系操作符:大于等于小于等等
- 逻辑操作符:逻辑与(&&),逻辑或(||)
- 条件操作符:也就是三目运算符
- 逗号表达式:多个表达式由逗号分隔
- 下标引用、函数调用和结构成员:点、->、()、[]
二、算数操作符
算数操作符,也就是对应数学上的算数运算符,最常见的为:加减乘除。
不过在C语言中,还会用到取模(取余)操作,算出操作符的书写形式如下:
cpp
+:加
-:减
*:乘
/:除
%:取余
可以看出,加减书写方式与数学中是一样的,乘除与数学中的不同,这里重点讲解一下取余操作符:
cpp
#include<stdio.h>
// 算数操作符示例代码:
int main()
{
int a = 10;
int b = 3;
printf("加法:%d\n", a + b);
printf("减法:%d\n", a - b);
printf("除法:%d\n", a / b);
printf("取余法:%d\n", a % b);
return 0;
}
通过结果可知:
- 除法的结果是商。
- 取余的结果是余数
我们再看看几个例子:
对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
加减乘除都可以用在不同类型中,那取余操作符呢?
我们可以得出:
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
- % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
三、移位操作符
cpp
<<:左移
>>:右移
关于移位操作符,我们先要了解一些前备知识点。
移位,移的是什么位呢?其实是二进制的位,那二进制是什么呢?
日常生活中,我们看到的数字是用十进制形式表示的,十进制的数据都是由0~9的数字组成的。相同的,二进制是常用在计算机中,由0~1的数字组成。当然,还有8进制、16进制等等,原理相同。
列如,我们算一下10的二进制,要算10的二进制,这里就涉及到如何将10进制转为二进制的知识:
10进制转二进制,我们常用除2取余的方法:
那将二进制转为其它进制呢?方法是权重法
回归主题,在C语言中,数据是以二进制的形式存储在内存中的,而移位操作符,正是对整数的二进制位进行移动。
既然是对整数的二进制位操作,那我们就需要了解整数的二进制是怎么样的呢?
整数的二进制表示形式有3种:原码、反码、补码
C语言规定:
- 正数的原反补码都一样,而负数的原反补不一样,要通过计算。
- 整数在内存中以补码的形式进行存储,对二进制位操作时,也是针对补码,但最终显示给我们看的是原码形式,怎么理解呢?
- 一个整形有32个二进制位,其中第32个位是符号位:正数符号位为0,负数符号位为1。
- 例如int a = -2; 显示给我们看的为-2,即二进制为:符号位(1).....010,但如果我们打印&a -2的地址,我们看到的与我们计算出来的不一样,这就是在内存中以补码形式进行存储,显示时以原码形式。
我们对-2进行图解,讲解原反补的概念:
我们也可以通过编译器来看看:-5在编译器中存储
既然内存中以补码形式存储,对于正数来说,原反补都一样,那负数进行二进制操作后,如何将补码转回原码呢?
两种方法:
- 补码-1取反
- 补码取反+1
<<左移操作符:二进制位相左移动,右边补0。
对a的二进制位进行操作,a的值会改变吗?并不会,这就跟int a = 2; int b = a+2;操作一样,并不会改变a的值,如果要改变,必须对自身赋值。
>>右移操作符:有两种:
- 算数右移:右边丢弃,左边用原理的符号位补充(0为正数、1为负数)
- 逻辑右移:右边丢弃,左边用0填充。
具体那种形式,是由编译器决定的,对于大多数编译器来说,用的是算数右移,具体可以看图解:
最后,关于移位操作符有两点需要注意:
- 移位操作符的操作数只能是整数。
- 对于移动运算符,不要移动负数位,这个是标准未定义的。如:a << -1、a >> -2 .....
四、位操作符
cpp
&:按位与
|:按位或
^:按位异或
注:他们的操作数必须是整数
关于位操作符,从名字就能猜出,是针对于二进制位的操作,具体作用如下:
&:一个为0,都为0,两个为1才为1
|:一个为1,都为1,两个为0才为0
^:相同为0,相异为1
代码+图解:
cpp
//位操作符
int main()
{
int num1 = 2;
int num2 = 3;
int num3 = num1 & num2;
int num4 = num1 | num2;
int num5 = num1 ^ num2;
printf("&:%d\n", num3);
printf("|:%d\n", num4);
printf("^:%d\n", num5);
return 0;
}
按位异或的三个特点:
- 两个相同值进行异或等于0,因为相同为0
- 任何数与0异或等于本身,因为0遇到1相异为1,遇到0,相同为0,还是本身的二进制
- 支持交换律,如:a^a^b == a^b^a
五、赋值操作符
赋值操作符是C语言用的最多的一个操作符,没什么好讲的,就是简单的赋值操作。
cpp
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
这样的代码感觉怎么样?
那同样的语义,你看看:
x = y+1;
a = x;
这样的写法是不是更加清晰爽朗而且易于调试。
赋值操作符还可以与其它操作符结合,称为复合赋值符:其实自己对自己操作后,赋值给自己
cpp
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
cpp
int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁。
这里要区分一个点:赋值和初始化是不同的:
- 创建一个变量的同时又赋值:初始化
- 创建后,再赋值:赋值
六、单目操作符
cpp
!:逻辑反操作
-:负值
+:正值
&:取地址
sizeof:操作数的类型长度(以字节为单位)
~:对一个数的二进制按位取反
--:前置、后置--
++:前置、后置++
*:间接访问操作符(解引用操作符)
(类型):强制类型转换
6.1 逻辑反操作
! 逻辑反操作符,只有两种结果:真变假,假变真,看代码:
cpp
//逻辑反操作
int main()
{
int a = 0;
if (!a) //当a为假时执行
{
;
}
return 0;
}
6.2 负值与正值
跟数学中的正负符号相同。不常用,看代码:
cpp
//正、负值
int main()
{
//将a变为-数
int a = 2;
int a = -a;
return 0;
}
6.3 取地址
&取地址操作符常用在获取一个数据的地址,常跟指针搭配使用,看代码:
cpp
//&取地址
int main()
{
//查看a的地址
int a = 2;
int* p = &a;
printf("%p\n", p);
return 0;
}
6.4 sizeof
sizeof是一个单目操作符,不是一个库函数哦,其作用是求类型的字节大小。看代码理解:
cpp
#include <stdio.h>
int main()
{
int a = -10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//这样写行不行?
printf("%d\n", sizeof int);//这样写行不行?
return 0;
}
sizeof(a)或sizeof(int) 都是正确的。还有一种特殊的写法:sizeof a , 就是()里放的是变量名时,可以把()去掉,但如果是类型,()不可以去掉。
6.5 取反操作符
~ 取反操作符是针对二进制位操作的,其作用是将二进制的每一位取反,0变1,1变0,如:
cpp
int a = 2;
a: 010
~a: 101
这里有一个在做OJ题常见的代码:
cpp
while(~scanf("%d", &n))
代码中的~操作怎么理解呢?
因为scanf的返回值是格式化符的个数,当scanf读取失败时,会返回EOF,EOF为-1,
那对-1按位取反,那结果就为0,0为假,则不会进入循环
6.6 --和++操作符
--和++操作符分为前置与后置,下面讲解前置和后置有什么区别:
前置:先++/--后使用。后置:先使用后++/--
具体看代码:
cpp
//++和--运算符
//前置++和--
#include <stdio.h>
int main()
{
int a = 10;
int x = ++a;
//先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
int y = --a;
//先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
return 0;
}
//后置++和--
#include <stdio.h>
int main()
{
int a = 10;
int x = a++;
//先对a先使用,再增加,这样x的值是10;之后a变成11;
int y = a--;
//先对a先使用,再自减,这样y的值是11;之后a变成10;
return 0;
}
我们看看这段代码:
cpp
int a = 1;
int b = (++a) + (++a) + (++a)
这是一段问题代码,在不同的编译器上运行的结果是不同的,因此对于++、--操作符,我们尽量不要在一个表达式中使用多次,这样会让代码阅读变差。
6.7 间接访问操作符(解引用操作符)
* 间接引用操作符用是指针的一种标志,跟随指针一起出现。
cpp
//*简接引用操作符
int main()
{
int a = 2;
//定义指针变量P
int* p = &a;
//访问a的值
printf("%d\n", *p);
return 0;
}
6.8 强制类型转换
(类型)强制类型转换通常用在对不同类型变量之间,想进行赋值操作时。
cpp
//强制类型转换
int main()
{
float b = 3.0f;
int res = (int)b;
printf("%d\n", res);
return 0;
}
对于强制类型转换,有两点要注意:
- 建议小转大,大转小会丢失精度
- 强制类型转化是一种临时的状态,不是永久的
七、关系操作符
cpp
>
>=
<
<=
!= 用于测试"不相等"
== 用于测试"相等"
这些运算符没什么讲的,需要注意:
- 在编程的过程中== 和=不小心写错,导致的错误。
- 两个等号为等与、一个等号为赋值
八、逻辑操作符
cpp
&&:逻辑与(并且)
||:逻辑或(或者)
逻辑操作符的结果只有两种:真或假。
注意区分与按位与和按位或的区别:
cpp按位与和按位或对二进制位操作。 逻辑与和逻辑或关注的是真和假 1&2----->0 1&&2---->1 1|2----->3 1||2---->1
我们看一到360的笔试题:
cpp#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; } //程序输出的结果是什么?
如何计算这种表达式呢?
cpp当多个||时,当从前往后执行,当发现前面的为1时,后面不需要计算了,均为1,如:(|| - 两个为假,式子为假,1个为真,式子才为真) int i = 0, a = 1, b = 2, c = 3, d = 5 i = a++ || ++b || d++ 当a==1时,就不需要执行后面的式子了,均为1,因此,当发现前面为1时,后面不会执行
将代码改为||呢?
cpp#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; } //程序输出的结果是什么?
cpp当多个&&时,当从前往后执行,当发现前面的为0时,后面不需要计算了,均为0,如:(&& 一个为假,式子为假,两个为真,式子才为真) int i = 0, a = 0, b = 2, c = 3, d = 5 i = a++ && ++b && d++ 当a==0时,就不需要执行后面的式子了,均为0,因此,当发现前面为0时,后面不会执行
因此,我们可以得出结论:
- &&操作符左边为假时,右边不会执行,表达式为假。
- ||操作符左边为1时,右边不会执行,表达式为真
- 这种现象也叫短路
九、条件操作符
cpp
//条件操作符(三目运算符)
exp1 ? exp2 : exp3
首先看exp1表达式结果,如果为真则表达式值为exp2,如果为假,表达式的值为exp3。
cpp
if (a > 5)
b = 3;
else
b = -3;
转换成条件表达式,是什么样?
int a = 2;
int b = 0;
int res = a>5 ? b=3 : b-3
res值为-3.
b的值为-3.
我们做个练习,使用条件表达式实现找两个数中较大值。
cpp
//使用条件表达式实现找两个数中较大值。
int main()
{
int a = 10;
int b = 20;
int max = a > b ? a : b;
printf("max: %d\n", max);
return 0;
}
十、逗号表达式
cpp
exp1, exp2, exp3,....expN
逗号表达式的特点:
- 逗号表达式,就是用逗号隔开的多个表达式。
- 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
例如以下几个例子:
cpp
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?
c为13。从左向右依次计算,到在最后一个表达式时,a的值已变为12,最后整个表达式的值是最后一个表达式,因此c为13.
cpp
//代码2
if (a =b + 1, c=a / 2, d > 0)
从左向右执行,代码2中的逗号表达式并没有直接与最后一个表达式关联的变量,因此判断条件仍然为d > 0.
cpp
//代码3
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}
从代码3可以看出,如果学会逗号表达式,可以大大提高代码书写效率。
十一、下标引用、函数调用和结构成员
11.1 [] 下标引用操作符
什么是操作数?操作数就是操作符作用的对象,如:3&&2,则3和2就是&&的操作数。那下标引用操作符的操作数是什么呢?
操作数:一个数组名 + 一个索引值。[]有两种用法如下:
cpp
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
11.2 () 函数调用操作符
其实就是调用函数时后边的圆括号
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
cpp
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
11.3 访问结构成员操作符
cpp
. : 结构体变量.结构成员名
-> : 结构体指针->结构成员名
结构体变量.结构成员名,常用于获取结构体变量形式的结构体成员,如以下代码:
cpp
//结构体变量.结构体成员名
struct S
{
int a;
};
int main()
{
struct S s = { 20 };
printf("%d", s.a);
return 0;
}
结构体指针.结构成员名,常用于结构体指针形式的结构体成员,如以下代码:
cpp
struct S
{
int a;
}s;
void test(struct S* p)
{
//当然,也可以写成结构体变量.结构体成员名形式
printf("%d\n", (*p).a);
//结构体指针.结构体成员名,更加方便
printf("%d\n", p->a);
}
int main()
{
test(&s);
return 0;
}
十二、表达式求值
关于C语言的操作符已经介绍完毕,学习了操作符,其实主要目的就是写出相对应的表达式,但是那么多种操作符如何进行组合呢?组合之后又该怎么确定谁先运行或者运行的方向是怎么样的呢?
关于表达式求值,有两点需要注意:
- 表达式求值的顺序一部分是由操作符的优先级和结合性决定。
- 同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
12.1 隐式类型转换
什么是隐式类型转换,通俗点说就是当char类型和short类型进行算术运算时,程序会隐式的对算数对象进行类型的提升,提升为int型,进行算术运算,具体的看如下讲解:
C的整型算术运算总是至少以缺省整型类型的精度来进行的。什么意思呢?通俗点讲就是在C语言中,进行整型算术运算时,总是以int类型进行的,也就是说,如果我们对char或short类型进行加减乘除运算的话,会默认将类型变为int来进行。
整型提升:为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型 提升。
整型提升有什么意义呢?先看一大段概念:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。
通俗讲解:
我们通过几个例子进行说明:
cpp
char a = 3;
char b = 127;
char c = a+ b;
两个char类型的变量进行相加,程序会隐式的对这两个变量提升为int型,如何提升呢?
我们知道char类型是1字节,也就是8个二进制位,而int是4字节,也就是32比特位,整型提升的提升本质上是提升二进制位:
a和b的值被提升为普通整型,然后在执行加法运行,计算完之后,发现接受的变量c是一个char类型,则需要进行截断操作,也就是只保留8个二进制位:
那么整型提升的规定是什么呢?
- 有符号位(也就是类型前不加unsigned):
- 按照变量的数据类型的符号位来提升
- 正数符号位为0,那就将不足的二进制填充为0
- 负数符号位为1,那就将不足的二进制填充为1
- 无符号位(也就是类型前加了unsigned):
- 直接高位补0(也就是剩余的二进制位填充0)
cpp
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
看看下面这两个实例:
cpp
//实例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;
}
实例1中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表 达式 c==0xb6000000 的结果是真.
cpp
//实例2
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
sizeof求类型的字节大小,单位为字节,为什么得出的大小不同呢?其实也是整型提升的原因,整型提升是针对char类型和short类型表达式的,注意!!是表达式,那+c和-c自然也是表达式了,那就被整型提升成了int类型,因此打印出4.
12.2 算数转换
C语言中不只有隐式转换,隐式转换是针对于小于Intl类型的转换,对象为char类型和short类型,那对于等于int类型或大于int类型的类型呢?就要用到算数转换了。
在进行算术计算时,如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类 型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
cpp
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运 算。
cpp
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
num最终为3,丢失掉了0.14
12.3 操作符的属性
上文提到过,表达式求值有三个影响的因素:
- 操作符的优先级(就是先后执行的意思)
- 操作符的结合性(就是执行方向,是从左到右还是从右到左)
- 是否控制求值顺序(就是某个条件下,操作数可能不会执行,如:&&、||、条件表达式、逗号表达式)
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。注意:操作符的优先级是针对两个相邻操作符进行比较。
可以参考以下这份表格:
|--------|-------------|------------------------|--------|-----|----------|
| 操作符 | 描述 | 用法示例 | 结构类型 | 结合性 | 是否控制求值顺序 |
| () | 聚组 | (表达式) | 与表达 式同 | N/A | 否 |
| () | 函数调用 | rexp(rexp,...,rexp) | rexp | L-R | 否 |
| [ ] | 下标引用 | rexp[rexp] | lexp | L-R | 否 |
| . | 访问结构成员 | lexp.member_name | lexp | L-R | 否 |
| -> | 访问结构指针成员 | rexp->member_name | lexp | L-R | 否 |
| ++ | 后缀自增 | lexp ++ | rexp | L-R | 否 |
| -- | 后缀自减 | lexp -- | rexp | L-R | 否 |
| ! | 逻辑反 | ! rexp | rexp | R-L | 否 |
| ~ | 按位取反 | ~ rexp | rexp | R-L | 否 |
| + | 单目,表示正值 | + rexp | rexp | R-L | 否 |
| - | 单目,表示负值 | - rexp | rexp | R-L | 否 |
| ++ | 前缀自增 | ++ lexp | rexp | R-L | 否 |
| -- | 前缀自减 | -- lexp | rexp | R-L | 否 |
| * | 间接访问 | * rexp | lexp | R-L | 否 |
| & | 取地址 | & lexp | rexp | R-L | 否 |
| sizeof | 取其长度,以字节 表示 | sizeof rexp sizeof(类型) | 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 | 是 |
[C语言操作符表]
按照操作符的优先级和结合性等等就能写出唯一路径的表达式吗??不一定,表达式的求值部分由操作符的优先级决定。如以下的例子:
cpp
//表达式1
a*b + c*d + e*f
代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不 能决定第三个*比第一个+早执行。所以表达式的计算机顺序就可能是:
cpp
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
cpp
//表达式2
c + --c;
同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得 知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义 的。
cpp
//代码3-非法表达式
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
表达式3在不同编译器中测试结果:非法表达式程序的结果
cpp
//代码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(); 中我们只能通过操作符的优先级得知:先算乘法, 再算减法。 函数的调用先后顺序无法通过操作符的优先级确定。
cpp
//代码5
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
同样的代码,在不同的编译器上运行出的结果不同。
**总结:**我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题 的。
总结
这就是我对C语言中操作符相关知识点的讲解,感谢老铁们的耐心阅读,希望多多支持!!!关注我!后续有更多的干货**❤❤❤❤**