一文讲解C语言操作符

C 语言提供了非常丰富的操作符,根据不同的语义选取不同的操作符,在程序中使用。根据操作符功能或使用方式对它们进行如下分类。

一文讲解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 的二进制值为 01011011a & b 的结果是 00001010a|b 的结果是 01111111a ^ b 的结果是 01110101

赋值操作符

C 提供了 = 作为赋值操作符。赋值是表达式的一种,而不是某种类型的语句。所以,只要是允许出现表达式的地方,都允许进行赋值。如下所示:

a=x=y+3;

上述表达式语句中 = 的操作数是变量 x 和 表达式 y+3 的值。= 把右操作数的值存储于左操作数的指定的位置。而上面的语句根据赋值操作符的结合性是从右到左,所以这个表达式相当于:

a=(x=y+3);

与下面的语句组合完全相同:

x=y+3;
a=x;

ax 不是被赋予了相同的值,这要看 xa 的数据类型。如果 x 是一个字符型变量,a 是一个整型变量,那么 y+3 的值就会被截去一段,以便容纳于字符类型的变量中,那么 a 所赋的值就是这个被截短后并在被提升为整型的值。

除了 = 赋值操作符外,还有= 与其他操作符合在一起的复合赋值符,如下所示:

+=     -=     *=    /=    %=
<<=    >>=    &=    ^=    |=

这些操作符使用基本都相似,下面给出 += 操作符的用法:

a += expres

功能相当于下面的表达式:

a=a+(expres)

唯一不同之处是 += 操作符的左操作数只求值一次。这里的括号是确保表达式在执行加法运算前已被完整求值,即使内部包含有优先级低于加法运算的操作符。

单目操作符

C 提供了以下单目运算符:

!    ++    -    &    sizeof
~    --    +    *    (type)
  • ! 操作符,对操作数执行逻辑反操作。如果操作数为真,其结果为假;如果操作数为假,其结果为真。和关系操作符一样,这个操作符实际上产生 01
  • ~ 操作符,对整型类型的操作数进行求补操作,操作数中所有原先为 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;

bc 的值被提升为普通整型,然后再执行加法运算。加法运算的结果将被截短,然后再存储于 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. 操作符的优先级。决定多个操作符的顺序,取决于操作符的优先级,当优先级相同时,执行顺序由结合性决定。
  2. 操作符的结合性。结合性是一串操作符是从左向右依次执行还是从右向左逐个执行。
  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语言操作符


原文始发于微信公众号(海人为记):一文讲解C语言操作符

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27531.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!