跳转至

构造类型

结构体

  • 产生及意义:把不同类型的数据存储在一块连续的空间。

  • 结构体描述。结构体定义时不占内存空间,不要在定义是初始化。简单意义上,结构体是一个类型,定义结构体是在描述结构体类型。类似你在描述int,int这个类型不占空间,而是int类型变量占空间。

  • 结构体成员是按照结构体描述的顺序在内存中顺序存放的。

  • 结构体可以只用值或地址在函数中传递,建议使用地址传递,以减少内存开销。

  • 结构体定义建议放在函数外,整个程序声明和定义的位置。

    struct birthday
    {
        int year, month, day;
    };
    
    struct student_st
    {
        int id;
        float math;
        struct birthday birth;
    };
    
    // 多种结构体赋值
    struct student_st stu = { 10011, 90, {2010, 10, 10} };
    struct student_st stu = { .id = 10012, .math = 100, .birth.day = 10 };
    
    struct student_st* p = &stu;
    stu.id = 10012;     // 通过变量名引用
    p->id = 10012;      // 通过指针引用
    
    // 结构体数组
    struct student_st arr[N];
    

  • 没有名字结构体(无头结构体),在声明时要将变量定义完成

    struct
    {
        int id;
        float math;
        struct birthday birth;
    } stu01 = { ... }, stu02, stu03, * stu04, * stu05;
    

  • 占用空间大小。结构体不要求地址对齐时,其大小就是其成员各个空间大小的和。地址对齐的大小根据机器环境决定。

    // 地址对齐:简单理解为 :addr % sizeof(当前这个成员变量);addr可以假设从0开始。
    // 当余数为0时存放,不为0时向下偏移直到余数为0时存放。
    // 空出的地址也属于该结构体变量,不能被其他人使用。
    
    // 禁止地址对齐
    struct birthday
    {
        int year, month, day;
    }__attribute__((packed));
    
    // __attribute__((packed)) 在msvc中无法编译,clang中编译通过。
    

  • 变长结构体

    /*
        例如在网络传输中,并不知道对方发送的数据大小,
        那么以最大空间进行接收,再进行判断。
    */
    struct node_st
    {
        struct node_st *prev;
        struct node_st *next;
    
        /* c99才允许长度为0的数组空间,旧的不允许 */
        char data[0]; 
        /* 
            char data[1];  
            保险写法可以是1,最后计算时减去该数组的大小
            根据地址对齐,最后减去的应该是4个字节。
        */
    };
    

  • 简易学生管理系统

    /*
        MSVC中编译通过
    */
    
    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    #define NAMESIZE 32
    
    struct student_st
    {
        int id;
        char name[NAMESIZE];
        int math;
        int chinese;
    };
    
    void stu_set(struct student_st* p, const struct student_st* q);
    void stu_show(struct student_st* p);
    void stu_changename(struct student_st* p, const char* newname);
    void menu(void);
    
    int main(void)
    {
        struct student_st stu, tmp;
        char newname[NAMESIZE];
        int choice = 0, ret = 0 ;
    
        do {
            menu();
            ret = scanf("%d", &choice);
            if (ret != 1)
            {
                break;
            }
    
            switch (choice)
            {
            case 1:
                printf("Please enter for the sud[id name math chinese]: ");
                ret = scanf("%d%s%d%d", &tmp.id, tmp.name, &tmp.math, &tmp.chinese);
                if (ret != 4)
                {
                    printf("Input Error\n");
                    exit(1);
                }
    
                stu_set(&stu, &tmp);
                break;
    
            case 2:
                printf("Please enter the newname: ");
                ret = scanf("%s", newname);
                if (ret != 1)
                {
                    printf("Input Error\n");
                    exit(1);
                }
    
                stu_changename(&stu, newname);
                stu_show(&stu);
                break;
    
            case 3:
                stu_show(&stu);
                break;
    
            default:
                printf("No this number\n");
                break;
            }
        } while (true);
    
        return 0;
    }
    
    /*
        将tmp中的信息拷贝到学生结构体stu中
    */
    void stu_set(struct student_st* stu, const struct student_st* tmp)
    {
        *stu = *tmp;
    }
    
    /*
        显示学生结构体信息
    */
    void stu_show(struct student_st* p)
    {
        printf("%d %s %d %d\n", p->id, p->name, p->math, p->chinese);
    }
    
    /*
        修改学生结构体中姓名
    */
    void stu_changename(struct student_st* p, const char* newname)
    {
        strncpy(p->name, newname, NAMESIZE);
    }
    
    /*
        选择菜单
    */
    void menu(void)
    {
        printf("\n1 set\t2 change name\t3 show\n");
        printf("Please enter the num(q for quit): ");
    }
    

