HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • C 语言入门教程

    • C语言系列教程
    • 第01章 基础概念与编译
    • 第02章 数据类型与变量
    • 第03章 运算符
    • 第04章 输入输出
    • 第05章 流程控制
    • 第06章 数组
    • 第07章 指针
    • 第08章 函数
    • 第09章 构造类型

第09章 构造类型

9.1 结构体 (struct)

9.1.1 结构体的定义

结构体: 将不同类型的数据组合在一起的复合数据类型。

语法:

struct 结构体名
{
    数据类型 成员1;
    数据类型 成员2;
    // ...
};

示例:

#include <stdio.h>

struct Student
{
    int id;
    char name[50];
    int age;
    float score;
};

int main(void)
{
    struct Student stu;  // 定义结构体变量

    stu.id = 1001;
    stu.age = 20;
    stu.score = 95.5;

    printf("ID: %d, 年龄: %d, 成绩: %.1f\n",
           stu.id, stu.age, stu.score);

    return 0;
}

9.1.2 结构体的初始化

方式1: 定义时初始化

struct Student stu = {1001, "张三", 20, 95.5};

方式2: 指定成员初始化(C99)

struct Student stu = {
    .id = 1001,
    .name = "张三",
    .age = 20,
    .score = 95.5
};

方式3: 部分初始化

struct Student stu = {1001, "张三"};  // age和score默认为0

完整示例:

#include <stdio.h>
#include <string.h>

struct Student
{
    int id;
    char name[50];
    int age;
    float score;
};

int main(void)
{
    // 方式1
    struct Student stu1 = {1001, "张三", 20, 95.5};

    // 方式2
    struct Student stu2 = {
        .id = 1002,
        .name = "李四",
        .age = 21,
        .score = 88.0
    };

    // 方式3
    struct Student stu3;
    stu3.id = 1003;
    strcpy(stu3.name, "王五");
    stu3.age = 19;
    stu3.score = 92.5;

    printf("学生1: ID=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
           stu1.id, stu1.name, stu1.age, stu1.score);

    printf("学生2: ID=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
           stu2.id, stu2.name, stu2.age, stu2.score);

    return 0;
}

9.1.3 typedef定义类型别名

语法:

typedef struct 结构体名
{
    // 成员
} 类型别名;

示例:

#include <stdio.h>

typedef struct
{
    int x;
    int y;
} Point;  // Point是类型别名

int main(void)
{
    Point p1 = {10, 20};  // 无需写struct
    Point p2 = {.x = 30, .y = 40};

    printf("p1: (%d, %d)\n", p1.x, p1.y);
    printf("p2: (%d, %d)\n", p2.x, p2.y);

    return 0;
}

9.1.4 结构体数组

#include <stdio.h>
#include <string.h>

typedef struct
{
    int id;
    char name[50];
    float score;
} Student;

int main(void)
{
    Student students[3] = {
        {1001, "张三", 95.5},
        {1002, "李四", 88.0},
        {1003, "王五", 92.5}
    };

    for (int i = 0; i < 3; i++)
    {
        printf("学生%d: ID=%d, 姓名=%s, 成绩=%.1f\n",
               i+1, students[i].id, students[i].name, students[i].score);
    }

    return 0;
}

9.1.5 结构体指针

#include <stdio.h>

typedef struct
{
    int x;
    int y;
} Point;

int main(void)
{
    Point p = {10, 20};
    Point *ptr = &p;

    // 方式1: 通过指针访问成员
    printf("x = %d, y = %d\n", (*ptr).x, (*ptr).y);

    // 方式2: 使用箭头运算符(推荐)
    printf("x = %d, y = %d\n", ptr->x, ptr->y);

    // 修改成员
    ptr->x = 30;
    ptr->y = 40;

    printf("修改后: x = %d, y = %d\n", p.x, p.y);

    return 0;
}

⚠️ 注意:

  • (*ptr).x 等价于 ptr->x
  • 箭头运算符 -> 更常用

9.1.6 结构体嵌套

#include <stdio.h>

typedef struct
{
    int year;
    int month;
    int day;
} Date;

