运算符

熟悉了如何表示数据之后,接下来我们学习如何处理数据。C 语言为处理数据提供了大量的操作,可以在程序中进行算术运算、比较值的大小、修改变量、逻辑地组合关系等。下面先为大家数量一下C语言中常用的运算符分类:

运算符类型 作用
算术运算符 用于处理四则运算
赋值运算符 用于将表达式的值赋给变量
比较运算符 用于表达式的比较,并返回一个真值或假值
逻辑运算符 用于根据表达式的值返回真值或假值
位运算符 用于处理数据的位运算
sizeof 运算符 用于求字节数长度

1. 算术运算符

下表中为大家列出了C语言中先关的算法运算符,其使用方法也很简单:

运算符 术语 示例 结果
+ 正号 +3 3
- 负号 -3 -3
+ 10 + 5 15
- 10 - 5 5
* 10 * 5 50
/ 10 / 5 2
% 取模(取余) 10 % 3 1
++ 前自增 a=2; b=++a; a=3; b=3;
++ 后自增 a=2; b=a++; a=3; b=2;
-- 前自减 a=2; b=–a; a=1; b=1;
-- 后自减 a=2; b=a–; a=1; b=2;

关于正号(+)、负号(-)、加(+)、减(-)、乘(*)、除(/)运算符这里就不再过多赘述了,使用发方法和我们学习的数学知识是一致的。

  • 取余(%):被除数和除数进行除法运算,舍弃商保留其余数。
  • 前缀自增(++):运算符++写到变量前面,先进行自增运算,然后取变量的值
  • 后缀自增(++):运算符++写到变量后面,先取变量的值,然后进行自增运算
  • 前缀自减(--):运算符--写到变量前面,先进行自增运算,然后取变量的值
  • 后缀自减(--):运算符--写到变量后面,先取变量的值,然后进行自减运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
int main()
{
int i = 5, j = 5;
int result1 = ++i;
int result2 = j++;
printf("result1 = %d, i = %d\n", result1, i);
printf("result2 = %d, j = %d\n", result2, j);
i++;
++j;
printf("i = %d, j = %d\n", i, j);
printf("==========================\n");
int a = 5, b = 6;
int c = a++ + b++;
printf("a = %d, b = %d, c = %d\n", a, b, c);
c = ++a + ++b;
printf("a = %d, b = %d, c = %d\n", a, b, c);
c = a++ + ++b;
printf("a = %d, b = %d, c = %d\n", a, b, c);

return 0;
}

程序输出的结果为:

1
2
3
4
5
6
7
result1 = 6, i = 6
result2 = 5, j = 6
i = 7, j = 7
==========================
a = 6, b = 7, c = 11
a = 7, b = 8, c = 15
a = 8, b = 9, c = 16

通过上面的例子可以得出以下结论:

  1. 对于变量自身来说,不论是前自增/自减还是后自增/自减,最后变量的值都会增/减 1,这种情况下前/后自增、前/后自减是等价的
  2. 如果想要取出某个自增/自减变量的值:
    • 后自增/自减是先取变量的值,然后变量再进行自增/自减运算
    • 前自增/自减是变量先进行自增/自减运算,然后再取该变量的值。

2. 赋值运算符

赋值运算符(Assignment Operators)的作用就是将右侧的值赋给左侧的变量。在C语言中,常见的赋值运算符有以下几种:

运算符 术语 示例 结果
= 赋值 a=2; b=3; a=2; b=3;
+= 加等于 a=0; a+=2;(a=a+2) a=2;
-= 减等于 a=5; a-=3;(a=a-3) a=2;
*= 乘等于 a=2; a*=2;(a=a*2) a=4;
/= 除等于 a=4; a/=2;(a=a/2) a=2;
%= 模等于 a=3; a%2;(a=a%2) a=1;

其他还有位运算赋值符(如:按位与等于&=、按位或等于|=、按位异或等于^=)和移位运算赋值符(如:左移等于<<=、右移等于>>=)。这些运算符可以进行类似的操作,将左侧变量和右侧的值进行位运算或移位运算,并将结果赋给左侧变量。

这些赋值运算符可以简化代码,同时更新变量的值。

3. 比较运算符

