3852 字
19 分钟
从零接触C语言(初览)-VIII: 操作符
TIP

如果你从未接触过 C 语言, 那么我建议你先阅读前面的文章:

📌 从零开始接触 C 语言

操作符#

认识条件分支和循环语句的文章 中, 介绍了逻辑运算符&& || !和关系运算符== != >= <= > <

这两类运算符是 C 语言操作符的一部分

什么是操作符#

操作符(Operator) 是 C 语言中用于对数据(变量、常量等)执行特定操作的符号或关键字

简单来说, C 语言中的操作符就是用来操作数据的, 特定的操作符可以对数据实现特定的操作

操作符介绍#

C 语言存在许多操作符, 本篇文章只做简单介绍, 某些操作符的具体使用方法, 在之后的文章中会涉及

算术运算符#

C 语言提供的算术运算符有:

运算符功能示例
+加, 实现两数相加1 + 1 => 2
-减, 实现两数相减1 - 1 => 0
*乘, 实现两数相乘2 * 2 => 4
/除, 实现两数相除2 / 2 => 1
%模, 取模, 即对整数求余数5 % 2 => 1

移位运算符#

移位运算符, 顾名思义是操作在数据的二进制层面的, 用于对数据二进制位进行左移和右移

运算符功能示例
<<左移位操作符, 实现将数据二进制位向左移动1 << 2 => 4
>>右移位操作符, 实现将数据二进制位向右移动8 >> 2 => 2

移位操作符, 就是将数据 在二进制位的层面上 向左或向右移动n

移位操作符可能有一些抽象, 如果对二进制不是很了解, 可能不容易理解

不过也可以通过例子简单理解一下:

/*
* 以 4(int) 为例
* 4 的 二进制是 00000000 00000000 00000000 00000100 (32位)
* 4 << 5, 就是 二进制位左移5位, 得:
* 00000000 00000000 00000000 00000000 10000000, 128
*
* 4 >> 2, 就是 二进制位右移2位, 得:
* 00000000 00000000 00000000 00000000 00000001, 1
*/
NOTE

有关移位操作还有一些细节与数据类型有关, 暂不做更多分析

但要注意, <<左移时 移动的位数只能小于数据类型大小, 否则就是 C 语言标准未定义的行为, 会出现未知的问题

// 左移的错误例子
#include <stdio.h>
int main() {
int age = 2;
printf("%u\n", age << 32);
return 0;
}

如果左移位数不小于数据类型的大小, 编译时也会提醒

位运算符#

位操作符, 也是操作在数据的二进制层面的, 但并不是移动:

运算符功能示例
&按位与, 对两数二进制位执行与操作, 对应位置均为 1, 结果位才为 12 & 3 => 2
|按位或, 对两数二进制位执行或操作, 对应位置均为 0, 结果位才为 02 | 4 => 6
~按位取反, 对数据二进制位执行取反操作, 对数据每位进行取反操作, 即 1 变 0, 0 变 1~1 => -2 (有符号)
^按位异或, 对两数二进制数据执行异或操作, 对应位置不同, 结果位才为 12 ^ 3 => 1

位操作符, 比移位操作符要更加抽象一些, 不过依旧可以通过例子进行理解:

/*
* 以 4(int) 和 7(int) 为例
* 4: 00000000 00000000 00000000 00000100
* 7: 00000000 00000000 00000000 00000111
*
* 4 & 7 (与):
* 00000000 00000000 00000000 00000100
* 00000000 00000000 00000000 00000111
* -----------------------------------
* 00000000 00000000 00000000 00000100 (对应位均为1, 结果位才为1)
*
* 4 | 7 (或):
* 00000000 00000000 00000000 00000100
* 00000000 00000000 00000000 00000111
* -----------------------------------
* 00000000 00000000 00000000 00000111 (对应位均为0, 结果位才为0)
*
* 4 ^ 7 (异或):
* 00000000 00000000 00000000 00000100
* 00000000 00000000 00000000 00000111
* -----------------------------------
* 00000000 00000000 00000000 00000011 (对应位不同, 结果位才为1)
*
* ~4 (取反);
* 00000000 00000000 00000000 00000100
* -----------------------------------
* 11111111 11111111 11111111 11111011 (对应位取反)
*/

