这篇文章讲了构造数据类型中的联合体,枚举,typedef,位运算和内存管理
共用体(联合体):一块内存,多种身份
什么是共用体?
想象你有一个“万能收纳盒”:它的大小刚好能装下最大的物品,但同一时间只能装一种东西。这就是联合体的本质——所有成员共享同一块内存空间,修改其中一个成员会影响其他成员的值。
和结构体的区别?结构体是“分房间住”(每个成员独立内存),联合体是“挤一间房”(共享内存)。
共用体定义:
union 共用体名
{
数据类型1 成员变量1;
数据类型2 成员变量2;
数据类型3 成员变量3;
};
变量定义:
union 共用体 变量名;
使用示例:
#include <stdio.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
data.i = 97; // 存入整数97(ASCII码'a')
printf("data.i = %d\\n", data.i); // 输出97
printf("data.f = %f\\n", data.f); // 输出乱码(内存被覆盖)
printf("data.str = %s\\n", data.str); // 输出"a"(因为97对应字符'a')
data.f = 3.14f; // 存入浮点数,覆盖之前的内存
printf("data.i = %d\\n", data.i); // 输出乱码(内存被覆盖)
return 0;
}
空间特性:
结构体成员变量空间独立,共用体成员变量共用一个空间
内存大小端:
内存低地址存放低数据位,称为内存小端。
内存高地址存放高数据位,称为内存大端
为什么用共用体?
节省内存:当多个变量不会同时使用时(比如一个设备可能返回整数或字符串,但不会同时返回),用联合体比结构体省空间。
类型转换:直接通过内存共享实现不同类型数据的“ reinterpret_cast”(类似强制类型转换,但更高效)。
枚举(Enum):给常量起“有意义的名字”
枚举类型定义
enum 类型名
{
枚举常量1; //里面没有类型,都是int
枚举常量2; //枚举常量的值都是int
枚举常量3;
};
示例:
enum Weekday {
MON, TUE, WED, THU, FRI, SAT, SUN // 默认值:0到6
};
enum Status {
SUCCESS = 200, FAIL = 404, ERROR = 500 // 显式赋值
};
使用示例:
#include <stdio.h>
enum Weekday { MON=1, TUE, WED, THU=10, FRI, SAT, SUN }; // 可跳跃赋值
int main() {
enum Weekday today = WED;
printf("今天是周%d\\n", today); // 输出3(MON=1,TUE=2,WED=3)
enum Weekday tomorrow = THU;
printf("明天是周%d\\n", tomorrow); // 输出10(THU显式赋值为10)
return 0;
}
注意事项
-
枚举常量默认从0开始递增,也可手动赋值(后续未赋值的常量在前一个基础上+1)。
-
枚举变量的本质是整数,可直接参与算术运算(但通常不建议)。
typedef:给类型起“外号”
什么是typedef?
typedef的作用是为已有的数据类型定义一个新名字(别名)。它不是创造新类型,而是简化复杂类型的书写。
类比:你有个朋友叫“张三”,你可以给他起外号“阿三”——别人喊“阿三”时,其实指的是张三。
常见用法
(1)简化结构体/联合体声明
// 传统写法(繁琐)
struct Student {
char name[20];
int age;
};
struct Student stu1; // 声明变量时需要重复写struct
// 用typedef简化
typedef struct {
char name[20];
int age;
} Student; // Student成为结构体类型的别名
Student stu2; // 直接用别名声明,简洁!
(2)简化指针类型
typedef int* IntPtr; // IntPtr等价于int*
IntPtr p1, p2; // 等价于int *p1, *p2(注意不是int* p1, p2!)
(3)简化函数指针(进阶)
// 普通函数指针声明:int (*func)(int, int)
typedef int (*MathFunc)(int, int); // MathFunc是函数指针的别名
MathFunc add = sum; // 用别名声明函数指针,更清晰
typedef vs #define
很多人会混淆 typedef和 #define,关键区别:
-
typedef是编译阶段处理,有类型检查;#define是预处理阶段替换,无类型检查。
-
typedef作用于类型,#define可以替换任意文本(比如常量)。
位运算:直接操作二进制位
为什么需要位运算?
计算机底层是二进制,位运算可以直接操控每一位(0或1),常用于:
-
硬件寄存器操作(嵌入式开发)
-
高效压缩数据(比如用一个字节存8个布尔值)
-
算法优化(如快速幂、交换变量)
六大位运算符
|
& |
按位与 |
两位都为1则结果为1 |
a&b=0001(1) |
|
| |
按位或 |
至少一位为1则结果为1 |
a|b=0111(7) |
|
^ |
按位异或 |
两位不同时结果为1 |
a^b=0110(6) |
|
~ |
按位取反 |
0变1,1变0 |
~a=1010(-6,补码表示) |
|
<< |
左移 |
所有位左移n位,低位补0 |
a<<1=1010(10) |
|
>> |
右移 |
所有位右移n位(高位补符号位或0) |
a>>1=0010(2) |
实用技巧
(1)判断奇偶:x & 1
奇数的二进制末位是1,x & 1结果为1;偶数末位是0,结果为0。
if (num & 1) {
printf("奇数");
} else {
printf("偶数");
}
(2)交换两数(不用临时变量):a ^= b; b ^= a; a ^= b;
原理:a^b^a = b,b^a^b = a。
int a = 3, b = 5;
a ^= b; // a=3^5=6
b ^= a; // b=5^6=3
a ^= b; // a=6^3=5 → 完成交换
(3)设置/清除指定位:用掩码(Mask)
比如,用 flag |= (1 << n)设置第n位为1;flag &= ~(1 << n)清除第n位为0。
内存管理
一张图看懂内存管理

语言的内存分为静态区、栈区、堆区。前两者由系统自动管理,而堆区需要手动申请(malloc/calloc)和释放(free),这就是动态内存管理。
5.1 核心函数
|
malloc |
申请指定字节数的内存 |
void* malloc(size_t size) |
不初始化,返回void*需强转 |
|
calloc |
申请n个size大小的内存并初始化0 |
void* calloc(size_t n, size_t size) |
初始化为0,适合数组 |
|
realloc |
调整已分配内存的大小 |
void* realloc(void* ptr, size_t new_size) |
可能移动内存位置 |
|
free |
释放动态申请的内存 |
void free(void* ptr) |
必须配对使用,避免泄漏 |
5.2 示例:动态数组
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
printf("输入数组长度:");
scanf("%d", &n);
// 申请n个int的空间(calloc会初始化为0)
int* arr = (int*)calloc(n, sizeof(int));
if (arr == NULL) { // 必须检查是否申请成功
printf("内存不足!");
exit(1);
}
// 输入数据
for (int i=0; i<n; i++) {
arr[i] = i*2;
}
// 打印
for (int i=0; i<n; i++) {
printf("%d ", arr[i]); // 输出0 2 4 6…
}
free(arr); // 释放内存,避免泄漏
arr = NULL; // 避免野指针(指向已释放的内存)
return 0;
}
5.3 常见错误
-
内存泄漏:申请了内存但忘记free(程序长期运行会耗尽内存)。
-
野指针:free后继续使用指针,或指针未初始化就使用。
-
越界访问:动态数组访问超过申请的大小(破坏其他内存数据)。
![第5章,[Win32 章节] :绘制填充区域-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260828173251-6a91c6434b552-220x150.png)
