操作符详解:从入门到精通
前言
操作符是C语言的灵魂,掌握好操作符的使用,就能写出更加高效、优雅的代码。本文将全面系统地讲解C语言中的各类操作符,包括它们的分类、使用方法和注意事项,帮助你彻底搞懂操作符的方方面面。
1. 操作符的分类
C语言中的操作符非常丰富,大致可以分为以下几类:
| 类别 | 操作符 |
|---|---|
| 算术操作符 | + - * / % |
| 移位操作符 | << >> |
| 位操作符 | & ` |
| 赋值操作符 | = += -= *= /= %= <<= >>= &= ` |
| 单目操作符 | ! ++ -- & * + - ~ sizeof (类型) |
| 关系操作符 | > >= < <= == != |
| 逻辑操作符 | && ` |
| 条件操作符 | ?: |
| 逗号表达式 | , |
| 下标引用 | [] |
| 函数调用 | () |
| 结构成员访问 | . -> |
今天我们将重点介绍与二进制相关的操作符(移位、位操作符),以及其他尚未详细讲解的操作符。
2. 二进制与进制转换
在深入位操作符之前,我们需要先了解二进制的基础知识。
2.1 什么是进制?
2进制、8进制、10进制、16进制只是数值的不同表示形式。比如数值15:
- 2进制:
1111 - 8进制:
17(以0开头) - 10进制:
15 - 16进制:
F(以0x开头)
2.2 2进制转10进制
2进制每一位都有权重,从右向左依次是:(2^0, 2^1, 2^2, \ldots)
例如:二进制 1101 = (1 \times 8 + 1 \times 4 + 0 \times 2 + 1 \times 1 = 13)
2.3 10进制转2进制
使用除2取余法,不断除以2,将余数从下往上排列。
例如:125转换为2进制 → 1111101
2.4 2进制转8进制和16进制
- 转8进制:从右向左每3位一组,转换为对应的8进制数字(最高位不足3位时直接转换)
- 转16进制:从右向左每4位一组,转换为对应的16进制数字
💡 小贴士:8进制数以
0开头,16进制数以0x开头。
3. 原码、反码、补码
整数的二进制表示有三种形式:原码、反码、补码。
3.1 基本规则
| 类型 | 符号位 | 正数 | 负数 |
|---|---|---|---|
| 原码 | 最高位,0正1负 | 同原码 | 数值位不变 |
| 反码 | 不变 | 同原码 | 符号位不变,数值位取反 |
| 补码 | 不变 | 同原码 | 反码+1 |
重要 :整数在内存中存储的是补码!
3.2 为什么用补码?
- 符号位和数值位可以统一处理
- 加法和减法可以统一处理(CPU只有加法器)
- 补码与原码的转换过程相同,不需要额外硬件
4. 移位操作符
移位操作符的操作数只能是整数。
4.1 左移操作符 <<
规则:左边抛弃,右边补0
c
int num = 10; // 二进制:1010
int n = num << 1; // 结果:10100 = 20
4.2 右移操作符 >>
右移分为两种:
| 类型 | 规则 |
|---|---|
| 逻辑右移 | 左边补0,右边丢弃 |
| 算术右移 | 左边补符号位,右边丢弃 |
⚠️ 警告 :不要移动负数位,如
num >> -1是未定义行为!
5. 位操作符:&、|、^、~
位操作符的操作数必须是整数。
| 操作符 | 名称 | 规则 |
|---|---|---|
& |
按位与 | 对应位都是1,结果为1 |
| ` | ` | 按位或 |
^ |
按位异或 | 对应位不同,结果为1 |
~ |
按位取反 | 0变1,1变0 |
c
int num1 = -3;
int num2 = 5;
printf("%d\n", num1 & num2); // 按位与
printf("%d\n", num1 | num2); // 按位或
printf("%d\n", num1 ^ num2); // 按位异或
printf("%d\n", ~0); // 按位取反
5.1 经典面试题:不创建临时变量交换两个整数
c
int a = 10, b = 20;
a = a ^ b;
b = a ^ b; // b = (a ^ b) ^ b = a
a = a ^ b; // a = (a ^ b) ^ a = b
printf("a = %d, b = %d\n", a, b);
原理 :异或运算满足自反性:(a ^ b) ^ b = a
5.2 练习1:求二进制中1的个数
方法1(有缺陷,不能处理负数):
c
while(num) {
if(num % 2 == 1) count++;
num /= 2;
}
方法2(循环32次):
c
for(i = 0; i < 32; i++) {
if(num & (1 << i)) count++;
}
方法3(最优解):
c
while(num) {
count++;
num = num & (num - 1); // 每次消去最右边的1
}
💡
num & (num - 1)这个技巧非常经典,可以快速消去二进制中最右边的1。
5.3 练习2:二进制位置0或置1
将13的第5位(从0开始计数)修改为1,再改回0:
c
int a = 13;
// 第5位置为1
a = a | (1 << 4);
// 第5位置为0
a = a & ~(1 << 4);
6. 单目操作符
单目操作符的特点是只有一个操作数:
| 操作符 | 名称 | 说明 |
|---|---|---|
! |
逻辑反 | 真变假,假变真 |
++ |
自增 | 前置或后置 |
-- |
自减 | 前置或后置 |
& |
取地址 | 获取变量的地址 |
* |
解引用 | 通过地址访问变量 |
+ |
正号 | 正数 |
- |
负号 | 负数 |
~ |
按位取反 | 二进制位取反 |
sizeof |
取大小 | 计算类型或变量的大小 |
(类型) |
强制类型转换 | 转换数据类型 |
7. 逗号表达式
逗号表达式从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
c
int a = 1, b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
// c的结果是 b = a + 1 = 13
// 实用场景:简化while循环
while (a = get_val(), count_val(a), a > 0) {
// 业务处理
}
8. 下标访问与函数调用
8.1 下标引用操作符 []
操作数:数组名 + 索引值
c
int arr[10]; // 创建数组
arr[9] = 10; // [] 的操作数是 arr 和 9
8.2 函数调用操作符 ()
操作数:函数名 + 传递给函数的参数
c
test1(); // () 的操作数是 test1
test2("hello"); // () 的操作数是 test2 和 "hello"
9. 结构成员访问操作符
9.1 结构体声明
c
struct Stu {
char name[20]; // 名字
int age; // 年龄
char sex[5]; // 性别
char id[20]; // 学号
};
9.2 直接访问:.
c
struct Stu s = {"张三", 20};
printf("%s %d\n", s.name, s.age);
9.3 间接访问:->
c
struct Stu *ptr = &s;
ptr->age = 21; // 等价于 (*ptr).age = 21
10. 操作符的优先级与结合性
10.1 优先级
优先级决定哪个运算符先执行。乘法优先级高于加法:
c
3 + 4 * 5; // 先算 4*5=20,再加3得23
10.2 结合性
当优先级相同时,结合性决定执行顺序:
- 左结合:从左到右执行(大部分运算符)
- 右结合:从右到左执行(赋值运算符等)
c
5 * 6 / 2; // 左结合,先算5*6=30,再除以2得15
10.3 常用优先级速查表
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1(最高) | () [] . -> |
左到右 |
| 2 | ++ -- & * + - ~ ! sizeof |
右到左 |
| 3 | * / % |
左到右 |
| 4 | + - |
左到右 |
| 5 | << >> |
左到右 |
| 6 | < <= > >= |
左到右 |
| 7 | == != |
左到右 |
| 8 | & |
左到右 |
| 9 | ^ |
左到右 |
| 10 | ` | ` |
| 11 | && |
左到右 |
| 12 | ` | |
| 13 | ?: |
右到左 |
| 14 | = 及复合赋值 |
右到左 |
| 15(最低) | , |
左到右 |
💡 建议 :记不住优先级没关系,使用圆括号
()明确表达式的计算顺序即可!
11. 表达式求值的陷阱
11.1 整型提升
C语言中,char、short等类型在进行算术运算时,会先转换为int或unsigned int,这就是整型提升。
提升规则:
- 有符号数:按符号位提升
- 无符号数:高位补0
c
char a, b, c;
a = b + c; // b和c先提升为int,计算后再截断存入a
11.2 算术转换
当操作数类型不同时,会进行算术转换,向更高精度转换:
long double > double > float > unsigned long > long > unsigned int > int
11.3 问题表达式
有些表达式虽然语法正确,但计算结果不确定,应避免写出:
c
// 问题1:操作数的求值顺序不确定
a*b + c*d + e*f;
// 问题2:i的修改和读取顺序不确定
c + --c;
// 问题3:函数调用顺序不确定
fun() - fun() * fun();
// 问题4:不同编译器结果不同
int ret = (++i) + (++i) + (++i);
⚠️ 核心原则:不要写出过于复杂的表达式,保持代码清晰、可读性强!
总结
本文全面介绍了C语言中的各类操作符,包括:
- 进制转换:理解二进制、八进制、十六进制
- 原反补码:理解整数在内存中的存储
- 移位和位操作:高效处理二进制位
- 优先级与结合性:理解表达式求值顺序
- 常见陷阱:避免写出有歧义的表达式
掌握好这些知识,你就能写出更加高效、准确的C语言代码。记住:简单清晰的代码比巧妙的代码更有价值!
如果觉得这篇文章对你有帮助,欢迎点赞、收藏、分享!