当前位置: 首页 > news >正文

C语言:20250801学习(构造类型)

内容提要

  • 构造类型
    • 结构体
    • 共用体/联合体

构造类型

数据类型

  1. 基本类型/基础类型/简单类型
    • 整型
      • 短整型:short – 2字节
      • 基本整型:int – 4字节
      • 长整型:long – 32位系统4字节/ 64位系统8字节
      • 长长整型:long long 8字节(大多数现代机器,旧机器可能超过8字节),C99新增
      • 注意:以上类型又分为signed(默认) 和 unsigned
    • 浮点型
      • 单精度型:float --4字节
      • 双精度型:double --8字节
      • 长双精度型:long double – 16字节(视平台而定),C99新增
    • 字符型: char --1字节
  2. 指针类型
    • 数据类型*:32位系统4字节,64位系统8字节
    • void*:通用指针类型(万能指针)32位系统4字节,64位系统8字节
  3. 空值类型
    • void:无返回值,无形参(不能修饰变量)
  4. 构造类型(自定义类型)
    • 结构体类型:struct
    • 共用体/联合体类型:union
    • 枚举类型:enum

结构体

结构体定义【定义类型】
  • **定义:**自定义数据类型的一种,关键字struct。

  • 语法:

    struct 结构体名 // 结构体名:自定义的数据类型名字  类似于int,double之类的
    {数据类型1 成员名称1;  // 结构体中的变量叫做成员数据类型2 成员名称2;...
    };
    

    注意:结构体中定义的变量,称之为成员变量(成员属性)

  • 格式说明:

    • 结构体名:合法的标识符,建议首字母大写(所谓的结构体名,就是自定义类型的类型名称)
    • 数据类型n:C语言支持的所有类型(包括函数,函数在这里用函数指针表示)
    • 成员名称n:合法的表示,就是变量的命名标准
    • 数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员
  • 注意:

    • 结构体在定义的时候,成员不能赋值,示例:

      struct Cat
      {int age = 5;      // 错误,结构体定义的时候,并未在内存中申请空间,因此无法进行赋值double height;    // 正确void (*run)(void);// 正确
      };
      
  • 常见的定义格式:

    • 方式1:常规定义(命名结构体,只定义数据类型)

      struct Student
      {int num;           // 学号char name[20];     // 姓名char sex;          // 性别int age;           // 年龄char address[100]; // 籍贯void (*info)(void);// 信息输出(函数指针)
      };
      
    • 方式2:匿名结构体(常用于作为其他结构体的成员使用)

      struct Dog // 命名结构体
      {char *name;        // 姓名int age;           // 年龄struct // 匿名结构体{// 定义结构体时不能省略成员,否则编译报错int year; // 年int month;// 月int day;  // 日} bithday;         // 年龄,匿名结构体一定要提供成员名称,否则无法访问
      };
      

      注意:定义匿名结构体的同时必须定义结构体成员,否则编译报错;结构体可以作为另一个结构体的成员。

      总结:

      • 结构体可以定义在局部位置(函数作用域、块作用域),也可以定义在全局位置(推荐,可以复用)
      • 全局位置的结构体名和局部位置的结构体名可以相同,遵循就近原则(和变量的定义同理)
    • 结构体类型的使用:

      利用结构体类型定义变量、数组,也可以作为函数的返回值和参数;结构体类型的使用与基本数据类型的使用类似。

结构体变量定义【定义变量】
三种形式定义结构体变量

说明:结构体变量也被称作结构体对象或者结构体实例。

  • 第一种方式:

    ① 先定义结构体(定义数据类型)

    ② 然后定义结构体变量(定义变量)

    示例:

    // 定义结构体(定义数据类型)
    struct A
    {int a;char b;
    };// 定义结构体实例/变量(定义变量)
    struct A x;   // A就是数据类型,x就是变量名
    struct A y;   // A就是数据类型,y就是变量名
    
  • 第二种方式:

    ① 在定义结构体的同时,定义结构体变量(同时定义数据类型和变量)

    语法:

    struct 结构体名
    {数据类型1 数据成员1;...
    } 变量列表;
    

    示例:

    struct A
    {int a;char b;
    } x, y; // A就是数据类型,x,y就是变量名
    

    此时定义了一个结构体,x和y就是这个结构体类型的变量。

  • 第三种方式:

    ① 在定义匿名结构体的同时,定义结构体变量。

    示例:

    struct
    {int a;char b;
    } x, y;
    

    此时定义了一个没有名字的结构体(匿名结构体),x,y是这个结构体类型的变量。

