在C语言程序设计中,结构体(struct)和共用体(union)是两种非常重要的复合数据类型。它们都允许我们将不同类型的数据组合在一起,但在内存使用方式和数据存储特性上有着本质区别。
结构体就像是一个"数据收纳盒",可以同时存放各种类型的数据成员。比如我们要描述一个学生信息:
c复制struct student {
char name[20]; // 姓名
int id; // 学号
float score; // 成绩
};
这里name、id、score三个成员会占用不同的内存空间,可以同时存储和访问。结构体特别适合用来描述具有多个属性的实体对象。
共用体则更像是一个"数据转换器",所有成员共享同一块内存空间。例如:
c复制union data {
int i;
float f;
char str[20];
};
同一时刻只能存储其中一个成员的值,修改一个成员会影响其他成员。共用体常用于需要节省内存或实现类型转换的场景。
关键区别:结构体各成员有独立存储空间,可以同时使用;共用体成员共享空间,同一时间只能使用一个成员。
结构体的声明有多种灵活方式,每种都有其适用场景:
c复制struct point {
int x;
int y;
};
struct point p1 = {10, 20};
c复制struct {
char name[20];
int age;
} person1, person2;
c复制typedef struct {
char title[50];
char author[50];
int pages;
} Book;
Book b1 = {"C Programming", "K&R", 274};
初始化时可以采用以下方式:
struct student s1 = {"Alice", 1001, 90.5};struct student s2 = {.name="Bob", .id=1002}struct point points[] = {{1,1}, {2,2}, {3,3}}访问结构体成员有两种主要方式:
c复制strcpy(s1.name, "Alice");
s1.score = 95.5;
c复制struct student *ps = &s1;
ps->id = 1003;
printf("%s", ps->name);
结构体支持整体赋值,但需要注意:
经验:对包含指针的结构体进行拷贝时,建议实现深拷贝函数,避免内存问题。
结构体可以作为函数参数和返回值:
c复制// 传值调用
void printStudent(struct student s) {
printf("Name: %s\nID: %d\nScore: %.1f\n",
s.name, s.id, s.score);
}
// 传址调用(推荐)
void modifyStudent(struct student *ps) {
ps->score += 5.0;
}
// 返回结构体
struct student createStudent() {
struct student s = {"Tom", 1004, 88.0};
return s;
}
对于大型结构体,传指针效率更高,可以避免拷贝开销。const指针可以保护数据不被修改:
c复制void displayStudent(const struct student *ps) {
// ps->id = 0; // 编译错误
printf("%d\n", ps->id);
}
共用体的所有成员共享同一块内存空间,其大小为最大成员的大小。例如:
c复制union example {
int i;
char c;
double d;
}; // sizeof(union example) == 8 (假设double为8字节)
这种特性使得共用体非常适合以下场景:
c复制union converter {
float f;
unsigned int u;
} c;
c.f = 3.14;
printf("%08x\n", c.u); // 输出浮点数的二进制表示
c复制union data {
int i;
float f;
char str[20];
};
union data arr[100]; // 只需要20*100字节,而非(4+4+20)*100
c复制struct variant {
int type;
union {
int i;
float f;
char *s;
} value;
};
c复制union ip_header {
struct {
unsigned char ver_ihl;
unsigned char tos;
unsigned short total_len;
// 其他字段...
} fields;
unsigned char raw[20];
};
c复制union status_reg {
struct {
unsigned ready:1;
unsigned error:1;
unsigned busy:1;
unsigned :5; // 保留位
} bits;
unsigned char byte;
};
c复制typedef enum { INT, FLOAT, STRING } Type;
typedef struct {
Type type;
union {
int i;
float f;
char *s;
} value;
} Variant;
void printVariant(Variant v) {
switch(v.type) {
case INT: printf("%d", v.value.i); break;
case FLOAT: printf("%f", v.value.f); break;
case STRING: printf("%s", v.value.s); break;
}
}
结构体支持位域定义,可以精确控制每个成员占用的位数:
c复制struct packed_data {
unsigned int flag1 : 1; // 1位
unsigned int flag2 : 3; // 3位
unsigned int count : 4; // 4位
unsigned int : 4; // 4位填充
unsigned int value : 20; // 20位
};
位域常用于:
注意事项:
C99引入的柔性数组成员特性:
c复制struct flex_array {
int length;
double data[]; // 柔性数组成员
};
struct flex_array *create_flex(int n) {
struct flex_array *fa = malloc(sizeof(struct flex_array) + n*sizeof(double));
fa->length = n;
return fa;
}
这种技术常用于:
结构体对齐影响内存布局和访问效率:
c复制struct aligned_example {
char c; // 1字节
// 3字节填充
int i; // 4字节
double d; // 8字节
}; // 总大小:16字节
优化技巧:
内存优化示例:
c复制// 优化前:12字节
struct unoptimized {
char c;
int i;
char c2;
};
// 优化后:8字节
struct optimized {
int i;
char c;
char c2;
};
c复制student s; // 错误:缺少struct关键字
struct student s; // 正确
c复制struct student *ps;
ps.name = "Alice"; // 错误
ps->name = "Alice"; // 正确
c复制union data u;
u.i = 10;
printf("%f", u.f); // 错误:类型混淆
c复制struct file_header {
char magic[4];
int version; // 可能在4字节边界不对齐
};
c复制printf("struct size: %zu\n", sizeof(struct student));
printf("union size: %zu\n", sizeof(union data));
c复制#include <stddef.h>
printf("name offset: %zu\n", offsetof(struct student, name));
c复制void dump_memory(void *p, size_t size) {
unsigned char *bytes = p;
for(size_t i=0; i<size; i++) {
printf("%02x ", bytes[i]);
}
printf("\n");
}
bash复制gcc -Wall -Wextra -pedantic -Wpadded ...
c复制struct __attribute__((aligned(64))) cache_line {
// 成员定义
};
频繁访问的成员放在结构体开头
避免大型结构体传值
合理使用位域节省内存
考虑使用union实现类型双关而非指针转换
在实际项目开发中,结构体和共用体的合理使用可以显著提高代码的可读性和内存效率。特别是在嵌入式系统、网络编程、文件格式处理等领域,这两种数据结构发挥着不可替代的作用。掌握它们的特性和使用技巧,是成为C语言高手的必经之路。