C语言操作符深度解析:从基础到高级应用

引言

操作符是C语言的灵魂,它们决定了数据的计算方式、逻辑判断和内存操作。理解操作符的优先级、结合性和使用规则,是写出正确、高效代码的基础。

C语言拥有丰富的操作符,包括算术操作符、移位操作符、位操作符、赋值操作符、逻辑操作符、条件操作符等。今天,我将从底层视角,全面讲解各类操作符的用法、注意事项以及常见陷阱。


第一部分:算术操作符

一、基本算术操作符

操作符 名称 示例 说明
+ 加法 a + b 两数相加
- 减法 a - b 两数相减
* 乘法 a * b 两数相乘
/ 除法 a / b 两数相除(整数除法截断)
% 取模 a % b 取余数,操作数必须为整数

二、注意事项

cpp 复制代码
#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(逻辑右移)
cpp 复制代码
#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;
}

二、移位操作的注意事项

cpp 复制代码
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
^ 按位异或 a ^ b 相同为0,不同为1
~ 按位取反 ~a 0变1,1变0(单目操作符)

二、按位与(&

cpp 复制代码
#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;
}

三、按位或(|

cpp 复制代码
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;
}

四、按位异或(^

cpp 复制代码
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;
}

五、按位取反(~

cpp 复制代码
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;
}

六、异或实现交换(无临时变量)

cpp 复制代码
#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 真变假,假变真

二、短路求值(重要!)

cpp 复制代码
#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
^= a ^= b a = a ^ b

二、自增自减操作符

cpp 复制代码
#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
cpp 复制代码
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;
}

第七部分:条件操作符(三目运算符)

一、语法与使用

cpp 复制代码
// 语法:表达式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;
}

二、嵌套使用

cpp 复制代码
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;
}

第八部分:逗号操作符

一、语法与使用

逗号操作符从左到右依次执行所有表达式,返回最后一个表达式的值。

cpp 复制代码
#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 既是操作符也是关键字,在编译时计算类型或变量占用的字节数。

cpp 复制代码
#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;
}

第十部分:类型转换

一、隐式类型转换(算术转换)

cpp 复制代码
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;
}

二、整型提升

cpp 复制代码
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;
}

三、强制类型转换

cpp 复制代码
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 , (逗号) 从左到右

二、常见优先级陷阱

cpp 复制代码
#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

cpp 复制代码
// auto 用于自动类型推导(C++11),C语言中很少使用
auto int a = 10;  // 等价于 int a = 10(默认就是auto)

二、static

cpp 复制代码
#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

cpp 复制代码
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

cpp 复制代码
// file1.c
int global_var = 100;

// file2.c
extern int global_var;  // 声明外部变量
int main() {
    printf("%d\n", global_var);  // 100
    return 0;
}

五、volatile

cpp 复制代码
// volatile 告诉编译器不要优化这个变量,每次直接从内存中读取
volatile int flag = 1;
// 常用于多线程编程或硬件寄存器访问

六、register

cpp 复制代码
// register 建议编译器将变量存储在寄存器中(现代编译器自动优化,很少使用)
register int counter = 0;
// 不能对register变量取地址
// int* p = &counter;  // 错误!

第十三部分:枚举常量(enum)

cpp 复制代码
#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(共用体)

一、基本用法

cpp 复制代码
#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;
}

二、判断大小端(经典面试题)

cpp 复制代码
#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;
}

总结

一、操作符优先级速记

  1. 括号()

  2. 单目++ -- ! ~ * & sizeof

  3. 算术* / %+ -

  4. 移位<< >>

  5. 关系< <= > >=== !=

  6. 位运算&^|

  7. 逻辑&&||

  8. 条件?:

  9. 赋值= += -=

  10. 逗号,

二、常见陷阱总结

陷阱 说明
=== 混淆 if (a = 10) 是赋值,永远为真
短路求值 && 和 `
整数除法截断 10 / 3 = 3,不是 3.33
移位位数过大 移位位数 ≥ 类型位数是未定义行为
优先级错误 a & b == 0 实际是 a & (b == 0)

操作符是C语言的基本构件,掌握它们的使用规则和优先级是写出正确代码的基础。

学习建议:

  1. 不确定优先级时使用括号明确顺序

  2. 理解短路求值对代码执行的影响

  3. 注意整数除法、取模的运算规则

  4. 熟悉位操作在嵌入式、加密、标志位等场景的应用

相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜7 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa7 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio