在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 01111111
b
提升为00000000 00000000 00000000 00000001
- 相加后
c = 128
(若直接存char
会溢出)。
11.2 避免歧义表达式
部分表达式因优先级/结合性无法确定唯一执行顺序,结果依赖编译器,应避免:
c + --c
:无法确定+
左右操作数的求值顺序。(++i) + (++i) + (++i)
:不同编译器结果不同(如GCC输出10,VS输出12)。
总结
操作符是C语言的基础,掌握其分类、特性及求值规则,能帮助我们写出更高效、无歧义的代码。重点关注位操作符的灵活应用(如交换变量、统计1的个数)、补码的存储逻辑,以及优先级对表达式的影响。实际开发中,复杂表达式建议用括号明确顺序,减少歧义。
希望本文对你理解C语言操作符有帮助,欢迎留言交流! 😊