typedef struct
{
    int id;
    char name[50];
    Date birthday;  // 嵌套结构体
} Person;

int main(void)
{
    Person p = {
        .id = 1001,
        .name = "张三",
        .birthday = {1990, 5, 20}
    };

    printf("姓名: %s\n", p.name);
    printf("生日: %d-%02d-%02d\n",
           p.birthday.year, p.birthday.month, p.birthday.day);

    return 0;
}

9.1.7 结构体作为函数参数

传值:

#include <stdio.h>

typedef struct
{
    int x;
    int y;
} Point;

void printPoint(Point p)  // 传值,拷贝整个结构体
{
    printf("Point: (%d, %d)\n", p.x, p.y);
}

int main(void)
{
    Point p = {10, 20};
    printPoint(p);

    return 0;
}

传指针(推荐):

#include <stdio.h>

typedef struct
{
    int x;
    int y;
} Point;

void printPoint(const Point *p)  // 传指针,高效
{
    printf("Point: (%d, %d)\n", p->x, p->y);
}

void movePoint(Point *p, int dx, int dy)
{
    p->x += dx;
    p->y += dy;
}

int main(void)
{
    Point p = {10, 20};

    printPoint(&p);

    movePoint(&p, 5, 10);
    printPoint(&p);

    return 0;
}

9.1.8 结构体赋值

#include <stdio.h>

typedef struct
{
    int x;
    int y;
} Point;

int main(void)
{
    Point p1 = {10, 20};
    Point p2;

    p2 = p1;  // 结构体可以直接赋值(拷贝所有成员)

    printf("p1: (%d, %d)\n", p1.x, p1.y);
    printf("p2: (%d, %d)\n", p2.x, p2.y);

    p2.x = 30;  // 修改p2不影响p1

    printf("修改p2后:\n");
    printf("p1: (%d, %d)\n", p1.x, p1.y);
    printf("p2: (%d, %d)\n", p2.x, p2.y);

    return 0;
}

9.2 内存对齐

9.2.1 什么是内存对齐

内存对齐: 编译器为了提高访问效率,会在结构体成员之间插入填充字节。

#include <stdio.h>

struct Test1
{
    char c;      // 1字节
    int i;       // 4字节
    short s;     // 2字节
};

int main(void)
{
    printf("sizeof(char):  %lu\n", sizeof(char));
    printf("sizeof(int):   %lu\n", sizeof(int));
    printf("sizeof(short): %lu\n", sizeof(short));

    printf("sizeof(Test1): %lu\n", sizeof(struct Test1));
    // 期望: 1 + 4 + 2 = 7
    // 实际: 12 (因为内存对齐)

    return 0;
}

9.2.2 对齐规则

  1. 结构体第一个成员偏移量为0
  2. 每个成员的偏移量是其大小的整数倍
  3. 结构体总大小是最大成员大小的整数倍

示例分析:

struct Test1
{
    char c;      // 偏移0,占1字节
    // 填充3字节(对齐到int)
    int i;       // 偏移4,占4字节
    short s;     // 偏移8,占2字节
    // 填充2字节(总大小对齐到int的4字节)
};
// 总大小: 12字节

struct Test2
{
    char c;      // 偏移0,占1字节
    short s;     // 偏移2,占2字节(填充1字节)
    int i;       // 偏移4,占4字节
};
// 总大小: 8字节

9.2.3 优化结构体大小

原则: 将相同或相近大小的成员放在一起

#include <stdio.h>

// 未优化
struct Unoptimized
{
    char c1;     // 1字节
    int i;       // 4字节
    char c2;     // 1字节
    short s;     // 2字节
};  // 总大小: 12字节

// 优化后
struct Optimized
{
    int i;       // 4字节
    short s;     // 2字节
    char c1;     // 1字节
    char c2;     // 1字节
};  // 总大小: 8字节

int main(void)
{
    printf("Unoptimized: %lu 字节\n", sizeof(struct Unoptimized));
    printf("Optimized:   %lu 字节\n", sizeof(struct Optimized));

    return 0;
}

9.2.4 指定对齐

使用#pragma pack:

