C语言自定义类型全解析

C 语言自定义类型学习笔记:结构体、联合体与枚举

最近系统学习了 C 语言自定义类型三大核心:结构体、联合体、枚举,这是 C 语言从基础语法走向实际开发的关键知识点。


第一部分:结构体(struct)- 最常用的自定义类型

结构体是把不同类型的数据打包在一起,用来描述一个完整的对象(学生、商品、节点等),是 C 语言数据封装的基础。

1. 结构体的 3 种声明方式 + typedef 重命名

声明时末尾分号绝对不能丢,这是我最开始反复踩的坑!

cpp 复制代码
#include <stdio.h>
// 1. 标准声明(最常用)
struct Student {
    // 成员变量(不同类型任意组合)
    char name[20];  // 姓名
    int age;        // 年龄
    float score;    // 成绩
};

// 2. 声明时直接定义变量
struct Teacher {
    char name[20];
    int id;
} t1, t2; // 直接定义全局变量t1、t2

// 3. typedef重命名(简化写法,后续不用写struct)
typedef struct Book {
    char bookName[30];
    float price;
} Book; // 以后直接用Book代替struct Book

2. 结构体的 3 种初始化方式

推荐指定成员初始化:

cpp 复制代码
int main() {
    // 方式1:按成员顺序初始化
    struct Student s1 = {"张三", 18, 95.5};

    // 方式2:指定成员初始化(C99标准,最灵活)
    struct Student s2 = {.score=88.0, .name="李四", .age=20};

    // 方式3:typedef重命名后初始化
    Book b1 = {"《C语言从入门到精通》", 59.9};

    // 打印验证
    printf("s1:%s %d %.1f\n", s1.name, s1.age, s1.score);
    printf("s2:%s %d %.1f\n", s2.name, s2.age, s2.score);
    printf("书籍:%s %.1f\n", b1.bookName, b1.price);
    return 0;
}

3. 匿名结构体(慎用!)

省略结构体名,只能用一次,编译器会把相同成员的匿名结构体当成不同类型!

cpp 复制代码
// 匿名结构体
struct {
    int a;
    char b;
} x; // 只能定义变量x,无法重复使用

// 错误:两个匿名结构体是不同类型,不能赋值
// struct {int a; char b;} y = x; 

4. 结构体自引用(链表核心!)

错误:直接包含自身变量 → 内存无限嵌套;正确: 包含自身指针 → 指针大小固定(4/8 字节)

cpp 复制代码
// 正确的结构体自引用(单链表节点)
typedef struct Node {
    int data;               // 数据域
    struct Node* next;     // 指针域:指向下一个节点
} Node;

// 实战:创建3个节点的简单链表
int main() {
    Node n1 = {10, NULL};
    Node n2 = {20, NULL};
    Node n3 = {30, NULL};
    // 建立连接
    n1.next = &n2;
    n2.next = &n3;

    // 遍历链表
    Node* p = &n1;
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }
    return 0;
}
// 输出:10 20 30

5. 核心难点:结构体内存对齐

5.1对齐规则(VS 默认对齐数 8)
  1. 第一个成员 → 偏移量 0
  2. 后续成员 → 对齐到 min(默认对齐数, 成员大小) 的整数倍
  3. 总大小 → 最大对齐数的整数倍
  4. 嵌套结构体 → 对齐到自身最大对齐数
5.2代码示例:计算结构体大小
cpp 复制代码
// 示例1
struct S1 {
    char c1;  // 1字节
    int i;    // 4字节
    char c2;  // 1字节
};
// 大小计算:1(对齐到4)+4+1(对齐到4) = 12字节

// 示例2:优化成员顺序(小成员集中)
struct S2 {
    char c1;
    char c2;
    int i;
};
// 大小计算:1+1(对齐到4)+4 = 8字节

// 示例3:嵌套结构体
struct S3 {
    char c;
    struct S2 s2;
    double d;
};
// 大小:1 + 7(填充) + 8 + 8 = 24字节

// 测试代码
int main() {
    printf("S1大小:%d\n", sizeof(struct S1)); // 12
    printf("S2大小:%d\n", sizeof(struct S2)); // 8
    printf("S3大小:%d\n", sizeof(struct S3)); // 24
    return 0;
}
5.3修改默认对齐数:
cpp 复制代码
#pragma pack(1) // 设置默认对齐数为1(无内存填充)
struct S4 {
    char c1;
    int i;
};
#pragma pack() // 恢复默认对齐数
// sizeof(S4) = 5

