欢迎光临
我们一直在努力

大学生版保姆式学《C和指针》——结构体与联合的底层逻辑

前言

       上一章咱们彻底攻克了高级指针的难点,搞懂了二级指针的核心场景与复杂声明的右左法则,终于走出了指针迷宫。这一章咱们切入《C和指针》里自定义数据类型的核心内容——结构体与联合的底层逻辑。        很多同学学结构体,只记住了struct的定义格式,却踩了无数坑:期末考结构体大小计算题永远算不对、结构体传参总导致程序效率低下、分不清struct和union的本质区别、用结构体指针总出现内存访问错误。核心原因就是你只记住了结构体的语法,却没搞懂它在内存里的真实布局,没理解结构体与指针结合的底层逻辑。        这一章咱们全程保姆式拆解,从结构体的内存封装本质,到期末100%会考的内存对齐规则,再到结构体指针、柔性数组、联合与枚举的核心用法,把《C和指针》里的自定义类型知识点拆成零基础也能懂的内容,同时覆盖期末考、校招面试的所有高频考点。


目录

  • 一、本章学习目标
  • 二、核心知识点
    • 2.1 结构体的本质:自定义数据类型的内存封装【理解的核心前提】
    • 2.2 结构体的内存对齐规则【期末选择考《C和指针》核心】
    • 2.3 结构体指针:地址与间接访问【指针核心联动】
    • 2.4 结构体作为函数参数:值传递vs地址传递【期末编程必考】
    • 2.5 柔性数组:结构体的动态内存技巧【校招高频考点】
    • 2.6 联合union的底层本质与核心用途【期末选择必考】
    • 2.7 枚举enum的基础用法与避坑指南
  • 三、Dev-C++保姆式实操
    • 3.1 实操1:结构体的定义、初始化与内存对齐验证
    • 3.2 实操2:结构体指针的定义与成员访问
    • 3.3 实操3:结构体函数传参的两种方式对比
    • 3.4 实操4:柔性数组的动态内存实现
    • 3.5 实操5:联合union的内存共享特性验证
    • 3.6 保姆式排错指南
  • 四、我的踩坑记录
  • 五、课后小练习
    • 基础必刷题(期末选择/填空考点)
    • 进阶高频题(校招面试/期末编程题)
  • 六、期末/面试考点速记
  • 七、小结