#include <stdio.h>

#pragma pack(1)  // 1字节对齐

struct Packed
{
    char c;
    int i;
    short s;
};

#pragma pack()  // 恢复默认对齐

int main(void)
{
    printf("sizeof(Packed): %lu\n", sizeof(struct Packed));  // 7字节

    return 0;
}

⚠️ 注意: 紧凑对齐可能降低访问效率,慎用!

9.3 共用体 (union)

9.3.1 共用体的定义

共用体: 所有成员共享同一块内存,同一时间只能存储一个成员。

语法:

union 共用体名
{
    数据类型 成员1;
    数据类型 成员2;
    // ...
};

示例:

#include <stdio.h>

union Data
{
    int i;
    float f;
    char str[20];
};

int main(void)
{
    union Data data;

    printf("union大小: %lu\n", sizeof(data));  // 20字节(最大成员)

    data.i = 10;
    printf("data.i = %d\n", data.i);

    data.f = 3.14;  // 覆盖了i的值
    printf("data.f = %.2f\n", data.f);
    printf("data.i = %d (已被覆盖)\n", data.i);  // 输出垃圾值

    return 0;
}

9.3.2 共用体的应用

应用1: 类型转换

#include <stdio.h>

union FloatInt
{
    float f;
    unsigned int i;
};

int main(void)
{
    union FloatInt fi;

    fi.f = 3.14;

    printf("float值: %f\n", fi.f);
    printf("对应的整数表示: 0x%08X\n", fi.i);

    return 0;
}

应用2: 变体类型

#include <stdio.h>

typedef enum
{
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
} DataType;

typedef struct
{
    DataType type;
    union
    {
        int i;
        float f;
        char str[50];
    } value;
} Variant;

void printVariant(Variant v)
{
    switch (v.type)
    {
        case TYPE_INT:
            printf("整数: %d\n", v.value.i);
            break;
        case TYPE_FLOAT:
            printf("浮点数: %.2f\n", v.value.f);
            break;
        case TYPE_STRING:
            printf("字符串: %s\n", v.value.str);
            break;
    }
}

int main(void)
{
    Variant v1 = {TYPE_INT, .value.i = 100};
    Variant v2 = {TYPE_FLOAT, .value.f = 3.14};
    Variant v3 = {TYPE_STRING, .value.str = "Hello"};

    printVariant(v1);
    printVariant(v2);
    printVariant(v3);

    return 0;
}

9.3.3 结构体 vs 共用体

特性structunion
内存所有成员独立存储所有成员共享内存
大小所有成员大小之和(+对齐)最大成员的大小
访问可同时访问所有成员同时只能使用一个成员
用途组合不同类型数据节省内存,类型转换

9.4 枚举 (enum)

9.4.1 枚举的定义

枚举: 定义一组命名的整数常量。

语法:

enum 枚举名
{
    枚举常量1,
    枚举常量2,
    // ...
};

示例:

#include <stdio.h>

enum Weekday
{
    MONDAY,     // 0
    TUESDAY,    // 1
    WEDNESDAY,  // 2
    THURSDAY,   // 3
    FRIDAY,     // 4
    SATURDAY,   // 5
    SUNDAY      // 6
};

int main(void)
{
    enum Weekday today = FRIDAY;

    printf("今天是星期: %d\n", today);

    if (today == FRIDAY)
    {
        printf("明天是周末!\n");
    }

    return 0;
}

9.4.2 指定枚举值

#include <stdio.h>

enum Color
{
    RED = 1,
    GREEN = 2,
    BLUE = 4,
    YELLOW = 8
};

enum Season
{
    SPRING = 1,
    SUMMER,      // 2
    AUTUMN = 10,
    WINTER       // 11
};

int main(void)
{
    enum Color c = RED;
    printf("RED = %d\n", c);

    enum Season s = SUMMER;
    printf("SUMMER = %d\n", s);
    printf("WINTER = %d\n", WINTER);

    return 0;
}

9.4.3 枚举的应用

应用1: 状态机

#include <stdio.h>