6. 结构体数组 + 结构体指针

cpp 复制代码
// 结构体数组:存储多个学生
struct Student arr[3] = {
    {"小明", 17, 90.0},
    {"小红", 18, 92.5},
    {"小刚", 19, 85.0}
};

// 结构体指针访问成员:两种方式
int main() {
    struct Student s = {"小王", 20, 88.0};
    struct Student* p = &s;

    // 方式1:(*指针).成员
    printf("%s ", (*p).name);
    // 方式2:指针->成员(最常用)
    printf("%d %.1f\n", p->age, p->score);
    return 0;
}

7. 结构体传参(必传指针!)

传值会拷贝整个结构体,效率极低;传指针仅 4/8 字节,更高效:

cpp 复制代码
struct Student {
    char name[20];
    int age;
};

//不推荐:传值(拷贝大结构体,开销大)
void print1(struct Student s) {
    printf("%s %d\n", s.name, s.age);
}

//推荐:传指针(加const防止修改)
void print2(const struct Student* s) {
    printf("%s %d\n", s->name, s->age);
}

int main() {
    struct Student s = {"小李", 21};
    print1(s);
    print2(&s);
    return 0;
}

8. 结构体进阶:位段(节省内存)

位段 = 结构体的特殊形式,按 bit 分配内存 ,用于网络协议、硬件编程。规则:成员必须是整型,: 后数字表示占用 bit 数

cpp 复制代码
// 位段声明
struct BitSeg {
    int a : 2;  // a占2bit
    int b : 5;  // b占5bit
    int c : 10; // c占10bit
    int d : 30; // d占30bit
};

int main() {
    struct BitSeg bs;
    bs.a = 3;  // 2bit最大存3
    bs.b = 20; // 5bit最大存31
    printf("位段大小:%d\n", sizeof(bs)); // 8字节(VS分配规则)
    return 0;
}

第二部分:联合体(union)

联合体也叫共用体 ,所有成员共用同一块内存,大小 = 最大成员大小(对齐后)。

1. 核心特性:内存共享(赋值互相覆盖)

cpp 复制代码
#include <stdio.h>
// 联合体声明
union Data {
    int i;
    char c;
    float f;
};

int main() {
    union Data data;
    data.i = 10;
    printf("i = %d\n", data.i); // 10

    data.c = 'A'; // 覆盖低字节
    printf("c = %c\n", data.c); // A
    printf("i = %d\n", data.i); // 65(内存被覆盖)
    return 0;
}

2. 联合体大小计算(对齐规则)

  1. 至少 = 最大成员大小
  2. 必须是最大对齐数的整数倍
cpp 复制代码
// 示例1
union Un1 {
    char c[5]; // 5字节,对齐数1
    int i;     // 4字节,对齐数4
};
// 大小:8(5对齐到4的整数倍)

// 示例2
union Un2 {
    short s[7]; // 14字节,对齐数2
    int i;      // 4字节,对齐数4
};
// 大小:16(14对齐到4的整数倍)

int main() {
    printf("Un1:%d\n", sizeof(union Un1)); // 8
    printf("Un2:%d\n", sizeof(union Un2)); // 16
    return 0;
}

3. 经典实战:判断机器大小端

小端 :低字节存低地址;大端:高字节存低地址

cpp 复制代码
// 联合体判断大小端(极简代码)
int check_sys() {
    union {
        int i;
        char c;
    } un;
    un.i = 1;
    // 返回1=小端,0=大端
    return un.c;
}

int main() {
    if (check_sys()) {
        printf("小端存储\n");
    } else {
        printf("大端存储\n");
    }
    return 0;
}

4. 多类型数据存储(节省内存)

cpp 复制代码
// 礼品兑换:同一时间只存一种礼品属性
typedef struct Gift {
    int type; // 0=图书,1=杯子,2=衣服
    // 联合体封装特殊属性
    union {
        struct { char author[20]; } book;
        struct { char color[10]; } cup;
        struct { int size; } clothes;
    } attr;
} Gift;

int main() {
    Gift g;
    g.type = 0;
    g.attr.book.author = "张三";
    return 0;
}

第三部分:枚举(enum)- 优雅定义常量

枚举 = 把有限的取值一一列举 ,替代#define,代码更规范、有类型检查。