仔细观察一下, 比较容易理解的

赋值运算符#

C 语言提供了赋值运算符, 可以给变量赋值: =

用法很简单:

#include <stdio.h>
int main() {
int age;
age = 20;
printf("%d\n", age);
return 0;
}

这段代码, age = 20;age变量赋值20

除此之外, C 语言还提供了一些赋值运算符的扩展, 被称为复合赋值运算符:

| 运算符 | 功能 | 示例 | 等价形式 | | ------ | -------------------------------------------------------- | ---------------------------------------------------- | ------------- | ----- | ------ | --- | | = | 赋值, 将右侧的值 赋给左侧变量 | a = 5; | a = 5; | | += | 加后赋值, 左侧变量 加 右侧值 再赋给左侧 | a += 3; | a = a + 3; | | -= | 减后赋值, 左侧变量 减 右侧值再赋给左侧 | a -= 2; | a = a - 2; | | *= | 乘后赋值, 左侧变量 乘 右侧值 再赋给左侧 | a *= 4; | a = a * 4; | | /= | 除后赋值, 左侧变量 除以 右侧值 再赋给左侧 | a /= 2; | a = a / 2; | | %= | 取模后赋值, 左侧变量 对 右侧值取模 再赋给左侧 | a %= 3; | a = a % 3; | | &= | 按位与后赋值, 左侧变量 与右侧值按位与 再赋给左侧 | a &= b; | a = a & b; | | | = | 按位或后赋值, 左侧变量 与右侧值按位或 再赋给左侧 | a | = b; | a = a | b; | | ^= | 按位异或后赋值, 左侧变量 与右侧值按位异或 再赋给左侧 | a ^= b; | a = a ^ b; | | <<= | 左移后赋值, 左侧变量 左移 右侧值位 再赋给左侧 | a <<= 2; | a = a << 2; | | >>= | 右移后赋值, 左侧变量 右移 右侧值位 再赋给左侧 | a >>= 1; | a = a >> 1; |

在不考虑编译器额外优化的情况下, 使用复合赋值运算符要比正常的计算后赋值, 运行效率要高一点

单目操作符#

单目操作符, 从名字来看 看不出是什么意思

事实上, 单目操作符就是操作单个对象的操作符

什么是操作单个对象呢?

以之前用过的计算变量大小的操作符为例: sizeof

sizeof的用法是这样的:

sizeof(int); // 操作类型
sizeof(1.23f); // 操作数据
long long age = 10;
sizeof(age); // 操作变量

可以看到, 操作的对象只有一个

上面已经介绍过的算术、移位、位、赋值运算符都是双目操作符, 他们操作的都是两个对象:

var1 + var1;
var1 - 1;
var1 << 2;
var1 & var2;
var1 += 1;
...

C 语言最常用的单目操作符有:

操作符功能示例
++自增, 即 自增 1. 但 区分前置和后置var++++var
--自减, 即 自减 1. 但 区分前置和后置var----var
sizeof求变量、类型等大小sizeof(int)sizeof int
&取地址, 可以取到变量空间在内存中地址值&var
+正值+var
-负值-var
!逻辑取反, 属于逻辑运算符!var
~按位取反, 属于位运算符~var
*解引用, 可以解引用指针, 按特定类型访问指针所指向空间的值
指针定义声明符, 用于声明 定义指针变量
*addr(解引用)
char*(声明指针)
(type)强制类型转换, 可以将变量、常量值强制转换为目标类型(int)23.33

