引言
操作符是C语言的灵魂,它们决定了数据的计算方式、逻辑判断和内存操作。理解操作符的优先级、结合性和使用规则,是写出正确、高效代码的基础。
C语言拥有丰富的操作符,包括算术操作符、移位操作符、位操作符、赋值操作符、逻辑操作符、条件操作符等。今天,我将从底层视角,全面讲解各类操作符的用法、注意事项以及常见陷阱。
第一部分:算术操作符
一、基本算术操作符
| 操作符 | 名称 | 示例 | 说明 |
|---|---|---|---|
+ | 加法 | a + b | 两数相加 |
- | 减法 | a - b | 两数相减 |
* | 乘法 | a * b | 两数相乘 |
/ | 除法 | a / b | 两数相除(整数除法截断) |
% | 取模 | a % b | 取余数,操作数必须为整数 |
二、注意事项
#include <stdio.h> int main() { // 整数除法:结果截断,不四舍五入 int a = 10 / 3; // 3,不是3.333 int b = 10 / 4; // 2 // 浮点数除法:至少有一个操作数为浮点数 double c = 10.0 / 3; // 3.333333 double d = 10 / 3.0; // 3.333333 // 取模操作符:操作数必须为整数 int e = 10 % 3; // 1 // double f = 10.0 % 3; // 错误!取模不能用于浮点数 // 负数取模:结果符号与被除数相同 int g = -10 % 3; // -1 int h = 10 % -3; // 1 int i = -10 % -3; // -1 printf("10 / 3 = %d\n", a); printf("10.0 / 3 = %lf\n", c); printf("-10 %% 3 = %d\n", g); return 0; }第二部分:移位操作符
一、左移(<<)和右移(>>)
移位操作符只针对整数类型(整型家族)。
| 操作符 | 名称 | 示例 | 说明 |
|---|---|---|---|
<< | 左移 | a << n | 高位丢弃,低位补0 |
>> | 右移 | a >> n | 低位丢弃,高位补符号位(算术右移)或0(逻辑右移) |
#include <stdio.h> int main() { // 左移:相当于乘以2的n次方 int a = 15; // 00000000 00000000 00000000 00001111 a <<= 7; // 00000000 00000000 00000111 10000000 = 1920 printf("15 << 7 = %d\n", a); // 1920 // 右移:相当于除以2的n次方(向下取整) int b = 320; // 00000000 00000000 00000001 01000000 b >>= 4; // 00000000 00000000 00000000 00010100 = 20 printf("320 >> 4 = %d\n", b); // 20 // 负数右移(算术右移,高位补1) int c = -10; // 11111111 11111111 11111111 11110110 c >>= 2; // 11111111 11111111 11111111 11111101 = -3 printf("-10 >> 2 = %d\n", c); // -3 return 0; }二、移位操作的注意事项
int main() { int a = 10; // 1. 移位位数不能大于等于类型位数(未定义行为) // a << 32; // 在32位系统中是未定义行为 // 2. 不能移位负数位 // a << -1; // 未定义行为 // 3. 移位不会改变原变量的值(除非使用赋值操作符) int b = 10; int c = b << 2; // b不变,c=40 b <<= 2; // b变为40 return 0; }第三部分:位操作符
一、位操作符分类
| 操作符 | 名称 | 示例 | 说明 |
|---|---|---|---|
& | 按位与 | a & b | 两个都为1,结果为1 |
| | 按位或 | a | b | 两个都为0,结果为0 |
^ | 按位异或 | a ^ b | 相同为0,不同为1 |
~ | 按位取反 | ~a | 0变1,1变0(单目操作符) |
二、按位与(&)
#include <stdio.h> int main() { // 判断奇偶数:与1按位与,结果为1是奇数,0是偶数 int a = 10; // 1010 int b = 7; // 0111 if (a & 1) { printf("%d 是奇数\n", a); // 不执行 } else { printf("%d 是偶数\n", a); // 执行 } // 将某一位清零(与0按位与) int c = 0xFF; // 11111111 int d = c & 0xFE; // 11111110,最低位清零 printf("0xFF & 0xFE = 0x%X\n", d); // 0xFE return 0; }三、按位或(|)
int main() { int a = 0b00001000; // 第3位为1 int b = 0b00000100; // 第2位为1 // 将某位置为1(与1按位或) int c = a | b; // 0b00001100,第2位和第3位都为1 // 将第4位置为1 int d = 0; // 00000000 d |= (1 << 4); // 00010000 printf("d = %d\n", d); // 16 return 0; }四、按位异或(^)
int main() { // 异或的特性: // a ^ a = 0 // a ^ 0 = a // a ^ b ^ b = a int a = 5; // 0101 int b = 3; // 0011 int c = a ^ b; // 0110 = 6 // 加密解密(相同密钥异或两次恢复原值) int plain = 123; int key = 456; int cipher = plain ^ key; // 加密 int decrypted = cipher ^ key; // 解密 printf("加密后:%d,解密后:%d\n", cipher, decrypted); return 0; }五、按位取反(~)
int main() { int a = 0; // 00000000 00000000 00000000 00000000 int b = ~a; // 11111111 11111111 11111111 11111111 = -1 int c = -1; // 11111111 11111111 11111111 11111111 int d = ~c; // 00000000 00000000 00000000 00000000 = 0 printf("~0 = %d\n", b); // -1 printf("~(-1) = %d\n", d); // 0 return 0; }六、异或实现交换(无临时变量)
#include <stdio.h> // 方法1:使用临时变量(推荐,易懂) void swap1(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 方法2:使用加减法(可能溢出) void swap2(int* a, int* b) { *a = *a + *b; *b = *a - *b; *a = *a - *b; } // 方法3:使用异或(无溢出风险,但只适用于整数) void swap3(int* a, int* b) { // a=5(101), b=3(011) *a = *a ^ *b; // a = 101 ^ 011 = 110 (6) *b = *a ^ *b; // b = 110 ^ 011 = 101 (5) *a = *a ^ *b; // a = 110 ^ 101 = 011 (3) } int main() { int x = 5, y = 3; printf("交换前:x=%d, y=%d\n", x, y); swap3(&x, &y); printf("交换后:x=%d, y=%d\n", x, y); return 0; }第四部分:逻辑操作符
一、逻辑与(&&)和逻辑或(||)
| 操作符 | 名称 | 示例 | 说明 |
|---|---|---|---|
&& | 逻辑与 | a && b | 两边都为真,结果为真 |
|| | 逻辑或 | a || b | 一边为真,结果为真 |
! | 逻辑非 | !a | 真变假,假变真 |
二、短路求值(重要!)
#include <stdio.h> int main() { int a = 0, b = 2, c = 3, d = 4; int i; // 逻辑与短路:左边为假,右边不执行 i = a++ && ++b && d++; // a++ = 0(假),整个表达式为假,++b和d++不执行 printf("i=%d, a=%d, b=%d, d=%d\n", i, a, b, d); // 输出:i=0, a=1, b=2, d=4 // 重置变量 a = 0, b = 2, c = 3, d = 4; // 逻辑或短路:左边为真,右边不执行 i = a++ || ++b || d++; // a++ = 0(假),继续执行++b = 3(真),整个表达式为真,d++不执行 printf("i=%d, a=%d, b=%d, d=%d\n", i, a, b, d); // 输出:i=1, a=1, b=3, d=4 printf("0 && 9 = %d\n", 0 && 9); // 0 printf("0 || 9 = %d\n", 0 || 9); // 1 return 0; }第五部分:赋值操作符
一、复合赋值操作符
| 操作符 | 示例 | 等价于 |
|---|---|---|
+= | a += b | a = a + b |
-= | a -= b | a = a - b |
*= | a *= b | a = a * b |
/= | a /= b | a = a / b |
%= | a %= b | a = a % b |
<<= | a <<= n | a = a << n |
>>= | a >>= n | a = a >> n |
&= | a &= b | a = a & b |
|= | a |= b | a = a | b |
^= | a ^= b | a = a ^ b |
二、自增自减操作符
#include <stdio.h> int main() { int a = 10; int b, c; // 后置++:先使用,后自增 b = a++; // b = 10, a = 11 printf("a=%d, b=%d\n", a, b); // a=11, b=10 // 前置++:先自增,后使用 a = 10; c = ++a; // a = 11, c = 11 printf("a=%d, c=%d\n", a, c); // a=11, c=11 // 表达式中的副作用 int p = 0; int l = p++; // l = 0, p = 1 printf("l=%d, p=%d\n", l, p); // l=0, p=1 return 0; }第六部分:关系操作符
| 操作符 | 名称 | 示例 |
|---|---|---|
> | 大于 | a > b |
>= | 大于等于 | a >= b |
< | 小于 | a < b |
<= | 小于等于 | a <= b |
== | 等于 | a == b |
!= | 不等于 | a != b |
int main() { int a = 10, b = 20; // 关系表达式的结果是整数:真为1,假为0 int r1 = a > b; // 0 int r2 = a < b; // 1 int r3 = a == b; // 0 printf("10 > 20 = %d\n", r1); printf("10 < 20 = %d\n", r2); return 0; }第七部分:条件操作符(三目运算符)
一、语法与使用
// 语法:表达式1 ? 表达式2 : 表达式3 // 如果表达式1为真,返回表达式2,否则返回表达式3 #include <stdio.h> int main() { int a = 10, b = 20; // 求最大值 int max = (a > b) ? a : b; printf("max = %d\n", max); // 20 // 求绝对值 int x = -5; int abs_x = (x >= 0) ? x : -x; printf("|%d| = %d\n", x, abs_x); // 5 return 0; }二、嵌套使用
int main() { int score = 85; // 成绩等级判断 char grade = (score >= 90) ? 'A' : (score >= 80) ? 'B' : (score >= 70) ? 'C' : (score >= 60) ? 'D' : 'F'; printf("成绩:%d,等级:%c\n", score, grade); // B return 0; }第八部分:逗号操作符
一、语法与使用
逗号操作符从左到右依次执行所有表达式,返回最后一个表达式的值。
#include <stdio.h> int main() { int a, b, c; // 逗号操作符示例 a = (1, 2, 3, 4, 5); // a = 5 printf("a = %d\n", a); // 在循环中使用 for (int i = 0, j = 10; i < j; i++, j--) { printf("i=%d, j=%d\n", i, j); } return 0; }第九部分:sizeof 操作符
一、sizeof 的特点
sizeof既是操作符也是关键字,在编译时计算类型或变量占用的字节数。
#include <stdio.h> int main() { int a = 10; int arr[10]; // sizeof 的三种用法 printf("sizeof(a) = %zu\n", sizeof(a)); // 4(使用变量) printf("sizeof(int) = %zu\n", sizeof(int)); // 4(使用类型) printf("sizeof a = %zu\n", sizeof a); // 4(省略括号) // 错误写法 // printf("%zu\n", sizeof int); // 错误!类型不能省略括号 // 计算数组元素个数 int len = sizeof(arr) / sizeof(arr[0]); printf("数组长度:%d\n", len); return 0; }第十部分:类型转换
一、隐式类型转换(算术转换)
int main() { // 算术转换顺序(下转上) // long double ← double ← float ← unsigned long ← long ← unsigned int ← int int a = 10; double b = 3.14; double c = a + b; // a被自动转换为double // 截断:赋值时大类型转小类型 int d = 3.14; // d = 3,小数部分被截断 return 0; }二、整型提升
int main() { char a = 0b10111111; // 0xBF char b = 0b10111111; // 0xBF // 运算时会发生整型提升:char → int int c = a + b; // 0xBF + 0xBF = 0x17E printf("%d\n", c); // 382 return 0; }三、强制类型转换
int main() { int a = 10, b = 3; // 整数除法 double c = a / b; // 3.0 double d = (double)a / b; // 3.33333 double e = a / (double)b; // 3.33333 printf("c = %lf\n", c); printf("d = %lf\n", d); return 0; }第十一部分:操作符优先级与结合性
一、优先级表(从高到低)
| 优先级 | 操作符 | 结合性 |
|---|---|---|
| 1 | ()[].-> | 从左到右 |
| 2 | ++--+-!~*&sizeof(单目) | 从右到左 |
| 3 | */% | 从左到右 |
| 4 | +- | 从左到右 |
| 5 | <<>> | 从左到右 |
| 6 | <<=>>= | 从左到右 |
| 7 | ==!= | 从左到右 |
| 8 | &(按位与) | 从左到右 |
| 9 | ^(按位异或) | 从左到右 |
| 10 | |(按位或) | 从左到右 |
| 11 | &&(逻辑与) | 从左到右 |
| 12 | ||(逻辑或) | 从左到右 |
| 13 | ?:(条件) | 从右到左 |
| 14 | =+=-=等 (赋值) | 从右到左 |
| 15 | ,(逗号) | 从左到右 |
二、常见优先级陷阱
#include <stdio.h> int main() { int a = 10, b = 20; // 陷阱1:== 优先级高于 & 和 | if (a & b == 0) { // 实际:a & (b == 0),不是 (a & b) == 0 // ... } // 正确写法 if ((a & b) == 0) { // ... } // 陷阱2:移位优先级低于加减 int c = a + b << 2; // 实际:(a + b) << 2 printf("c = %d\n", c); // 陷阱3:逻辑操作符优先级低于关系操作符 if (a > 0 && b > 0) { // 正确:实际上就是 (a > 0) && (b > 0) printf("a和b都为正数\n"); } return 0; }第十二部分:常见关键字说明
一、auto
// auto 用于自动类型推导(C++11),C语言中很少使用 auto int a = 10; // 等价于 int a = 10(默认就是auto)二、static
#include <stdio.h> // 1. 修饰局部变量:延长生命周期至整个程序 void test_static() { static int a = 1; // 只初始化一次 a++; printf("%d\n", a); } // 2. 修饰全局变量:限制作用域为本文件 // static int global_var = 100; // 其他文件无法访问 // 3. 修饰函数:限制作用域为本文件 static void helper() { // 只能在本文件内调用 } int main() { test_static(); // 2 test_static(); // 3 test_static(); // 4 return 0; }三、const
int main() { const int a = 10; // a是只读变量,不能修改 // a = 20; // 错误! // const修饰指针 int x = 10, y = 20; const int* p1 = &x; // 指向常量的指针:不能通过p1修改指向的值 int* const p2 = &x; // 常量指针:p2的指向不能改变 const int* const p3 = &x; // 既不能修改值,也不能修改指向 p1 = &y; // 可以修改指向 *p2 = 30; // 可以修改值 // p2 = &y; // 错误! return 0; }四、extern
// file1.c int global_var = 100; // file2.c extern int global_var; // 声明外部变量 int main() { printf("%d\n", global_var); // 100 return 0; }五、volatile
// volatile 告诉编译器不要优化这个变量,每次直接从内存中读取 volatile int flag = 1; // 常用于多线程编程或硬件寄存器访问六、register
// register 建议编译器将变量存储在寄存器中(现代编译器自动优化,很少使用) register int counter = 0; // 不能对register变量取地址 // int* p = &counter; // 错误!第十三部分:枚举常量(enum)
#include <stdio.h> enum Color { RED, // 默认0 GREEN, // 1 BLUE // 2 }; enum Weekday { MON = 1, TUE, // 2 WED, // 3 THU, // 4 FRI, // 5 SAT, // 6 SUN // 7 }; int main() { enum Color c = RED; printf("RED = %d\n", RED); // 0 printf("GREEN = %d\n", GREEN); // 1 printf("BLUE = %d\n", BLUE); // 2 printf("MON = %d\n", MON); // 1 printf("SUN = %d\n", SUN); // 7 return 0; }第十四部分:union(共用体)
一、基本用法
#include <stdio.h> union Data { int i; char c; float f; }; int main() { union Data d; printf("union大小:%zu\n", sizeof(d)); // 4(最大成员的大小) d.i = 65; printf("d.i = %d\n", d.i); // 65 printf("d.c = %c\n", d.c); // 'A' d.f = 3.14; printf("d.f = %f\n", d.f); // 3.14 return 0; }二、判断大小端(经典面试题)
#include <stdio.h> // 方法1:使用union union Endian { int a; char b; }; int isLittleEndian1() { union Endian e; e.a = 1; return e.b == 1; // 小端返回1,大端返回0 } // 方法2:使用指针 int isLittleEndian2() { int a = 1; char* p = (char*)&a; return *p == 1; // 小端返回1,大端返回0 } int main() { if (isLittleEndian1()) { printf("小端模式\n"); } else { printf("大端模式\n"); } return 0; }总结
一、操作符优先级速记
括号:
()单目:
++--!~*&sizeof算术:
*/%→+-移位:
<<>>关系:
<<=>>=→==!=位运算:
&→^→|逻辑:
&&→||条件:
?:赋值:
=+=-=等逗号:
,
二、常见陷阱总结
| 陷阱 | 说明 |
|---|---|
=和==混淆 | if (a = 10)是赋值,永远为真 |
| 短路求值 | &&和||会短路,后面的表达式可能不执行 |
| 整数除法截断 | 10 / 3 = 3,不是3.33 |
| 移位位数过大 | 移位位数 ≥ 类型位数是未定义行为 |
| 优先级错误 | a & b == 0实际是a & (b == 0) |
操作符是C语言的基本构件,掌握它们的使用规则和优先级是写出正确代码的基础。
学习建议:
不确定优先级时使用括号明确顺序
理解短路求值对代码执行的影响
注意整数除法、取模的运算规则
熟悉位操作在嵌入式、加密、标志位等场景的应用