1. 枚举声明 + 赋值规则

默认从 0 开始,依次 + 1;可手动赋值,后续自动递增

cpp 复制代码
// 1. 默认赋值:RED=0, GREEN=1, BLUE=2
enum Color {
    RED,
    GREEN,
    BLUE
};

// 2. 手动赋值:MON=1, TUE=2, WED=3...
enum Week {
    MON=1,
    TUE,
    WED,
    THU,
    FRI,
    SAT,
    SUN
};

2. 枚举完整使用示例

cpp 复制代码
#include <stdio.h>
enum Week {MON=1,TUE,WED,THU,FRI,SAT,SUN};

int main() {
    enum Week today = WED;
    printf("今天是星期%d\n", today); // 3

    // 枚举做分支判断(代码可读性拉满)
    if (today == SAT || today == SUN) {
        printf("周末休息!\n");
    } else {
        printf("工作日\n");
    }
    return 0;
}

3. 枚举 VS #define(为什么用枚举?)

cpp 复制代码
// #define 无类型、无作用域、调试不显示
#define RED 1
#define GREEN 2
#define BLUE 3

// 枚举:有类型、有作用域、可调试、一次定义多个
enum Color {cRED=1, cGREEN, cBLUE};

// 枚举传参(类型安全,#define做不到)
void printColor(enum Color c) {
    printf("%d\n", c);
}

第四部分:综合实战(整合所有知识点)

4.1 简易学生信息管理系统

cpp 复制代码
#include <stdio.h>
// 结构体封装学生
typedef struct Stu {
    char name[20];
    int age;
    float score;
} Stu;

// 枚举定义菜单
enum Menu {EXIT, ADD, SHOW};

// 显示菜单
void menu() {
    printf("1.添加学生 2.显示学生 0.退出\n");
}

// 添加学生(传指针)
void addStu(Stu* s, int i) {
    printf("输入第%d个学生信息:\n", i+1);
    scanf("%s %d %f", s->name, &s->age, &s->score);
}

// 显示学生
void showStu(Stu* s, int n) {
    for (int i=0; i<n; i++) {
        printf("%s %d %.1f\n", s[i].name, s[i].age, s[i].score);
    }
}

int main() {
    Stu arr[50];
    int n = 0;
    int choice;
    while (1) {
        menu();
        scanf("%d", &choice);
        switch (choice) {
            case ADD:
                addStu(&arr[n], n);
                n++;
                break;
            case SHOW:
                showStu(arr, n);
                break;
            case EXIT:
                return 0;
            default:
                printf("输入错误!\n");
        }
    }
}

我的学习总结

  1. 结构体 :封装不同数据,重点掌握内存对齐、传指针、自引用;位段用于节省内存。
  2. 联合体 :成员共享内存,大小 = 最大成员(对齐后),大小端判断是必考用法。
  3. 枚举 :定义有限常量,比#define更安全、可读性更高,适合分支判断。
  4. 开发原则:结构体传参必传指针;成员顺序优化节省内存;优先用枚举代替宏常量。
相关推荐
Yupureki2 小时前
《Linux系统编程》19.线程同步与互斥
java·linux·服务器·c语言·开发语言·数据结构·c++
深蓝海拓3 小时前
西门子S7-1500PLC的常用Area地址以及网络读写
笔记·学习·plc
一轮弯弯的明月3 小时前
博弈论-Nim游戏
笔记·蓝桥杯·学习心得
Hello_Embed3 小时前
嵌入式上位机开发入门(五):UDP 编程 —— Server 端实现
笔记·单片机·网络协议·udp·嵌入式
热水过敏3 小时前
前路迷茫,再次起航
笔记·程序人生·职场和发展
chase。3 小时前
【学习笔记】RoboForge:让文本指令“落地”到人形机器人——一个物理优化与隐式驱动的端到端框架
笔记·学习·机器人
chase。4 小时前
【学习笔记】从经典算法到通用神经运动规划器
笔记·学习·算法
牛奶咖啡134 小时前
免费笔记软件且优先本地私有化——Joplin、Obsidian
笔记·obsidian·joplin·待办事项应用程序·开源笔记应用·可私有化本地笔记应用·笔记的同步与插件安装
Rabitebla4 小时前
快速排序(QuickSort)完全指南 —— 从原理到工业级优化
c语言·数据结构·c++·算法·github