在C语言中,操作符是构建表达式的基础,掌握各类操作符的用法、优先级及特性,对写出高效且正确的代码至关重要。本文将系统梳理C语言操作符的核心知识点,包含实例代码与详细解析,助你彻底搞懂操作符。
1. 操作符的分类
C语言操作符可按功能分为以下几类,清晰的分类有助于理解其特性:
| 类别 | 操作符 |
|---|---|
| 算术操作符 | +、-、*、/、% |
| 移位操作符 | <<、>> |
| 位操作符 | &、|、^、~ |
| 赋值操作符 | =、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^= |
| 单目操作符 | !、++、--、&、*、+、-、~、sizeof、(类型)(强制类型转换) |
| 关系操作符 | >、>=、<、<=、==、!= |
| 逻辑操作符 | &&、|| |
| 条件操作符 | ? : |
| 逗号表达式 | , |
| 下标引用 | [] |
| 函数调用 | () |
| 结构成员访问操作符 | .、-> |
2. 二进制与进制转换
计算机底层使用二进制存储数据,掌握进制转换是理解位操作的基础。
2.1 二进制与十进制转换
-
二进制转十进制:按权重展开求和(权重为20,21,22,...2^0, 2^1, 2^2,...20,21,22,...,从右向左)。
例:二进制1101转十进制:
1×23+1×22+0×21+1×20=8+4+0+1=131×2^3 + 1×2^2 + 0×2^1 + 1×2^0 = 8 + 4 + 0 + 1 = 131×23+1×22+0×21+1×20=8+4+0+1=13 -
十进制转二进制:除2取余,余数倒序排列。
例:十进制125转二进制:
125 ÷ 2 = 62 余1
62 ÷ 2 = 31 余0
31 ÷ 2 = 15 余1
15 ÷ 2 = 7 余1
7 ÷ 2 = 3 余1
3 ÷ 2 = 1 余1
1 ÷ 2 = 0 余1
结果:1111101
2.2 二进制与八/十六进制转换
-
二进制转八进制:从右向左每3位一组,不足补0,每组对应1个八进制数(0-7)。
例:二进制01101011→ 分组01 101 011→ 转换为八进制153(前缀0表示八进制)。 -
二进制转十六进制:从右向左每4位一组,不足补0,每组对应1个十六进制数(0-9, a-f)。
例:二进制01101011→ 分组0110 1011→ 转换为十六进制0x6b(前缀0x表示十六进制)。
3. 原码、反码、补码
整数在内存中以补码存储,原因是:统一符号位与数值位处理,简化加法运算(CPU仅需加法器)。
-
原码:直接表示符号位(0正1负)和数值位。
例:int a = 3→ 原码00000000 00000000 00000000 00000011
int b = -3→ 原码10000000 00000000 00000000 00000011 -
反码:原码符号位不变,数值位取反。
例:-3的反码11111111 11111111 11111111 11111100 -
补码:反码+1(负数补码);正数原码=反码=补码。
例:-3的补码11111111 11111111 11111111 11111101
4. 移位操作符
移位操作符仅作用于整数,分为左移和右移。
4.1 左移操作符 <<
- 规则:左边丢弃,右边补0。
- 示例:
#include <stdio.h>
int main() {int num = 10; // 二进制:00000000 00000000 00000000 00001010int n = num << 1; // 左移1位:00000000 00000000 00000000 00010100 → 20printf("n = %d\n", n); // 输出:20printf("num = %d\n", num); // 输出:10(原变量不变)return 0;
}
4.2 右移操作符 >>
- 逻辑右移:左边补0,右边丢弃(无符号数)。
- 算术右移:左边补符号位,右边丢弃(有符号数,主流编译器采用)。
示例(算术右移):
#include <stdio.h>
int main() {int num = -1; // 补码:11111111 11111111 11111111 11111111int n = num >> 1; // 算术右移1位:11111111 11111111 11111111 11111111 → 仍为-1printf("n = %d\n", n); // 输出:-1return 0;
}
⚠️ 注意:禁止移动负数位(如num >> -1,行为未定义)。
5. 位操作符
位操作符直接对二进制位进行操作,操作数为整数。
| 操作符 | 功能 | 示例(二进制) |
|---|---|---|
| `&` | 按位与(同1则1) | 1010 & 0111 = 0010 |
| `|` | 按位或(有1则1) | 1010 | 0111 = 1111 |
| `^` | 按位异或(不同则1) | 1010 ^ 0111 = 1101 |
| `~` | 按位取反(0变1,1变0) | ~1010 = 0101(假设4位) |
经典案例
- 不创建临时变量交换两数:
#include <stdio.h>
int main() {int a = 10, b = 20;a = a ^ b; // a = 10^20b = a ^ b; // b = (10^20)^20 = 10a = a ^ b; // a = (10^20)^10 = 20printf("a = %d, b = %d\n", a, b); // 输出:a=20, b=10return 0;
}
- 求二进制中1的个数:
// 方法3(最优):每次消去最后一个1,循环次数=1的个数
#include <stdio.h>
int count_one(int num) {int count = 0;while (num) {num &= num - 1; // 消去最后一个1count++;}return count;
}
int main() {printf("%d\n", count_one(10)); // 1010 → 2个1 → 输出2return 0;
}
6. 单目操作符
单目操作符仅需一个操作数,重点如下:
| 操作符 | 功能 | 示例 |
|---|---|---|
! | 逻辑非(真变假,假变真) | !0 = 1,!5 = 0 |
++ | 自增(前置先增后用,后置先用后增) | int a=3; printf("%d", ++a); // 4 |
-- | 自减(类似++) | int a=3; printf("%d", a--); // 3 |
sizeof | 求类型/变量所占字节数 | sizeof(int) = 4,sizeof(a) = 4(a为int) |
(类型) | 强制类型转换 | (int)3.14 = 3 |
7. 逗号表达式
形式:exp1, exp2, ..., expN
- 执行顺序:从左到右依次执行。
- 结果:最后一个表达式的值。
示例:
#include <stdio.h>
int main() {int a = 1, b = 2;int c = (a > b, a = b + 10, a, b = a + 1); // 执行:a>b(假)→ a=12 → 取a=12 → b=13 → 结果c=13printf("c = %d\n", c); // 输出:13return 0;
}
8. 下标访问与函数调用
-
下标访问
[]:操作数为数组名+索引,如arr[3]等价于*(arr+3)。int arr[5] = {1,2,3,4,5}; printf("%d\n", arr[2]); // 输出:3(访问第3个元素) -
函数调用
():操作数为函数名+参数列表。#include <stdio.h> void print_hello() {printf("Hello, C!\n"); } int add(int a, int b) {return a + b; } int main() {print_hello(); // 函数调用,无参数printf("3+5 = %d\n", add(3, 5)); // 函数调用,有参数 → 输出8return 0; }
9. 结构体成员访问
结构体成员访问有两种方式:
.:直接访问(结构体变量)。->:间接访问(结构体指针)。
示例:
#include <stdio.h>
#include <string.h>// 定义学生结构体
struct Stu {char name[20];int age;
};int main() {struct Stu s = {"张三", 20}; // 结构体变量struct Stu* ps = &s; // 结构体指针// 访问成员printf("name: %s, age: %d\n", s.name, s.age); // .访问 → 张三, 20strcpy(ps->name, "李四"); // ->访问:修改姓名ps->age = 22; // ->访问:修改年龄printf("name: %s, age: %d\n", s.name, s.age); // 李四, 22return 0;
}
10. 操作符的优先级与结合性
表达式求值由优先级和结合性决定:
-
优先级:决定操作顺序(如
*优先级高于+)。
例:3 + 4 * 5→ 先算4*5=20,再算3+20=23。 -
结合性:优先级相同时,左结合(从左到右)或右结合(从右到左,如赋值
=)。
例:5 * 6 / 2→ 左结合,先算5*6=30,再算30/2=15。
优先级简表(从高到低)
| 优先级 | 操作符 | 结合性 |
|---|---|---|
| 1 | ()、[]、.、-> | 左结合 |
| 2 | ++、--、!、~、sizeof | 右结合 |
| 3 | *、/、% | 左结合 |
| 4 | +、-(算术) | 左结合 |
| 5 | <<、>> | 左结合 |
| 6 | <、>、<=、>= | 左结合 |
| 7 | ==、!= | 左结合 |
| 8 | & | 左结合 |
| 9 | ^ | 左结合 |
| 10 | ` | ` |
| 11 | && | 左结合 |
| 12 | ` | |
| 13 | ? : | 右结合 |
| 14 | =、+=、-=等 | 右结合 |
11. 表达式求值注意事项
11.1 整型提升
长度小于int的整型(如char、short)在运算时会提升为int或unsigned int,避免精度丢失。
例:char a = 127, b = 1; int c = a + b;
a提升为00000000 00000000 00000000 01111111b提升为00000000 00000000 00000000 00000001- 相加后
c = 128(若直接存char会溢出)。
11.2 避免歧义表达式
部分表达式因优先级/结合性无法确定唯一执行顺序,结果依赖编译器,应避免:
c + --c:无法确定+左右操作数的求值顺序。(++i) + (++i) + (++i):不同编译器结果不同(如GCC输出10,VS输出12)。
总结
操作符是C语言的基础,掌握其分类、特性及求值规则,能帮助我们写出更高效、无歧义的代码。重点关注位操作符的灵活应用(如交换变量、统计1的个数)、补码的存储逻辑,以及优先级对表达式的影响。实际开发中,复杂表达式建议用括号明确顺序,减少歧义。
希望本文对你理解C语言操作符有帮助,欢迎留言交流! 😊