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

目录

前言

一、操作符分类

二、算数操作符

三、移位操作符

四、位操作符

五、赋值操作符

六、单目操作符

[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取反
  2. 补码取反+1

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

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


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

  1. 算数右移:右边丢弃,左边用原理的符号位补充(0为正数、1为负数)
  2. 逻辑右移:右边丢弃,左边用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):
  1. 按照变量的数据类型的符号位来提升
  2. 正数符号位为0,那就将不足的二进制填充为0
  3. 负数符号位为1,那就将不足的二进制填充为1
  • 无符号位(也就是类型前加了unsigned):
  1. 直接高位补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 操作符的属性

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

  1. 操作符的优先级(就是先后执行的意思)
  2. 操作符的结合性(就是执行方向,是从左到右还是从右到左)
  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语言中操作符相关知识点的讲解,感谢老铁们的耐心阅读,希望多多支持!!!关注我!后续有更多的干货**❤❤❤❤**

相关推荐
代码雕刻家22 分钟前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
雨中rain22 分钟前
算法 | 位运算(哈希思想)
算法
Kalika0-02 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
代码雕刻家2 小时前
课设实验-数据结构-单链表-文教文化用品品牌
c语言·开发语言·数据结构
龙图:会赢的2 小时前
[C语言]--编译和链接
c语言·开发语言
sp_fyf_20242 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02
人工智能·神经网络·算法·计算机视觉·语言模型·自然语言处理·数据挖掘
Cons.W4 小时前
Codeforces Round 975 (Div. 1) C. Tree Pruning
c语言·开发语言·剪枝
我是哈哈hh4 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
Tisfy4 小时前
LeetCode 2187.完成旅途的最少时间:二分查找
算法·leetcode·二分查找·题解·二分
挥剑决浮云 -4 小时前
Linux 之 安装软件、GCC编译器、Linux 操作系统基础
linux·服务器·c语言·c++·经验分享·笔记