单目操作符中, 有几个需要特别介绍一下:

  1. ++--#

    自增和自减操作符

    如果不区分前置和后置, 从功能上很容易理解, 就是自增 1, 它与+= 1是等价的

    但自增和自减操作符是区分前置和后置的, 且在使用上有很大的区别, 即 i++++i是存在区别的

    可以直接通过代码来介绍:

    #include <stdio.h>
    int main() {
    int index = 0;
    index++;
    printf("index: %d\n", index);
    ++index;
    printf("index: %d\n", index);
    return 0;
    }

    这段代码的运行结果很明显, 只是index变量两次自增 1

    从结果来看好像没有区别

    实际上, 要观察区别, 应该这样观察:

    #include <stdio.h>
    int main() {
    int index = 0;
    int res1 = index++;
    int res2 = index;
    int res3 = ++index;
    printf("res1: %d\n", res1);
    printf("res2: %d\n", res2);
    printf("res3: %d\n", res3);
    return 0;
    }

    这段代码的运行结果是什么呢?

    自增的执行顺序还是 先index++++index

    但从结果可以看到res1 = 0; (index++) res2 = 1; (index) res3 = 2; (++index)

    为什么res1 = 0? 不是自增了吗?

    res = 1可以看出来index确实自增了

    为什么res3 = 2? 按照res1的结果推测, res3不应该是1吗?

    造成这个结果的原因就是 前置++和后置++存在的区别了

    ++前后置调用的区别是:

    前置++, 先执行自增 1 的操作, 然后将自增后的结果返回

    后置++, 先保存原值, 再执行自增 1 的操作, 然后返回原值

    所以, 从没有优化的角度来看, 前置++要比后置++性能稍微好一点点


    --自减操作符, 与++自增操作符相同, 同样区分前置使用和后置使用

  2. &* (简单介绍)#

    C 语言中, &取地址操作符 和 *解引用操作符 是两个密切关联的操作符

    &取地址操作符, 作用于变量, 作用是 取变量在内存中占用空间的地址

    *解引用操作符, 作用于指针, 作用是 按类型访问指针所指向地址的空间

    这两个操作符的使用通常是相关的:

    #include <stdio.h>
    int main() {
    int index = 10;
    int* pIndex = &index;
    printf("index在内存中的地址: %p, index的值: %d\n", pIndex, *pIndex);
    return 0;
    }

    简单介绍一下这段代码具体做了什么操作:

    1. int index = 10;, 定义一个整型变量index并初始化为10

    2. int* pIndex = &index;, 定义一个int*类型变量pIndex并初始化为 变量index的地址

      &取地址操作符, 作用于变量, 可以取出这个变量在内存中的地址

      上面列表中介绍了*可以用来声明定义指针变量, int*就是一个int类型的指针变量

      指针(pointer)变量是用来存储地址的一种变量, 在此例中pIndex这个变量中存储的就是index的地址

    3. 访问pIndex

      pIndex中存储的是index这个变量的地址, 可以看作pIndex指针变脸实际指向index的地址空间

      那么, 访问pIndex就能获取到index的地址

    4. 访问*pIndex

      *解引用操作符, 始终作用于指针, 用于按指针类型访问指针所指向的空间

      pIndex是一个int类型的指针, 存储的是变量index的地址

      那么*pIndex就会按照int类型去访问pIndex的存储的地址, 实际访问到的就是变量index的空间

      所以*pIndex就能访问到index的值

    NOTE

    这两个操作符的使用细节 以及 指针的使用细节

    将在之后具体介绍指针的文章中涉及

  3. (类型)#

    C 语言可以对数据进行强制类型转换, 即 将非目标类型的数据强制转换为目标类型

    使用上也非常简单:

    #include <stdio.h>
    int main() {
    float varF = 12.333;
    printf("%d %d\n", (int)varF, varF);
    return 0;
    }

    这段代码的执行结果:

    同样用%d打印整型数据的格式化字符, (int)varF正确打印出了12, 而varF却是0

    在 C 语言标准中, 使用特定类型的格式化字符 尝试去 格式化特定类型的行为, 是没有被定义的, 即 是一种未定义行为

    所以使用%d去格式化浮点数据, 是一种未定义行为, 结果通常是未知的

    但可以通过 (int)floatData将浮点类型数据强制转换为整型数据, 这样再使用%d进行格式化, 就不是未定义行为了

条件操作符#

C 语言标准中定义了一个唯一一个三目操作符, 同时也被称作条件操作符: condition ? result1 : result2

condition表示条件表达式, result1result2表示两个结果

条件操作符的用法为: 条件满足时, 执行并返回result1, 否则 执行并返回result2

以一个简单的例子做介绍:

(a > b) ? (a - b) : (b - a)

例子的意思为:

如果a > b成立, 则执行a - b并返回结果, b - a不会被执行

如果a > b不成立, 则执行b - a并返回其结果, a - b不会被执行

可以将? :当作一个简写的if-else条件语句, 逻辑上是完全等价的

逗号表达式#

C 语言标准中提供了一个特殊的操作符,

用法是这样的:

expression1, expression2, expression3, expression4, expression5, expression6, expression7, expression8, ...

可以通过,组成不限制长度的逗号表达式

逗号表达式的规则为: 从左到右按顺序执行所有的表达式, 但整个逗号表达式的结果为最后一个表达式的执行结果

以简单的代码为例:

#include <stdio.h>
int main() {
int index = 0;
index = ++index, ++index, ++index, ++index, ++index;
printf("index: %d\n", index);
return 0;
}

执行结果为:

可以看到, index的最终结果为5, 即为最后一次执行++index的结果

特殊操作符#

除了上述的操作符, C 语言标准中还存在几个特殊的操作符, 具体功能需要结合具体的情况进行使用:

  1. []数组下标访问操作符
  2. ()函数调用操作符
  3. .结构体成员访问操作符
  4. ->结构体成员访问操作符

[]()没有什么需要特别注意的

.->在介绍结构体时再进行介绍

操作符的运算优先级#

C 语言操作符的使用, 也是有优先级的

就像小学就学过的”先乘除后加减”一样, C 语言也一样

不过, C 语言中这么多的操作符, 运算优先级也都不尽相同:

| 优先级 | 操作符 | 描述 | 结合性 | | ------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------ | ------ | ------ | | 1 | () [] -> . | 函数调用、数组下标、结构体成员访问 | 左到右 | | 2 | ++ -- | 后缀自增、后缀自减 | 左到右 | | 3 | ++ -- + - ! ~ (type) * & sizeof | 前缀自增、前缀自减、正号、负号、逻辑非、按位取反、类型转换、解引用、取地址、求字节大小 | 右到左 | | 4 | * / % | 乘法、除法、取模 | 左到右 | | 5 | + - | 加法、减法 | 左到右 | | 6 | << >> | 左移、右移 | 左到右 | | 7 | < <= > >= | 关系比较 | 左到右 | | 8 | == != | 相等比较 | 左到右 | | 9 | & | 按位与 | 左到右 | | 10 | ^ | 按位异或 | 左到右 | | 11 | | | 按位或 | 左到右 | | 12 | && | 逻辑与 | 左到右 | | 13 | | | | 逻辑或 | 左到右 | | 14 | ?: | 条件操作符(三目运算符) | 右到左 | | 15 | = += -= *= /= %= <<= >>= &= ^= \|= | 赋值操作符 | 右到左 | | 16 | , | 逗号表达式 | 左到右 |

C 语言操作符的优先级是需要牢记的, 序号越小 运算优先级越高

当然, 如果你在使用操作符时不确实不确定哪个优先级高, 你可以用()进行包裹, 被()包裹的表达式总是优先计算

作者
Humid1ch
发布于
2025-08-07
许可协议
CC BY-NC-SA 4.0