C/C++ Linux 开发教程(GCC + CMake) / 08 — 结构体、联合体与枚举
结构体、联合体与枚举
1. 结构体定义与访问
结构体(struct)将不同类型的变量组合成一个自定义类型:
#include <stdio.h>
#include <string.h>
// 定义结构体
struct Point {
double x;
double y;
};
// 使用 typedef 简化
typedef struct {
char name[64];
int age;
double height;
} Person;
int main(void)
{
// 初始化
struct Point p1 = {3.0, 4.0};
struct Point p2 = {.y = 1.0, .x = 2.0}; // C99 指定初始化器
Person alice = {"Alice", 30, 1.65};
Person bob = {.name = "Bob", .age = 25, .height = 1.80};
// 用 . 访问成员
printf("p1 = (%.1f, %.1f)\n", p1.x, p1.y);
printf("%s, %d 岁, %.2f 米\n", alice.name, alice.age, alice.height);
// 修改成员
alice.age = 31;
strcpy(alice.name, "Alice Smith");
// 结构体赋值(整体复制)
struct Point p3 = p1;
p3.x = 100;
printf("p1 = (%.1f, %.1f), p3 = (%.1f, %.1f)\n", p1.x, p1.y, p3.x, p3.y);
return 0;
}
1.1 结构体指针与 -> 运算符
#include <stdio.h>
typedef struct {
double x, y, z;
} Vec3;
int main(void)
{
Vec3 v = {1.0, 2.0, 3.0};
Vec3 *vp = &v;
// 以下两种方式等价
printf("v.x = %.1f\n", v.x);
printf("v.x = %.1f\n", vp->x); // 等价于 (*vp).x
// 通过指针修改
vp->x = 10.0;
printf("修改后 v.x = %.1f\n", v.x);
return 0;
}
| 访问方式 | 语法 | 使用场景 |
|---|---|---|
| 点运算符 | obj.member | 直接访问结构体变量 |
| 箭头运算符 | ptr->member | 通过指针访问 |
2. 结构体大小与内存对齐
编译器会在结构体成员之间插入填充字节(padding)以满足对齐要求:
#include <stdio.h>
struct A {
char a; // 1 字节
// 3 字节填充
int b; // 4 字节
char c; // 1 字节
// 3 字节填充
};
struct B {
int b; // 4 字节
char a; // 1 字节
char c; // 1 字节
// 2 字节填充
};
int main(void)
{
printf("sizeof(struct A) = %zu\n", sizeof(struct A)); // 12
printf("sizeof(struct B) = %zu\n", sizeof(struct B)); // 8
return 0;
}
| 结构体 | 大小 | 原因 |
|---|---|---|
| A(char, int, char) | 12 字节 | int 需要 4 字节对齐 |
| B(int, char, char) | 8 字节 | 成员排列更紧凑 |
💡 提示: 将较大的成员放在前面、较小的成员放在后面,可以减少填充浪费。
2.1 pragma pack
#include <stdio.h>
#pragma pack(push, 1) // 设置对齐为 1 字节
struct Packed {
char a;
int b;
char c;
};
#pragma pack(pop) // 恢复默认对齐
int main(void)
{
printf("sizeof(Packed) = %zu\n", sizeof(struct Packed)); // 6
printf("sizeof(A) = %zu\n", sizeof(struct A)); // 12(上面定义的)
return 0;
}
⚠️ 注意: #pragma pack 可能导致性能下降(非对齐访问),且不可移植。仅在需要精确控制内存布局(如网络协议解析)时使用。
3. 结构体嵌套
#include <stdio.h>
#include <math.h>
typedef struct {
double x, y;
} Point;
typedef struct {
Point center;
double radius;
} Circle;
typedef struct {
Point top_left;
Point bottom_right;
} Rectangle;
double distance(Point a, Point b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
double circle_area(Circle c)
{
return M_PI * c.radius * c.radius;
}
int point_in_rect(Point p, Rectangle r)
{
return p.x >= r.top_left.x && p.x <= r.bottom_right.x &&
p.y >= r.top_left.y && p.y <= r.bottom_right.y;
}
int main(void)
{
Circle c = {{0.0, 0.0}, 5.0};
Rectangle r = {{-10, -10}, {10, 10}};
Point p = {3.0, 4.0};
printf("圆面积: %.2f\n", circle_area(c));
printf("点到圆心距离: %.2f\n", distance(p, c.center));
printf("点在圆内? %s\n", distance(p, c.center) <= c.radius ? "是" : "否");
printf("点在矩形内? %s\n", point_in_rect(p, r) ? "是" : "否");
return 0;
}
4. 结构体数组
#include <stdio.h>
#include <string.h>
typedef struct {
char name[32];
int score;
} Student;
int main(void)
{
Student class[] = {
{"Alice", 85},
{"Bob", 92},
{"Charlie", 78},
{"Diana", 95},
{"Eve", 88}
};
int n = sizeof(class) / sizeof(class[0]);
// 按成绩排序(冒泡)
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (class[j].score < class[j + 1].score) {
Student temp = class[j];
class[j] = class[j + 1];
class[j + 1] = temp;
}
}
}
printf("成绩排名:\n");
for (int i = 0; i < n; i++) {
printf(" %d. %-10s %d\n", i + 1, class[i].name, class[i].score);
}
// 查找最高分
Student *best = &class[0];
for (int i = 1; i < n; i++) {
if (class[i].score > best->score) best = &class[i];
}
printf("最高分: %s (%d)\n", best->name, best->score);
return 0;
}
5. 联合体 (union)
联合体的所有成员共享同一块内存,大小等于最大成员的大小。
#include <stdio.h>
typedef union {
int i;
float f;
char c;
} Value;
int main(void)
{
Value v;
v.i = 42;
printf("v.i = %d\n", v.i);
v.f = 3.14f; // 写入 f 会覆盖 i 的值
printf("v.f = %f\n", v.f);
// printf("v.i = %d\n", v.i); // 值已无意义
printf("sizeof(Value) = %zu\n", sizeof(Value)); // 4(最大成员大小)
return 0;
}
| 特性 | struct | union |
|---|---|---|
| 内存布局 | 成员依次排列 | 成员重叠(共享) |
| 大小 | 所有成员大小之和(含填充) | 最大成员的大小 |
| 同时使用 | 可以同时访问所有成员 | 同一时刻只能有效使用一个成员 |
6. 联合体应用
6.1 类型双关(Type Punning)
#include <stdio.h>
typedef union {
float f;
unsigned int u;
} FloatBits;
void print_float_bits(float f)
{
FloatBits fb;
fb.f = f;
printf("%.6f 的二进制: ", f);
for (int i = 31; i >= 0; i--) {
printf("%d", (fb.u >> i) & 1);
if (i == 31 || i == 23) printf(" "); // 分隔符号位和指数位
}
printf("\n");
}
int main(void)
{
print_float_bits(1.0f);
print_float_bits(-1.0f);
print_float_bits(3.14f);
return 0;
}
6.2 网络协议解析
#include <stdio.h>
#include <stdint.h>
// IPv4 头部(前 20 字节,简化版)
typedef union {
uint8_t bytes[20];
struct {
uint8_t version_ihl;
uint8_t tos;
uint16_t total_length;
uint16_t id;
uint16_t flags_fragment;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t src_addr;
uint32_t dst_addr;
} fields;
} IPv4Header;
int main(void)
{
// 模拟网络数据
IPv4Header header = {{
0x45, 0x00, // version=4, ihl=5
0x00, 0x3C, // total_length=60
0x00, 0x01, // id=1
0x40, 0x00, // flags=DF, fragment=0
0x40, 0x06, // ttl=64, protocol=TCP
0x00, 0x00, // checksum (简化为 0)
0xC0, 0xA8, 0x01, 0x01, // 192.168.1.1
0xC0, 0xA8, 0x01, 0x02 // 192.168.1.2
}};
printf("版本: %d\n", header.fields.version_ihl >> 4);
printf("头部长度: %d 字节\n", (header.fields.version_ihl & 0x0F) * 4);
printf("总长度: %u\n", header.fields.total_length);
printf("TTL: %u\n", header.fields.ttl);
printf("协议: %u\n", header.fields.protocol);
return 0;
}
7. 枚举 (enum)
#include <stdio.h>
// 基本枚举(底层类型为 int)
typedef enum {
COLOR_RED = 0,
COLOR_GREEN = 1,
COLOR_BLUE = 2,
COLOR_YELLOW = 3,
COLOR_COUNT // 用于计数
} Color;
// 指定底层值
typedef enum {
STATUS_OK = 200,
STATUS_NOT_FOUND = 404,
STATUS_ERROR = 500
} StatusCode;
// 使用枚举
const char *color_name(Color c)
{
switch (c) {
case COLOR_RED: return "红色";
case COLOR_GREEN: return "绿色";
case COLOR_BLUE: return "蓝色";
case COLOR_YELLOW: return "黄色";
default: return "未知";
}
}
int main(void)
{
Color favorite = COLOR_BLUE;
printf("我最喜欢的颜色: %s (%d)\n", color_name(favorite), favorite);
// 枚举可用于数组索引
int color_counts[COLOR_COUNT] = {0};
Color samples[] = {COLOR_RED, COLOR_BLUE, COLOR_RED, COLOR_GREEN, COLOR_RED};
int n = sizeof(samples) / sizeof(samples[0]);
for (int i = 0; i < n; i++) {
color_counts[samples[i]]++;
}
for (int i = 0; i < COLOR_COUNT; i++) {
printf("%s: %d\n", color_name((Color)i), color_counts[i]);
}
StatusCode status = STATUS_NOT_FOUND;
printf("HTTP 状态码: %d\n", status);
return 0;
}
💡 提示: 枚举本质上是整数常量。使用
enum而非#define可以让编译器检查类型(虽然 C 的枚举类型检查较弱)。
8. 位域 (Bit-field)
#include <stdio.h>
// 嵌入式开发中常见的寄存器定义
typedef struct {
unsigned int enabled : 1; // 1 位
unsigned int mode : 2; // 2 位(0-3)
unsigned int level : 4; // 4 位(0-15)
unsigned int reserved : 1; // 1 位保留
} Config;
// 权限标志
typedef struct {
unsigned int read : 1;
unsigned int write : 1;
unsigned int execute : 1;
unsigned int admin : 1;
} Permissions;
int main(void)
{
Config cfg = {0};
cfg.enabled = 1;
cfg.mode = 2;
cfg.level = 7;
printf("sizeof(Config) = %zu\n", sizeof(Config)); // 4
printf("enabled: %u\n", cfg.enabled);
printf("mode: %u\n", cfg.mode);
printf("level: %u\n", cfg.level);
Permissions perms = {.read = 1, .write = 1, .execute = 0, .admin = 0};
printf("\n权限: 读=%u 写=%u 执行=%u 管理=%u\n",
perms.read, perms.write, perms.execute, perms.admin);
return 0;
}
⚠️ 注意: 位域的存储布局是实现定义的(依赖编译器和平台),不适合用于需要精确控制二进制布局的场景。网络协议解析建议用移位操作。
9. 结构体与函数参数
#include <stdio.h>
#include <math.h>
typedef struct {
double x, y;
} Vec2;
// 值传递(适合小型结构体)
Vec2 vec2_add(Vec2 a, Vec2 b)
{
return (Vec2){a.x + b.x, a.y + b.y};
}
Vec2 vec2_scale(Vec2 v, double s)
{
return (Vec2){v.x * s, v.y * s};
}
double vec2_length(Vec2 v)
{
return sqrt(v.x * v.x + v.y * v.y);
}
// 指针传递(适合大型结构体,避免复制开销)
void vec2_normalize(Vec2 *v)
{
double len = vec2_length(*v);
if (len > 0) {
v->x /= len;
v->y /= len;
}
}
void vec2_print(const char *label, Vec2 v)
{
printf("%s = (%.4f, %.4f)\n", label, v.x, v.y);
}
int main(void)
{
Vec2 a = {3.0, 4.0};
Vec2 b = {1.0, 2.0};
Vec2 sum = vec2_add(a, b);
vec2_print("a + b", sum);
Vec2 scaled = vec2_scale(a, 2.0);
vec2_print("a * 2", scaled);
printf("|a| = %.4f\n", vec2_length(a));
vec2_normalize(&a);
vec2_print("normalize(a)", a);
return 0;
}
| 传递方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 值传递 | 小型结构体(≤16 字节) | 安全,不修改原值 | 大型结构体有复制开销 |
| 指针传递 | 大型结构体 | 高效 | 需注意 const 保护 |
10. 链表实现
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
// 创建节点
Node *node_create(int data)
{
Node *n = malloc(sizeof(Node));
if (n) {
n->data = data;
n->next = NULL;
}
return n;
}
// 头插法
void list_push_front(Node **head, int data)
{
Node *n = node_create(data);
n->next = *head;
*head = n;
}
// 尾插法
void list_push_back(Node **head, int data)
{
Node *n = node_create(data);
if (*head == NULL) {
*head = n;
return;
}
Node *curr = *head;
while (curr->next) curr = curr->next;
curr->next = n;
}
// 查找
Node *list_find(Node *head, int data)
{
while (head) {
if (head->data == data) return head;
head = head->next;
}
return NULL;
}
// 删除
void list_remove(Node **head, int data)
{
Node **curr = head;
while (*curr) {
if ((*curr)->data == data) {
Node *temp = *curr;
*curr = (*curr)->next;
free(temp);
return;
}
curr = &(*curr)->next;
}
}
// 打印
void list_print(Node *head)
{
while (head) {
printf("%d -> ", head->data);
head = head->next;
}
printf("NULL\n");
}
// 释放
void list_free(Node **head)
{
while (*head) {
Node *temp = *head;
*head = (*head)->next;
free(temp);
}
}
// 反转链表
void list_reverse(Node **head)
{
Node *prev = NULL;
Node *curr = *head;
while (curr) {
Node *next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
*head = prev;
}
int main(void)
{
Node *list = NULL;
list_push_back(&list, 1);
list_push_back(&list, 2);
list_push_back(&list, 3);
list_push_front(&list, 0);
printf("原始: ");
list_print(list);
list_reverse(&list);
printf("反转: ");
list_print(list);
list_remove(&list, 2);
printf("删除 2: ");
list_print(list);
Node *found = list_find(list, 3);
printf("查找 3: %s\n", found ? "找到" : "未找到");
list_free(&list);
return 0;
}