匿名结构体
  • 优点:少写一个结构体名称
  • 缺点:只能使用一次,定义结构体类型的同时必须定义变量。
  • 应用场景:
    • 当结构体的类型只需要使用一次,并且定义类型的同时定义了变量。
    • 作为其他结构体的成员使用。
定义结构体同时变量初始化

说明:定义结构体的同时,定义结构体变量并初始化

struct Cat
{int age;char color[20];
} cat;
  • 结构体成员部分初始化,大括号不能省略
  • 结构体成员,没有默认值,是随机值,和局部作用域的变量一致。

案例:

/*************************************************************************> File Name:    demo01.c> Author:       rch> Description:  ************************************************************************/#include <stdio.h>/*** 先定义结构体,再定义结构体变量(实例)*/ 
void fun1()
{// 定义结构体struct A{int a;char b;};// 定义结构体变量struct A x;struct A y;struct A x1,y1;}/*** 定义结构体的同时定义结构体变量*/ 
void fun2()
{struct A{int a;char b;} x,y;struct A z;struct A x1,y1;
}/*** 定义匿名结构体的同时定义结构体变量*/ 
void fun3()
{struct{int a;char b;} x,y;struct{int a;char b;} z;
}int main(int argc,char *argv[])
{fun1();fun2();fun3();return 0;
}
结构体变量的使用【变量使用】
结构体变量访问成员
  • 语法:

    结构体变量名(实例名).成员名;
    

    ① 可以通过访问成员进行赋值(存数据)

    ② 可以通过访问成员进行取值(取数据)

  • 结构体变量未初始化,结构体成员的值是随机的(和局部作用域的变量和数组同理)

结构体变量定义时初始化成员
  • 建议用大括号{}标明数据的范围。
  • 结构体成员初始化,可以部分初始化(和数组类似),部分初始化时一定要带大括号标明数据范围。未初始化的成员使用清零操作。
案例
/*************************************************************************> File Name:    demo01.c> Author:       rch> Description:  > Created Time: 2025-08-01 10:47:18************************************************************************/
#include <stdio.h>/** 定义全局的结构体,方便被多个函数访问*/
struct Dog
{char *name;        // 名字int    age;        // 年龄char   sex;        // 性别 M:公,W:母void (*eat)(void); // 吃狗粮
};void eat()
{printf("狗狗正在吃狗粮!\n");
}/*** 方式1:先定义,再初始化   ---> 结构体变量访问成员*/
void fun1()
{// 定义结构体变量struct Dog dog;// 结构体变量成员赋值dog.name = "旺财";dog.age = 5;dog.sex = 'M';dog.eat = eat;// 结构体变量成员访问printf("%s,%d,%c\n", dog.name, dog.age, dog.sex);// 访问成员方法(函数)dog.eat();
}/*** 方式2:定义的同时初始化,给变量成员初始化*/
void fun2()
{// 定义结构体变量的同时,给变量成员初始化struct Dog d1 = {"旺财", 5, 'M', eat}; // 完整初始化,按照顺序赋值struct Dog d2 = {.name = "莱德", .sex = 'M'}; // C99之后,支持部分成员初始化,未初始化的成员自动填充0struct Dog d3 = {"旺财"}; // C99之后,可以默认初始化第一个成员,其他成员用0填充// 结构体变量成员访问printf("%s,%d,%c\n", d1.name, d1.age, d1.sex);printf("%s,%d,%c\n", d2.name, d2.age, d2.sex);printf("%s,%d,%c\n", d3.name, d3.age, d3.sex);}int main(int argc, char *argv[])
{fun1();printf("\n------------\n");fun2();return 0;
}
结构体数组的定义【数组定义】
什么时候需要结构体数组

比如:我们需要管理一个学生对象,只需要定义一个struct Student yifanjiao;

假如:我们需要管理一个班的学生对象,此时就需要定义一个结构体数组struct Student stus[50];

定义

存放结构体实例的数组,称之为结构体数组。

四种形式定义结构体数组
  • 第一种方式:

    // 第一步:定义一个结构体数组
    struct Student
    {char      *name;     // 姓名int         age;     // 年龄float scores[3];     // 三门课程的成绩
    } stus[3];// 第二步:赋值
    stus[0].name = "张三";
    stus[0].age = 21;
    stus[0].scores[0] = 89;
    stus[0].scores[1] = 99;
    stus[0].scores[2] = 87;stus[1].name = "李四";
    stus[1].age = 22;
    stus[1].scores[0] = 66;
    stus[1].scores[1] = 77;
    stus[1].scores[2] = 88;
    
  • 第二种方式:

    // 第一步:定义一个学生结构体(定义数据类型)
    struct Student
    {char      *name;     // 姓名int         age;     // 年龄float scores[3];     // 三门课程的成绩
    };// 第二步:定义结构体实例(定义结构体实例)
    struct Student zhangsan = {"张三", 21, {89,99,87}};
    struct Student lisi     = {"李四", 22, {66,77,88}};// 第三步:定义结构体数组
    struct Student stus[] = {zhangsan, lisi};
    
  • 第三种方式:

    // 第一步:定义一个学生结构体(定义数据类型)
    struct Student
    {char      *name;     // 姓名int         age;     // 年龄float scores[3];     // 三门课程的成绩
    };// 第二步:定义结构体数组并初始化成员
    struct Student stus[] = {{"张三", 21, {89,99,87}},{"李四", 22, {66,77,88}}
    };
    
  • 第四种方式:

    // 第一步:定义一个结构体数组,并初始化
    struct Student
    {char      *name;     // 姓名int         age;     // 年龄float scores[3];     // 三门课程的成绩   
    } stus[] = {{"张三", 21, {89,99,87}},{"李四", 22, {66,77,88}}
    };
    
结构体数组的访问【数组访问】

语法:

结构体成员.成员名
结构体指针 -> 成员名     // -> 结构体指针成员访问符

举例:

// 方式1:结构体成员访问
(*p).成员名
// 方式2:结构体指针访问
p -> 成员名

案例:

/*************************************************************************> File Name:    demo02.c> Author:       rch> Description:  > Created Time: 2025-08-01 11:29:40************************************************************************/
#include <stdio.h>/* 定义全局的Student结构体 */
struct Student
{int          id;         // 编号char      *name;         // 姓名int         age;         // 年龄float scores[3];         // 三门课成绩void (*info)(char*,int); // 信息输出
};void info(char* name, int age)
{printf("大家好,我是%s,今年%d岁!\n", name, age);
}int main(int argc, char *argv[])
{// 定义结构体实例并初始化struct Student zhangsan = {1, "张三", 21, {78,88,98}, info};struct Student lisi     = {2, "李四", 22, {90,98,91}, info};// lisi.info = info// 定义结构体数组并初始化struct Student stus[] = {zhangsan, lisi};// 计算数组的大小int len = sizeof(stus) / sizeof(stus[0]);// 用一个指针进行遍历struct Student *p = stus;// 表格-表头printf("序号\t姓名\t年龄\t语文\t数学\t英语\n");for (; p < stus + len; p++){// 结构体成员访问,不推荐// printf("%d\t%s\t%d\t%.2f\t%.2f\t%.2f\t\n",(*p).id, (*p).name, (*p).age, (*p).scores[0], (*p).scores[1], (*p).scores[2]);// (*p).info((*p).name, (*p).age);// 结构体指针访问,推荐printf("%d\t%s\t%d\t%.2f\t%.2f\t%.2f\t\n",p->id, p->name, p->age, p->scores[0], p->scores[1], p->scores[2]);// 函数调用p->info(p->name, p->age);}printf("\n");return 0;
}

结构体类型

结构体数组
案例
  • 需求:对候选人得票的统计程序。设有3个候选人,每次输入一个得票的候选人名字,要求最后输出个人得票的结果。

  • 案例:

    /*************************************************************************> File Name:    demo03.c> Author:       rch> Description:  > Created Time: 2025-08-01 14:11:32************************************************************************/
    #include <stdio.h>
    #include <string.h>#define LEN 3/* 定义一个候选人结构体 */
    struct Person
    {char name[20];     // 名字int count;         // 票数
    };/*** 定义候选人数组,并初始化*/
    struct Person persons[LEN] = {{"张三", 0},{"李四", 0},{"王五", 0}	
    };int main(int argc, char *argv[])
    {// 定义循环变量register int i,  j;// 创建一个数组,用来接收控制台录入的候选人名字char leader_name[20];// 使用一个for循环,默认10个人参与投票printf("世纪美男投票系统!\n");for (i = 0; i < 10; i++){printf("请输入您觉得最帅的那位哥哥的名字:\n");scanf("%s", leader_name);// 从候选人列表中匹配被投票的人 count++for (j = 0; j < LEN; j++){// 判断两个字符串是否相等  strcmpif (strcmp(leader_name, persons[j].name) == 0){persons[j].count++; // 票数+1}}}printf("\n");printf("\n投票结果:\n");struct Person *p = persons; // 指针p指向数组persons的第一个元素// 遍历数组:使用指针变量来遍历数组for (; p < persons + LEN; p++){printf(" %s:%d\n",p->name, p->count);}printf("\n");// 遍历数组:使用指针来遍历数组for (i = 0; i < LEN; i++){printf(" %s:%d\n",(persons + i)->name, (persons + i)->count);}printf("\n");// 遍历数组:使用下标来遍历数组for (i = 0; i < LEN; i++){printf(" %s:%d\n",persons[i].name, persons[i].count);}return 0;
    }
    
结构体指针
  • **定义:**指向结构体变量或者结构体数组的起始地址的指针叫做结构体指针。

  • 语法:

    struct 结构体名 *指针变量列表;
    
  • 举例:

    /*************************************************************************> File Name:    demo04.c> Author:       rch> Description:  > Created Time: 2025-08-01 14:44:34************************************************************************/
    #include <stdio.h>// 定义一个Dog结构体
    struct Dog
    {char name[20];int age;
    };int main(int argc, char *argv[])
    {// 创建Dog实例struct Dog dog = {"苟富贵", 5};// 基于结构体变量的结构体指针struct Dog *p = &dog;printf("%s,%d\n", p->name, p->age);// 创建Dog数组struct Dog dogs[] = {{"苟富贵", 5},{"勿相忘", 6}};// 基于结构体数组元素的结构体指针struct Dog *p1 = dogs;int len = sizeof(dogs)/sizeof(dogs[0]);for (; p1 < dogs + len ; p1++){printf("%s,%d\n", p1->name, p1->age);}return 0;
    }
    
结构体成员的访问
结构体成员访问
  • 结构体数组名访问结构体成员

    • 语法:

       结构体数组名 -> 成员名;(*结构体数组名).成员名; // 等价于上面写法
      
    • 举例:

       printf("%s:%d\n",persons->name, persons->count);
      
  • 结构体成员访问符

    • .:左侧是结构体变量,也可以叫做结构体对象访问成员符,右侧是结构体成员。

    • ->:左侧是结构体指针,也可以叫做结构体指针访问成员符,右侧是结构体成员。

    • 举例:

       struct Person *p = persons;  // p就是结构体指针for(; p < persons + len; p++)printf("%s:%d\n",p->name,p->count);
      
  • 访问结构体成员有两种类型,三种方式:

    • **类型1:**通过结构体变量(对象|示例)访问成员

       struct Stu{int id;char name[20];} stu; // 结构体变量// 访问成员stu.name;
      
    • **类型2:**通过结构体指针访问成员

      • **第1种:**指针引用访问成员

         struct Stu{int id;char name[20];} stu;struct Stu *p = &stu;// 指针引用访问成员p -> name; // 等价于 (*p).name;    
        
      • **第2种:**指针解引用间接访问成员

         struct Stu{int id;char name[20];} stu;struct Stu *p = &stu;// 指针引用访问成员(*p).name; // 等价于 p -> name;    
        
    • 结构体数组中元素的访问

       // 结构体数组struct Stu{int id;         // 编号(成员)char name[20];  // 名字(成员)float scores[3];// 三门成绩(成员)} stus[3] = {{1,"张三"{90,89,78},{2,"李四"{90,88,78},{3,"王五"{77,89,78},};// 取数据 --- 下标法printf("%s,%.2f\n", stus[1].name, stus[1].scores[1]);// 李四 88// 取数据 --- 指针法printf("%s,%.2f\n", stus->name, stus->scores[2]);// 张三 78printf("%s,%.2f\n", (stus+1) -> name, (stus+1) -> scores[1]);// 李四 88printf("%s,%.2f\n", (*(stus+1)).name, (*(stus+1)).scores[1]);// 李四 88
      

      小贴士:

      结构体是自定义数据类型,它是数据类型,用法类似于基本类型的int;

      结构体数组它是存放结构体对象的数组,类似于int数组存放int数据;

      基本类型数组怎么用,结构体数组就怎么用—>可以遍历,可以作为形式参数,也可以做指针等;

  • 结构体类型的使用案例

    结构体可以作为函数的返回类型,形式参数等。

    举例:

    /*************************************************************************> File Name:    demo05.c> Author:       rch> Description:  > Created Time: 2025-08-01 14:55:44************************************************************************/
    #include <stdio.h>
    #include <string.h>/*** 定义一个Cat结构体*/
    struct Cat
    {char     *name;      // 姓名int        age;      // 年龄char color[20];      // 颜色
    };/*** 结构体类型作为形式参数*/
    void test1(struct Cat c)
    {printf("test1:\n%s,%d,%s\n", c.name, c.age, c.color);
    }/*** 结构体类型作为函数返回类型*/
    struct Cat test2(struct Cat c)
    {c.name = "金宝";c.age = 3;strcpy(c.color, "黄色");return c;
    }/*** 结构体指针作为参数和返回类型* 需求:根据Cat的name,在Cat数组中匹配Cat对象*/
    struct Cat *test3(struct Cat *cats, int len, char* name)
    {struct Cat *p = cats;for (; p < cats + len; p++){if (strcmp(name, p->name) == 0) return p; // p是指针}return NULL;
    }int main(int argc, char *argv[])
    {struct Cat cat = {"招财", 5, "白色"};test1(cat);struct Cat c = test2(cat);printf("test2:\n%s,%d,%s\n", c.name, c.age, c.color);struct Cat cats[] = {{"招财", 5, "白色"},{"金宝", 3, "金色"}};struct Cat *c2 = test3(cats, sizeof(cats)/sizeof(cats[0]),"招财");printf("test3:\n%s,%d,%s\n", c2->name, c2->age, c2->color);return 0;
    }
    
结构体类型求大小
字节对齐
  • 字节对齐的原因:

    1. 硬件要求 某些硬件平台(如ARM、x86)要求特定类型的数据必须对齐到特定地址,否则会引发性能下降或硬件异常。
    2. 优化性能 对齐的数据访问速度更快。例如,CPU访问对齐的 int 数据只需一次内存操作,而未对齐的数据可能需要多次操作。
  • 字节对齐规则:

    1. 默认对齐规则
      • 结构体的每个成员按其类型大小和编译器默认对齐数(通常是类型的自然对齐数)对齐。
      • 结构体的总大小必须是最大对齐数的整数倍。
    2. 对齐细节
      • 基本类型的对齐数char(1字节)、short(2字节)、int(4字节)、double(8字节)。
      • 结构体成员的对齐每个成员的起始地址必须是对齐数的整数倍
      • 结构体总大小的对齐结构体的总大小必须是其最大对齐数的整数倍
    3. #pragma pack(n) 的影响 使用 #pragma pack(n) 可以强制指定对齐数为 nn 为 1、2、4、8、16)。此时:
      • 每个成员的对齐数取 n 和其类型大小的较小值。
      • 结构体的总大小必须是 n 和最大对齐数中的较小值的整数倍。
  • 对齐示例

    • 默认对齐

       struct S1{char c;  // 1字节,偏移0int i;   // 4字节,(需对齐到4,填充3字节,偏移4-7)double d;// 8字节,(需对齐到8,偏移8~15)}
      
       struct S2{double d;char c;  int i;       }
      
       struct S3{char c;  double d;   int i; }
      

      总结,结构体中,成员的顺序会影响到结构体最终的大小

      • 使用#pragma pack(1) 自定义对齐规则,(1)对齐的字节数

          #pragma pack(1)  // 对齐数之前的字节数能被1整数struct S1{char c; // 1字节 (偏移0) 1int i;// 4字节 (偏移1~4)4double d;// 8字节 (偏移5~12)8}; #pragma pack()// S1 的大小为 13字节
        
          #pragma pack(2) // 对齐数之前的字节数能被2整数struct S1{char c; // 1字节 (偏移0,填充1字节) 2int i;// 4字节 (偏移2~5)4double d;// 8字节 (偏移6~13)8}; #pragma pack()// S1 的大小为 14字节   char -1 + 1  int
        
          #pragma pack(4) // 对齐数之前的字节数能被4整数struct S1{char c; // 1字节 (偏移0,填充3字节) 4int i;// 4字节 (偏移4~7)4double d;// 8字节 (偏移8~15)8}; #pragma pack()// S1 的大小为 16字节
        
      • 在GNU标准中,可以在定义结构体时,指定对齐规则:

           __attribute__((packed));      -- 结构体所占内存大小是所有成员所占内存大小之和——attribute__((aligned(n)));  -- 设置结构体占n个字节,如果n比默认值小,n不起作用;n必须是2的次方
        
      • 案例:

        /*************************************************************************> File Name:    demo06.c> Author:       rch> Description:  > Created Time: 2025-08-01 16:08:50************************************************************************/
        #include <stdio.h>int main(int argc, char *argv[])
        {struct Cat{char sex __attribute((aligned(2))); // 设置 char按照2字节对齐int id;   // 4char name[20];// 20}__attribute__((packed));  // 28  25printf("%ld\n",sizeof(struct Cat));// 28 25return 0;
        }
        
柔性数组

定义:柔性数组不占有结构体的大小。

特点:

  1. 柔性数组必须是结构体的最后一个成员
  2. 结构体大小在编译时是未确定的
  3. 必须动态分配内存,指定数组的大小

语法:

 struct St{...char arr[];}

案例:

/*************************************************************************> File Name:    demo07.c> Author:       rch> Description:  > Created Time: 2025-08-01 16:21:01************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>/* 定义包含柔性数组的结构体 */
struct FAStruct
{int     len;    // 用于记录柔性数组的长度char data[];    // 柔性数组,注意这里没有指定大小。柔性数组只能用于结构体,并且只能是最后一个成员。
};int main(int argc, char *argv[])
{const char* str1 = "Hello, Flexible Array!";const char* str2 = "hello world!";int str_len1 = strlen(str1) + 1; // +1 为了存储\0int str_len2 = strlen(str2) + 1;// 使用calloc分配内存,为结构头和柔性数组部分分配内存struct FAStruct *fas1 = (struct FAStruct*)calloc(1,sizeof(struct FAStruct) + str_len1); // 结构体空间大小 + 数组空间大小struct FAStruct *fas2 = (struct FAStruct*)calloc(1,sizeof(struct FAStruct) + str_len2); // 结构体空间大小 + 数组空间大小if (!fas1 || !fas2){perror("内存申请失败!");return -1;}fas1->len = str_len1 - 1; // 不包含字符串结束符的长度strcpy(fas1->data, str1);fas2->len = str_len2 - 1; // 不包含字符串结束符的长度strcpy(fas2->data, str2);printf("字符串1:%s,%d\n", fas1->data,fas1->len);printf("字符串2:%s,%d\n", fas2->data,fas2->len);// 释放内存free(fas1);free(fas2);return 0;
}
课堂练习

计算以下结构体的大小

/*************************************************************************> File Name:    demo04.c> Author:       rch> Description:  结构体类型求大小> Created Time: 2025年8月1日 ************************************************************************/#include <stdio.h>// 定义测试结构体
struct TEST1
{char a;int b; 
}; struct TEST1_1
{char a;int b;
}__attribute__((packed));// 取消字节对齐,取消之后,结构体数据类型大小就等于其所有成员的数据类型之和struct TEST1_2
{char a __attribute__((aligned(2)));int b;
};struct TEST2
{char a;short c; int b; 
};struct TEST3
{int num;char name[10];char sex;int age;double score;
};struct TEST3_1
{int num;char name[10];char sex;double score;int age;
};struct TEST4
{int num;short name[5];char sex;int age;int scores[2];
};
int main(int argc,char *argv[])
{// 创建结构体变量struct TEST1 test1;struct TEST2 test2;struct TEST3 test3;struct TEST3_1 test3_1;struct TEST4 test4;  struct TEST1_1 test1_1;struct TEST1_2 test1_2;// 计算大小printf("%lu\n",sizeof(test1));// 8printf("%lu\n",sizeof(test2));// 8printf("%lu\n",sizeof(test3));// 32printf("%lu\n",sizeof(test3_1));// 28printf("%lu\n",sizeof(test4));// 28printf("%lu\n",sizeof(test1_1));// 5printf("%lu\n",sizeof(test1_2));// 8return 0;
}

结构体的常见陷阱与最佳实践

常见陷阱
  1. 成员访问越界:访问不存在的结构体成员。
 struct Point p;p.z = 10; // 错误,Point结构体没有z成员
  1. 内存泄漏:忘记释放动态分配的结构体内存。
 struct Point *p = malloc(sizeof(struct Point));p->x = 10;// 没有free(p)导致内存泄漏
  1. 悬挂指针/空悬指针:使用已经释放的内存或未初始化的指针。
 struct Point *p = malloc(sizeof(struct Point));free(p); // 释放了p指向的内存p->x = 10; // 错误,p现在是悬挂指针
  1. 结构体大小计算错误:忘记考虑编译器的内存对齐。
 struct {char a;int b;} s;// sizeof(s)可能是8而不是5
最佳实践
  1. 使用typedef简化语法:为结构体创建简短的别名。
 typedef struct {int x;int y;} Point;
  1. 将相关数据组织在一起:使用结构体封装相关数据。
 struct Student {char name[50];int age;float grade;};
  1. 避免过大的结构体:将大型数据结构分解为多个较小的结构体。
  2. 合理使用指针:对于大型结构体,使用指针而不是值传递。
  3. 注意内存管理:确保正确分配和释放动态内存。

共用体/联合体类型

  • 定义:使几个不同变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是使用该空间,它们的首地址是相同。

  • 定义格式

      union 共用体名称{数据类型 成员名;数据类型 成员名;...};
    
  • 共用体的定义和结构体类似。

    • 可以有名字,也可以匿名

    • 共用体在定义时也可以定义共用体变量

    • 共用体在定义时也可以初始化成员

    • 共用体也可以作为形参和返回值类型使用

    • 共用体也可以定义共用体变量

    • 也就是说,结构体的语法,共用体都支持

  • 注意:

    • 共用体弊大于利,尽量少用,一般很少用;

    • 共用体变量在某一时刻只能存储一个数据,并且也只能取出一个数

    • 共用体所有成员共享同一内存空间,同一时间只能存储一个值,可能导致数据覆盖

      image-20250326172130719
    • 共用体和结构体都是自定义数据类型,用法类似于基本数据类型

      • 共用体可以是共用体的成员,也可以是结构体的成员
      • 结构体可以是结构体的成员,也可以是共用体的成员

案例:

/*************************************************************************> File Name:    demo06.c> Author:       rch> Description:  > Created Time: 2025年08月1日 ************************************************************************/#include <stdio.h>/*** 定义共用体*/ 
union S
{char a;float b;int c;
};// S的大小是4字节// 共用体作为共用体成员
union F
{char a;union S s; // 4字节
};// F的大小是4字节// 定义一个结构体
struct H
{int a;char b;
};// H的大小是8字节// 结构体作为结构体成员
struct I
{int a;int b;struct H h;
}; // I的大小是16字节// 共用体作为结构体成员
struct J
{int a; // 4char b; // 1 + 3union S s; // 4
}; // J的大小是12字节void test1()
{// 定义一个共用体(数据类型)union Obj{int num;char sex;double score;};// 定义匿名共用体union{int a;char c;} c;// 定义变量union Obj obj;// 存储数据obj.num = 10; // 共用体空间数据:10obj.sex = 'A';// 共用体空间数据:'A' = 65 覆盖数据// 运算obj.num += 5; // 共用体空间数据:70  覆盖数据  'F'   sizeof(数据类型|变量名)printf("%lu,%lu,%d,%c,%.2lf\n",sizeof(obj),sizeof(union Obj), obj.num, obj.sex, obj.score);
}int main(int argc,char *argv[])
{test1();return 0;
}

案例:

/*************************************************************************> File Name:    demo05.c> Author:       rch> Description:  > Created Time: 2025-08-01 17:14:13************************************************************************/
#include <stdio.h>union Object
{char a;  // 8字节int b;   // 8字节double c;// 8字节
}; // 8字节,共用体所有成员共享最大成员的内存空间。union Object fun(union Object obj)
{return obj;
}int main(int argc, char *argv[])
{union Object obj;obj.a = 65;// charprintf("%c\n", fun(obj).a); // charobj.b = 10000000;printf("%d\n", fun(obj).b);// intobj.c = 12.25;printf("%f\n", fun(obj).c);// doublereturn 0;
}

要求:共用体在使用的时候,建议存取时使用的成员是一致的。也就是使用成员a存数据,必须使用成员b取数据。

结构体与共用体的主要区别

  1. 内存占用
    • 结构体的成员是连续存储的,每个成员都有自己的内存空间。
    • 共用体的所有成员共享同一块内存空间(共享所有成员中最大成员的空间)。
  2. 访问方式
    • 结构体的成员可以同时访问。
    • 共用体的成员不能同时访问,因为它们共享同一块内存空间。
  3. 使用场景
    • 结构体适用于需要同时存储多个不同类型数据的情况。
    • 共用体适用于需要在同一时间存储不同类型数据中的一种的情况,可以节省内存。
http://www.lryc.cn/news/607675.html

相关文章:

  • 机器学习:开启智能时代的钥匙
  • MySQL 高并发下如何保证事务提交的绝对顺序?
  • 学习笔记:原子操作与锁以及share_ptr的c++实现
  • synchronized 深度剖析:从语法到锁升级的完整演进
  • 什么是Sedex审核?Sedex审核的主要内容,Sedex审核的流程
  • 通用障碍物调研
  • 【C++进阶】一文吃透静态绑定、动态绑定与多态底层机制(含虚函数、vptr、thunk、RTTI)
  • 测试分类:详解各类测试方式与方法
  • 使用gcc代替v语言的tcc编译器提高编译后二进制文件执行速度
  • Trust Management System (TMS)
  • MySQL锁的分类 MVCC和S/X锁的互补关系
  • Linux编程: 10、线程池与初识网络编程
  • GESP2025年6月认证C++八级( 第三部分编程题(1)树上旅行)
  • 链表【各种题型+对应LeetCode习题练习】
  • 《C++》STL--list容器详解
  • UnionApplication
  • 江协科技STM32 12-2 BKP备份寄存器RTC实时时钟
  • 【Shell脚本自动化编写——报警邮件,检查磁盘,web服务检测】
  • Windows安装虚拟机遇到内容解码失败
  • python-异常(笔记)
  • Java学习-运算符
  • Java:JWT 从原理到高频面试题解析
  • 【Linux】重生之从零开始学习运维之Mysql
  • Rust在CentOS 6上的移植
  • 2025.8.1
  • 1661. 每台机器的进程平均运行时间
  • 系统开机时自动执行指令
  • 基于python大数据的招聘数据可视化及推荐系统
  • 算法思想之 多源 BFS 问题
  • 【Node.js安装注意事项】-安装路径不能有空格