C语言操作符深度解析:从基础到高级应用
引言操作符是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不变c40 b 2; // b变为40 return 0; }第三部分位操作符一、位操作符分类操作符名称示例说明按位与a b两个都为1结果为1|按位或a | b两个都为0结果为0^按位异或a ^ b相同为0不同为1~按位取反~a0变11变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) { // a5(101), b3(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); // 输出i0, a1, b2, d4 // 重置变量 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); // 输出i1, a1, b3, d4 printf(0 9 %d\n, 0 9); // 0 printf(0 || 9 %d\n, 0 || 9); // 1 return 0; }第五部分赋值操作符一、复合赋值操作符操作符示例等价于a ba a b-a - ba a - b*a * ba a * b/a / ba a / b%a % ba a % ba na a na na a na ba a b|a | ba a | b^a ^ ba 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); // a11, b10 // 前置先自增后使用 a 10; c a; // a 11, c 11 printf(a%d, c%d\n, a, c); // a11, c11 // 表达式中的副作用 int p 0; int l p; // l 0, p 1 printf(l%d, p%d\n, l, p); // l0, p1 return 0; }第六部分关系操作符操作符名称示例大于a b大于等于a b小于a b小于等于a b等于a b!不等于a ! bint 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 用于自动类型推导C11C语言中很少使用 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; }三、constint 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语言的基本构件掌握它们的使用规则和优先级是写出正确代码的基础。学习建议不确定优先级时使用括号明确顺序理解短路求值对代码执行的影响注意整数除法、取模的运算规则熟悉位操作在嵌入式、加密、标志位等场景的应用