一、本章学习目标

  • 搞懂结构体的本质,理解自定义数据类型的内存封装逻辑,掌握结构体的定义与初始化规则;
  • 吃透结构体内存对齐的4大规则,能精准计算结构体的内存大小,期末选择题100%考;
  • 熟练掌握结构体指针的用法,理解->和.的区别,联动之前的指针核心知识;
  • 分清结构体作为函数参数的值传递与地址传递的区别,掌握最优传参方式,期末编程必考;
  • 理解柔性数组的用法与适用场景,掌握结构体动态内存的进阶技巧;
  • 搞懂联合union的底层内存共享本质,分清struct与union的核心区别,期末选择题必考;
  • 掌握枚举enum的基础用法,避开枚举的常见坑点。

  • 二、核心知识点

    2.1 结构体的本质:自定义数据类型的内存封装【理解的核心前提】

    C语言内置的int、char、double等类型,只能描述单一数据,而我们现实中需要描述“一个学生”“一本书”这种包含多个不同类型属性的复杂对象,结构体就是为了解决这个问题诞生的。

    1. 结构体的本质

    结构体,就是把多个不同类型的变量打包封装在一起,形成的一个自定义的复合数据类型。它本质上是一块连续的内存空间,用来存放多个不同类型的成员变量。

    通俗比喻:结构体就像一个快递打包盒,里面可以放不同大小的物品(不同类型的成员变量),打包盒本身就是一个整体,有自己的地址和大小。

    2. 结构体的定义与初始化
    标准定义格式

    // struct是结构体关键字,Student是结构体标签(类型名)
    struct Student {
    // 结构体的成员变量,可以是任意类型
    char name[20]; // 姓名
    int age; // 年龄
    double score; // 分数
    }; // 分号绝对不能少!

    三种初始化方式

    // 1. 定义变量时按顺序初始化
    struct Student s1 = {"张三", 18, 90.5};

    // 2. 指定成员初始化(C99标准,推荐,可读性高)
    struct Student s2 = {
    .name = "李四",
    .age = 19,
    .score = 95.0
    };

    // 3. 定义结构体的同时定义变量(不推荐,可读性差)
    struct Teacher {
    char name[20];
    char subject[20];
    } t1 = {"王老师", "C语言"};

    3. 结构体成员的访问

    普通结构体变量,用.运算符访问成员:

    struct Student s = {"张三", 18, 90.5};
    // 访问成员
    printf("姓名:%s\\n", s.name);
    printf("年龄:%d\\n", s.age);
    // 修改成员
    s.score = 92.0;


    2.2 结构体的内存对齐规则【期末选择100%考·《C和指针》核心】

    这是《C和指针》里重点讲解的核心知识点,也是期末选择题的必考题,更是新手90%会算错的内容。很多同学以为结构体的大小就是所有成员大小的总和,实则完全不是!

    结构体的内存大小,不是简单的成员大小相加,而是遵循内存对齐规则,编译器会自动给结构体的成员之间、末尾填充空白字节,这就是内存对齐。

    1. 为什么要内存对齐?
    • 提升CPU访问效率:CPU读取内存不是逐字节读的,而是按“字长”(32位系统4字节,64位系统8字节)块读取的。如果数据没有对齐,CPU需要多次读取内存才能拿到完整数据,效率极低;
    • 平台兼容性:部分硬件平台只能访问对齐的内存地址,未对齐的数据会直接导致程序崩溃。
    2. 内存对齐的4大核心规则(期末必考,必须背熟)

    我们以默认对齐数(编译器默认的对齐单位,Dev-C++、VS默认是8字节)为基础,规则如下:

  • 规则1:结构体的第一个成员,永远放在偏移量为0的地址处(结构体起始地址);
  • 规则2:从第二个成员开始,每个成员的偏移量,必须是该成员自身大小和默认对齐数中,较小值的整数倍;
  • 规则3:结构体的总大小,必须是所有成员中最大的基本类型大小和默认对齐数中,较小值的整数倍,不够就末尾填充字节;
  • 规则4:结构体嵌套结构体时,嵌套的结构体的偏移量,必须是它内部最大成员大小的整数倍,总大小也要按规则3补齐。
  • 3. 保姆式计算示例(32位系统,默认对齐数8)

    // 计算这个结构体的大小
    struct Test {
    char a; // 1字节
    int b; // 4字节
    char c; // 1字节
    };

    分步计算:
  • 第一个成员char a,放在偏移量0处,占1字节,占用偏移量0;
  • 第二个成员int b,自身大小4字节,默认对齐数8,取较小值4。偏移量必须是4的整数倍,当前偏移量1不是4的倍数,所以填充3个空白字节,偏移量跳到4,b从偏移量4开始,占4字节,占用偏移量4-7;
  • 第三个成员char c,自身大小1字节,取较小值1,偏移量8是1的倍数,直接存放,占1字节,占用偏移量8;
  • 现在总占用了9字节,所有成员中最大的基本类型大小是4字节(int),总大小必须是4的整数倍,所以末尾填充3个字节,总大小变成12字节。
  • 最终结果:这个结构体的大小是12字节,不是1+4+1=6字节!


    2.3 结构体指针:地址与间接访问【指针核心联动】

    结构体指针,就是指向结构体变量的指针,存储的是结构体变量的起始内存地址,这是结构体与指针结合的核心用法,也是后续链表实现的基础。

    1. 结构体指针的定义与初始化

    struct Student s = {"张三", 18, 90.5};
    // 结构体指针p,指向结构体变量s
    struct Student *p = &s;

    2. 结构体指针的成员访问:->运算符

    结构体指针访问成员,有两种等价方式,推荐用->运算符,更简洁直观:

    struct Student s = {"张三", 18, 90.5};
    struct Student *p = &s;

    // 方式1:先解引用,再用.访问(不推荐,括号不能少)
    printf("姓名:%s\\n", (*p).name);
    // 方式2:用->运算符访问(推荐,《C和指针》标准写法)
    printf("年龄:%d\\n", p->age);
    // 修改成员
    p->score = 92.0;

    【核心区别】普通结构体变量用.访问成员,结构体指针用->访问成员,期末选择题必考两者的使用场景。


    2.4 结构体作为函数参数:值传递vs地址传递【期末编程必考】

    结构体作为函数参数,有两种传参方式,两者的效率、效果天差地别,期末编程题必考,也是新手最容易踩的效率坑。

    1. 值传递:传递结构体本身

    值传递会把整个结构体完整拷贝一份,传给函数的形参,函数里修改形参,完全不影响外面的实参。

    // 值传递:形参是结构体本身
    void printStudent(struct Student s) {
    printf("姓名:%s,年龄:%d,分数:%.1lf\\n", s.name, s.age, s.score);
    s.age = 20; // 修改的是拷贝的形参,外面的实参不变
    }

    【致命缺点】如果结构体很大,拷贝整个结构体会消耗大量栈内存,执行效率极低,实际开发中几乎不用值传递传结构体。

    2. 地址传递:传递结构体指针(推荐,最优方式)

    地址传递只需要把结构体的起始地址传给函数,形参是结构体指针,不会拷贝整个结构体,只拷贝4/8字节的地址,效率极高,还能通过指针修改外面的实参。

    // 地址传递:形参是结构体指针,const保护结构体不被意外修改
    void printStudent(const struct Student *p) {
    printf("姓名:%s,年龄:%d,分数:%.1lf\\n", p->name, p->age, p->score);
    }

    // 需要修改结构体的场景,不加const
    void modifyScore(struct Student *p, double new_score) {
    p->score = new_score; // 通过指针直接修改实参
    }

    【《C和指针》推荐规范】结构体传参,优先用地址传递,不需要修改的话,一定要加const保护,避免意外修改结构体内容。


    2.5 柔性数组:结构体的动态内存技巧【校招高频考点】

    柔性数组是C99标准引入的特性,指的是结构体中最后一个元素是大小未知的数组,它可以实现结构体的动态内存分配,是校招面试的高频考点。

    1. 柔性数组的定义格式

    struct FlexArray {
    int len; // 数组长度
    // 柔性数组:放在结构体最后,[]里不写大小,或者写0
    int arr[];
    };

    2. 柔性数组的核心特点
  • 柔性数组必须是结构体的最后一个成员;
  • 结构体中必须至少有一个其他固定成员;
  • sizeof(结构体)计算大小时,不包含柔性数组的内存,只计算固定成员的大小;
  • 柔性数组的内存需要用malloc动态申请,申请的总大小 = 结构体固定大小 + 柔性数组的总字节数。
  • 3. 核心优势

    相比在结构体里放一个指针,再给指针malloc内存,柔性数组有两个核心优势:

    • 内存连续:整个结构体和柔性数组的内存是连续的,访问效率更高,只需要一次free就能释放所有内存,避免内存泄漏;
    • 没有内存碎片:一次malloc申请所有内存,不会产生多次malloc的内存碎片。

    2.6 联合union的底层本质与核心用途【期末选择必考】

    联合(也叫共用体),和结构体的定义格式很像,但内存逻辑完全不同,是期末选择题的必考点,新手很容易和结构体搞混。

    1. 联合的底层本质

    联合的所有成员,共享同一块内存空间,同一个时间点,联合只能存储一个成员的值,修改其中一个成员,会覆盖其他成员的值。联合的总大小,等于所有成员中最大的那个成员的大小。

    通俗比喻:联合就像一个酒店的共享房间,同一时间只能住一个人(一个成员),不同时间可以住不同的人,房间的大小由最高的那个人(最大的成员)决定。

    2. 联合的定义与使用

    // 联合的定义,关键字union
    union Test {
    char a; // 1字节
    int b; // 4字节
    double c;// 8字节
    };

    这个联合的大小是8字节,等于最大的成员double的大小,所有成员共享这8字节的内存。

    3. 联合的核心用途
  • 节省内存:多个变量不会同时使用,同一时间只用一个,用联合可以极大节省内存;
  • 判断系统大小端字节序:这是校招面试的经典笔试题,利用联合的内存共享特性,判断系统是大端还是小端。

  • 2.7 枚举enum的基础用法与避坑指南

    枚举就是把一个变量可能的所有取值,一一列举出来,限定变量的取值范围,让代码的可读性更高,比用#define宏定义更安全,有完整的类型检查。

    1. 枚举的定义与使用

    // 枚举定义,关键字enum
    enum Week {
    // 枚举常量,默认从0开始,依次递增1
    Mon, // 0
    Tue, // 1
    Wed, // 2
    Thu, // 3
    Fri, // 4
    Sat, // 5
    Sun // 6
    };

    int main() {
    // 定义枚举变量,只能赋值枚举里的常量
    enum Week today = Mon;
    if (today == Sat || today == Sun) {
    printf("周末\\n");
    } else {
    printf("工作日\\n");
    }
    return 0;
    }

    2. 自定义枚举常量的值

    enum Season {
    Spring = 1, // 手动指定为1
    Summer, // 2
    Autumn = 10,// 手动指定为10
    Winter // 11
    };

    3. 避坑指南
  • 枚举常量是int类型,不能直接赋值字符串,也不能赋值非int类型的值;
  • 枚举变量虽然本质是int,但不要随便赋值枚举常量之外的整数,会失去枚举的类型限制意义;
  • 同一个作用域里,枚举常量的名字不能和其他变量、宏重名。

  • 三、Dev-C++保姆式实操

    3.1 实操1:结构体的定义、初始化与内存对齐验证

    #include <stdio.h>

    // 测试结构体
    struct Test {
    char a;
    int b;
    char c;
    };

    // 嵌套结构体
    struct Nest {
    short s;
    struct Test t;
    double d;
    };

    int main() {
    struct Test t = {'A', 100, 'B'};
    printf("===== 结构体内存对齐验证 =====\\n");
    printf("char a的偏移量:%d\\n", (int)&t.a (int)&t);
    printf("int b的偏移量:%d\\n", (int)&t.b (int)&t);
    printf("char c的偏移量:%d\\n", (int)&t.c (int)&t);
    printf("struct Test总大小:%d 字节\\n", sizeof(struct Test));

    printf("\\n===== 嵌套结构体验证 =====\\n");
    printf("struct Nest总大小:%d 字节\\n", sizeof(struct Nest));

    getchar();
    return 0;
    }

    运行结果示例(32位系统)

    ===== 结构体内存对齐验证 =====
    char a的偏移量:0
    int b的偏移量:4
    char c的偏移量:8
    struct Test总大小:12 字节

    ===== 嵌套结构体验证 =====
    struct Nest总大小:24 字节

    亲手运行一遍,观察成员的偏移量和结构体总大小,彻底理解内存对齐规则,期末计算题100%考。


    3.2 实操2:结构体指针的定义与成员访问

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

    // 学生结构体
    struct Student {
    char name[20];
    int age;
    double score;
    };

    int main() {
    struct Student s = {"张三", 18, 90.5};
    // 结构体指针指向s
    struct Student *p = &s;

    printf("===== 结构体指针访问 =====\\n");
    // 两种访问方式
    printf("姓名:%s\\n", (*p).name);
    printf("年龄:%d\\n", p->age);
    printf("分数:%.1lf\\n", p->score);

    // 通过指针修改成员
    strcpy(p->name, "李四");
    p->age = 19;
    p->score = 95.0;
    printf("\\n修改后的信息:\\n");
    printf("姓名:%s\\n", p->name);
    printf("年龄:%d\\n", p->age);
    printf("分数:%.1lf\\n", p->score);

    getchar();
    return 0;
    }

    运行结果

    ===== 结构体指针访问 =====
    姓名:张三
    年龄:18
    分数:90.5

    修改后的信息:
    姓名:李四
    年龄:19
    分数:95.0

    亲手敲一遍,记住.和->的使用区别,这是后续链表的基础。


    3.3 实操3:结构体函数传参的两种方式对比

    #include <stdio.h>

    struct Student {
    char name[20];
    int age;
    double score;
    };

    // 方式1:值传递,拷贝整个结构体
    void printByValue(struct Student s) {
    printf("值传递:姓名=%s,年龄=%d,分数=%.1lf\\n", s.name, s.age, s.score);
    s.age = 20; // 修改不影响实参
    printf("值传递内部修改后年龄:%d\\n", s.age);
    }

    // 方式2:地址传递,只传地址,const保护不被修改
    void printByPointer(const struct Student *p) {
    printf("地址传递:姓名=%s,年龄=%d,分数=%.1lf\\n", p->name, p->age, p->score);
    // p->age = 20; // 加了const,修改会直接编译报错,安全
    }

    // 地址传递修改结构体
    void modifyScore(struct Student *p, double new_score) {
    p->score = new_score;
    }

    int main() {
    struct Student s = {"张三", 18, 90.5};
    printf("原数据年龄:%d\\n", s.age);

    printByValue(s);
    printf("值传递后,实参年龄:%d\\n\\n", s.age);

    printByPointer(&s);
    modifyScore(&s, 98.0);
    printf("地址传递修改分数后:%.1lf\\n", s.score);

    getchar();
    return 0;
    }

    运行结果

    原数据年龄:18
    值传递:姓名=张三,年龄=18,分数=90.5
    值传递内部修改后年龄:20
    值传递后,实参年龄:18

    地址传递:姓名=张三,年龄=18,分数=90.5
    地址传递修改分数后:98.0

    期末编程题必考,亲手运行一遍,理解两种传参方式的区别,记住优先用地址传递。


    3.4 实操4:柔性数组的动态内存实现

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

    // 包含柔性数组的结构体
    struct FlexArray {
    int len;
    int arr[]; // 柔性数组
    };

    int main() {
    int n = 5;
    // 动态申请内存:结构体固定大小 + 柔性数组n个int的大小
    struct FlexArray *p = (struct FlexArray*)malloc(sizeof(struct FlexArray) + n * sizeof(int));
    if (p == NULL) {
    printf("内存申请失败!\\n");
    return 1;
    }

    p->len = n;
    // 给柔性数组赋值
    for (int i = 0; i < p->len; i++) {
    p->arr[i] = i + 1;
    }

    printf("===== 柔性数组内容 =====\\n");
    for (int i = 0; i < p->len; i++) {
    printf("%d ", p->arr[i]);
    }

    // 一次free释放所有内存
    free(p);
    p = NULL;

    getchar();
    return 0;
    }

    运行结果

    ===== 柔性数组内容 =====
    1 2 3 4 5

    校招高频考点,亲手敲一遍,理解柔性数组的内存连续特性。


    3.5 实操5:联合union的内存共享特性验证

    #include <stdio.h>

    // 联合定义
    union Test {
    char a;
    int b;
    double c;
    };

    // 用联合判断大小端字节序
    int isLittleEndian() {
    union EndianTest {
    int num;
    char ch;
    } test;
    test.num = 0x12345678;
    // 小端:低地址存低位,ch是0x78;大端:ch是0x12
    return test.ch == 0x78;
    }

    int main() {
    union Test t;
    printf("union Test总大小:%d 字节\\n", sizeof(union Test));

    // 验证内存共享
    t.b = 0x12345678;
    printf("给b赋值后,a的值:0x%X\\n", t.a);

    t.a = 0xAA;
    printf("给a赋值后,b的值:0x%X\\n", t.b);

    // 判断大小端
    if (isLittleEndian()) {
    printf("当前系统是小端字节序\\n");
    } else {
    printf("当前系统是大端字节序\\n");
    }

    getchar();
    return 0;
    }

    运行结果示例(32位小端系统)

    union Test总大小:8 字节
    给b赋值后,a的值:0x78
    给a赋值后,b的值:0x123456AA
    当前系统是小端字节序

    期末选择题必考struct和union的区别,校招面试常考大小端判断,亲手运行一遍,理解联合的内存共享本质。


    3.6 保姆式排错指南

    报错/异常现象核心原因保姆级解决方法
    编译报错“incomplete type is not allowed” 结构体定义后忘记加分号,或者使用了未定义的结构体标签 结构体定义末尾必须加分号,使用前必须先定义结构体
    结构体大小计算永远不对 没考虑内存对齐规则,直接把成员大小相加 严格按照4大内存对齐规则分步计算,注意偏移量和总大小的补齐
    编译报错“expression must have struct or union type” 结构体指针用了.访问成员,或者普通结构体变量用了-> 普通变量用.,结构体指针用->,严格区分使用场景
    结构体传参后,外面的实参没变化 用了值传递,修改的是形参的拷贝 需要修改实参必须用地址传递,传结构体指针给函数
    程序崩溃/内存泄漏 柔性数组忘记malloc,或者多次free 柔性数组必须动态申请内存,整个结构体只需要一次malloc和一次free
    联合的成员值错乱 同时使用了联合的多个成员,覆盖了内存 联合同一时间只能使用一个成员,修改一个成员会覆盖其他成员

    四、我的踩坑记录

  • 结构体定义忘加分号:第一次写结构体,定义末尾忘记加分号,编译报一堆莫名其妙的错误,找了半天才发现少了个分号;
  • 内存对齐算错坑:期末模拟考结构体大小计算题,直接把成员大小相加,结果完全不对,扣了全部分数,才背熟了内存对齐的4大规则;
  • .和->搞混坑:写结构体指针的时候,用.访问成员,编译一直报错,才记住指针必须用->;
  • 值传递效率坑:写学生管理系统,用值传递传结构体,结构体里有200字节的数组成员,程序运行巨卡,后来改成地址传递,效率直接提升了几十倍;
  • 柔性数组malloc坑:第一次用柔性数组,只给结构体malloc了固定大小,没给柔性数组申请内存,直接访问导致程序崩溃;
  • 联合多成员使用坑:给联合的int成员赋值后,又用char成员,结果int的值被覆盖了,数据完全错乱,才记住联合同一时间只能用一个成员;
  • 嵌套结构体对齐坑:算嵌套结构体的大小,忘记了嵌套结构体的偏移量规则,结果算错了,期末选择题直接丢分。

  • 五、课后小练习

    基础必刷题(期末选择/填空考点)

    题1

    要求:计算下面结构体的总大小(32位系统,默认对齐数8),并写出分步计算过程。

    struct Test {
    char a;
    short b;
    int c;
    double d;
    };

    答案:总大小16字节
    分步计算:
    1. char a放在偏移量0,占1字节;
    2. short b自身大小2,对齐数8,取2,偏移量必须是2的倍数,填充1字节,偏移量2开始,占2字节,到偏移量3;
    3. int c自身大小4,取4,偏移量4开始,占4字节,到偏移量7;
    4. double d自身大小8,取8,偏移量8开始,占8字节,到偏移量15;
    5. 总大小16字节,是最大成员大小8的整数倍,无需额外填充。

    题2

    要求:简述struct和union的3个核心区别。

    答案:
    1. 内存布局不同:struct的每个成员有独立的内存空间,union的所有成员共享同一块内存空间;
    2. 大小计算不同:struct的大小是所有成员大小之和+对齐填充,union的大小是最大成员的大小+对齐填充;
    3. 使用方式不同:struct可以同时使用多个成员,union同一时间只能使用一个成员,修改一个成员会覆盖其他成员。

    题3

    要求:定义一个图书结构体Book,包含书名、作者、价格三个成员,定义结构体变量并初始化,用结构体指针访问并输出成员信息。

    #include <stdio.h>

    struct Book {
    char name[50];
    char author[20];
    double price;
    };

    int main() {
    struct Book book = {"C和指针", "肯尼斯·里科", 59.0};
    struct Book *p = &book;
    printf("书名:%s\\n", p->name);
    printf("作者:%s\\n", p->author);
    printf("价格:%.2lf元\\n", p->price);
    getchar();
    return 0;
    }


    进阶高频题(校招面试/期末编程题)

    题4

    要求:用结构体实现学生信息管理,定义学生结构体,包含姓名、学号、3门课成绩,写函数实现:输入学生信息、计算平均分、输出学生信息。

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

    #define N 3 // 学生人数

    struct Student {
    char name[20];
    char id[20];
    double score[3];
    double avg;
    };

    // 输入学生信息
    void inputStudent(struct Student *p, int n) {
    for (int i = 0; i < n; i++) {
    printf("请输入第%d个学生信息(姓名 学号 3门课成绩):\\n", i+1);
    scanf("%s %s %lf %lf %lf", (p+i)->name, (p+i)->id, &(p+i)->score[0], &(p+i)->score[1], &(p+i)->score[2]);
    // 计算平均分
    (p+i)->avg = ((p+i)->score[0] + (p+i)->score[1] + (p+i)->score[2]) / 3.0;
    }
    }

    // 输出学生信息
    void printStudent(const struct Student *p, int n) {
    printf("\\n===== 学生信息表 =====\\n");
    printf("姓名\\t学号\\t平均分\\n");
    for (int i = 0; i < n; i++) {
    printf("%s\\t%s\\t%.1lf\\n", (p+i)->name, (p+i)->id, (p+i)->avg);
    }
    }

    int main() {
    struct Student stus[N];
    inputStudent(stus, N);
    printStudent(stus, N);
    getchar();
    getchar();
    return 0;
    }

    题5

    要求:用联合实现,判断int类型的4个字节中,每个字节的值。

    #include <stdio.h>

    union IntByte {
    int num;
    unsigned char byte[4];
    };

    int main() {
    union IntByte test;
    test.num = 0x12345678;
    printf("int值:0x%X\\n", test.num);
    printf("每个字节的值:\\n");
    for (int i = 0; i < 4; i++) {
    printf("byte[%d] = 0x%X\\n", i, test.byte[i]);
    }
    getchar();
    return 0;
    }


    六、期末/面试考点速记

  • 结构体本质:打包多个不同类型成员的自定义复合类型,内存连续存储;
  • 内存对齐4大规则:首成员偏移0,后续成员偏移量是自身大小和对齐数较小值的整数倍,总大小是最大成员和对齐数较小值的整数倍,嵌套结构体按内部最大成员对齐;
  • 成员访问:普通结构体变量用.,结构体指针用->,期末必考;
  • 结构体传参:优先用地址传递(结构体指针),效率高,需要修改实参必须用地址传递,不需要修改加const保护;
  • 柔性数组:结构体最后一个成员,sizeof不计算大小,内存连续,一次malloc和free,校招高频;
  • 联合union本质:所有成员共享同一块内存,总大小等于最大成员的大小,同一时间只能用一个成员;
  • 联合经典用途:判断系统大小端字节序,校招面试必考;
  • 枚举enum:列举变量的所有可能取值,默认从0开始递增,提升代码可读性,有类型检查。

  • 七、小结

           本章咱们彻底啃透了结构体与联合的底层逻辑,从结构体的内存封装、期末必考的内存对齐规则,到结构体指针、函数传参、柔性数组,再到联合的内存共享特性、枚举的用法,终于掌握了C语言自定义数据类型的所有核心内容。        结构体是《C和指针》里的重要章节,更是期末编程题、校招面试的高频考点,也是后续实现链表、树等数据结构的基础。我踩过的内存对齐算错、传参效率低、.和->搞混这些坑,大家千万别再踩!还是那句贯穿整个专栏的老话:学C语言没有捷径,敲代码、敲代码、敲代码才是硬道理!亲手把每个实操代码敲一遍,比看十遍都管用。        下一章咱们学习动态内存管理:malloc与free全解,结合结构体实现更灵活的动态数据存储,为下一章链表的实现打下基础,继续冲~

    赞(0)
    未经允许不得转载:171主机测评 » 大学生版保姆式学《C和指针》——结构体与联合的底层逻辑
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址