共用体

  • 产生及意义:有多个成员,但只有一个存在,内存根据成员中所占空间最大的来分配。说白了就是多个成员共用同一块空间。

  • 基本实例

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    #define NAMESIZE 32
    
    union MyUnion
    {
        int n;
        char ch;
        struct
        {
            int id;
            char name[NAMESIZE];
        } student;
    };
    
    int main(void)
    {
    
        union MyUnion un;
        strncpy(un.student.name, "Lisa", NAMESIZE);
        printf("%s\n", un.student.name);
    
        return 0;
    }
    

  • 共用体和结构体嵌套使用的技巧

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    #include <stdint.h>
    
    int main(void)
    {
        /*
            任务:将内存中高16位和低16位相加
        */ 
    
        uint32_t n = 0x11223344;
    
        // 位运算实现示例
        printf("%x\n", (n >> 16) + n & 0xFFFF); // 4466
    
        // 共用体和结构体嵌套实现示例
        union
        {
            struct
            {
                uint16_t i;
                uint16_t j;
            } add;
            uint32_t n;
        } test;
        test.n = n;     // 先按32位存储
        printf("%x\n", test.add.i + test.add.j); // 再看成两个16位相加
    
        return 0;
    }
    

  • 位域

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    #include <stdint.h>
    
    union MyUnion
    {
        struct MyStruct
        {
            unsigned char a : 1;    // 只占 1 位
            unsigned char b : 2;    // 只占 2 位
            unsigned char c : 1;    // 只占 1 位
        } x;
        unsigned char y;
    } test;
    
    int main(void)
    {
        /*
            -----------------
            | | | | | | | |1|
            -----------------
                           a        a 占一位
                       b b          b 占两位
                     c              c 占一位
    
            二进制以补码存储
        */
    
    
        test.y = 1;                 // 只有a是1,其他位都是0
        printf("%u\n", test.x.a);   // 输出:1
    
        test.y = 4;                 // 4 的二进制 100   b的第二位为1,第一位和a都是0
        printf("%u\n", test.x.a);   // 输出:0
        printf("%u\n", test.x.b);   // 输出:2
    
        return 0;
    }
    

枚举类型

  • 枚举类型很多时候可以作为宏来使用,但不能等同于宏。枚举类型并不会在预处理阶段被解析。

  • 枚举类型示例

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    #include <stdint.h>
    
    enum
    {
        MON = 1,
        TUS,
        THR,
        WES,
        FRI,
        SAT,
        SUN,
    };
    
    enum
    {
        STATE_RUNNING = 1,
        STATE_CANCELED,
        STATE_OVER,
        LOOP = 1,
        RUN,
    };
    
    enum
    {
        A,
        B,
        C,
    };
    
    enum INSERT_MODE
    {
        INSERT_FORWARD = 1,
        INSERT_BACKWARD
    };
    
    int main(void)
    {
        enum INSERT_MODE mode = INSERT_FORWARD;
    
        printf("%d\n", FRI);
        printf("%d\n", STATE_OVER);
    
        // RUN的上一个重新写为1,那么后面的内容也从1开始重新 +1
        // 所有输出为 2
        printf("%d\n", RUN);
    
        // 不写的话从 0 开始
        printf("%d\n", A);
    
        return 0;
    }