比较运算符(Comparison Operators)用于比较两个值之间的关系,并返回一个布尔值(truefalse)。在C语言中,常见的比较运算符有以下几种:

运算符 术语 示例 结果
== 相等于 4 == 3 0
!= 不等于 4 != 3 1
< 小于 4 < 3 0
> 大于 4 > 3 1
<= 小于等于 4 <= 3 0
>= 大于等于 4 >= 1 1

这些比较运算符常用于条件语句(如if语句、循环语句等)中,根据表达式的结果来决定程序的执行逻辑(后续章节中会进行详细讲解)。

4. 逻辑运算符

逻辑运算符(Logical Operators)用于对多个条件进行逻辑运算,并返回一个布尔值(true或false)。在C语言中,常见的逻辑运算符有以下几种:

运算符 术语 示例 结果
! !a 如果a为假,则!a为真;如果a为真,则!a为假。
&& a && b 如果a和b都为真,则结果为真,否则为假。
|| a || b 如果a和b有一个为真,则结果为真,二者都为假时,结果为假。
  1. 逻辑与(&&):当所有条件都为 true 时,返回 true,否则返回 false。
  2. 逻辑或(||):当至少一个条件为 true 时,返回 true,否则返回 false。
  3. 逻辑非(!):对一个条件取反。
    • 如果条件为true,则返回 false
    • 如果条件为false,则返回 true。

这些逻辑运算符通常用于复合条件的判断,可以通过组合多个条件来确定程序的执行逻辑。逻辑与运算符和逻辑或运算符经常与条件语句(如if语句)结合使用,而逻辑非运算符可以用于取反一个条件的结果。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdbool.h>
int main()
{
bool flag = true;
flag = !flag;
printf("flag = %d\n", flag);
flag = !flag;
printf("flag = %d\n", flag);

return 0;
}

程序输出的结果为:

1
2
flag = 0
flag = 1

5. 位运算符

所谓的位运算其实是就是针对于二进制数据的运算。在 C 语言中,常用的位运算符有以下几种:

  • 按位与运算符:&
  • 按位或运算符:|
  • 按位异或运算符:^
  • 按位取反运算符:~
  • 左移运算符:<<
  • 右移运算符:>>

5.1 按位与

按位与运算(&)是对两个操作数的每个二进制位进行与操作,只有当对应位上的两个操作数都为1时,结果位才为1,否则为0。

关于按位与的特点可以精简为一句话:0 和任何数(0和1)都为 0。

1
2
3
int a = 2;
int b = 4;
int result = a & b;
  • 整形数 2 对应的二进制数是10
  • 整形数 4 对应的二进制数是100
1
2
3
4
  010
& 100
------
000

通过按位与操作,我们最终得到的结果为 0。

使用按位与可以快速判断整数的奇偶性,奇数的二进制表示的最低位为1,偶数的二进制表示的最低位为0。示例:

1
2
3
4
5
6
7
8
9
int num = 9;
if (num & 1)
{
// num 是奇数
}
else
{
// num 是偶数
}

5.2 按位或

按位或运算(|)是对两个操作数的每个二进制位进行或操作,只要对应位上的两个操作数中至少有一个为1,结果位就为1,否则为0。

关于按位或的特点可以精简为一句话:1 和任何数(0和1)都为 1。

1
2
3
int a = 2;
int b = 4;
int result = a | b;
  • 整形数 2 对应的二进制数是10
  • 整形数 4 对应的二进制数是100
1
2
3
4
  010
| 100
------
110

通过按位或操作,我们最终得到的二进制结果为 110,也就是十进制的 6。

5.3 按位异或

5.3.1 基本使用

按位异或运算(^)是对两个操作数的每个位进行异或操作,当对应位上的两个操作数不同时,结果位为1,否则为0。

关于按位异或的特点可以精简为一句话:相同为0,不同为 1。

1
2
3
int a = 2;
int b = 6;
int result = a ^ b;
  • 整形数 2 对应的二进制数是10
  • 整形数 4 对应的二进制数是110
1
2
3
4
  010
^ 110
------
100

通过按位异或操作,最终得到的二进制结果为 100,也就是十进制的 4。

5.3.2 实际应用

