编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
        • 3.0 运算符介绍
        • 3.1 自增运算符
          • 3.1.1 自增自减运算
          • 3.1.2 前缀后缀区别
          • 3.1.3 增减注意事项
          • 3.1.4 综合案例与思考
        • 3.2 算术运算符
          • 3.2.1 基本数学运算
          • 3.2.2 赋值算术运算
          • 3.2.3 综合案例与思考
        • 3.3 关系运算符
          • 3.3.1 比较表达式
          • 3.3.2 综合案例与思考
        • 3.4 逻辑运算符
          • 3.4.1 逻辑运算案例
          • 3.4.2 短路运算
          • 3.4.3 短路运算作用
          • 3.4.4 综合案例与思考
        • 3.5 条件运算符
          • 3.5.1 基础语法
          • 3.5.2 三目运算符
          • 3.5.3 综合案例与思考
        • 3.6 位运算符
          • 3.6.1 取反运算符~
          • 3.6.2 与运算符&
          • 3.6.3 或运算符|
          • 3.6.4 异或运算符^
          • 3.6.5 左移运算符<<
          • 3.6.6 右移运算符>>
          • 3.6.7 综合案例与思考
        • 3.7 运算符优先级
        • 3.8 sizeof运算符
          • 3.8.1 sizeof的作用
          • 3.8.2 获取数据类型大小
          • 3.8.3 获取变量大小
          • 3.8.4 sizeof注意事项
          • 3.8.5 sizeof返回值
          • 3.8.6 综合案例与思考
        • 3.9 其他运算符
      • 循环和选择
      • 输入输出
      • 函数
      • 指针
      • 数组和容器
      • 类和内存
      • 流与文件
      • 结构体
      • 线程和锁
      • 预处理器
      • 高级数据
    • 综合案例

    • 专栏博客

    • 标准集库

  • Cpp入门到精通

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • C语言入门精通
  • 入门教程
杨充
2025-07-21
目录

运算符

# 03.运算符

# 目录介绍

  • 3.0 运算符介绍
  • 3.1 自增运算符
    • 3.1.1 自增自减运算
    • 3.1.2 前缀后缀区别
    • 3.1.3 增减注意事项
    • 3.1.4 综合案例与思考
  • 3.2 算术运算符
    • 3.2.1 基本数学运算
    • 3.2.2 赋值算术运算
    • 3.2.3 综合案例与思考
  • 3.3 关系运算符
    • 3.3.1 比较表达式
    • 3.3.2 综合案例与思考
  • 3.4 逻辑运算符
    • 3.4.1 逻辑运算案例
    • 3.4.2 短路运算
    • 3.4.3 短路运算作用
    • 3.4.4 综合案例与思考
  • 3.5 条件运算符
    • 3.5.1 基础语法
    • 3.5.2 三目运算符
    • 3.5.3 综合案例与思考
  • 3.6 位运算符
    • 3.6.1 取反运算符~
    • 3.6.2 与运算符&
    • 3.6.3 或运算符|
    • 3.6.4 异或运算符^
    • 3.6.5 左移运算符<<
    • 3.6.6 右移运算符>>
    • 3.6.7 综合案例与思考
  • 3.7 运算符优先级
  • 3.8 sizeof运算符
    • 3.8.1 sizeof的作用
    • 3.8.2 获取数据类型大小
    • 3.8.3 获取变量大小
    • 3.8.4 sizeof注意事项
    • 3.8.5 sizeof返回值
    • 3.8.6 综合案例与思考
  • 3.9 其他运算符

# 3.0 运算符介绍

在 C 语言中,运算符(Operators)用于对变量和值执行各种操作。C 语言提供了丰富的运算符,包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符等。

# 3.1 自增运算符

# 3.1.1 自增自减运算

C 语言提供两个运算符,对变量自身进行+ 1和- 1的操作。

运算符 描述 示例
++ 自增(前/后) a++ 或 ++a
-- 自减(前/后) a-- 或 --a
i++; // 等同于 i = i + 1
i--; // 等同于 i = i - 1
1
2

这两个运算符放在变量的前面或后面,结果是不一样的。++var和--var是先执行自增或自减操作,再返回操作后var的值;var++和var--则是先返回操作前var的值,再执行自增或自减操作。

int i = 42;
int j;

j = (i++ + 10);
// i: 43
// j: 52

j = (++i + 10)
// i: 44
// j: 54
1
2
3
4
5
6
7
8
9
10

# 3.1.2 前缀后缀区别

注意:++ 和 -- 如果 ==不是单独使用==(如:用在表达式中),前缀和后缀 ==差异巨大==

放在变量前,先 +1、-1 再取值使用。

int a = 10;
int res = ++a;  // 先 +1,再取 a 值给 res。 (先加再用)
1
2

放在变量后,先 取值用,再 +1、-1。

int b = 10;
int res2 = b--; // 先取 b 值给 res2, 而后 b 再 -1  (先用再减)
1
2

# 3.1.3 增减注意事项

  1. 不能用于常量
  2. 不能用于表达式
  3. 优先级:整体高于算数运算符(必然高于比较、赋值);后缀高于前缀
  4. 不要在一个表达式中,对同一变量 多次 ++、-- 运算。可读性差,且不用编译系统结果不同。
// 目标: 掌握 ++/-- 使用注意事项
int main(void) {
	// 1. 不能用于常量
	//printf("%d\n", 10++);

	// 2. 不能用于表达式
	int a = 10;
	// (a - 1)++;
	// ++(-a);

	// 3. 全部 高于算数运算
	//int b = ++a * 3;
	int b = a-- * 3;
	printf("b = %d\n", b);

	// 3. 后缀高于前缀
	printf("a = %d\n", -a++);  

	// 对同一变量 多次 ++、-- 运算。可读性差,且不同编译系统结果不同。
	int i = 10;
	int j = 5;

	int res = i++ + ++i - --j - ++j + 3 + i--;
	printf("res = %d\n", res);	// 26  --- Linux下gcc编译器: 28
	printf("i = %d\n", i);		// 11
	printf("j = %d\n", j);		// 5

	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 3.1.4 综合案例与思考

综合案例:自增自减运算的各种场景

#include <stdio.h>

int main() {
    // 场景1:循环中的自增
    printf("for循环中的i++:\n");
    for (int i = 0; i < 5; i++) {
        printf("%d ", i);
    }
    printf("\n");
    
    // 场景2:前缀和后缀在赋值中的区别
    int a = 10, b = 10;
    int x = a++;  // x=10, a=11(先用再加)
    int y = ++b;  // y=11, b=11(先加再用)
    printf("后缀: x=%d, a=%d\n", x, a);
    printf("前缀: y=%d, b=%d\n", y, b);
    
    // 场景3:在printf中使用
    int n = 5;
    printf("n=%d, n++=%d, 之后n=%d\n", n, n++, n);
    // 注意:上面的行为是未定义的!参数求值顺序不确定
    // 正确做法:分开写
    n = 5;
    printf("当前n=%d\n", n);
    n++;
    printf("自增后n=%d\n", n);
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

原理说明:i++ 和 ++i 在底层实现上,i++ 需要先保存原值到临时变量,然后再自增;而 ++i 直接自增后返回。虽然现代编译器会优化两者的性能差异,但在复杂表达式中,它们的语义区别可能导致难以发现的bug。

思考题:

  1. 在 for 循环中使用 i++ 和 ++i 结果一样吗?哪个更好?为什么?
  2. 为什么不推荐在一个表达式中对同一变量多次使用 ++ 或 --?
  3. printf("n=%d, n++=%d", n, n++) 的输出结果能确定吗?为什么?

# 3.2 算术运算符

# 3.2.1 基本数学运算

用于执行基本的数学运算。

运算符 描述 示例
+ 加法 a + b
- 减法 a - b
* 乘法 a * b
/ 除法 a / b
% 取模(取余) a % b

示例:

int a = 10, b = 3;
printf("%d\n", a + b); // 输出 13
printf("%d\n", a % b); // 输出 1

//运算符/用来完成除法。注意,两个整数相除,得到还是一个整数。
float x = 6 / 4;
printf("%f\n", x); // 输出 000000
1
2
3
4
5
6
7

注意点:变量x的类型是float(浮点数),但是6 / 4得到的结果是0,而不是5。原因就在于 C 语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。

如果希望得到浮点数的结果,两个运算数必须至少有一个浮点数,这时 C 语言就会进行浮点数除法。

float x = 6.0 / 4; // 或者写成 6 / 4.0
printf("%f\n", x); // 输出 500000
1
2

# 3.2.2 赋值算术运算

赋值运算的简写形式: 如果变量对自身的值进行算术运算,C 语言提供了简写形式,允许将赋值运算符和算术运算符结合成一个运算符。

运算符 描述 示例
= 简单赋值 a = b
+= 加后赋值 a += b
-= 减后赋值 a -= b
*= 乘后赋值 a *= b
/= 除后赋值 a /= b
%= 取模后赋值 a %= b
&= 按位与后赋值 a &= b
| = 按位或后赋值 a |= b
^= 按位异或后赋值 a ^= b
<<= 左移后赋值 a <<= b
>>= 右移后赋值 a >>= b

**注意:**赋值运算符,会修改变量的原始值。 赋值符左侧,必须可修改(变量)。

int main(void) {
	// = 赋值符, 自右向左
	int a = 2, b = 1, c = 0;
    
	a = a + b;
	printf("a = %d\n", a);		
	b = a < c;
	printf("b = %d\n", b);  // 直接 打印 b = a < c
	a = b = c = 8;
	printf("a = %d, b = %d, c = %d\n", a, b, c);

	// += -= 复合赋值符。需求:微信钱包有 15.8 元, 发 2.9 元红包,收 13.6 元红包。
	double money;
    money = 15.8;
	money += 2.9;		// 等价于: money = money + 2.9;
	printf("money = %lf\n", money);
	
	// 需求: 发红包
	money -= 13.6;		// 等价于: money = money - 13.6;
	printf("money = %lf\n", money);

    // 其他 复合赋值符。
	int i, j;
    i = 10, j = 5;
	//i *= j;	// 等价于:i = i * j;
	//i /= j;	// 等价与:i = i / j;
	//i %= j;	// 等价与:i = i % j;
	printf("i = %d\n", i);

	// 思考:
	i %= j - 2;			// 等价于 i = i % (j - 2);  %= 右侧看做一个整体.
	printf("i = %d\n", i);
    
    // 注意:赋值符,左侧必须可修改
    3 += 2;

	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 3.2.3 综合案例与思考

综合案例:用算术运算实现温度转换计算器

#include <stdio.h>

int main() {
    double celsius = 36.5;
    
    // 摄氏度转华氏度: F = C * 9/5 + 32
    double fahrenheit = celsius * 9.0 / 5.0 + 32.0;
    printf("%.1f°C = %.1f°F\n", celsius, fahrenheit);
    
    // 注意整数除法的陷阱
    double wrong = celsius * 9 / 5 + 32;    // 9/5=1 (整数除法!)
    double right = celsius * 9.0 / 5.0 + 32; // 9.0/5.0=1.8
    printf("错误(整数除法): %.1f°F\n", wrong);
    printf("正确(浮点除法): %.1f°F\n", right);
    
    // 取模运算的应用:判断奇偶
    for (int i = 1; i <= 10; i++) {
        printf("%d是%s数 ", i, (i % 2 == 0) ? "偶" : "奇");
    }
    printf("\n");
    
    // 复合赋值运算
    int score = 60;
    score += 10;  // 加分
    score -= 5;   // 扣分
    score *= 2;   // 翻倍
    printf("最终分数: %d\n", score);
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

思考题:

  1. 5 / 2 和 5.0 / 2 的结果分别是什么?为什么不同?
  2. 负数取模 -7 % 3 的结果是什么?C语言中取模运算的符号规则是什么?
  3. a *= b + c 等价于 a = a * (b + c) 还是 a = a * b + c?为什么?

# 3.3 关系运算符

# 3.3.1 比较表达式

C 语言用于比较的表达式,称为“关系表达式”(relational expression),里面使用的运算符就称为“关系运算符”(relational operator),主要有下面6个。

用于比较两个值的大小关系,返回 1(真)或 0(假)。

运算符 描述 示例
== 等于 a == b
!= 不等于 a != b
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
<= 小于等于 a <= b

示例:

int a = 12, b = 20;
printf("%d\n", a > b);  // 输出 0(假)
printf("%d\n", a != b); // 输出 1(真)
1
2
3

关系表达式通常返回0或1,表示真伪。C 语言中,0表示伪,所有非零值表示真。比如,20 > 12返回1,12 > 20返回0。

# 3.3.2 综合案例与思考

综合案例:关系运算在实际判断中的应用

#include <stdio.h>
#include <math.h>

int main() {
    int age = 20;
    int score = 85;
    
    // 基本比较
    printf("age >= 18: %d\n", age >= 18);     // 1 (真)
    printf("score == 100: %d\n", score == 100); // 0 (假)
    
    // 常见错误:== 写成 =
    int x = 5;
    // if (x = 3)  // 这是赋值!不是比较!永远为真
    if (x == 3) {
        printf("x等于3\n");
    }
    
    // 浮点数比较的陷阱
    double a = 0.1 + 0.2;
    double b = 0.3;
    printf("0.1+0.2 == 0.3? %d\n", a == b);  // 0 (假!因为精度问题)
    printf("正确比较: %d\n", fabs(a - b) < 1e-9);  // 1 (真)
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

思考题:

  1. if (x = 5) 和 if (x == 5) 有什么区别?为什么前者是常见的bug?
  2. 为什么不能用 == 直接比较两个浮点数是否相等?
  3. C语言中比较运算的结果是什么类型?返回值有哪些?

# 3.4 逻辑运算符

# 3.4.1 逻辑运算案例

用于组合多个条件,返回 1(真)或 0(假)。逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。

运算符 描述 示例
&& 逻辑与(AND) a && b
|| 逻辑或(OR) a || b
! 逻辑非(NOT) !a

示例:

int a = 1, b = 0;
printf("%d\n", a && b); // 输出 0(假)
printf("%d\n", a || b); // 输出 1(真)
printf("%d\n", !a);     // 输出 0(假)
1
2
3
4

# 3.4.2 短路运算

逻辑运算符还有一个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的。如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。

在程序中:

  • 判断 表达式 a && (b+c) 的结果:当 a 为假时,不必计算 b+c,可直接得出表达式为假的结论。
  • 判断 表达式 a || (b+c) 的结果:当 a 为真时,则不必计算 b+c,就能判断出表达式的值为真。
if (number != 0 && 12/number == 2)
1

上面示例中,如果&&左侧的表达式(number != 0)为伪,即number等于0时,右侧的表达式(12/number == 2)是不会执行的。因为这时左侧表达式返回0,整个&&表达式肯定为伪,就直接返回0,不再执行右侧的表达式了。

# 3.4.3 短路运算作用

在代码中利用短路运算,可 避免不必要运算,提高程序执行效率。 比如有如下代码:

int a = 5, b = 28;
int c = (a > 3) || ((b / 7)*4 % 2 != 0)
printf("c = %d\n", c);
1
2
3

现要计算 c 值。= 右侧表达式含有逻辑或 || 运算符,运算特性 有真为真。|| 左边表达式很容易判断为 真,可确定整个表达式结果为 真。 右侧 b 相关的 复杂表达式 不需要计算 。

小知识:合理利用短路运算特性,提高程序执行效率

  • 编写含**==&&==**表达式时,&& 同真为真,左表达式值为假时,右表达式不计算, 建议将 ==易假 值放左边==。
  • 编写含**==||==**表达式时,|| 有真为真,左表达式值为真时,右表达式不计算。 建议将 ==易真 值放左边==。

# 3.4.4 综合案例与思考

综合案例:利用短路运算安全地进行除法和数组访问

#include <stdio.h>

int main() {
    // 短路运算避免除零错误
    int divisor = 0;
    int dividend = 10;
    // 如果divisor为0,&&右边不会执行,避免了除零
    if (divisor != 0 && dividend / divisor > 2) {
        printf("商大于2\n");
    } else {
        printf("除数为0或商不大于2\n");
    }
    
    // 短路运算避免空指针访问
    int *ptr = NULL;
    if (ptr != NULL && *ptr > 0) {
        printf("值为正数\n");
    } else {
        printf("指针为空或值非正\n");
    }
    
    // 逻辑运算综合:判断闰年
    int year = 2024;
    // 闰年:能被4整除但不能被100整除,或者能被400整除
    int isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    printf("%d年%s闰年\n", year, isLeap ? "是" : "不是");
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

思考题:

  1. 短路运算在实际编程中最常见的应用场景是什么?请举两个例子。
  2. if (a++ && b++) 中,如果 a 初始值为 0,b 的值会被自增吗?为什么?
  3. 逻辑运算符 && 和位运算符 & 有什么区别?能互相替代吗?

# 3.5 条件运算符

# 3.5.1 基础语法

语法:表达式1 ?表达式 2:表达式 3

  • 表达式 1 一定起 判别 作用。表达式 2 和 表达式 3 只能有一个执行。
  • 三目运算的结果,必须被使用。

# 3.5.2 三目运算符

用于简化 if-else 语句。

运算符 描述 示例
? : 条件运算符 a > b ? a : b

示例:

int a = 10, b = 20;
int max = (a > b) ? a : b;
printf("%d\n", max); // 输出 20
1
2
3

# 3.5.3 综合案例与思考

综合案例:三目运算符的实用场景

#include <stdio.h>

int main() {
    int a = 15, b = 28, c = 9;
    
    // 求三个数中的最大值
    int max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
    printf("最大值: %d\n", max);
    
    // 求绝对值
    int num = -42;
    int abs_val = (num >= 0) ? num : -num;
    printf("|%d| = %d\n", num, abs_val);
    
    // 判断奇偶
    for (int i = 1; i <= 5; i++) {
        printf("%d是%s数\n", i, (i % 2 == 0) ? "偶" : "奇");
    }
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

思考题:

  1. 三目运算符 a ? b : c 可以完全替代 if-else 语句吗?什么情况下不适合使用?
  2. 嵌套三目运算符 a > b ? a : b > c ? b : c 的执行顺序是什么?如何加括号使其更清晰?

# 3.6 位运算符

用于对二进制位进行操作。

运算符 描述 示例
& 按位与 a & b
| 按位或 a | b
^ 按位异或 a ^ b
~ 按位取反 ~a
<< 左移 a << 1
>> 右移 a >> 1

示例:

int a = 5, b = 3; // 5: 0101, 3: 0011
printf("%d\n", a & b);  // 输出 1 (0001)
printf("%d\n", a | b);  // 输出 7 (0111)
printf("%d\n", a << 1); // 输出 10 (1010)
1
2
3
4

# 3.6.1 取反运算符~

取反运算符~是一个一元运算符,用来将每一个二进制位变成相反值,即0变成1,1变成0。

// 返回 01101100
~ 10010011
1
2

上面示例中,~对每个二进制位取反,就得到了一个新的值。

注意,~运算符不会改变变量的值,只是返回一个新的值。

# 3.6.2 与运算符&

与运算符&将两个值的每一个二进制位进行比较,返回一个新的值。当两个二进制位都为1,就返回1,否则返回0。

// 返回 00010001
10010011 & 00111101
1
2

上面示例中,两个八位二进制数进行逐位比较,返回一个新的值。

与运算符&可以与赋值运算符=结合,简写成&=。

int val = 3;
val = val & 0377;

// 简写成
val &= 0377;
1
2
3
4
5

# 3.6.3 或运算符|

或运算符|将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位只要有一个为1(包含两个都为1的情况),就返回1,否则返回0。

// 返回 10111111
10010011 | 00111101
1
2

或运算符|可以与赋值运算符=结合,简写成|=。

int val = 3;
val = val | 0377;

// 简写为
val |= 0377;
1
2
3
4
5

# 3.6.4 异或运算符^

异或运算符^将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位有且仅有一个为1,就返回1,否则返回0。

// 返回 10101110
10010011 ^ 00111101
1
2

异或运算符^可以与赋值运算符=结合,简写成^=。

int val = 3;
val = val ^ 0377;

// 简写为
val ^= 0377;
1
2
3
4
5

# 3.6.5 左移运算符<<

左移运算符<<将左侧运算数的每一位,向左移动指定的位数,尾部空出来的位置使用0填充。

// 1000101000
10001010 << 2
1
2

上面示例中,10001010的每一个二进制位,都向左侧移动了两位。

左移运算符相当于将运算数乘以2的指定次方,比如左移2位相当于乘以4(2的2次方)。

左移运算符<<可以与赋值运算符=结合,简写成<<=。

int val = 1;
val = val << 2;

// 简写为
val <<= 2;
1
2
3
4
5

# 3.6.6 右移运算符>>

右移运算符>>将左侧运算数的每一位,向右移动指定的位数,尾部无法容纳的值将丢弃,头部空出来的位置使用0填充。

// 返回 00100010
10001010 >> 2
1
2

上面示例中,10001010的每一个二进制位,都向右移动两位。最低的两位10被丢弃,头部多出来的两位补0,所以最后得到00100010。

注意,右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。

右移运算符相当于将运算数除以2的指定次方,比如右移2位就相当于除以4(2的2次方)。

右移运算符>>可以与赋值运算符=结合,简写成>>=。

int val = 1;
val = val >> 2;

// 简写为
val >>= 2;
1
2
3
4
5

# 3.6.7 综合案例与思考

综合案例:位运算的实际应用

#include <stdio.h>

// 用位运算交换两个数(不需要临时变量)
void swapXOR(int *a, int *b) {
    *a ^= *b;  // a = a ^ b
    *b ^= *a;  // b = b ^ (a ^ b) = a
    *a ^= *b;  // a = (a ^ b) ^ a = b
}

// 判断一个数是否是2的幂
int isPowerOfTwo(int n) {
    return n > 0 && (n & (n - 1)) == 0;
}

// 统计一个整数的二进制中有多少个1
int countBits(int n) {
    int count = 0;
    while (n) {
        count += n & 1;
        n >>= 1;
    }
    return count;
}

int main() {
    // 用位运算交换
    int a = 10, b = 20;
    printf("交换前: a=%d, b=%d\n", a, b);
    swapXOR(&a, &b);
    printf("交换后: a=%d, b=%d\n", a, b);
    
    // 判断2的幂
    for (int i = 1; i <= 16; i++) {
        printf("%d %s 2的幂\n", i, isPowerOfTwo(i) ? "是" : "不是");
    }
    
    // 统计二进制1的个数
    int num = 255;
    printf("%d 的二进制有 %d 个1\n", num, countBits(num));
    
    // 用左移实现乘法(效率高于普通乘法)
    int x = 5;
    printf("%d << 3 = %d (等于 %d * 8)\n", x, x << 3, x * 8);
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

原理说明:位运算直接操作数据的二进制位,是最接近硬件的操作,效率极高。n & (n-1) 能消除 n 的最低位的 1,因此 2 的幂只有一个 1,消除后变为 0。异或运算满足交换律和结合律:a ^ b ^ b = a,因此可用于不需要临时变量的交换。

思考题:

  1. 为什么 n & (n-1) == 0 可以判断 n 是否是 2 的幂?请用二进制说明。
  2. 用位运算实现 x * 7 且不使用乘法运算符,该怎么写?
  3. 右移运算对有符号负数的处理方式在不同系统上可能不同,这叫什么?如何避免?

# 3.7 运算符优先级

运算符的优先级决定了表达式中运算的顺序。以下是一些常见运算符的优先级(从高到低):

  1. ()(括号)
  2. ++、--(自增、自减)
  3. *、/、%(乘法、除法、取模)
  4. +、-(加法、减法)
  5. <<、>>(位左移、位右移)
  6. <、<=、>、>=(关系运算符)
  7. ==、!=(相等性运算符)
  8. &、|、^(位运算符)
  9. &&、||(逻辑运算符)
  10. ? :(条件运算符)
  11. =、+=、-= 等(赋值运算符)

示例:

int a = 10, b = 20, c = 30;
int result = a + b * c; // 先计算 b * c,再计算 a + (b * c)
printf("%d\n", result); // 输出 610
1
2
3

# 3.8 sizeof运算符

# 3.8.1 sizeof的作用

在 C 语言中,sizeof 是一个非常重要的运算符,用于获取数据类型或变量在内存中所占的字节数。它的参数可以是数据类型的关键字,也可以是变量名或某个具体的值。

运算符 描述 示例
sizeof 获取变量或类型的大小 sizeof(int)
  • 获取数据类型或变量在内存中的大小(以字节为单位)。
  • 可以用于静态类型(如 int、float)和动态分配的内存(如数组、结构体)。
  • 在编译时计算,不会影响程序的运行时性能。

目标:会查看变量、类型占用内存大小

# 3.8.2 获取数据类型大小

sizeof(type)
1

type 是数据类型,如 int、float、char 等。

#include <stdio.h>

int main() {
    printf("Size of int: %zu bytes\n", sizeof(int));
    printf("Size of float: %zu bytes\n", sizeof(float));
    printf("Size of char: %zu bytes\n", sizeof(char));
    printf("Size of double: %zu bytes\n", sizeof(double));
    return 0;
}
1
2
3
4
5
6
7
8
9

输出:

Size of int: 4 bytes
Size of float: 4 bytes
Size of char: 1 bytes
Size of double: 8 bytes
1
2
3
4

# 3.8.3 获取变量大小

sizeof(variable)
1

variable 是变量名,可以是基本类型变量、数组、结构体等。

#include <stdio.h>

int main() {
    int a = 10;
    double b = 3.14;
    char c = 'A';

    printf("Size of a: %zu bytes\n", sizeof(a));
    printf("Size of b: %zu bytes\n", sizeof(b));
    printf("Size of c: %zu bytes\n", sizeof(c));
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12

输出:

Size of a: 4 bytes
Size of b: 8 bytes
Size of c: 1 bytes
1
2
3

获取数组的大小

#include <stdio.h>

int main() {
    int arr[10];
    printf("Size of arr: %zu bytes\n", sizeof(arr)); // 数组总大小
    printf("Size of arr[0]: %zu bytes\n", sizeof(arr[0])); // 单个元素大小
    printf("Number of elements in arr: %zu\n", sizeof(arr) / sizeof(arr[0])); // 数组元素个数
    return 0;
}
1
2
3
4
5
6
7
8
9

输出:

Size of arr: 40 bytes
Size of arr[0]: 4 bytes
Number of elements in arr: 10
1
2
3

获取结构体的大小

#include <stdio.h>

struct Student {
    int id;
    char name[20];
    float score;
};

int main() {
    struct Student s;
    printf("Size of struct Student: %zu bytes\n", sizeof(s));
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出:

Size of struct Student: 28 bytes
1

获取指针的大小

#include <stdio.h>

int main() {
    int *ptr;
    printf("Size of ptr: %zu bytes\n", sizeof(ptr)); // 指针的大小
    return 0;
}
1
2
3
4
5
6
7

输出:

Size of ptr: 8 bytes
1

# 3.8.4 sizeof注意事项

  1. sizeof 是编译时运算符:

    • sizeof 在编译时计算,不会在运行时执行。
    • 例如,sizeof(int) 在编译时就已经确定。
  2. sizeof 与数组:

    • sizeof 可以用于获取数组的总大小,但不能用于获取动态分配数组的大小。
    • 例如:
    int *arr = malloc(10 * sizeof(int));
    printf("%zu\n", sizeof(arr)); // 输出指针的大小,而不是数组的大小
    
    1
    2
  3. sizeof 与结构体:

    • 结构体的大小可能包含填充字节(Padding),因此 sizeof 返回的值可能大于成员大小的总和。
  4. sizeof 与字符串:

    • sizeof 可以用于获取字符数组的大小,但不能用于获取字符串的长度。
    • 例如:
    char str[] = "Hello";
    printf("%zu\n", sizeof(str)); // 输出 6(包括 '\0')
    printf("%zu\n", strlen(str)); // 输出 5(不包括 '\0')
    
    1
    2
    3

# 3.8.5 sizeof返回值

sizeof 的返回值**

  • sizeof 返回一个 size_t 类型的值,表示数据类型或变量占用的字节数。
  • size_t 是一个无符号整数类型,通常定义在 <stddef.h> 头文件中。

# 3.8.6 综合案例与思考

综合案例:用sizeof实现安全的数组操作

#include <stdio.h>
#include <string.h>

// 安全地遍历数组(利用sizeof计算长度)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

int main() {
    int numbers[] = {10, 20, 30, 40, 50, 60};
    int len = ARRAY_SIZE(numbers);
    
    printf("数组元素个数: %d\n", len);
    printf("数组总大小: %zu 字节\n", sizeof(numbers));
    printf("单个元素大小: %zu 字节\n", sizeof(numbers[0]));
    
    // 安全遍历
    for (int i = 0; i < len; i++) {
        printf("numbers[%d] = %d\n", i, numbers[i]);
    }
    
    // sizeof与字符串
    char str[] = "Hello";
    printf("\nsizeof(str) = %zu (包含'\\0')\n", sizeof(str));   // 6
    printf("strlen(str) = %zu (不包含'\\0')\n", strlen(str));   // 5
    
    // sizeof与指针
    int *p = numbers;
    printf("\nsizeof(p) = %zu (指针大小,不是数组大小!)\n", sizeof(p));
    printf("sizeof(numbers) = %zu (数组大小)\n", sizeof(numbers));
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

思考题:

  1. sizeof 是在编译时还是运行时计算的?这意味着什么?
  2. 数组作为函数参数传入后,在函数内部用 sizeof 能获取数组的真实大小吗?为什么?
  3. sizeof("Hello") 的结果是 5 还是 6?为什么?

# 3.9 其他运算符

运算符 描述 示例
& 取地址 &a
* 指针解引用 *ptr
-> 结构体指针成员访问 ptr->member

示例:

int a = 10;
int *ptr = &a;
printf("%d\n", *ptr); // 输出 10
1
2
3
上次更新: 2026/06/10, 11:13:41
数据类型
循环和选择

← 数据类型 循环和选择→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式