typedef enum
{
    STATE_IDLE,
    STATE_RUNNING,
    STATE_PAUSED,
    STATE_STOPPED
} State;

void processState(State state)
{
    switch (state)
    {
        case STATE_IDLE:
            printf("空闲状态\n");
            break;
        case STATE_RUNNING:
            printf("运行状态\n");
            break;
        case STATE_PAUSED:
            printf("暂停状态\n");
            break;
        case STATE_STOPPED:
            printf("停止状态\n");
            break;
    }
}

int main(void)
{
    State state = STATE_IDLE;

    processState(state);

    state = STATE_RUNNING;
    processState(state);

    return 0;
}

应用2: 错误码

#include <stdio.h>

typedef enum
{
    ERR_SUCCESS = 0,
    ERR_INVALID_PARAM = -1,
    ERR_FILE_NOT_FOUND = -2,
    ERR_NO_MEMORY = -3,
    ERR_TIMEOUT = -4
} ErrorCode;

const char* getErrorString(ErrorCode err)
{
    switch (err)
    {
        case ERR_SUCCESS:
            return "成功";
        case ERR_INVALID_PARAM:
            return "参数错误";
        case ERR_FILE_NOT_FOUND:
            return "文件未找到";
        case ERR_NO_MEMORY:
            return "内存不足";
        case ERR_TIMEOUT:
            return "超时";
        default:
            return "未知错误";
    }
}

int main(void)
{
    ErrorCode err = ERR_FILE_NOT_FOUND;

    printf("错误码: %d\n", err);
    printf("错误信息: %s\n", getErrorString(err));

    return 0;
}

9.4.4 typedef枚举

#include <stdio.h>

typedef enum
{
    FALSE,
    TRUE
} Bool;  // Bool是类型别名

int main(void)
{
    Bool flag = TRUE;  // 无需写enum

    if (flag == TRUE)
    {
        printf("标志为真\n");
    }

    return 0;
}

9.5 位域

9.5.1 位域的定义

位域: 结构体成员可以指定占用的位数,节省空间。

语法:

struct 结构体名
{
    数据类型 成员名 : 位数;
};

示例:

#include <stdio.h>

struct Flags
{
    unsigned int flag1 : 1;  // 1位
    unsigned int flag2 : 1;  // 1位
    unsigned int flag3 : 1;  // 1位
    unsigned int value : 5;  // 5位
};

int main(void)
{
    struct Flags f = {0};

    printf("sizeof(Flags): %lu 字节\n", sizeof(f));  // 4字节

    f.flag1 = 1;
    f.flag2 = 0;
    f.flag3 = 1;
    f.value = 20;

    printf("flag1=%u, flag2=%u, flag3=%u, value=%u\n",
           f.flag1, f.flag2, f.flag3, f.value);

    return 0;
}

9.5.2 位域的应用

应用: 寄存器配置

#include <stdio.h>

typedef struct
{
    unsigned int enable : 1;
    unsigned int mode : 2;
    unsigned int speed : 3;
    unsigned int reserved : 2;
} RegisterConfig;

int main(void)
{
    RegisterConfig reg = {0};

    reg.enable = 1;
    reg.mode = 2;    // 0-3
    reg.speed = 5;   // 0-7

    printf("使能: %u\n", reg.enable);
    printf("模式: %u\n", reg.mode);
    printf("速度: %u\n", reg.speed);

    return 0;
}

⚠️ 注意:

  • 位域不能取地址
  • 位域的内存布局依赖编译器
  • 不可移植,慎用

9.6 实践练习

练习1: 学生管理系统

#include <stdio.h>
#include <string.h>

#define MAX_STUDENTS 100

typedef struct
{
    int id;
    char name[50];
    int age;
    float score;
} Student;

typedef struct
{
    Student students[MAX_STUDENTS];
    int count;
} StudentDB;

void addStudent(StudentDB *db, int id, const char *name, int age, float score)
{
    if (db->count >= MAX_STUDENTS)
    {
        printf("数据库已满!\n");
        return;
    }

    Student *s = &db->students[db->count];
    s->id = id;
    strncpy(s->name, name, sizeof(s->name) - 1);
    s->age = age;
    s->score = score;

    db->count++;
    printf("添加学生成功: %s\n", name);
}

