强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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;
}
特性structunion
内存布局成员依次排列成员重叠(共享)
大小所有成员大小之和(含填充)最大成员的大小
同时使用可以同时访问所有成员同一时刻只能有效使用一个成员

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;
}

11. 扩展阅读