目录
- 算术操作符:数学运算的基石
- 移位操作符:二进制世界的搬运工
- 位操作符:精准操控每一位
- 赋值与复合赋值操作符:高效修改变量
- 单目操作符:一元操作的魔法
- 关系与逻辑操作符:构建条件逻辑
- 条件与逗号表达式:灵活控制流程
- 下标引用与函数调用:操作符的扩展应用
- 隐式类型转换:编译器的“自动修正”
- 表达式求值与操作符优先级:避免陷阱的关键
- 实战案例:经典问题的操作符实现
- 总结与最佳实践
1. 算术操作符:数学运算的基石
算术操作符包括 +
、-
、*
、/
、%
,用于执行基本数学运算。
关键点解析:
-
整数除法 vs 浮点除法:
int a = 7 / 3; // 结果:2(整数除法,丢弃小数部分) float b = 7.0 / 3; // 结果:2.333333(浮点除法)
陷阱:若两个整数相除,结果仍为整数,需显式转换类型以保留小数。
-
取模操作符
%
的限制:int c = 7 % 3; // 结果:1(余数) // int d = 7 % 3.0; // 错误:操作数必须为整数
应用场景:判断奇偶性、循环缓冲区索引计算。
2. 移位操作符:二进制世界的搬运工
移位操作符 <<
(左移)和 >>
(右移)直接操作二进制位。
左移操作符 <<
- 规则:高位丢弃,低位补0。
int a = 5; // 二进制 00000101 int b = a << 2; // 左移两位 → 00010100(十进制20)
右移操作符 >>
- 逻辑右移 vs 算术右移:
- 逻辑右移:高位补0(适用于无符号数)。
- 算术右移:高位补符号位(适用于有符号数,保留正负性)。
int c = -8; // 补码:11111111111111111111111111111000 int d = c >> 1; // 算术右移 → 11111111111111111111111111111100(十进制-4)
注意事项:
- 移位负数位是未定义行为:
int e = 10 >> -1; // 未定义行为,编译器可能报错或产生不可预测结果
3. 位操作符:精准操控每一位
位操作符包括 &
(按位与)、|
(按位或)、^
(按位异或)。
按位与 &
- 应用:掩码操作、清零特定位。
int a = 0b1100; // 12 int mask = 0b1010; // 10 int b = a & mask; // 0b1000(8)
按位异或 ^
- 特性:相同位为0,不同位为1。
应用:交换变量值、数据加密。int x = 10, y = 20; x = x ^ y; // x = 10 ^ 20 y = x ^ y; // y = 10 ^ 20 ^ 20 → 10 x = x ^ y; // x = 10 ^ 20 ^ 10 → 20
按位或 |
- 应用:设置特定位为1。
int flags = 0b0000; flags |= 0b1001; // 设置第0位和第3位为1 → 0b1001(9)
4. 赋值与复合赋值操作符:高效修改变量
赋值操作符 =
可与算术或位操作符结合,形成复合赋值符。
示例:
int a = 10;
a += 5; // 等价于 a = a + 5 → 15
a <<= 1; // 左移一位 → 30
a &= 0x0F; // 按位与掩码 → 14(0x0E)
注意事项:
- 连续赋值的可读性问题:
int x = y = z = 0; // 合法但可读性差 // 推荐分开赋值:int x=0, y=0, z=0;
5. 单目操作符:一元操作的魔法
单目操作符仅需一个操作数,包括 !
、~
、++
、--
、sizeof
等。
自增与自减操作符
- 前置 vs 后置:
int a = 10; int b = a++; // b=10, a=11(后置:先赋值,后自增) int c = ++a; // c=12, a=12(前置:先自增,后赋值)
sizeof
操作符
- 计算变量或类型的大小:
printf("int size: %zu\n", sizeof(int)); // 输出4(32/64位系统可能不同) int arr[10]; printf("array size: %zu\n", sizeof(arr)); // 输出40(假设int为4字节)
按位取反 ~
- 二进制取反操作:
int a = 0; // 二进制全0 int b = ~a; // 二进制全1 → -1(补码表示)
6. 关系与逻辑操作符:构建条件逻辑
关系操作符(>
, >=
, <
, <=
, ==
, !=
)和逻辑操作符(&&
, ||
)用于条件判断。
短路求值特性:
-
&&
的短路特性:若左操作数为假,右操作数不执行。int a = 0, b = 1; if (a++ && b++) { /* 不执行 */ } // a=1, b=1(因a初始为0,右操作数b++未执行)
-
||
的短路特性:若左操作数为真,右操作数不执行。if (a++ || b++) { /* 执行 */ } // a=2, b=1(a++为1,触发短路)
7. 条件与逗号表达式:灵活控制流程
条件操作符 ?:
- 简化条件赋值:
int max = (a > b) ? a : b; // 取a和b中的较大值
逗号表达式
- 从左到右求值,结果为最后一个表达式:
int d = (c = 5, a = c + 3, b = a - 4, c += 5); // d=10
8. 下标引用与函数调用:操作符的扩展应用
下标引用 []
- 访问数组元素:
int arr[5] = {1, 2, 3, 4, 5}; arr[2] = 10; // 修改第三个元素
函数调用 ()
- 传递参数与执行函数:
int add(int x, int y) { return x + y; } int result = add(3, 5); // 调用函数,result=8
9. 隐式类型转换:编译器的“自动修正”
整型提升
char
和short
在运算时提升为int
:char a = 3, b = 127; char c = a + b; // 整型提升后计算,结果为-126(二进制截断)
算术转换规则
- 低精度向高精度转换:
int a = 5; float b = 3.14; float sum = a + b; // a隐式转换为float
更加详细的隐式类型转换内容:隐式类型转换详解
10. 表达式求值与操作符优先级:避免陷阱的关键
以下是整理后的表格:
以下是补充说明后的完整表格,包含 rexp(右值表达式)、lexp(左值表达式)、结合性(L-R/R-L/N/A) 以及 是否控制求值顺序 的详细解释:
C/C++ 操作符属性表
操作符 | 描述 | 用法示例 | 结果类型 | 结合性 | 是否控制求值顺序 | 备注 |
---|---|---|---|---|---|---|
() | 聚组 | (表达式) | 与表达式同 | N/A | 否 | 强制优先级,不改变求值顺序。 |
() | 函数调用 | rexp(rexp, ..., rexp) | rexp | L-R | 否 | 参数求值顺序未定义(C/C++标准未规定)。 |
[] | 下标引用 | rexp[rexp] | lexp | L-R | 否 | 等价于 *(rexp + rexp) 。 |
. | 访问结构成员 | lexp.member_name | lexp | L-R | 否 | lexp 必须是左值(如结构体变量)。 |
-> | 访问结构指针成员 | rexp->member_name | lexp | L-R | 否 | rexp 必须是指向结构体的指针。 |
++ | 后缀自增 | lexp++ | rexp | L-R | 否 | 返回自增前的值,lexp 需为可修改左值。 |
-- | 后缀自减 | lexp-- | rexp | L-R | 否 | 返回自减前的值。 |
! | 逻辑反 | !rexp | rexp | R-L | 否 | 将 rexp 转换为布尔值后取反。 |
~ | 按位取反 | ~rexp | rexp | R-L | 否 | 按位取反操作。 |
+ | 单目,表示正值 | +rexp | rexp | R-L | 否 | 通常无实际效果,用于类型提升。 |
- | 单目,表示负值 | -rexp | rexp | R-L | 否 | 算术负值。 |
++ | 前缀自增 | ++lexp | rexp | R-L | 否 | 返回自增后的值。 |
-- | 前缀自减 | --lexp | rexp | R-L | 否 | 返回自减后的值。 |
* | 间接访问 | *rexp | lexp | R-L | 否 | rexp 必须是指针。 |
& | 取地址 | &lexp | rexp | R-L | 否 | 返回 lexp 的地址。 |
sizeof | 取其长度(字节) | sizeof rexp 或 sizeof(类型) | rexp | R-L | 否 | 编译时计算,不求值 rexp (如 sizeof(++x) 不会自增 x )。 |
(类型) | 类型转换 | (类型)rexp | rexp | R-L | 否 | 强制类型转换。 |
* | 乘法 | rexp * rexp | rexp | L-R | 否 | 算术乘法。 |
/ | 除法 | rexp / rexp | rexp | L-R | 否 | 整数除法会截断小数部分。 |
% | 整数取余 | rexp % rexp | rexp | L-R | 否 | 操作数必须为整数。 |
+ | 加法 | rexp + rexp | rexp | L-R | 否 | 算术加法或指针偏移(如 ptr + 1 )。 |
- | 减法 | rexp - rexp | rexp | L-R | 否 | 算术减法或指针偏移。 |
<< | 左移位 | rexp << rexp | rexp | L-R | 否 | 逻辑左移,低位补 0。 |
>> | 右移位 | rexp >> rexp | rexp | L-R | 否 | 算术右移(有符号数补符号位)或逻辑右移(无符号数补 0)。 |
> | 大于 | rexp > rexp | rexp | L-R | 否 | 返回布尔值。 |
>= | 大于等于 | rexp >= rexp | rexp | L-R | 否 | 返回布尔值。 |
< | 小于 | rexp < rexp | rexp | L-R | 否 | 返回布尔值。 |
<= | 小于等于 | rexp <= rexp | rexp | L-R | 否 | 返回布尔值。 |
== | 等于 | rexp == rexp | rexp | L-R | 否 | 返回布尔值。 |
!= | 不等于 | rexp != rexp | rexp | L-R | 否 | 返回布尔值。 |
& | 位与 | rexp & rexp | rexp | L-R | 否 | 按位与操作。 |
^ | 位异或 | rexp ^ rexp | rexp | L-R | 否 | 按位异或操作。 |
| | 位或 | rexp | rexp | rexp | L-R | 否 | 按位或操作(注意 | 需转义)。 |
&& | 逻辑与 | rexp && rexp | rexp | L-R | 是 | 短路求值:若左操作数为 false ,右操作数不求值。 |
|| | 逻辑或 | rexp || rexp | rexp | L-R | 是 | 短路求值:若左操作数为 true ,右操作数不求值。 |
?: | 条件操作符 | rexp ? rexp : rexp | rexp | N/A | 是 | 仅求值一侧:根据 rexp 的真假决定求值第二个或第三个表达式。 |
= | 赋值 | lexp = rexp | rexp | R-L | 否 | lexp 必须是可修改左值。 |
+= -= 等 | 复合赋值 | lexp += rexp | rexp | R-L | 否 | 等价于 lexp = lexp + rexp (其他复合赋值类似)。 |
, | 逗号 | rexp, rexp | rexp | L-R | 是 | 顺序求值:先求值左操作数,再求值右操作数,返回右操作数的结果。 |
关键说明
-
lexp(左值表达式)
- 表示可被赋值的对象(如变量、数组元素、解引用指针等)。
- 例如:
a
,*ptr
,arr[0]
。
-
rexp(右值表达式)
- 表示临时值或不可被赋值的表达式(如常量、算术结果、函数返回值等)。
- 例如:
1
,a + b
,func()
。
-
结合性
- L-R(从左到右):如
a + b + c
等价于(a + b) + c
。 - R-L(从右到左):如
*ptr++
等价于*(ptr++)
(因++
优先级高于*
但结合性为 R-L)。
- L-R(从左到右):如
-
求值顺序控制
- 仅
&&
、||
、?:
和,
明确控制求值顺序(短路求值或顺序求值)。 - 其他操作符的求值顺序未定义(如
f() + g()
中f
和g
的调用顺序不确定)。
- 仅
此表格可用于快速查阅操作符的优先级、结合性和求值规则,适合编程参考或面试准备。
操作符优先级表(部分)
示例分析:
int result = a + b * c; // 先计算b*c,再与a相加
int flag = a & b == c; // 等价于a & (b == c),可能非预期!
11. 实战案例:经典问题的操作符实现
案例1:求二进制中1的个数
int count_ones(int n) {
int count = 0;
while (n) {
n &= (n - 1); // 每次消除最右侧的1
count++;
}
return count;
}
// 示例:n=7(0b0111) → 3个1
案例2:矩阵转置
void transpose(int n, int m, int src[n][m], int dest[m][n]) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
dest[j][i] = src[i][j]; // 行列交换
}
}
}
案例3:水仙花数判断
int is_narcissistic(int num) {
int n = 0, sum = 0, tmp = num;
while (tmp) { n++; tmp /= 10; } // 计算位数
tmp = num;
while (tmp) {
sum += pow(tmp % 10, n);
tmp /= 10;
}
return sum == num;
}
// 示例:153 = 1³ + 5³ + 3³ → 是水仙花数
操作符并不需要记忆,作为一个小白熟悉比记忆更加重要,通过大量的代码练习,就可以通过熟能生巧,灵活运用操作符了。