void printAllStudents(const StudentDB *db)
{
    printf("\n========学生列表========\n");
    printf("%-6s %-10s %-6s %-6s\n", "ID", "姓名", "年龄", "成绩");

    for (int i = 0; i < db->count; i++)
    {
        const Student *s = &db->students[i];
        printf("%-6d %-10s %-6d %-6.1f\n",
               s->id, s->name, s->age, s->score);
    }
}

Student* findStudent(StudentDB *db, int id)
{
    for (int i = 0; i < db->count; i++)
    {
        if (db->students[i].id == id)
        {
            return &db->students[i];
        }
    }
    return NULL;
}

int main(void)
{
    StudentDB db = {0};

    addStudent(&db, 1001, "张三", 20, 95.5);
    addStudent(&db, 1002, "李四", 21, 88.0);
    addStudent(&db, 1003, "王五", 19, 92.5);

    printAllStudents(&db);

    int searchId = 1002;
    Student *s = findStudent(&db, searchId);
    if (s != NULL)
    {
        printf("\n找到学生: ID=%d, 姓名=%s, 成绩=%.1f\n",
               s->id, s->name, s->score);
    }

    return 0;
}

练习2: 链表节点

#include <stdio.h>
#include <stdlib.h>

typedef struct Node
{
    int data;
    struct Node *next;
} Node;

Node* createNode(int data)
{
    Node *node = (Node*)malloc(sizeof(Node));
    if (node == NULL)
    {
        return NULL;
    }

    node->data = data;
    node->next = NULL;

    return node;
}

void printList(Node *head)
{
    Node *current = head;

    while (current != NULL)
    {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

void freeList(Node *head)
{
    Node *current = head;

    while (current != NULL)
    {
        Node *next = current->next;
        free(current);
        current = next;
    }
}

int main(void)
{
    Node *head = createNode(1);
    head->next = createNode(2);
    head->next->next = createNode(3);

    printList(head);

    freeList(head);

    return 0;
}

9.7 常见错误

错误1: 访问未初始化的结构体

struct Point
{
    int x;
    int y;
};

struct Point p;  // 未初始化
printf("%d\n", p.x);  // 垃圾值

// 正确
struct Point p = {0};  // 全部初始化为0

错误2: 结构体数组越界

Student students[10];
students[10].id = 1;  // 错误!越界

错误3: 共用体成员混用

union Data
{
    int i;
    float f;
};

union Data d;
d.i = 10;
printf("%f\n", d.f);  // 错误!读取未设置的成员

错误4: 位域取地址

struct Flags
{
    unsigned int flag : 1;
};

struct Flags f;
int *p = &f.flag;  // 错误!位域不能取地址

错误5: 忘记typedef

struct Point
{
    int x, y;
};

Point p;  // 错误!Point不是类型名

// 正确
struct Point p;
// 或使用typedef
typedef struct Point Point;
Point p;

9.8 小结

本章学习了:

结构体的定义、初始化和使用 内存对齐原理及优化 共用体的特点和应用 枚举类型定义常量 位域节省内存空间 结构体嵌套、数组和指针 结构体作为函数参数

重点提示:

  1. 结构体成员用.访问,指针用->
  2. 结构体可以直接赋值,会拷贝所有成员
  3. 传递结构体参数时优先使用指针
  4. 理解内存对齐,合理排列成员节省空间
  5. 共用体同时只能使用一个成员
  6. 枚举提高代码可读性,用于定义常量集合
  7. typedef简化类型名,提高可读性

设计建议:

  • 相关数据用结构体组合
  • 成员按大小排列,优化内存
  • 使用const保护只读数据
  • 枚举代替魔术数字
  • typedef提高可读性

上一章: 第08章 函数

🎉 恭喜完成C语言系列教程!

继续学习:

  • 文件操作 (FILE*)
  • 动态内存管理 (malloc/free)
  • 预处理器 (#define, #include, #ifdef)
  • 多文件编程与Makefile
  • 数据结构与算法
Prev
第08章 函数