内容提要
- 构造类型
- 结构体
- 共用体/联合体
构造类型
数据类型
- 基本类型/基础类型/简单类型
- 整型
- 短整型: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字节
- 整型
- 指针类型
- 数据类型*:32位系统4字节,64位系统8字节
- void*:通用指针类型(万能指针)32位系统4字节,64位系统8字节
- 空值类型
- void:无返回值,无形参(不能修饰变量)
- 构造类型(自定义类型)
- 结构体类型:struct
- 共用体/联合体类型:union
- 枚举类型:enum
结构体
结构体定义【定义类型】
-
**定义:**自定义数据类型的一种,关键字struct。
-
语法:
cstruct 结构体名 // 结构体名:自定义的数据类型名字 类似于int,double之类的 { 数据类型1 成员名称1; // 结构体中的变量叫做成员 数据类型2 成员名称2; ... };
注意:结构体中定义的变量,称之为成员变量(成员属性)
-
格式说明:
- 结构体名:合法的标识符,建议首字母大写(所谓的结构体名,就是自定义类型的类型名称)
- 数据类型n:C语言支持的所有类型(包括函数,函数在这里用函数指针表示)
- 成员名称n:合法的表示,就是变量的命名标准
- 数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员
-
注意:
-
结构体在定义的时候,成员不能赋值,示例:
cstruct Cat { int age = 5; // 错误,结构体定义的时候,并未在内存中申请空间,因此无法进行赋值 double height; // 正确 void (*run)(void);// 正确 };
-
-
常见的定义格式:
-
方式1:常规定义(命名结构体,只定义数据类型)
cstruct Student { int num; // 学号 char name[20]; // 姓名 char sex; // 性别 int age; // 年龄 char address[100]; // 籍贯 void (*info)(void);// 信息输出(函数指针) };
-
方式2:匿名结构体(常用于作为其他结构体的成员使用)
cstruct Dog // 命名结构体 { char *name; // 姓名 int age; // 年龄 struct // 匿名结构体 { // 定义结构体时不能省略成员,否则编译报错 int year; // 年 int month;// 月 int day; // 日 } bithday; // 年龄,匿名结构体一定要提供成员名称,否则无法访问 };
注意:定义匿名结构体的同时必须定义结构体成员,否则编译报错;结构体可以作为另一个结构体的成员。
总结:
- 结构体可以定义在局部位置(函数作用域、块作用域),也可以定义在全局位置(推荐,可以复用)
- 全局位置的结构体名和局部位置的结构体名可以相同,遵循就近原则(和变量的定义同理)
-
结构体类型的使用:
利用结构体类型定义变量、数组,也可以作为函数的返回值和参数;结构体类型的使用与基本数据类型的使用类似。
-
结构体变量定义【定义变量】
三种形式定义结构体变量
说明: 结构体变量也被称作结构体对象或者结构体实例。
-
第一种方式:
① 先定义结构体(定义数据类型)
② 然后定义结构体变量(定义变量)
示例:
c// 定义结构体(定义数据类型) struct A { int a; char b; }; // 定义结构体实例/变量(定义变量) struct A x; // A就是数据类型,x就是变量名 struct A y; // A就是数据类型,y就是变量名
-
第二种方式:
① 在定义结构体的同时,定义结构体变量(同时定义数据类型和变量)
语法:
cstruct 结构体名 { 数据类型1 数据成员1; ... } 变量列表;
示例:
cstruct A { int a; char b; } x, y; // A就是数据类型,x,y就是变量名
此时定义了一个结构体,x和y就是这个结构体类型的变量。
-
第三种方式:
① 在定义匿名结构体的同时,定义结构体变量。
示例:
cstruct { int a; char b; } x, y;
此时定义了一个没有名字的结构体(匿名结构体),x,y是这个结构体类型的变量。
匿名结构体
- 优点:少写一个结构体名称
- 缺点:只能使用一次,定义结构体类型的同时必须定义变量。
- 应用场景:
- 当结构体的类型只需要使用一次,并且定义类型的同时定义了变量。
- 作为其他结构体的成员使用。
定义结构体同时变量初始化
说明:定义结构体的同时,定义结构体变量并初始化
c
struct Cat
{
int age;
char color[20];
} cat;
- 结构体成员部分初始化,大括号不能省略
- 结构体成员,没有默认值,是随机值,和局部作用域的变量一致。
案例:
c
/*************************************************************************
> 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;
}
结构体变量的使用【变量使用】
结构体变量访问成员
-
语法:
c结构体变量名(实例名).成员名;
① 可以通过访问成员进行赋值(存数据)
② 可以通过访问成员进行取值(取数据)
-
结构体变量未初始化,结构体成员的值是随机的(和局部作用域的变量和数组同理)
结构体变量定义时初始化成员
- 建议用大括号
{}
标明数据的范围。 - 结构体成员初始化,可以部分初始化(和数组类似),部分初始化时一定要带大括号标明数据范围。未初始化的成员使用清零操作。
案例
c
/*************************************************************************
> 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之后,支持部分成员初始化,未初始化的成员自动填充0
struct 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];
定义
存放结构体实例的数组,称之为结构体数组。
四种形式定义结构体数组
-
第一种方式:
c// 第一步:定义一个结构体数组 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;
-
第二种方式:
c// 第一步:定义一个学生结构体(定义数据类型) 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};
-
第三种方式:
c// 第一步:定义一个学生结构体(定义数据类型) struct Student { char *name; // 姓名 int age; // 年龄 float scores[3]; // 三门课程的成绩 }; // 第二步:定义结构体数组并初始化成员 struct Student stus[] = { {"张三", 21, {89,99,87}}, {"李四", 22, {66,77,88}} };
-
第四种方式:
c// 第一步:定义一个结构体数组,并初始化 struct Student { char *name; // 姓名 int age; // 年龄 float scores[3]; // 三门课程的成绩 } stus[] = { {"张三", 21, {89,99,87}}, {"李四", 22, {66,77,88}} };
结构体数组的访问【数组访问】
语法:
c
结构体成员.成员名
结构体指针 -> 成员名 // -> 结构体指针成员访问符
举例:
c
// 方式1:结构体成员访问
(*p).成员名
// 方式2:结构体指针访问
p -> 成员名
案例:
c
/*************************************************************************
> 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个候选人,每次输入一个得票的候选人名字,要求最后输出个人得票的结果。
-
案例:
c/************************************************************************* > 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++) { // 判断两个字符串是否相等 strcmp if (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; }
结构体指针
-
**定义:**指向结构体变量或者结构体数组的起始地址的指针叫做结构体指针。
-
语法:
cstruct 结构体名 *指针变量列表;
-
举例:
c/************************************************************************* > 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; }
结构体成员的访问
结构体成员访问
-
结构体数组名访问结构体成员
-
语法:
c结构体数组名 -> 成员名; (*结构体数组名).成员名; // 等价于上面写法
-
举例:
cprintf("%s:%d\n",persons->name, persons->count);
-
-
结构体成员访问符
-
.
:左侧是结构体变量,也可以叫做结构体对象访问成员符,右侧是结构体成员。 -
->
:左侧是结构体指针,也可以叫做结构体指针访问成员符,右侧是结构体成员。 -
举例:
cstruct Person *p = persons; // p就是结构体指针 for(; p < persons + len; p++) printf("%s:%d\n",p->name,p->count);
-
-
访问结构体成员有两种类型,三种方式:
-
**类型1:**通过结构体变量(对象|示例)访问成员
cstruct Stu { int id; char name[20]; } stu; // 结构体变量 // 访问成员 stu.name;
-
**类型2:**通过结构体指针访问成员
-
**第1种:**指针引用访问成员
cstruct Stu { int id; char name[20]; } stu; struct Stu *p = &stu; // 指针引用访问成员 p -> name; // 等价于 (*p).name;
-
**第2种:**指针解引用间接访问成员
cstruct Stu { int id; char name[20]; } stu; struct Stu *p = &stu; // 指针引用访问成员 (*p).name; // 等价于 p -> name;
-
-
结构体数组中元素的访问
c// 结构体数组 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]);// 张三 78 printf("%s,%.2f\n", (stus+1) -> name, (stus+1) -> scores[1]);// 李四 88 printf("%s,%.2f\n", (*(stus+1)).name, (*(stus+1)).scores[1]);// 李四 88
小贴士:
结构体是自定义数据类型,它是数据类型,用法类似于基本类型的int;
结构体数组它是存放结构体对象的数组,类似于int数组存放int数据;
基本类型数组怎么用,结构体数组就怎么用--->可以遍历,可以作为形式参数,也可以做指针等;
-
-
结构体类型的使用案例
结构体可以作为函数的返回类型,形式参数等。
举例:
c/************************************************************************* > 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; }
结构体类型求大小
字节对齐
-
字节对齐的原因:
- 硬件要求 某些硬件平台(如ARM、x86)要求特定类型的数据必须对齐到特定地址,否则会引发性能下降或硬件异常。
- 优化性能 对齐的数据访问速度更快。例如,CPU访问对齐的
int
数据只需一次内存操作,而未对齐的数据可能需要多次操作。
-
字节对齐规则:
- 默认对齐规则
- 结构体的每个成员按其类型大小和编译器默认对齐数(通常是类型的自然对齐数)对齐。
- 结构体的总大小必须是最大对齐数的整数倍。
- 对齐细节
- 基本类型的对齐数 :
char
(1字节)、short
(2字节)、int
(4字节)、double
(8字节)。 - 结构体成员的对齐 :每个成员的起始地址必须是对齐数的整数倍。
- 结构体总大小的对齐 :结构体的总大小必须是其最大对齐数的整数倍。
- 基本类型的对齐数 :
#pragma pack(n)
的影响 使用#pragma pack(n)
可以强制指定对齐数为n
(n
为 1、2、4、8、16)。此时:- 每个成员的对齐数取
n
和其类型大小的较小值。 - 结构体的总大小必须是
n
和最大对齐数中的较小值的整数倍。
- 每个成员的对齐数取
- 默认对齐规则
-
对齐示例
-
默认对齐
cstruct S1 { char c; // 1字节,偏移0 int i; // 4字节,(需对齐到4,填充3字节,偏移4-7) double d;// 8字节,(需对齐到8,偏移8~15) }
cstruct S2 { double d; char c; int i; }
cstruct S3 { char c; double d; int i; }
总结,结构体中,成员的顺序会影响到结构体最终的大小。
-
使用
#pragma pack(1)
自定义对齐规则,(1)
对齐的字节数c#pragma pack(1) // 对齐数之前的字节数能被1整数 struct S1 { char c; // 1字节 (偏移0) 1 int i;// 4字节 (偏移1~4)4 double d;// 8字节 (偏移5~12)8 }; #pragma pack() // S1 的大小为 13字节
c#pragma pack(2) // 对齐数之前的字节数能被2整数 struct S1 { char c; // 1字节 (偏移0,填充1字节) 2 int i;// 4字节 (偏移2~5)4 double d;// 8字节 (偏移6~13)8 }; #pragma pack() // S1 的大小为 14字节 char -1 + 1 int
c#pragma pack(4) // 对齐数之前的字节数能被4整数 struct S1 { char c; // 1字节 (偏移0,填充3字节) 4 int i;// 4字节 (偏移4~7)4 double d;// 8字节 (偏移8~15)8 }; #pragma pack() // S1 的大小为 16字节
-
在GNU标准中,可以在定义结构体时,指定对齐规则:
shell__attribute__((packed)); -- 结构体所占内存大小是所有成员所占内存大小之和 ------attribute__((aligned(n))); -- 设置结构体占n个字节,如果n比默认值小,n不起作用;n必须是2的次方
-
案例:
c/************************************************************************* > 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; // 4 char name[20];// 20 }__attribute__((packed)); // 28 25 printf("%ld\n",sizeof(struct Cat));// 28 25 return 0; }
-
-
柔性数组
定义:柔性数组不占有结构体的大小。
特点:
- 柔性数组必须是结构体的最后一个成员
- 结构体大小在编译时是未确定的
- 必须动态分配内存,指定数组的大小
语法:
c
struct St
{
...
char arr[];
}
案例:
c
/*************************************************************************
> 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 为了存储\0
int 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;
}
课堂练习
计算以下结构体的大小
c
/*************************************************************************
> 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));// 8
printf("%lu\n",sizeof(test2));// 8
printf("%lu\n",sizeof(test3));// 32
printf("%lu\n",sizeof(test3_1));// 28
printf("%lu\n",sizeof(test4));// 28
printf("%lu\n",sizeof(test1_1));// 5
printf("%lu\n",sizeof(test1_2));// 8
return 0;
}
结构体的常见陷阱与最佳实践
常见陷阱
- 成员访问越界:访问不存在的结构体成员。
C
struct Point p;
p.z = 10; // 错误,Point结构体没有z成员
- 内存泄漏:忘记释放动态分配的结构体内存。
C
struct Point *p = malloc(sizeof(struct Point));
p->x = 10;
// 没有free(p)导致内存泄漏
- 悬挂指针/空悬指针:使用已经释放的内存或未初始化的指针。
C
struct Point *p = malloc(sizeof(struct Point));
free(p); // 释放了p指向的内存
p->x = 10; // 错误,p现在是悬挂指针
- 结构体大小计算错误:忘记考虑编译器的内存对齐。
C
struct {
char a;
int b;
} s;
// sizeof(s)可能是8而不是5
最佳实践
- 使用typedef简化语法:为结构体创建简短的别名。
C
typedef struct {
int x;
int y;
} Point;
- 将相关数据组织在一起:使用结构体封装相关数据。
C
struct Student {
char name[50];
int age;
float grade;
};
- 避免过大的结构体:将大型数据结构分解为多个较小的结构体。
- 合理使用指针:对于大型结构体,使用指针而不是值传递。
- 注意内存管理:确保正确分配和释放动态内存。
共用体/联合体类型
-
定义:使几个不同变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是使用该空间,它们的首地址是相同。
-
定义格式:
cunion 共用体名称 { 数据类型 成员名; 数据类型 成员名; ... };
-
共用体的定义和结构体类似。
-
可以有名字,也可以匿名
-
共用体在定义时也可以定义共用体变量
-
共用体在定义时也可以初始化成员
-
共用体也可以作为形参和返回值类型使用
-
共用体也可以定义共用体变量
-
...
也就是说,结构体的语法,共用体都支持
-
-
注意:
-
共用体弊大于利,尽量少用,一般很少用;
-
共用体变量在某一时刻只能存储一个数据,并且也只能取出一个数
-
共用体所有成员共享同一内存空间,同一时间只能存储一个值,可能导致数据覆盖
-
共用体和结构体都是自定义数据类型,用法类似于基本数据类型
- 共用体可以是共用体的成员,也可以是结构体的成员
- 结构体可以是结构体的成员,也可以是共用体的成员
-
案例:
c
/*************************************************************************
> 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; // 4
char b; // 1 + 3
union 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; // 共用体空间数据:10
obj.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;
}
案例:
c
/*************************************************************************
> 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;// char
printf("%c\n", fun(obj).a); // char
obj.b = 10000000;
printf("%d\n", fun(obj).b);// int
obj.c = 12.25;
printf("%f\n", fun(obj).c);// double
return 0;
}
要求:共用体在使用的时候,建议存取时使用的成员是一致的。也就是使用成员a存数据,必须使用成员b取数据。
结构体与共用体的主要区别
- 内存占用 :
- 结构体的成员是连续存储的,每个成员都有自己的内存空间。
- 共用体的所有成员共享同一块内存空间(共享所有成员中最大成员的空间)。
- 访问方式 :
- 结构体的成员可以同时访问。
- 共用体的成员不能同时访问,因为它们共享同一块内存空间。
- 使用场景 :
- 结构体适用于需要同时存储多个不同类型数据的情况。
- 共用体适用于需要在同一时间存储不同类型数据中的一种的情况,可以节省内存。