目录
1、操作符分类
2、操作符的属性
3、算术操作符
4、移位操作符
5、位操作符
6、赋值操作符
7、单目操作符
8、关系操作符
9、逻辑操作符
10、条件操作符
11、逗号操作符
12、下标引用、函数调用和结构成员
1、操作符分类
算术操作符(+,-,*,/,%)
移位操作符(<<,>>)
位操作符(&,|,^)
赋值操作符(=,+=,-=,/=,*=,%=,<<=,>>=)
单目操作符(!,-,+,&,sizeof,~,--,++,*,(类型))
关系操作符(>,>=,<,<=,!=,==)
逻辑操作符(&&,||)
条件操作符(exp1?exp2:exp3)
逗号表达式(exp1,exp2,exp3,-expN)
下标引用、函数调用和结构成员([],(),->)
2、操作符的属性
复杂表达式的求值有三个影响的因素:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪一个?取决于它们的优先级。如果两者的优先级相同,取决于它们的结合性
操作符 | 描述用 | 用法示例 | 结果类型 | 结合性 | 是否控制求值顺序 |
() | 聚组 | (表达式) | 与表达式相同 | 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 | 是 |
3、算术操作符
算数操作符有+,-,*,/,%
加法运算符用于加法运算,使其两侧的值相加。相加的值(运算对象)可以是变量,也可以是常量
sum = num1 + num2
计算机会查看加法运算符右侧的两个变量,把它们相加,然后把和赋给变量sum
sum,num1,num2都是可修改的左值,每个变量都标识了一个可被赋值的数据对象。
但是表达式num1+num2是一个右值
减法运算符用于减法运算,使其左侧的数减去右侧的数,+和-运算符被称为二元运算符,这些运算符需要两个运算对象才能完成操作。
但+和-也能作为符号运算符,减号可用于标明或改变一个值的代数符号。
a = -100;
b = -a;
以这种形式使用的负号被称为一元运算符,一元运算符只需要一个运算对象,C90标准添加了一元+运算符,它不会改变运算对象的值或符号。
乘法运算符用*号来表示,使其两侧的值相乘。相乘的值(运算对象)可以是变量,也可以是常量
除法运算符用/号来表示,/左侧的值是被除数,右侧的值是除数。有着整数除法和浮点数除法,整数除法和浮点数除法不同。浮点数除法的结果是浮点数,而整数除法的结果是整数。整数是没有小数部分的数。在C语言中,整数除法结果的小数部分被丢弃,在一过程被称为截断。
当/操作符的两个操作符都为整数,执行整数除法;而只要有浮点数执行的就是浮点数除法。
求模运算符用%号来表示,求模运算符用于整数计算。求模运算符给出其左侧整数除以右侧整数的余数。求模运算符的两个操作数必须位整数,返回的是整除之后余数,不能用于浮点数。
4、移位操作符
移位运算符有左移运算符(<<)和右移运算符(>>)。用二进制数做案例。
左移运算符(<<)将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数。左侧运算对象移出左末尾段位的值丢失,用0填充空出的位置。
(0000 0001)<< 2 //表达式
(0000 0100) //结果值
右移运算符(>>)将左侧运算对象每一位的值向右移动其右侧对象指定的位数。左侧运算对象移出右末端位的值。对于无符号类型,用0填充空出的位置(逻辑移位);对于有符号类型,其结果取决于机器,空出的位置可用0填充,或者用符号位(最左端的位)的副本填充(算术右移)。
移位运算符针对2的幂提供快速有效的乘法和除法:
number << n number乘于2的n次幂
number >> n 如果number为非负,则用number除以2的n次幂
移位运算符还可用于从较大单元中提取一些位
5、位操作符
位运算符有按位取反~,按位与&,按位或|,按位异或^
都是作用于整形数据,包括char。之所以叫位运算符,是因为整型操作都是针对每一个位进行,不影响它左右两边的位。
一元运算符~把1变为0,把0变为1。
~(1001 1010) --- 》 (0110 0101)
二元运算符&通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中响应的位都为1时,结果才为1(从真/假方面看,只有当两个位为都为真时,结果才为真)
(1001 0011) & (0011 1101) --- 》 (0001 0001 )
二元运算符|,通过逐位比较两个运算对象,生成一个新值。对于每个 位,如果两个运算对象中相应的位为1,结果就为1(从真/假方面看,如果两个运算对象中相应的一个位为真或两个位都为真,那么结果为真)。
(1001 0011) | (0011 1101) ---》 (1011 1111)
二元运算符^逐位比较两个运算对象。对于每个位,如果两个运算对象 中相应的位一个为1(但不是两个为1),结果为1(从真/假方面看,如果两 个运算对象中相应的一个位为真且不是两个为同为1,那么结果为真)。
(1001 0011) ^ (0011 1101) ---》 (1010 1110)
一道有趣的面试题:不能利用临时变量,如何实现两个数的交换,读者可以想想有哪几种方法?
6、赋值操作符
赋值运算符可以给自己定义的变量重新赋值,例如:
int score = 60;
score = 99;
赋值运算符可以和前面的算术运算符,位运算符,移位运算符组成复合赋值符,这是C语言语法所允许的。
赋值运算符允许连续使用,例如:
a = b = c + 1;
7、单目操作符
单目操作符包括!,~,+,-,&,sizeof,--,++,*,(类型)
这里就不介绍!,~,+,-,前面或后面已经有介绍了,这里主要介绍其他几个。
这里的&可不是按位与&,而是取地址符&,取地址符就是获取当前变量的内存地址,想要获得那个变量的地址,就用&后面跟上那个变量。&后只能跟变量,不能跟常量,因为常量是一个立即数,不是容器,没有地址。
前面就提到过sizeof一嘴,这里再对sizeof进行进一步了解,sizeof运算符以字节为单位返回运算对象的大小(在C中,1字节定义为char类型占用的空间大小。过去,1字节通常是8位,但是一些字符集可能使用更大的字节)。运算对象可 以是具体的数据对象(如,变量名)或类型。如果运算对象是类型(如, float),则必须用圆括号将其括起来。
C 语言规定,sizeof 返回 size_t 类型的值。这是一个无符号整数类型, 但它不是新类型。C有一个 typedef机制(后面再介绍),允许程序员为现有类型创建别名。
++和--,递增运算符和递减运算符,执行简单的任务,将其运算对象递增(减)1,该类运算符以两种形式出现。第1种方式,++出现在其作用的变量前面, 这是前缀模式;第2种方式,++出现在其作用的变量后面,这是后缀模式。 两种模式的区别在于递增行为发生的时间不同。下面用一些例子来仔细地查看两者地区别:
num1后缀递加是原来的值,num2前缀递加是原来的值+1,当我们再次打印num1的值的时候,发现num1的值实际上是加1了,这里就是前缀和后缀的区别了,前缀++先加1再赋值,后缀++先赋值再加1。初学者有可能在这里会陷入混乱。但是提供一个记忆方法:
“前缀++处于前,所以理应先++后赋值;后缀++处于后,所以理应先赋值后++”
前缀加加和后缀加加除了上面的区别,还有着操作符优先级的优先关系,后缀比前缀的优先级要高,这是要牢记的!
间接访问操作符,通过一个指针访问它所指向的地址的过程称为间接访问或解引用指针。这个用于执行间接访问的操作符是单目操作符*。当我们定义了一个指针变量的时候,当我们使用*得到的值就是该指针变量指向地址的变量的值。
在C语言中,类型之间的转换是允许的。而(类型)就是其中一种,被称为强制类型转换。
强制转发语法:
目标类型 变量名 = (目标类型)源变量名
可以把高类型数据转换为低类型数据,虽然有可能存在隐患,但实际上是可以转换的,高数据类型转换为低数据类型会产生丢失
低数据类型到高数据类型排序如下:
char -> short -> int -> long(unsigned char -> unsigned short -> unsigned int -> unsigned long
8、关系操作符
关系操作符包括>,>=,<,<=,!=,==
就跟数学里的比较一样,但是要记住一点不能使用数学思维去写判断,下面用一个案例来说明原因:
一些初学者可能会写出类似的判断语句,但是结果却不如意,因为当执行完第一个关系运算符之后会返回真(1)或者假(0),此时进行第二个关系运算符运算的时候,结果很有可能和实际的结果不一样,在这种多个比较之间需要用到逻辑操作符(&&,||)
9、逻辑操作符
逻辑操作符有逻辑与&&,逻辑或||和逻辑非!
逻辑运算符 | 含义 |
&& | 与 |
|| | 或 |
! | 非 |
逻辑运算符两侧的条件必须都为真,整个表达式才为真。逻辑运算符的优先级比关系运算符低,所以不必在子表达式两侧加圆括号。
假设exp1和exp2是两个简单的关系表达式(如a > b或c == 1000),那么:
当且仅当exp1和exp2都为真时,exp1 && exp2才为真;
如果exp1或exp2为真,则exp1 || exp2为真;
如果exp1为假,则!exp1为真;如果exp1为真,则!exp1为假。
同时需要记得一点,逻辑运算符是执行短路求值的,何为短路求值?
在逻辑与&&中,当第一个判断条件为假的时候直接结束,不对第二个判断条件进行判断;在逻辑或||中,当第一个判断条件为真的时候直接结束,不对第二个判断条件进行判断。
10、条件操作符
C提供条件表达式(conditional expression)作为表达if else语句的一种 便捷方式,该表达式使用?:条件运算符。该运算符分为两部分,需要 3 个运算对象。
条件运算符是C语言中唯一的三元运算符。条件操作符的格式:expr1?expr2:expr3
如果 expr1 为真(非 0),那么整个条件表达式的值与 expr2 的值相同;如果expr1为假(0),那么整个条件表达式的值与 expr3的值相同。
但是条件操作符也是一个有趣的操作符,看下面这段代码:
int main(void)
{
int a = 10, b = 20;
int c = 0;
c = (a > b ? a : b);
printf("a=%d,b=%d,c=%d\n", a, b, c); //运行之后发现c的值和a的值一样
return 0;
}
运行之后发现c的值和a的值是一样的,条件操作符返回的是一个变量,可以赋值。
11、逗号操作符
表达式说明:
表达式1,表达式2,表达式3,...... ,表达式n
逗号表达式的要领:
(1) 逗号表达式的运算过程为:从左往右逐个计算表达式。
(2) 逗号表达式作为一个整体,它的值为最后一个表达式(也即表达式n)的值。
(3) 逗号运算符的优先级别在所有运算符中最低。
12、下标引用、函数调用和结构成员
[]下标引用操作符
操作数:一个数组名+一个索引值
下标引用操作符允许对数组单个元素直接赋值
()函数调用操作符
接收一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
当我们通过访问结构体变量访问成员变量的时候,我们直接使用直接访问符(.),当我们通过访问结构体指针访问成员变量的时候,我们使用简洁访问(->)。
通过一个案例来展示一下: