C语言:共用体、枚举与位操作
摘要:本文介绍了C语言中的四种进阶特性:1.共用体(Union):所有成员共享内存空间,用于判断系统大小端和节省内存;2.枚举(Enum):定义命名常量集合,提升代码可读性,适用于状态机设计;3.位操作:包含核心运算符和实用技巧,用于寄存器操作和高效数值交换;4.堆内存管理:详细讲解malloc/free的使用方法、常见错误及realloc的用法。这些特性在嵌入式开发、系统编程中具有重要价值,能有
一、共用体(Union)
共用体是一种特殊的自定义数据类型,其核心特性是所有成员共享同一块内存空间—— 这意味着,同一时间只有一个成员能被有效访问,修改一个成员会覆盖其他成员的值。
1. 定义与内存布局
// 定义一个包含int、char、double的共用体
union Data {
int i;
char c;
double d;
};
- 共用体的大小等于最大成员的字节数(上述例子中是
double的 8 字节)。 - 所有成员的内存起始地址相同,比如
&data.i == &data.c == &data.d。
2. 经典应用场景
(1)判断系统大小端
大小端是数据存储的字节顺序:小端(低字节存低地址)是主流架构(x86、ARM),大端(低字节存高地址)常见于网络传输、部分嵌入式设备。
利用共用体可以快速判断:
#include <stdio.h>
union EndianTest {
int num;
char byte;
};
int main() {
union EndianTest test;
test.num = 1; // 二进制:00000000 00000000 00000000 00000001
// 小端:byte取低地址,值为1;大端:byte取高地址,值为0
if (test.byte == 1) {
printf("小端存储\n");
} else {
printf("大端存储\n");
}
return 0;
}
(2)节省内存的 “变体数据”
在嵌入式系统中,内存资源紧张时,可用共用体存储 “互斥使用” 的数据。比如串口通信中,数据可能是字符或整数:
union CommData {
char cmd; // 命令(字符型)
int value; // 参数(整型)
};
// 同一帧数据要么传命令,要么传参数,无需为两者分配独立空间
void handle_comm(union CommData data, int type) {
if (type == 0) {
printf("收到命令:%c\n", data.cmd);
} else {
printf("收到参数:%d\n", data.value);
}
}
二、枚举(Enum)
枚举用于定义有限的、命名的常量集合,替代魔法数字(直接写的数字),让代码可读性和可维护性翻倍。
1. 基础用法与自定义值
// 默认从0开始递增,也可手动指定值
enum Week {
Mon = 1, Tue, Wed, Thu, Fri, Sat = 6, Sun // Tue=2, Wed=3... Sun=7
};
// 枚举变量只能取枚举值范围内的值
enum Week today = Wed;
2. 实战案例:状态机设计
在嵌入式或游戏开发中,状态机是常用模式,枚举能清晰定义状态:
#include <stdio.h>
enum State {
IDLE, // 空闲
RUNNING, // 运行
PAUSED, // 暂停
STOPPED // 停止
};
void handle_state(enum State state) {
switch (state) {
case IDLE: printf("系统空闲,等待指令\n"); break;
case RUNNING: printf("系统运行中...\n"); break;
case PAUSED: printf("系统已暂停\n"); break;
case STOPPED: printf("系统已停止\n"); break;
default: printf("无效状态\n");
}
}
int main() {
enum State sys_state = RUNNING;
handle_state(sys_state); // 输出:系统运行中...
return 0;
}
3. 枚举的 “坑”:本质是整型
枚举值本质是int类型,可被赋值为枚举外的整数,需注意校验:
enum Week day = 10; // 语法不报错,但逻辑错误
三、位操作
位操作直接对二进制位进行运算,是驱动开发、嵌入式编程的核心技能 —— 比如操作寄存器、处理标志位、数据压缩等。
1. 核心运算符与常用技巧
| 运算符 | 作用 | 示例(a=0b1010, b=0b0110) | ||
|---|---|---|---|---|
& |
按位与(清零、检测) | a&b=0b0010 |
||
| | | 按位或(置 1) | a|b=0b1110 | ||
^ |
按位异或(翻转、交换) | a^b=0b1100 |
||
~ |
按位取反(取反码) | ~a=0b0101(假设 4 位) |
||
<< |
左移(乘 2ⁿ) | a<<1=0b0100 |
||
>> |
右移(除 2ⁿ) | a>>1=0b0101 |
(1)寄存器位操作
嵌入式中常需操作寄存器的某几位(比如 GPIO 引脚配置):
#define REG 0x40000000 // 寄存器地址
// 1. 将REG的bit3置1(其他位不变)
*(unsigned int*)REG |= (1 << 3);
// 2. 将REG的bit5清零
*(unsigned int*)REG &= ~(1 << 5);
// 3. 翻转REG的bit7
*(unsigned int*)REG ^= (1 << 7);
// 4. 检测REG的bit2是否为1
if (*(unsigned int*)REG & (1 << 2)) {
printf("bit2为1\n");
}
(2)高效交换两个数
用异或实现无临时变量交换:
void swap(int *a, int *b) {
if (a != b) { // 避免自身交换导致值为0
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
}
int main() {
int x = 3, y = 5;
swap(&x, &y);
printf("x=%d, y=%d\n", x, y); // 输出:x=5, y=3
return 0;
}
四、堆内存管理:malloc/free 的深度实战
堆内存是程序运行时动态分配的内存(栈内存是静态分配),由malloc申请、free释放,是处理大数据(如数组、结构体)的关键。
1. malloc:动态申请内存
#include <stdlib.h> // 需包含头文件
// 语法:void* malloc(size_t size);
// size:申请的字节数,返回void*指针,需强制类型转换
// 示例1:申请10个int的数组(40字节)
int *arr = (int*)malloc(10 * sizeof(int));
if (arr == NULL) { // 必须检查!申请失败返回NULL(如内存不足)
perror("malloc failed"); // 打印错误原因
return -1;
}
// 示例2:申请结构体数组
typedef struct {
int id;
char name[20];
} Student;
Student *stu_arr = (Student*)malloc(5 * sizeof(Student));
if (stu_arr == NULL) {
perror("malloc failed");
return -1;
}
2. free:释放堆内存
堆内存不会自动释放,用完必须free,否则导致内存泄漏(可用内存越来越少):
free(arr); // 释放数组内存
arr = NULL; // 避免悬空指针(指向已释放内存的指针)
free(stu_arr);
stu_arr = NULL;
3. 常见坑点与避坑指南
(1)内存泄漏
void bad_func() {
int *p = (int*)malloc(4);
// 未free(p),函数结束后p销毁,内存无法访问→泄漏
}
(2)重复释放
int *p = (int*)malloc(4);
free(p);
free(p); // 重复free→程序崩溃
(3)悬空指针
int *p = (int*)malloc(4);
free(p);
*p = 10; // 操作已释放的内存→未定义行为(崩溃/数据错乱)
(4)申请大小计算错误
// 错误:sizeof(int*)是指针大小(8字节),不是int大小(4字节)
int *arr = (int*)malloc(10 * sizeof(int*));
// 正确:用sizeof(int)或sizeof(*arr)
int *arr = (int*)malloc(10 * sizeof(*arr));
4. 进阶:realloc 动态调整内存
realloc可修改已分配内存的大小(扩大 / 缩小):
// 将arr从10个int扩大到20个int
int *new_arr = (int*)realloc(arr, 20 * sizeof(int));
if (new_arr == NULL) {
perror("realloc failed");
free(arr); // 原内存未释放,需手动free
return -1;
}
arr = new_arr; // 指向新内存
总结:进阶特性的核心价值
- 共用体:用内存共享实现 “互斥数据” 的高效存储,适合资源受限场景。
- 枚举:用语义化常量替代魔法数字,大幅提升代码可读性。
- 位操作:直击硬件底层,实现高效的位级控制,是嵌入式 / 驱动开发的必备技能。
- 堆内存:突破栈内存大小限制,灵活管理动态数据,但需严格遵循 “申请 - 释放” 规则,避免内存问题。
更多推荐
所有评论(0)