第10讲:操作符详解——掌握C语言的“运算密码”
🧰 第10讲:操作符详解——掌握C语言的“运算密码” 🔐
适合已掌握基本语法的你,深入理解操作符背后的二进制世界!
📚 目录
- 操作符分类总览 🗂️
- 进制与转换:二进制的奥秘 🔢
- 原码、反码、补码:计算机的“负数语言” 📝
- 移位操作符:<< 和 >> 的魔法 ✨
- 位操作符:&、|、^、~ 的位运算艺术 🎨
- 单目操作符回顾 🔍
- 逗号表达式:被低估的“顺序执行器” 💬
- 下标
[]
与函数调用()
操作符 🧩 - 结构成员访问:
.
与->
🏗️ - 操作符属性:优先级与结合性 ⚖️
- 表达式求值的“陷阱”与规则 🕳️
操作符分类总览 🗂️
类别 | 操作符 | 示例 |
---|---|---|
算术 | + , - , * , / , % | 5 + 3 |
移位 | << , >> | 8 << 1 |
位操作 | & , ` | , ^`, `~` |
赋值 | = , += , -= , *= , /= , %= <<= , >>= , &= , ` | =, ^=` |
单目 | ! , ++ , -- , & , * , + , - , ~ , sizeof , (类型) | !flag , ++i |
关系 | > , >= , < , <= , == , != | a > b |
逻辑 | && , ` | |
条件 | ? : | a > b ? a : b |
逗号 | , | a=1, b=2, c=3 |
下标 | [] | arr[0] |
函数调用 | () | func() |
结构成员 | . , -> | s.name , ptr->age |
📌 本讲重点:深入讲解移位、位操作、结构体、优先级、表达式求值等底层机制。
进制与转换:二进制的奥秘 🔢
🌍 什么是进制?
- 进制是数值的不同表示形式。
- 常见进制:2进制(0,1)、8进制(0-7)、10进制(0-9)、16进制(0-9, a-f)
✅ 示例:数值15的表示
进制 | 表示 | 前缀 |
---|---|---|
二进制 | 1111 | - |
八进制 | 17 | 0 |
十进制 | 15 | - |
十六进制 | F | 0x |
🔁 二进制 ↔ 十进制转换
🔄 2进制 → 10进制
方法:按权展开,从右到左,权重为 2^0, 2^1, 2^2, ...
二进制: 1101= 1×2³ + 1×2² + 0×2¹ + 1×2⁰= 8 + 4 + 0 + 1 = 13
🔁 10进制 → 2进制
方法:除2取余,逆序排列
13 ÷ 2 = 6 余 1
6 ÷ 2 = 3 余 0
3 ÷ 2 = 1 余 1
1 ÷ 2 = 0 余 1
→ 二进制: 1101
🔀 2进制 ↔ 8进制 / 16进制
🔄 2进制 → 8进制
- 规则:每3位二进制 → 1位八进制(从右向左)
二进制: 011 010 113 2 3
→ 八进制: 0323
🔄 2进制 → 16进制
- 规则:每4位二进制 → 1位十六进制
二进制: 0110 10116 b
→ 十六进制: 0x6b
原码、反码、补码:计算机的“负数语言” 📝
计算机中整数以补码形式存储!
类型 | 符号位 | 数值位 |
---|---|---|
原码 | 0正1负 | 直接二进制 |
反码 | 不变 | 原码取反 |
补码 | 不变 | 反码 + 1 |
🧮 示例:-3 的表示(8位)
原码: 10000011
反码: 11111100
补码: 11111101
✅ 正数:原、反、补码相同
✅ 存储:内存中存放的是补码
移位操作符:<< 和 >> 的魔法 ✨
只能用于整数!
⬅️ 左移 <<
- 规则:左边丢弃,右边补0
- 效果:
num << n
≈num * 2^n
int num = 10; // 1010
int n = num << 1; // 10100 = 20
➡️ 右移 >>
- 逻辑右移:左边补0(无符号数)
- 算术右移:左边补符号位(有符号数)
int num = 10; // 1010
int n = num >> 1; // 101 = 5
⚠️ 警告
- 禁止移位负数位:
num >> -1
→ 未定义行为!
位操作符:&、|、^、~ 的位运算艺术 🎨
操作数必须是整数!
操作符 | 名称 | 规则 |
---|---|---|
& | 按位与 | 同1为1 |
` | ` | 按位或 |
^ | 按位异或 | 不同为1 |
~ | 按位取反 | 0变1,1变0 |
💡 实战案例
🔄 交换两个数(不使用临时变量)
int a = 10, b = 20;
a = a ^ b;
b = a ^ b; // b = (a^b)^b = a
a = a ^ b; // a = (a^b)^a = b
📊 统计二进制中1的个数
// 方法1:循环32次
int count = 0;
for (int i = 0; i < 32; i++) {if (num & (1 << i)) count++;
}// 方法2:优化版(推荐)
int count = 0;
while (num) {count++;num &= (num - 1); // 消除最右边的1
}
🔧 二进制位置0或置1
int a = 13; // 1101
a |= (1 << 4); // 第5位置1 → 11101
a &= ~(1 << 4); // 第5位置0 → 1101
单目操作符回顾 🔍
操作符 | 说明 |
---|---|
! | 逻辑非 |
++ , -- | 自增/自减 |
& | 取地址(指针部分讲解) |
* | 解引用(指针部分讲解) |
+ , - | 正负号 |
~ | 按位取反 |
sizeof | 计算大小(字节) |
(类型) | 强制类型转换 |
逗号表达式:被低估的“顺序执行器” 💬
格式:exp1, exp2, ..., expN
规则:从左到右执行,结果为最后一个表达式的值。
int c = (a=1, b=2, a+b); // c = 3
🔄 实际应用:简化循环
// 传统写法
a = get_val();
count_val(a);
while (a > 0) {// 处理a = get_val();count_val(a);
}// 逗号表达式写法
while (a = get_val(), count_val(a), a > 0) {// 处理
}
下标 []
与函数调用 ()
操作符 🧩
🧱 []
下标访问
- 操作数:数组名 + 索引
- 等价:
arr[i]
⇔*(arr + i)
int arr[10];
arr[9] = 10; // 使用下标操作符
🧩 ()
函数调用
- 操作数:函数名 + 参数列表
void test(const char *str) {printf("%s\n", str);
}
test("hello"); // () 是函数调用操作符
结构成员访问:.
与 ->
🏗️
🏗️ 结构体定义
struct Stu {char name[20];int age;char id[20];
};
✅ 成员访问
操作符 | 用法 | 场景 |
---|---|---|
. | s.name | 结构体变量 |
-> | ptr->name | 结构体指针 |
struct Stu s = {"zhangsan", 20};
struct Stu *ptr = &s;printf("%s\n", s.name); // .
printf("%d\n", ptr->age); // ->
📌 更多结构体知识见第19讲。
操作符属性:优先级与结合性 ⚖️
决定表达式求值顺序的两大法则!
🔝 10.1 优先级
- 优先级高的操作符先执行。
3 + 4 * 5; // 先算 4*5,因为 * 优先级 > +
↔️ 10.2 结合性
- 优先级相同时,按结合性决定顺序。
类别 | 例子 | 执行顺序 |
---|---|---|
左结合 | 5 * 6 / 2 | (5*6)/2 |
右结合 | a = b = 5 | a=(b=5) |
📊 常见操作符优先级(从高到低)
1. () // 圆括号
2. ++ -- // 自增/自减
3. + - ~ ! // 单目
4. * / % // 算术
5. + - // 算术
6. < <= > >= // 关系
7. == != // 相等
8. & ^ | // 位操作
9. && || // 逻辑
10. ?: // 条件
11. = += -= // 赋值
12. , // 逗号
📌 建议:复杂表达式加括号明确优先级!
表达式求值的“陷阱”与规则 🕳️
🔼 11.1 整型提升(Integer Promotion)
- 规则:小于
int
的类型(如char
)在运算前会提升为int
。 - 目的:CPU运算器通常以
int
长度操作。
char a = 1, b = 2;
char c = a + b; // a,b 提升为int,相加后截断赋值
📌 提升规则
- 有符号:高位补符号位
- 无符号:高位补0
🔁 11.2 算术转换(Usual Arithmetic Conversion)
当操作数类型不同时,按以下顺序转换:
long double
double
float
unsigned long int
long int
unsigned int
int
原则:低类型 → 高类型
⚠️ 11.3 问题表达式解析
❌ 表达式1:a*b + c*d + e*f
*
优先级高于+
,但无法确定c*d
和e*f
谁先计算。- 结论:依赖编译器优化,但结果确定。
❌ 表达式2:c + --c
--
优先级高,但无法确定c
的取值时机。- 结果:未定义,有歧义 ❌
❌ 表达式3:i = i-- - --i * (i = -3) * i++ + ++i;
- 包含多个副作用(
++
,--
,=
),求值顺序未定义。 - 不同编译器结果不同 → 非法表达式!
❌ 表达式4:answer = fun() - fun() * fun();
*
优先级高,但三个fun()
调用顺序不确定。- 副作用:静态变量
count
的结果依赖调用顺序。
❌ 表达式5:ret = (++i) + (++i) + (++i);
- GCC 和 VS 结果不同!
- 原因:前置
++
和+
的执行顺序未由优先级决定。
✅ 学习收获总结
技能 | 掌握情况 |
---|---|
✅ 理解二进制、八进制、十六进制转换 | ✔️ |
✅ 掌握原码、反码、补码表示 | ✔️ |
✅ 熟练使用移位和位操作符 | ✔️ |
✅ 理解结构体与成员访问 | ✔️ |
✅ 掌握操作符优先级与结合性 | ✔️ |
✅ 识别危险表达式,避免未定义行为 | ✔️ |
🎯 操作符是C语言的“底层语言”
你已经掌握了位运算、补码、表达式求值等核心知识,下一步就是深入指针的世界!💪🔥
💬 需要本讲的 位运算可视化工具 / 表达式求值动画 / 源码工程?欢迎继续提问,我可以一键打包给你!