飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 编程基础概念

    • 冯·诺依曼体系结构
    • 数据在计算机中的表示
    • 编程语言的层次
    • C语言的起源与发展
    • C99标准的主要改进
    • 开发环境搭建
    • 第一个C程序
    • 编译与运行流程
    • 可移植性风险的三级体系
  • 第2章 数据类型与运算

    • 字符集与标识符
    • 关键字
    • 注释
    • char 类型
    • short 与 int
    • long 与 long long
    • 有符号与无符号
    • 取值范围与 limits.h
    • float 与 double
    • long double
    • _Bool 类型
    • 变量声明与定义
    • 常量
    • 转义序列
    • 算术运算符
    • 赋值运算符
    • 自增自减运算符
    • 关系与判等运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 逗号运算符
    • 运算符优先级
    • 隐式类型转换
    • 显式类型转换
  • 第3章 控制流

    • 表达式语句与空语句
    • 复合语句
    • if 语句
    • switch 语句
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
    • goto 语句
    • return 语句
  • 第4章 函数与模块化编程

    • 函数定义
    • 函数声明与原型
    • main 函数
    • 函数调用机制
    • 传值调用
    • 数组参数
    • 作用域
    • 存储期
    • 链接属性
    • static 与 extern
    • 递归
    • 头文件与源文件
    • 头文件保护
    • include 规则
  • 第5章 数组与字符串

    • 一维数组声明与初始化
    • 数组的存储模型
    • 数组访问与越界
    • 数组操作
    • 二维数组
    • 变长数组 VLA
    • 字符串基础
    • 字符串输入输出
    • 字符串处理函数
    • 字符串与数字转换
  • 第6章 指针

    • 指针的概念
    • 指针的声明与使用
    • 指针运算
    • const 与指针
    • 数组名与指针
    • 指针遍历数组
    • 指针与多维数组
    • 指针作为函数参数
    • 函数返回指针
    • 函数指针
    • 二级指针
    • 复杂声明解析
  • 第7章 结构体、联合体与枚举

    • 结构体定义与声明
    • 结构体初始化
    • 结构体成员访问
    • 结构体嵌套
    • 结构体指针
    • 结构体与函数
    • 联合体
    • 联合体与类型双关
    • 枚举类型
    • 位域
    • 内存对齐与填充
  • 第8章 动态内存管理

    • malloc 与 free
    • calloc 与 realloc
    • 内存泄漏
    • 悬垂指针
    • 内存分配策略
    • 自定义内存池
    • Valgrind 与内存检测
    • 内存碎片
    • 内存对齐分配
    • 常见内存错误
  • 第9章 文件输入输出

    • 文件打开与关闭
    • 文本读写
    • 格式化输入输出
    • 二进制读写
    • 文件定位
    • 错误处理
    • 标准流
    • 临时文件
    • 文件操作示例
  • 第10章 预处理器

    • 预处理器基础
    • 宏定义
    • 带参数的宏
    • 条件编译
    • 头文件包含
    • 预定义宏
    • 宏的高级技巧
    • 预处理器陷阱
    • 编译器特定扩展
  • 第11章 标准库概览

    • 标准库概述
    • assert.h
    • ctype.h
    • errno.h
    • float.h
    • limits.h
    • locale.h
    • math.h
    • setjmp.h
    • signal.h
    • stdarg.h
    • stddef.h
    • stdlib.h
  • 第12章 进阶主题

    • 内联函数
    • 变长数组 VLA
    • 复数类型
    • 布尔类型
    • stdint 与 inttypes
    • 灵活数组成员
    • 匿名结构体与联合体
    • 静态断言
    • 线程支持
    • 原子操作

位运算符

位运算符直接操作整数的二进制位,是 C 语言贴近硬件的体现。它们用于标志位管理、掩码操作、权限控制、数据压缩等底层场景。理解位运算,能让你高效地操控数据的每一位,也是阅读系统代码和硬件驱动的基础。

基本位运算符

运算符名称示例说明
&按位与a & b两位都为 1,结果才为 1
|按位或a | b两位至少一个为 1,结果就为 1
^按位异或a ^ b两位不同,结果为 1;相同为 0
~按位取反~a0 变 1,1 变 0
<<左移a << n所有位左移 n 位,右侧补 0
>>右移a >> n所有位右移 n 位,左侧补什么取决于类型
unsigned int a = 0b1010;    /* 10 */
unsigned int b = 0b1100;    /* 12 */

printf("%u\n", a & b);      /* 1000 = 8 */
printf("%u\n", a | b);      /* 1110 = 14 */
printf("%u\n", a ^ b);      /* 0110 = 6 */
printf("%u\n", ~a);         /* 按位取反,结果取决于 int 的位数 */

按位与 &

按位与常用于掩码操作——提取或保留特定的位:

unsigned int flags = 0b1101;    /* 13 */
unsigned int mask = 0b0011;     /* 3 */

unsigned int result = flags & mask;     /* 0001 = 1 */

/* 检查第 2 位是否为 1 */
if (flags & 0b0100)
    printf("Bit 2 is set\n");

/* 检查最低位 */
if (flags & 1)
    printf("LSB is set\n");

按位或 |

按位或用于设置标志位:

unsigned int flags = 0;

flags = flags | 0b0001;     /* 设置第 0 位 */
flags |= 0b0010;            /* 设置第 1 位(复合赋值) */
flags |= 0b0100;            /* 设置第 2 位 */

/* 现在 flags = 0b0111 = 7 */

按位异或 ^

异或的特性:相同为 0,不同为 1。一个有趣的性质是 a ^ a = 0,a ^ 0 = a:

unsigned int a = 0b1010;

printf("%u\n", a ^ a);      /* 0 */
printf("%u\n", a ^ 0);      /* 1010 = 10 */

/* 切换(翻转)特定位 */
unsigned int flags = 0b1100;
flags ^= 0b0100;            /* 翻转第 2 位:1100 ^ 0100 = 1000 */

/* 交换两个变量(不使用临时变量)——不推荐实际使用 */
int x = 5, y = 3;
x = x ^ y;
y = x ^ y;                  /* y = (x^y)^y = x */
x = x ^ y;                  /* x = (x^y)^x = y */

异或交换在 x 和 y 指向同一内存时会失败(结果为 0),且现代编译器对普通交换的优化已经足够好,这种技巧没有实际优势。

按位取反 ~

~ 将所有位取反。注意:如果操作数是有符号整数,结果可能为负:

unsigned int u = 0;
printf("%u\n", ~u);         /* 4294967295(32 位全 1) */

int s = 0;
printf("%d\n", ~s);          /* -1(补码表示) */

移位运算

左移 <<:所有位左移,右侧补 0。左移 n 位等价于乘以 2ⁿ(对无溢出情况):

unsigned int a = 1;
printf("%u\n", a << 0);     /* 1 */
printf("%u\n", a << 1);     /* 2 */
printf("%u\n", a << 3);     /* 8 */
printf("%u\n", a << 8);     /* 256 */

/* 快速乘 2 */
unsigned int x = 10;
unsigned int doubled = x << 1;      /* 20 */

左移溢出时,移出的位丢失:

unsigned int a = 0x80000000;        /* 最高位为 1 */
printf("%u\n", a << 1);             /* 0:最高位移出,变为 0 */

右移 >>:所有位右移。无符号数左侧补 0(逻辑右移);有符号数左侧补符号位还是补 0 是实现定义的(算术右移 vs 逻辑右移):

unsigned int u = 0xF0000000;
printf("%X\n", u >> 4);      /* 0F000000:左侧补 0 */

int s = -16;                /* 0xFFFFFFF0 */
printf("%d\n", s >> 2);     /* -4:算术右移,补符号位 1 */
                            /* 或 1073741820:逻辑右移,补 0(实现定义) */

C99 §6.5.7/5 规定:有符号负数的右移是实现定义行为。为保证可移植性,需要算术右移时应使用无符号数的技巧,或确认目标平台的行为。

移位位数限制:

unsigned int a = 1;
unsigned int b = a << 32;     /* 未定义行为!移位位数 >= 类型位数 */
unsigned int c = a << 31;     /* 合法:最高位移到符号位 */

如果移位位数大于等于操作数的位数,或者为负数,结果是未定义行为。

实际应用

标志位管理:

#define FLAG_READ   0x01    /* 0001 */
#define FLAG_WRITE  0x02    /* 0010 */
#define FLAG_EXEC   0x04    /* 0100 */

unsigned int permissions = FLAG_READ | FLAG_WRITE;    /* 设置读和写 */

if (permissions & FLAG_READ)
    printf("Can read\n");

permissions &= ~FLAG_WRITE;     /* 清除写权限 */
permissions ^= FLAG_EXEC;       /* 切换执行权限 */

提取颜色分量:

unsigned int color = 0xFFAA55;      /* RGB */

unsigned char r = (color >> 16) & 0xFF;     /* 0xFF */
unsigned char g = (color >> 8) & 0xFF;       /* 0xAA */
unsigned char b = color & 0xFF;             /* 0x55 */

判断奇偶:

if (n & 1)
    printf("Odd\n");       /* 最低位为 1 */
else
    printf("Even\n");      /* 最低位为 0 */

常见错误

混淆逻辑与位运算符:

if (flags & FLAG_READ)      /* 正确:位与检查标志 */
if (flags && FLAG_READ)     /* 错误:逻辑与,flags 非零就真 */

对有符号数进行位运算:

int flags = 0x80;
if (flags >> 7)             /* 实现定义:可能为 -1 或 1 */
    /* ... */

/* 安全做法 */
unsigned int uflags = 0x80;
if (uflags >> 7)            /* 总是 1 */
    /* ... */

移位溢出:

int a = 1;
int b = a << 31;            /* 有符号数:最高位为 1,变为负数 */
                              /* 如果 int 是 32 位,这是符号位 */

位运算应优先使用无符号类型,避免符号位带来的歧义。

上一页
逻辑运算符
下一页
条件运算符