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. 熟悉位操作在嵌入式、加密、标志位等场景的应用

相关推荐
z小天才b2 小时前
Java 设计模式完全指南:从入门到精通
java·开发语言·设计模式
zs宝来了2 小时前
网络篇15-网络收发包应用之iptable
开发语言·网络·php
烤麻辣烫2 小时前
算法--二分搜索
java·开发语言·学习·算法·intellij-idea
编码浪子2 小时前
《安全 Rust 的边界在哪?》— 中文解读
开发语言·安全·rust
kyriewen112 小时前
Next.js:让你的React应用从“裸奔”到“穿衣服”
开发语言·前端·javascript·react.js·设计模式·ecmascript
三品吉他手会点灯2 小时前
C语言学习笔记 - 18.C编程预备计算机专业知识 - 什么是变量
c语言·开发语言·笔记·学习
好奇龙猫2 小时前
[大学院-python-base gammer learning2: python base programming ]
开发语言·python
海盗12343 小时前
C#上位机开发-S7协议通信
开发语言·c#