C语言运算符
1. 引言
运算符(Operator)是 C 语言的基石,它告诉编译器要对一个或多个操作数执行特定的数学运算、逻辑运算或数值操作。在 C 语言中,运算符的种类丰富、优先级复杂,是初学者最容易踩坑的地方,也是进阶程序员编写高效代码的利器。
本文将从运算符的分类到具体用法,结合真实可运行的代码示例,带大家系统梳理 C 语言中的各种运算符,让你不再为 ++、&&、-> 等符号发愁。
2. C 语言运算符分类
C 语言中的运算符可以粗略分为以下几大类:
- 算术运算符:
+-*/% - 关系运算符:
><>=<===!= - 逻辑运算符:
&&||! - 位运算符:
&|^~<<>> - 赋值运算符:
=+=-=*=/=%=<<=>>=&=|=^= - 条件运算符(三元):
? : - 逗号运算符:
, sizeof运算符- 指针与成员访问运算符:
*&->. - 其它:如强制类型转换
(type)、函数调用()、下标[]
3. 算术运算符
算术运算符用于基本的数学计算。
| 运算符 | 含义 | 示例 |
|---|---|---|
+ | 加法 / 正号 | a + b |
- | 减法 / 负号 | a - b |
* | 乘法 | a * b |
/ | 除法 | a / b |
% | 取模(求余) | a % b |
注意:
- 除号
/在两个整数相除时执行整数除法,结果舍去小数部分。如5 / 2结果为2。 - 取模运算符
%的两个操作数都必须是整数,结果是整除后的余数,如5 % 2结果为1。
示例:
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("a + b = %d\n", a + b); // 13
printf("a - b = %d\n", a - b); // 7
printf("a * b = %d\n", a * b); // 30
printf("a / b = %d\n", a / b); // 3 (整数除法)
printf("a %% b = %d\n", a % b); // 1
return 0;
}
4. 关系运算符
关系运算符用来比较两个值的大小关系,结果是一个布尔值(C89 中用 0 表示假,非 0 表示真;C99 开始提供 _Bool 类型和 stdbool.h 中的 true/false)。
| 运算符 | 含义 | 示例 |
|---|---|---|
> | 大于 | a > b |
< | 小于 | a < b |
>= | 大于等于 | a >= b |
<= | 小于等于 | a <= b |
== | 等于 | a == b |
!= | 不等于 | a != b |
注意不要错把 == 写成 =(赋值),这是 C 语言中最常见的 bug 之一。
#include <stdio.h>
#include <stdbool.h>
int main() {
int x = 10, y = 20;
bool result = (x > y); // false
printf("x > y ? %d\n", result);
printf("x == y ? %d\n", x == y); // 0
printf("x != y ? %d\n", x != y); // 1
return 0;
}
5. 逻辑运算符
逻辑运算符用于对布尔值进行“与或非”运算,通常与关系运算符结合使用。
| 运算符 | 含义 | 示例 |
|---|---|---|
&& | 逻辑与 | a && b |
| ` | ` | |
! | 逻辑非 | !a |
短路特性:
- 对于
a && b,如果a为假,则不再计算b,直接返回假。 - 对于
a || b,如果a为真,则不再计算b,直接返回真。
这个特性经常被用来编写安全的条件判断,例如:
if (ptr != NULL && ptr->value > 0) {
// 安全:只有 ptr 非空才会访问 ptr->value
}
6. 赋值运算符
赋值运算符用于将一个值赋给一个变量。
- 基本赋值:
= - 复合赋值:
+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=
复合赋值运算符相当于先执行运算再赋值,可以简化代码并可能优化效率。例如:
int a = 10;
a += 5; // 等价于 a = a + 5;
a *= 2; // 等价于 a = a * 2;
a %= 3; // 等价于 a = a % 3;
7. 自增自减运算符
++ 和 -- 是 C 语言的特色,分别将变量的值增加 1 或减少 1。它们又分为前缀和后缀两种形式。
- 前缀形式:
++a/--a—— 先执行自增/自减,再使用新值进行表达式计算。 - 后缀形式:
a++/a--—— 先使用原值进行表达式计算,再自增/自减。
int a = 5, b;
b = ++a; // a 先变为 6,然后 b = 6
b = a--; // 先将 a(6)赋给 b,然后 a 变为 5
由于自增自减容易造成代码难以理解,建议不要在同一表达式里多次对同一个变量使用,例如 b = a++ + ++a; 的行为是未定义的。
8. 位运算符
C 语言可以直接对整数类型的二进制位进行操作,这是 C 强大且贴近底层的一面。
| 运算符 | 含义 | 示例 |
|---|---|---|
& | 按位与 | a & b |
| ` | ` | 按位或 |
^ | 按位异或 | a ^ b |
~ | 按位取反(一元) | ~a |
<< | 左移 | a << n |
>> | 右移 | a >> n |
注意:
- 左移相当乘 2 的幂,右移相当除以 2 的幂(注意符号位)。
- 移位运算只对整数类型有意义。
- 位操作常用于掩码、状态标志、加密、性能优化等场景。
示例:
unsigned char flag = 0b00001111;
flag |= (1 << 4); // 设置第 4 位为 1
flag &= ~(1 << 3); // 清除第 3 位
if (flag & (1 << 2)) {
// 检查第 2 位是否为 1
}
9. 条件运算符(三目运算符)
条件运算符是 C 语言中唯一的三目运算符,语法为:
条件 ? 表达式1 : 表达式2
若条件为真,返回表达式1的值;否则返回表达式2的值。它常被用来简洁地代替简单的 if-else。
int max = (a > b) ? a : b;
printf("较大的数是 %d\n", max);
10. 逗号运算符
逗号运算符 , 用于连接两个表达式,按从左到右的顺序依次执行,并返回最后一个表达式的值。它通常用在 for 循环的初始化和递增部分。
for (int i = 0, j = 10; i < j; i++, j--) {
printf("i=%d, j=%d\n", i, j);
}
11. sizeof 运算符
sizeof 用于获取某个类型或变量在内存中所占的字节数,其值由编译器在编译时确定(除了 C99 的可变长数组)。它既可以带括号使用,也可以不带。
int a;
printf("int: %zu\n", sizeof(int));
printf("a: %zu\n", sizeof a);
printf("double: %zu\n", sizeof(double));
sizeof 在动态分配内存、计算数组元素个数时非常实用:
int arr[10];
int n = sizeof(arr) / sizeof(arr[0]); // 10
12. 指针与成员访问运算符
C 语言的指针操作是一大核心,相关的运算符包括:
*:间接引用(解引用)运算符,获取指针指向的值。&:取地址运算符,获取变量的地址。->:通过结构体指针访问成员。.:通过结构体变量访问成员。
struct Point {
int x, y;
};
struct Point p1 = {10, 20};
struct Point *ptr = &p1;
printf("p1.x = %d\n", p1.x); // 使用 . 访问
printf("ptr->y = %d\n", ptr->y); // 等效于 (*ptr).y
13. 运算符优先级与结合性
当一个表达式中出现多个运算符时,编译器需要按一定的规则决定运算顺序,这就是优先级和结合性。
- 优先级:高优先级的运算符先参与运算。例如乘除优先于加减。
- 结合性:当同一优先级的运算符同时出现时,表达式的计算顺序由结合性决定。大多数运算符是左结合(从左向右),少数是右结合(如赋值、条件、一元运算符)。
下面是一张简化版优先级表(从高到低):
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1 | () [] -> . | 左 |
| 2 | ! ~ ++ -- +(正) -(负) *(指针) &(取地址) sizeof (type) | 右 |
| 3 | * / % | 左 |
| 4 | + - | 左 |
| 5 | << >> | 左 |
| 6 | < <= > >= | 左 |
| 7 | == != | 左 |
| 8 | &(按位与) | 左 |
| 9 | ^ | 左 |
| 10 | ` | ` |
| 11 | && | 左 |
| 12 | ` | |
| 13 | ? : | 右 |
| 14 | = += -= 等 | 右 |
| 15 | , | 左 |
建议:当不能确定优先级时,使用括号明确表达意图,括号拥有最高优先级且可读性最好。
// 容易产生歧义
int result = a + b * c; // 等价于 a + (b * c)
// 推荐写法
int result = a + (b * c);
14. 运算符使用建议与常见问题
14.1 使用建议
1. 善用括号,提高可读性
运算符优先级虽然由语言规范严格定义,但人脑记忆有限。与其写出让人挠头的复杂表达式,不如多用括号把意图表达清楚:
// 不推荐:依赖优先级,容易读错
if (flag & mask == 0) { ... } // & 优先级低于 ==
// 推荐:括号显式分组
if ((flag & mask) == 0) { ... }
2. 避免在复杂表达式中混用自增自减
在同一表达式中对同一变量多次使用 ++ / -- 不仅难以阅读,行为还可能未定义:
// 危险:行为未定义
int x = (i++) + (++i);
// 推荐:分开写
int x = i;
i++;
x += i;
i++;
3. 利用短路特性简化条件判断
短路求值是 C 语言的明确保证,可以放心用来避免空指针解引用:
// 安全的空指针检查
if (p != NULL && p->next != NULL) {
p = p->next;
}
4. 用 sizeof 而不是硬编码数值
写死类型大小会导致跨平台问题,始终用 sizeof 计算:
// 不推荐
int *ptr = malloc(4 * n); // 假设 int 是 4 字节
// 推荐
int *ptr = malloc(sizeof(int) * n);
5. 位运算优先用无符号类型
有符号整数在移位时的行为因编译器而异(尤其是右移),处理位掩码和位标志时优先使用 unsigned 类型:
unsigned int flags = 0;
flags |= (1U << 31); // 安全设置高位
14.2 常见问题
Q1: == 和 = 搞混了怎么办?
这是 C 语言的头号陷阱。一个简单有效的习惯是:拿常量与变量比较时,把常量放在前面:
// 推荐写法:如果误写成 =,编译器会报错
if (0 == ret) { ... }
if (NULL == ptr) { ... }
因为 0 = ret 是非法的赋值语句,编译器会立刻拒绝,帮你发现问题。
Q2: 整数除法丢失小数怎么办?
当需要小数结果时,务必把操作数转换为浮点数再除:
int a = 5, b = 2;
// 不推荐:结果 = 2
double result = a / b;
// 推荐:显式转换为浮点
double result = (double)a / b;
// 或更清晰的写法
double result = 5.0 / 2;
Q3: 取模运算符能用负数吗?
可以,但结果的正负号依赖于 C 标准的版本:
printf("%d\n", -5 % 3); // C89: 结果依赖于实现;C99: 结果为 -2
建议只对非负整数使用 %,或先转成正数再取模。
Q4: 三元运算符能嵌套吗?
能,但可读性会急剧下降:
// 不推荐:阅读困难
int grade = (score >= 90) ? 1 : (score >= 60) ? 2 : 3;
// 推荐:用 if-else 或提前计算
int grade;
if (score >= 90) grade = 1;
else if (score >= 60) grade = 2;
else grade = 3;
Q5: 为什么 sizeof(arr) 在函数参数里不好使?
数组作为函数参数传递时会退化为指针,此时 sizeof 返回的是指针大小而非数组总大小:
void func(int arr[]) {
// ⚠️ 始终返回指针大小(通常 4 或 8),不是数组元素数
size_t n = sizeof(arr) / sizeof(arr[0]);
}
// 正确做法:把元素数也传进去
void func(int arr[], size_t len) {
for (size_t i = 0; i < len; i++) { ... }
}
Q6: 位运算优先级低于比较运算符?
是的,这是高频踩坑点:
// 错误:先比较再按位与
if (flags & MASK == MASK) { ... } // 等价于 flags & (MASK == MASK)
// 正确:括号包裹位运算
if ((flags & MASK) == MASK) { ... }
Q7: 逗号表达式的值到底是多少?
逗号运算符返回最后一个表达式的值,常用于 for 循环:
int a = (1, 2, 3); // a = 3
int b = (a++, a * 2); // a 先加 1,然后 b = (新 a) * 2
这种用法容易造成迷惑,建议只在 for 循环头部使用逗号运算符。
15. 总结
本文覆盖了 C 语言中几乎所有的运算符类型,从算术、关系、逻辑到位运算、赋值、条件、逗号、sizeof 以及指针相关,最后梳理了优先级和结合性。
掌握运算符不仅是写出正确代码的基础,更是理解复杂表达式、编写高效底层逻辑的关键。在练习中多动手,结合编译器的警告和优化,会让你对 C 语言的运算符有越来越深的把握。

4万+

被折叠的 条评论
为什么被折叠?



