• 【C语言】万字详讲操作符


    目录

    前言

    一、操作符分类

    二、算数操作符

    三、移位操作符

    四、位操作符

    五、赋值操作符

    六、单目操作符

    6.1 逻辑反操作

    6.2 负值与正值

    6.3 取地址

    6.4 sizeof

    6.5 取反操作符

    6.6 --和++操作符

    6.7 间接访问操作符(解引用操作符)

    6.8 强制类型转换

    七、关系操作符

    八、逻辑操作符

    九、条件操作符

    十、逗号表达式

    十一、下标引用、函数调用和结构成员

    11.1 [] 下标引用操作符

    11.2  () 函数调用操作符

    11.3 访问结构成员操作符

    十二、表达式求值

    12.1 隐式类型转换

    12.2 算数转换

    12.3 操作符的属性

    总结


    前言

    这篇文章将对C语言的操作符相关知识点,进行详细的讲解,读完本篇文章,您对C语言的了解将更进一步。


    一、操作符分类

    C语言中对于操作符一共分为十类:(进行简单介绍)

    • 算数操作符:加减乘除取余
    • 移位操作符:左移(<<),右移(>>)
    • 位操作符:按位与(&),按为或(|),按位异或(^)
    • 赋值操作符:简单赋值:=;复合赋值:与前三类结合
    • 单目操作符:sizeof、取反~、取非!、+、-、++、--、*、强制类型转换()
    • 关系操作符:大于等于小于等等
    • 逻辑操作符:逻辑与(&&),逻辑或(||)
    • 条件操作符:也就是三目运算符
    • 逗号表达式:多个表达式由逗号分隔
    • 下标引用、函数调用和结构成员:点、->、()、[]

    二、算数操作符

    算数操作符,也就是对应数学上的算数运算符,最常见的为:加减乘除。

    不过在C语言中,还会用到取模(取余)操作,算出操作符的书写形式如下:

    1. +:加
    2. -:减
    3. *:乘
    4. /:除
    5. %:取余

    可以看出,加减书写方式与数学中是一样的,乘除与数学中的不同,这里重点讲解一下取余操作符:

    1. #include
    2. // 算数操作符示例代码:
    3. int main()
    4. {
    5. int a = 10;
    6. int b = 3;
    7. printf("加法:%d\n", a + b);
    8. printf("减法:%d\n", a - b);
    9. printf("除法:%d\n", a / b);
    10. printf("取余法:%d\n", a % b);
    11. return 0;
    12. }

    通过结果可知:

    • 除法的结果是商。
    • 取余的结果是余数

    我们再看看几个例子:

    对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

    加减乘除都可以用在不同类型中,那取余操作符呢?

    我们可以得出:

    • 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
    • % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

    三、移位操作符

    1. <<:左移
    2. >>:右移

    关于移位操作符,我们先要了解一些前备知识点。

    移位,移的是什么位呢?其实是二进制的位,那二进制是什么呢?

    日常生活中,我们看到的数字是用十进制形式表示的,十进制的数据都是由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取反
    2. 补码取反+1

    <<左移操作符:二进制位相左移动,右边补0。

    对a的二进制位进行操作,a的值会改变吗?并不会,这就跟int a = 2; int b = a+2;操作一样,并不会改变a的值,如果要改变,必须对自身赋值。


    >>右移操作符:有两种:

    1. 算数右移:右边丢弃,左边用原理的符号位补充(0为正数、1为负数)
    2. 逻辑右移:右边丢弃,左边用0填充。

    具体那种形式,是由编译器决定的,对于大多数编译器来说,用的是算数右移,具体可以看图解:

    最后,关于移位操作符有两点需要注意:

    • 移位操作符的操作数只能是整数。
    • 对于移动运算符,不要移动负数位,这个是标准未定义的。如:a << -1、a >> -2 .....

    四、位操作符

    1. &:按位与
    2. |:按位或
    3. ^:按位异或
    4. 注:他们的操作数必须是整数

    关于位操作符,从名字就能猜出,是针对于二进制位的操作,具体作用如下:

    &:一个为0,都为0,两个为1才为1

    |:一个为1,都为1,两个为0才为0

    ^:相同为0,相异为1

    代码+图解:

    1. //位操作符
    2. int main()
    3. {
    4. int num1 = 2;
    5. int num2 = 3;
    6. int num3 = num1 & num2;
    7. int num4 = num1 | num2;
    8. int num5 = num1 ^ num2;
    9. printf("&:%d\n", num3);
    10. printf("|:%d\n", num4);
    11. printf("^:%d\n", num5);
    12. return 0;
    13. }

    按位异或的三个特点:

    • 两个相同值进行异或等于0,因为相同为0
    • 任何数与0异或等于本身,因为0遇到1相异为1,遇到0,相同为0,还是本身的二进制
    • 支持交换律,如:a^a^b == a^b^a

    五、赋值操作符

    赋值操作符是C语言用的最多的一个操作符,没什么好讲的,就是简单的赋值操作。

    1. int weight = 120;//体重
    2. weight = 89;//不满意就赋值
    3. double salary = 10000.0;
    4. salary = 20000.0;//使用赋值操作符赋值。
    5. 赋值操作符可以连续使用,比如:
    6. int a = 10;
    7. int x = 0;
    8. int y = 20;
    9. a = x = y+1;//连续赋值
    10. 这样的代码感觉怎么样?
    11. 那同样的语义,你看看:
    12. x = y+1;
    13. a = x;
    14. 这样的写法是不是更加清晰爽朗而且易于调试。

    赋值操作符还可以与其它操作符结合,称为复合赋值符:其实自己对自己操作后,赋值给自己

    1. +=
    2. -=
    3. *=
    4. /=
    5. %=
    6. >>=
    7. <<=
    8. &=
    9. |=
    10. ^=
    1. int x = 10;
    2. x = x+10;
    3. x += 10;//复合赋值
    4. //其他运算符一样的道理。这样写更加简洁。

    这里要区分一个点:赋值和初始化是不同的:

    • 创建一个变量的同时又赋值:初始化
    • 创建后,再赋值:赋值

    六、单目操作符

    1. !:逻辑反操作
    2. -:负值
    3. +:正值
    4. &:取地址
    5. sizeof:操作数的类型长度(以字节为单位)
    6. ~:对一个数的二进制按位取反
    7. --:前置、后置--
    8. ++:前置、后置++
    9. *:间接访问操作符(解引用操作符)
    10. (类型):强制类型转换

    6.1 逻辑反操作

    ! 逻辑反操作符,只有两种结果:真变假,假变真,看代码:

    1. //逻辑反操作
    2. int main()
    3. {
    4. int a = 0;
    5. if (!a) //当a为假时执行
    6. {
    7. ;
    8. }
    9. return 0;
    10. }

    6.2 负值与正值

    跟数学中的正负符号相同。不常用,看代码:

    1. //正、负值
    2. int main()
    3. {
    4. //将a变为-数
    5. int a = 2;
    6. int a = -a;
    7. return 0;
    8. }

    6.3 取地址

    &取地址操作符常用在获取一个数据的地址,常跟指针搭配使用,看代码:

    1. //&取地址
    2. int main()
    3. {
    4. //查看a的地址
    5. int a = 2;
    6. int* p = &a;
    7. printf("%p\n", p);
    8. return 0;
    9. }

    6.4 sizeof

    sizeof是一个单目操作符,不是一个库函数哦,其作用是求类型的字节大小。看代码理解:

    1. #include
    2. int main()
    3. {
    4. int a = -10;
    5. printf("%d\n", sizeof(a));
    6. printf("%d\n", sizeof(int));
    7. printf("%d\n", sizeof a);//这样写行不行?
    8. printf("%d\n", sizeof int);//这样写行不行?
    9. return 0;
    10. }

    sizeof(a)或sizeof(int) 都是正确的。还有一种特殊的写法:sizeof a , 就是()里放的是变量名时,可以把()去掉,但如果是类型,()不可以去掉。


    6.5 取反操作符

    ~ 取反操作符是针对二进制位操作的,其作用是将二进制的每一位取反,0变1,1变0,如:

    1. int a = 2;
    2. a: 010
    3. ~a: 101

    这里有一个在做OJ题常见的代码:

    while(~scanf("%d", &n))

    代码中的~操作怎么理解呢?

    因为scanf的返回值是格式化符的个数,当scanf读取失败时,会返回EOF,EOF为-1,
    那对-1按位取反,那结果就为0,0为假,则不会进入循环


    6.6 --和++操作符

    --和++操作符分为前置与后置,下面讲解前置和后置有什么区别:

    前置:先++/--后使用。

    后置:先使用后++/--

    具体看代码:

    1. //++和--运算符
    2. //前置++和--
    3. #include
    4. int main()
    5. {
    6. int a = 10;
    7. int x = ++a;
    8. //先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
    9. int y = --a;
    10. //先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
    11. return 0;
    12. }
    13. //后置++和--
    14. #include
    15. int main()
    16. {
    17. int a = 10;
    18. int x = a++;
    19. //先对a先使用,再增加,这样x的值是10;之后a变成11;
    20. int y = a--;
    21. //先对a先使用,再自减,这样y的值是11;之后a变成10;
    22. return 0;
    23. }

    我们看看这段代码:

    1. int a = 1;
    2. int b = (++a) + (++a) + (++a)

    这是一段问题代码,在不同的编译器上运行的结果是不同的,因此对于++、--操作符,我们尽量不要在一个表达式中使用多次,这样会让代码阅读变差。


    6.7 间接访问操作符(解引用操作符)

    * 间接引用操作符用是指针的一种标志,跟随指针一起出现。

    1. //*简接引用操作符
    2. int main()
    3. {
    4. int a = 2;
    5. //定义指针变量P
    6. int* p = &a;
    7. //访问a的值
    8. printf("%d\n", *p);
    9. return 0;
    10. }

    6.8 强制类型转换

    (类型)强制类型转换通常用在对不同类型变量之间,想进行赋值操作时。

    1. //强制类型转换
    2. int main()
    3. {
    4. float b = 3.0f;
    5. int res = (int)b;
    6. printf("%d\n", res);
    7. return 0;
    8. }

    对于强制类型转换,有两点要注意:

    • 建议小转大,大转小会丢失精度
    • 强制类型转化是一种临时的状态,不是永久的

    七、关系操作符

    1. >
    2. >=
    3. <
    4. <=
    5. != 用于测试“不相等”
    6. == 用于测试“相等”

    这些运算符没什么讲的,需要注意:

    • 在编程的过程中== 和=不小心写错,导致的错误。
    • 两个等号为等与、一个等号为赋值

    八、逻辑操作符

    1. &&:逻辑与(并且)
    2. ||:逻辑或(或者)

    逻辑操作符的结果只有两种:真或假。

    注意区分与按位与和按位或的区别:

    1. 按位与和按位或对二进制位操作。
    2. 逻辑与和逻辑或关注的是真和假
    3. 1&2----->0
    4. 1&&2---->1
    5. 1|2----->3
    6. 1||2---->1

    我们看一到360的笔试题:

    1. #include
    2. int main()
    3. {
    4. int i = 0,a=0,b=2,c =3,d=4;
    5. i = a++ && ++b && d++;
    6. //i = a++||++b||d++;
    7. printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    8. return 0;
    9. }
    10. //程序输出的结果是什么?

    如何计算这种表达式呢?

    1. 当多个||时,当从前往后执行,当发现前面的为1时,后面不需要计算了,均为1,如:(|| - 两个为假,式子为假,1个为真,式子才为真)
    2. int i = 0, a = 1, b = 2, c = 3, d = 5
    3. i = a++ || ++b || d++
    4. 当a==1时,就不需要执行后面的式子了,均为1,因此,当发现前面为1时,后面不会执行

    将代码改为||呢?

    1. #include
    2. int main()
    3. {
    4. int i = 0,a=0,b=2,c =3,d=4;
    5. i = a++ && ++b && d++;
    6. //i = a++&&++b&&d++;
    7. printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    8. return 0;
    9. }
    10. //程序输出的结果是什么?
    1. 当多个&&时,当从前往后执行,当发现前面的为0时,后面不需要计算了,均为0,如:(&& 一个为假,式子为假,两个为真,式子才为真)
    2. int i = 0, a = 0, b = 2, c = 3, d = 5
    3. i = a++ && ++b && d++
    4. 当a==0时,就不需要执行后面的式子了,均为0,因此,当发现前面为0时,后面不会执行

    因此,我们可以得出结论:

    • &&操作符左边为假时,右边不会执行,表达式为假。
    • ||操作符左边为1时,右边不会执行,表达式为真
    • 这种现象也叫短路

    九、条件操作符

    1. //条件操作符(三目运算符)
    2. exp1 ? exp2 : exp3

    首先看exp1表达式结果,如果为真则表达式值为exp2,如果为假,表达式的值为exp3。

    1. if (a > 5)
    2. b = 3;
    3. else
    4. b = -3;
    5. 转换成条件表达式,是什么样?
    6. int a = 2;
    7. int b = 0;
    8. int res = a>5 ? b=3 : b-3
    9. res值为-3.
    10. b的值为-3.

    我们做个练习,使用条件表达式实现找两个数中较大值。

    1. //使用条件表达式实现找两个数中较大值。
    2. int main()
    3. {
    4. int a = 10;
    5. int b = 20;
    6. int max = a > b ? a : b;
    7. printf("max: %d\n", max);
    8. return 0;
    9. }


    十、逗号表达式

    exp1, exp2, exp3,....expN

    逗号表达式的特点:

    • 逗号表达式,就是用逗号隔开的多个表达式。
    • 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

    例如以下几个例子:

    1. //代码1
    2. int a = 1;
    3. int b = 2;
    4. int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
    5. c是多少?

    c为13。从左向右依次计算,到在最后一个表达式时,a的值已变为12,最后整个表达式的值是最后一个表达式,因此c为13.

    1. //代码2
    2. if (a =b + 1, c=a / 2, d > 0)

    从左向右执行,代码2中的逗号表达式并没有直接与最后一个表达式关联的变量,因此判断条件仍然为d > 0.

    1. //代码3
    2. a = get_val();
    3. count_val(a);
    4. while (a > 0)
    5. {
    6. //业务处理
    7. a = get_val();
    8. count_val(a);
    9. }
    10. 如果使用逗号表达式,改写:
    11. while (a = get_val(), count_val(a), a>0)
    12. {
    13. //业务处理
    14. }

    从代码3可以看出,如果学会逗号表达式,可以大大提高代码书写效率。


    十一、下标引用、函数调用和结构成员

    11.1 [] 下标引用操作符

    什么是操作数?操作数就是操作符作用的对象,如:3&&2,则3和2就是&&的操作数。那下标引用操作符的操作数是什么呢?

    操作数:一个数组名 + 一个索引值。[]有两种用法如下:

    1. int arr[10];//创建数组
    2. arr[9] = 10;//实用下标引用操作符。
    3. [ ]的两个操作数是arr和9

    11.2  () 函数调用操作符

    其实就是调用函数时后边的圆括号 

    接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

    1. #include
    2. void test1()
    3. {
    4. printf("hehe\n");
    5. }
    6. void test2(const char *str)
    7. {
    8. printf("%s\n", str);
    9. }
    10. int main()
    11. {
    12. test1(); //实用()作为函数调用操作符。
    13. test2("hello bit.");//实用()作为函数调用操作符。
    14. return 0;
    15. }

    11.3 访问结构成员操作符

    1. . : 结构体变量.结构成员名
    2. -> : 结构体指针->结构成员名

    结构体变量.结构成员名,常用于获取结构体变量形式的结构体成员,如以下代码:

    1. //结构体变量.结构体成员名
    2. struct S
    3. {
    4. int a;
    5. };
    6. int main()
    7. {
    8. struct S s = { 20 };
    9. printf("%d", s.a);
    10. return 0;
    11. }

    结构体指针.结构成员名,常用于结构体指针形式的结构体成员,如以下代码:

    1. struct S
    2. {
    3. int a;
    4. }s;
    5. void test(struct S* p)
    6. {
    7. //当然,也可以写成结构体变量.结构体成员名形式
    8. printf("%d\n", (*p).a);
    9. //结构体指针.结构体成员名,更加方便
    10. printf("%d\n", p->a);
    11. }
    12. int main()
    13. {
    14. test(&s);
    15. return 0;
    16. }

    十二、表达式求值

    关于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去执行运算。

    通俗讲解:

    我们通过几个例子进行说明:

    1. char a = 3;
    2. char b = 127;
    3. char c = a+ b;

    两个char类型的变量进行相加,程序会隐式的对这两个变量提升为int型,如何提升呢?

    我们知道char类型是1字节,也就是8个二进制位,而int是4字节,也就是32比特位,整型提升的提升本质上是提升二进制位:

     

    a和b的值被提升为普通整型,然后在执行加法运行,计算完之后,发现接受的变量c是一个char类型,则需要进行截断操作,也就是只保留8个二进制位:


    那么整型提升的规定是什么呢?

    • 有符号位(也就是类型前不加unsigned):
    1. 按照变量的数据类型的符号位来提升
    2. 正数符号位为0,那就将不足的二进制填充为0
    3. 负数符号位为1,那就将不足的二进制填充为1
    • 无符号位(也就是类型前加了unsigned):
    1. 直接高位补0(也就是剩余的二进制位填充0)
    1. //负数的整形提升
    2. char c1 = -1;
    3. 变量c1的二进制位(补码)中只有8个比特位:
    4. 1111111
    5. 因为 char 为有符号的 char
    6. 所以整形提升的时候,高位补充符号位,即为1
    7. 提升之后的结果是:
    8. 11111111111111111111111111111111
    9. //正数的整形提升
    10. char c2 = 1;
    11. 变量c2的二进制位(补码)中只有8个比特位:
    12. 00000001
    13. 因为 char 为有符号的 char
    14. 所以整形提升的时候,高位补充符号位,即为0
    15. 提升之后的结果是:
    16. 00000000000000000000000000000001
    17. //无符号整形提升,高位补0

    看看下面这两个实例:

    1. //实例1
    2. int main()
    3. {
    4. char a = 0xb6;
    5. short b = 0xb600;
    6. int c = 0xb6000000;
    7. if(a==0xb6)
    8. printf("a");
    9. if(b==0xb600)
    10. printf("b");
    11. if(c==0xb6000000)
    12. printf("c");
    13. return 0;
    14. }

    实例1中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表 达式 c==0xb6000000 的结果是真. 

    1. //实例2
    2. int main()
    3. {
    4. char c = 1;
    5. printf("%u\n", sizeof(c));
    6. printf("%u\n", sizeof(+c));
    7. printf("%u\n", sizeof(-c));
    8. return 0;
    9. }

    sizeof求类型的字节大小,单位为字节,为什么得出的大小不同呢?其实也是整型提升的原因,整型提升是针对char类型和short类型表达式的,注意!!是表达式,那+c和-c自然也是表达式了,那就被整型提升成了int类型,因此打印出4.


    12.2 算数转换

    C语言中不只有隐式转换,隐式转换是针对于小于Intl类型的转换,对象为char类型和short类型,那对于等于int类型或大于int类型的类型呢?就要用到算数转换了。

    在进行算术计算时,如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类 型,否则操作就无法进行。下面的层次体系称为寻常算术转换。

    1. long double
    2. double
    3. float
    4. unsigned long int
    5. long int
    6. unsigned int
    7. int

    如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运 算。

    1. float f = 3.14;
    2. int num = f;//隐式转换,会有精度丢失
    3. num最终为3,丢失掉了0.14

    12.3 操作符的属性

    上文提到过,表达式求值有三个影响的因素:

    1. 操作符的优先级(就是先后执行的意思)
    2. 操作符的结合性(就是执行方向,是从左到右还是从右到左)
    3. 是否控制求值顺序(就是某个条件下,操作数可能不会执行,如:&&、||、条件表达式、逗号表达式)

    两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。注意:操作符的优先级是针对两个相邻操作符进行比较。

    可以参考以下这份表格:

    C语言操作符表
    操作符描述用法示例结构类型结合性是否控制求值顺序
    ()聚组(表达式)与表达 式同N/A
    ()函数调用rexp(rexp,...,rexp)rexpL-R
    [ ]下标引用rexp[rexp]lexpL-R
    .访问结构成员lexp.member_namelexpL-R
    ->访问结构指针成员rexp->member_namelexpL-R
    ++后缀自增lexp ++rexpL-R
    --后缀自减lexp --rexpL-R
    !逻辑反! rexprexpR-L
    ~按位取反~ rexprexpR-L
    +单目,表示正值+ rexprexpR-L
    -单目,表示负值- rexprexpR-L

    ++前缀自增++ lexprexpR-L

    --前缀自减-- lexprexpR-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

    按照操作符的优先级和结合性等等就能写出唯一路径的表达式吗??不一定,表达式的求值部分由操作符的优先级决定。如以下的例子:

    1. //表达式1
    2. a*b + c*d + e*f

    代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不 能决定第三个*比第一个+早执行。所以表达式的计算机顺序就可能是:

    1. a*b
    2. c*d
    3. a*b + c*d
    4. e*f
    5. a*b + c*d + e*f
    6. 或者:
    7. a*b
    8. c*d
    9. e*f
    10. a*b + c*d
    11. a*b + c*d + e*f

    1. //表达式2
    2. c + --c;

    同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得 知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义 的。


    1. //代码3-非法表达式
    2. int main()
    3. {
    4. int i = 10;
    5. i = i-- - --i * ( i = -3 ) * i++ + ++i;
    6. printf("i = %d\n", i);
    7. return 0;
    8. }

    表达式3在不同编译器中测试结果:非法表达式程序的结果


    1. //代码4
    2. int fun()
    3. {
    4. static int count = 1;
    5. return ++count;
    6. }
    7. int main()
    8. {
    9. int answer;
    10. answer = fun() - fun() * fun();
    11. printf( "%d\n", answer);//输出多少?
    12. return 0;
    13. }

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


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

    同样的代码,在不同的编译器上运行出的结果不同。


    总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题 的。


    总结

    这就是我对C语言中操作符相关知识点的讲解,感谢老铁们的耐心阅读,希望多多支持!!!关注我!后续有更多的干货❤❤❤❤

  • 相关阅读:
    前端最常用、易忘的命令
    vue3弹性布局 类似九宫格排列(多选选项)
    【AI视野·今日CV 计算机视觉论文速览 第259期】Tue, 3 Oct 2023
    一文帮你搞定H5、小程序、Taro长列表曝光埋点
    当mybatisPlus与tk.mybatis遇到更新
    基本数据结构与算法JavaAPI【1】--线性表篇
    java-net-php-python-SSM城市管理综合执法系统计算机毕业设计程序
    2.5 贝叶斯分类器
    【C++】反向迭代器--迭代器适配器
    MySQL数据去重
  • 原文地址:https://blog.csdn.net/m0_74088266/article/details/137996105