C 语言提供了非常丰富的操作符,根据不同的语义选取不同的操作符,在程序中使用。根据操作符功能或使用方式对它们进行如下分类。
算术操作符
C 提供了以下几种算术操作符:
+ - * / %
-
+
、-
、*
、/
既适用于浮点类型又适用于整数类型。/
操作符的两个操作数都是整数时,执行整除运算,其他情况下执行浮点数除法。 -
%
操作符为取模操作符,接受两个整型操作数,把左操作数除以右操作数,返回的值是余数而不是商。
移位操作符
C 提供了以下移位操作符:
<< >>
-
<<
左移位操作符。把一个值的位向左移动,值最左边的几位被丢弃,右边多出来的几个空位由0
补齐。 -
>>
右移位操作符。把一个值的位向右移动,左边移入新位时,有两种方案可选。一种是逻辑移位,左边移入的位用0
填充;另一种是算术移位,左边移入的位由原先该值的符号位决定,符号位为1
则移入的位均为1
,符号位为0
,则移入的均为0
,这样能保证原数的正负形式不变。
0 1 1 0 1 1 0 1 // 移位前
0 1 1 0 1 1 0 1 // 左移后三位
0 1 1 0 1 1 0 1 0 0 0 // 移位后补零
0 1 1 0 1 0 0 0 // 最终结果
当使用移位操作符时,左操作数的值将移动由右操作数指定的位数。两个操作数都必须是整型类型。
位操作符
C 提供了以下位操作符:
& | ^
-
&
按位与操作符,执行AND
逻辑操作。如果两个位都是 1,结果为 1,否则结果为 0。 -
|
按位或操作符,执行OR
逻辑操作。如果两个位都是 0,结果为 0,否则结果为 1。 -
^
按位异或操作符,执行XOR
逻辑操作。如果两个位不同,结果为 1,如果相同,结果为 0。
位操作符要求操作数必须是整数的二进制数,它们对操作数对应的位进行指定的操作,每次对左右操作数的各一位进行操作。举例说明,假定变量 a
的二进制值为 00101110
,变量 b
的二进制值为 01011011
。a & b
的结果是 00001010
,a|b
的结果是 01111111
,a ^ b 的结果是 01110101
。
赋值操作符
C 提供了 =
作为赋值操作符。赋值是表达式的一种,而不是某种类型的语句。所以,只要是允许出现表达式的地方,都允许进行赋值。如下所示:
a=x=y+3;
上述表达式语句中 =
的操作数是变量 x
和 表达式 y+3
的值。=
把右操作数的值存储于左操作数的指定的位置。而上面的语句根据赋值操作符的结合性是从右到左,所以这个表达式相当于:
a=(x=y+3);
与下面的语句组合完全相同:
x=y+3;
a=x;
但 a
和 x
不是被赋予了相同的值,这要看 x
和 a
的数据类型。如果 x
是一个字符型变量,a
是一个整型变量,那么 y+3
的值就会被截去一段,以便容纳于字符类型的变量中,那么 a
所赋的值就是这个被截短后并在被提升为整型的值。
除了 =
赋值操作符外,还有=
与其他操作符合在一起的复合赋值符,如下所示:
+= -= *= /= %=
<<= >>= &= ^= |=
这些操作符使用基本都相似,下面给出 +=
操作符的用法:
a += expres
功能相当于下面的表达式:
a=a+(expres)
唯一不同之处是 +=
操作符的左操作数只求值一次。这里的括号是确保表达式在执行加法运算前已被完整求值,即使内部包含有优先级低于加法运算的操作符。
单目操作符
C 提供了以下单目运算符:
! ++ - & sizeof
~ -- + * (type)
-
!
操作符,对操作数执行逻辑反操作。如果操作数为真,其结果为假;如果操作数为假,其结果为真。和关系操作符一样,这个操作符实际上产生0
或1
。 -
~
操作符,对整型类型的操作数进行求补操作,操作数中所有原先为1
的位变为0
,所有原先为0
的位变为1
。 -
-
操作符,产生操作数的负值。 -
+
操作符,产生操作数的正值。该操作符什么也不干,之所以提供这个操作符,是为了与-
操作符组成对称的一对。 -
&
操作符,产生它的操作数的地址。如一个整型变量int a
可以通过&
操作符取地址,一个指针变量int *b
,然后将整形变量的地址赋值给指针变量b = &a
。 -
*
操作符,间接访问操作符,与指针一起使用,用于访问指针所指向的值。 -
sizeof
操作符,判断它的操作数的类型长度,以字节为单位表示。操作数既可以是个表达式,也可以是两边加上括号的类型名,如sizeof(int)
、sizeof(x)
,不用括号也可以sizeof x
。 -
(type)
操作符,强制类型转换,用于显式地把表达式的值转换为另外的类型,如整型变量转换为浮点数值(float)a
。 -
++
操作符,增值操作符。表示操作数的值被增加,分为前缀形式和后缀形式,操作符出现在操作数前面++i
,表达式的值是操作数增加后的值;操作符出现在操作数后面i++
,表达式的值是操作数增加前的值。 -
--
操作符,减值操作符。表示操作数的值被减小,分为前缀形式和后缀形式,工作原理与++
相同。
单目操作符为只接受一个操作数的操作符。
下面给出 ++
操作符的例子。
int a,b,c,d;
...
a=b=10; // a 和 b 得到值 10
c= ++a; // a增加至11,c得到的值为11
d= b++; // b增加至11,但d得到的值仍为 10
前缀和后缀形式的增值操作符是复制了一份变量值的拷贝,用于周围表达式的值正是这份拷贝。前缀操作符在进行复制之前增加变量的值,后缀操作符在进行复制之后才增加变量的值。这些操作符的结果不是被它们所修改的变量,而是变量值的拷贝,因此不能像 ++a=10
这样使用操作符,因为 ++a
的结果是 a
值的拷贝,并不是变量本身,因此无法向一个值进行赋值。
关系操作符
C 提供了以下关系操作符,用于测试操作数之间的各种关系。
> >= < <= != ==
-
>
操作符。左值大于右值,为真;否则为假。 -
>=
操作符。左值大于等于右值,为真;否则为假。 -
<
操作符。左值小于右值,为真;否则为假。 -
<=
操作符。左值小于等于右值,为真;否则为假。 -
!=
操作符。左值与右值不相等,为真;否则为假。 -
==
操作符。左值与右值相等,为真;否则为假。
关系操作符的结果是一个整形值,而不是布尔值。如果两端的操作数符合操作符指定的关系,表达式的结果是 1
,被认为是真,如果不符合,表达式的结果是 0
,被认为是假。
!=
与 ==
在 if
语句中可以有一些简写方法。
if (e != 0) ... 等价于 if (e) ...
if (e == 0) ... 等价于 if (!e) ...
C99之前还未提供布尔类型,使用的是整数代替,其规则是零是假,任何非零值皆为真。
逻辑操作符
C 提供了以下逻辑操作符,用于表达式求值,测试值是真还是假。
&& ||
-
&&
操作符。逻辑与操作符,两边表达式同时为真,结果为真;否则为假。当最前面的表达式为假,后面不用执行,结果为假。 -
||
操作符。逻辑或操作符,两边表达式只要有一个为真,结果为真;否则为假。当最前面的表达式为真,后面不用执行,结果为真。
位操作符与逻辑操作符较为相似,但不要混淆,它们不可互换,逻辑操作符具有短路性质,根据左操作数就可决定,而位操作符两边都需要进行求值;逻辑操作符用于测试零值和非零值,而位操作符用于比较它们的操作数中对应的位。
条件操作符
C 提供了接收三个表达式的条件操作符:
? :
条件操作符也叫三元操作符,使用方式如下所示。
a > 5 ? b-2 : c/3
当 a>5
为真时,整个表达式的值就是 b-2
;如果为假,整个表达式的值就是 c/3
。
逗号操作符
C 提供逗号操作符的用法如下所示。
expre1, expre2,...,expreN
通过逗号,将多个表达式分割开来,自左向右逐个求值,整个逗号表达式的值就是最后那个表达式的值。如 if (b+1, c/2, d > 0)
,只需判断 d
是否大于 0 即可。
逗号和分号不易区分,避免过度使用。
下标引用
C 提供了下标引用,用于访问数组下标中对应的值,下标引用符号是一对方括号 []
。
array[index]
-
array
要访问的数组名称。 -
index
要访问的数组的下标索引。
下标引用并不仅限于数组名。C 的下标值是从 0
开始,并且不会对下标值进行有效性检查。除了优先级不同之处,下标引用操作和间接访问表达式是等价的。
array[index]
*(array+(index))
函数调用
C 提供了函数调用操作符,用于接受一个或多个操作数。函数调用操作符用圆括号 ()
。
int num(int a, int b) {
return a + b;
}
int main() {
int a=10, b=20;
int c = num(a, b); // 调用num函数使用的() 就是函数调用操作符
return 0;
}
num(a, b)
函数调用时,函数名为 num
,而函数调用操作符里是传递给函数的参数。
结构成员
C 提供了 .
和 ->
操作符,用于访问结构的成员。
typedef struct {
int data; //数据域
struct BiTNode * lchild, *rchild; //左右孩子指针
} BiTNode;
当声明一个结构体变量时,可以使用 .
和 ->
。
BiTNode bi;
bi.data = 1;
printf("%dn", bi.data);
bi
是结构变量,可以通过 bi.data
访问 bi
中名为 data
的成员。
BiTNode *pi = malloc(sizeof(BiTNode));
pi->data = 1;
printf("%d", pi->data);
pi
是一个指向 BiTNode
结构的指针,使用 ->
操作符访问它的成员。
左值和右值
C 语言中的左值是出现在赋值符号左边的值,右值是出现在赋值符号右边的值。如下所示:
x = y + 5;
x
是左值,标识了一个可以存储结果值的地点。y+5
是右值,指定了一个值。但是左右值不能互换,因为 y+5
并未标识一个特定的位置。
类型转换
在C 语言的语句和表达式中,应使用类型相同的变量和常量。当使用混合类型时,C 语言会采用一定规则转换为某种共同的类型,但是具有一定的危险性。
int a = 3.14; // 将 double 类型的 3.14 隐式转换为 int 类型
double b = 314; // 将 int 类型的 314 隐式转换为 double 类型
由上述可知,在赋值表达式语句中,计算的最终经过会被转换成被赋值变量的类型,这个过程可能导致类型升级或降级。
所谓升级 promotion
,指的是把一种类型转换成更高级别的类型;所谓降级 demotion
,指的是把一种类型转换成更低级别的类型。
整型提升 intergral promotion
是隐式转换中类型升级的一种,是表达式中的字符型和短整型操作数在使用之前被转换为普通整型,如下所示。
char a,b,c;
···
a = b + c;
b
和 c
的值被提升为普通整型,然后再执行加法运算。加法运算的结果将被截短,然后再存储于 a 中。
不同类型的操作数进行算术运算时,会转换为同一种类型进行运算,否则操作无法操作。寻常算术转换 usual arithmetic conversion
的层次结构如下所示。
long double
double
float
unsigned long int
long int
unsigned int
int
如果操作数的类型在上面列表中排名较低,那首先将转换为另外一个操作数的类型然后执行操作。但算术转换要合理,否则会存在一些潜在的问题,如下所示。
int a = 5000;
int b = 25;
long c = a * b;
表达式 a*b
以整型进行计算,获取的值在 32
位整数的机器上没有问题,但在 16
位整数的机器上,运算会产生溢出,c
会被初始化为错误的值。解决方案是在执行前把其中一个或两个操作数转换为长整型。
long c = (long) a * b;
整型值转换为 float
型时,可能会损失精度。float
型值转换为整型时,小数部分被舍弃,并不进行四舍五入。如果浮点数的值过于庞大,无法容纳于整型值时,其结果将是未定义的。
操作符的属性
复杂表达式的求值顺序由 3 个因素决定:
-
操作符的优先级。决定多个操作符的顺序,取决于操作符的优先级,当优先级相同时,执行顺序由结合性决定。 -
操作符的结合性。结合性是一串操作符是从左向右依次执行还是从右向左逐个执行。 -
操作符是否控制执行的顺序。可以对整个表达式求值顺序施加控制,根据不同条件施加不同的求值过程。
下表给出了操作符的所有属性。
优先级 | 操作符 | 描述 | 用法示例 | 结合性 | 是否控制求值顺序 |
---|---|---|---|---|---|
1 |
() | 聚组/函数调用 | (表达式)/rexp(rexp,…,rexp) | N/A / L-R | 否 |
[] | 下标引用 | rexp[rexp] | L-R | 否 | |
. | 访问结构成员 | lexp.member_name | L-R | 否 | |
-> | 访问结构指针成员 | rexp->member_name | L-R | 否 | |
2 |
++ | 后缀自增 | lexp++ | L-R | 否 |
— | 后缀自减 | lexp– | L-R | 否 | |
! | 逻辑反 | !lexp | R-L | 否 | |
~ | 按位取反 | ~rexp | R-L | 否 | |
+ | 单目,表示正值 | +rexp | R-L | 否 | |
– | 单目,表示负值 | -rexp | R-L | 否 | |
++ | 前缀自增 | ++lexp | R-L | 否 | |
— | 前缀自减 | –lexp | R-L | 否 | |
* | 间接访问 | *rexp | R-L | 否 | |
& | 取地址 | &rexp | R-L | 否 | |
sizeof | 取其长度,以字节表示 | sizeof rexp sizeof(type) |
R-L | 否 | |
(type) | 类型转换 | (type)rexp | R-L | 否 | |
3 |
* | 乘法 | rexp*rexp | L-R | 否 |
/ | 除法 | rexp/rexp | L-R | 否 | |
% | 整数取余 | rexp%rexp | L-R | 否 | |
4 |
+ | 加法 | rexp+rexp | L-R | 否 |
– | 减法 | rexp-rexp | L-R | 否 | |
5 |
<< | 左移位 | rexp<<rexp | L-R | 否 |
>> | 右移位 | rexp>>rexp | L-R | 否 | |
6 |
> | 大于 | rexp>rexp | L-R | 否 |
>= | 大于等于 | rexp>=rexp | L-R | 否 | |
< | 小于 | rexp<rexp | L-R | 否 | |
<= | 小于等于 | rexp<=rexp | L-R | 否 | |
7 |
== | 等于 | rexp==rexp | L-R | 否 |
!= | 不等于 | rexp!=rexp | L-R | 否 | |
8 | & | 位与 | rexp&rexp | L-R | 否 |
9 | ^ | 位异或 | rexp^rexp | L-R | 否 |
10 | | | 位或 | rexp|rexp | L-R | 否 |
11 | && | 逻辑与 | rexp&&rexp | L-R | 是 |
12 | || | 逻辑或 | rexp||rexp | L-R | 是 |
13 | ?: | 条件操作符 | rexp?rexp:rexp | N/A | 是 |
14 |
= | 赋值 | lexp=rexp | R-L | 否 |
+= | 以…加 | lexp+=rexp | R-L | 否 | |
-= | 以…减 | lexp-=rexp | R-L | 否 | |
*= | 以…乘 | lexp*=rexp | R-L | 否 | |
/= | 以…除 | lexp/=rexp | R-L | 否 | |
%= | 以…取模 | lexp%=rexp | R-L | 否 | |
<<= | 以…左移 | lexp<<=rexp | R-L | 否 | |
>>= | 以…右移 | lexp>>=rexp | R-L | 否 | |
&= | 以…与 | lexp&=rexp | R-L | 否 | |
^= | 以…异或 | lexp^=rexp | R-L | 否 | |
|= | 以…或 | lexp|=rexp | R=L | 否 | |
15 | , | 逗号 | rexp,rexp | L-R | 是 |
当表达式中的操作符超过一个时,其执行顺序由操作符的优先级决定。如果优先级相同,执行顺序由结合性决定,除此之外,编译器可以自由决定使用任何顺序对表达式进行求值,只要不违背逗号、&&
、||
和 ?:
操作符所施加的限制。
原文始发于微信公众号(海人为记):一文讲解C语言操作符
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27531.html