按位异或(^)在实际应用中有以下两个常见用途(第二个个例子有循环, 可以在学完后面的知识点之后,再返回来消化吸收一下):

  1. 交换两个变量的值:通过按位异或可以交换两个变量的值,而不需要使用临时变量。示例:

    1
    2
    3
    4
    5
    6
    int a = 5;
    int b = 7;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    // 现在 a = 7, b = 5
  2. 加密与解密:按位异或可用于简单的加密和解密算法。通过将数据与一个密钥按位异或,可以对数据进行加密。同样地,将加密后的数据再与密钥按位异或,可以解密数据。示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    unsigned char data[] = {0x45, 0x23, 0x7F, 0x18};
    unsigned char key = 0xAB;
    // 加密
    for (int i = 0; i < sizeof(data); i++)
    {
    data[i] = data[i] ^ key;
    }

    // 解密
    for (int i = 0; i < sizeof(data); i++)
    {
    data[i] = data[i] ^ key;
    }

这些是按位异或运算符在实际应用中的几个常见用途,但也可以根据具体需求进行其他更复杂的运算和处理。按位异或操作的特性使得它在某些场景下非常有用,可以提供高效且简单的实现方式。

5.3 按位取反

按位取反运算(~)是对操作数的每个位进行取反操作,将1变为0,将0变为1。

1
2
3
4
int a = 1;
int b = 5;
a = ~a;
b = ~b;

接下来,我们将十进制数值转换为二进制进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 十进制的 1
~01 = 1111 1111 1111 1111 1111 1111 1111 1110
补码: 1111 1111 1111 1111 1111 1111 1111 1110
// 原码 = 补码的数据位取反 + 1 (符号位不变)
原码: 1000 0000 0000 0000 0000 0000 0000 0010

===============================================

// 十进制的 5
~101 = 1111 1111 1111 1111 1111 1111 1111 1010
补码: 1111 1111 1111 1111 1111 1111 1111 1010
// 原码 = 补码的数据位取反 + 1 (符号位不变)
原码: 1000 0000 0000 0000 0000 0000 0000 0110

根据上面的推导就可以得到如下结果:a = -2, b = -6

5.4 左移和右移

左移运算(<<)是将一个数的二进制表示向左移动指定的位数,右侧空出的位用0填充。

右移运算(>>)是将一个数的二进制表示向右移动指定的位数,左侧空出的位根据类型来填充:

  • 对于有符号数,使用算术右移,用符号位填充左侧空位
  • 对于无符号数,用 0 填充左侧空位
1
2
3
4
5
6
7
8
9
10
11
int main()
{
int a = 6;
int b = 32;
int c = -64;
a = a << 2;
b = b >> 2;
c = c >> 3;
printf("a = %d, b = %d, c = %d\n", a, b, c);
return 0;
}

程序输出的结果为:

1
a = 24, b = 8, c = -8
  • 对于正数,右移一位相当于除以 2,向左移一位相当于乘以 2

  • 在右移操作中,负数右移一位并不严格等于除以2,而是依赖于具体的编译器实现方式,因为 C 语言中没有明确规定。一种常见的实现方式是使用算术右移,即保留符号位并用符号位填充左侧空出的位。因此,负数右移一位并不一定等于除以2。

  • 对于除以2的操作,除非对被除数是无符号整数,否则建议使用除法运算符(/)来达到目的。

这些位运算符可以用于位级操作和位级运算,用于对二进制数的位进行操作。它们在一些特定的场景下非常有用,如位掩码、位标志、图像处理等。

6. 运算符优先级

在C语言中,不同的运算符具有不同的优先级,这会影响表达式中运算符的计算顺序。下图是C语言中所有运算符的优先级顺序表:

在上面的表里边有关于运算符运算对象的多少有三个分类:

  • 一元运算符,即单目运算符
  • 二元运算符,即双目运算符
  • 三元运算符,即三目运算符

通过上表还可以看到,我们将C语言中的运算符的优先级划分了15个等级,其中很多运算符现阶段我们还没有接触到,随着学习的深入会经常查阅该表中的内容,记不住没关系,以后随用随查,用的多了自然就忘不掉了。

其中有一点需要大家一定要注意不要自作聪明,在一个语句或者表达式中使用太多的运算符,这样会降低代码的可读性,甚至自己都会被搞糊涂,远离bug珍爱生命。