【C语言】结构体与联合体之间的“爱恨情仇”

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、结构体

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.定义与初始化

struct Point
{
 	int x;
	int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2

struct Point p3 = {x, y};//初始化:定义变量的同时赋初值。

struct Stu        //类型声明
{
 	char name[15];//名字
 	int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化

struct Node
{
	 int data;
 	struct Point p;
 	struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化

struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

结构体类型的定义与初始化可以一次性解决,也可以分步骤进行。


2.大小计算

结构体大小计算与内存对齐紧密相关,因此在计算结构体大小之前我们需要知道结构体的对齐规则:

  • 第一个成员在与结构体变量偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
    例如我配置的VSCode编译器中默认的值为8字节。
  • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

内存对齐的原因:

  • 平台原因(移植原因)
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
    定类型的数据,否则抛出硬件异常。
  • 性能原因
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
    问。

    总结: 结构体的内存对齐是拿空间来换取时间的做法。
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};

int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

在VSCode编译器上运行后,可以发现S1的大小为12,而S2的大小为8。

内存对齐如图所示:
在这里插入图片描述
如果明白了结构体计算大小的规则的话,就可以试一试计算一下这个结构体的大小,欢迎大家将答案写在评论区。

struct S3
{
	double d;
	char c;
	int i;
};


3.成员访问和结构体传参

结构体成员的访问

  • 结构体指针变量访问成员
    结构变量的成员是通过点操作符(.)访问的。
  • 结构体指针变量访问成员
    结构指针变量的成员是通过操作符(->)访问的。
struct Stu
{
	char name[20];
	int age;
};

void print(struct Stu* ps)
{
	printf("name = %s   age = %d\n", (*ps).name, (*ps).age);
    //使用结构体指针访问指向对象的成员
	printf("name = %s   age = %d\n", ps->name, ps->age);
}
int main()
{
    struct Stu s = {"zhangsan", 20};
    print(&s);//结构体地址传参
    return 0;
}

结构体传参

struct S
{
    int data[1000];
    int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
    printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
    printf("%d\n", ps->num);
}
int main()
{
    print1(s);  //传结构体
    print2(&s); //传地址
    return 0;
}

函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的
下降。因此,结构体传参的时候,要传结构体的地址


二、联合体

联合也是一种特殊的自定义类型。这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

1.定义与初始化

//联合类型的声明
union Un
{
	char c;
	int i;
};
//联合变量的定义
union Un un;
//计算两个变量的大小
printf("%d\n", sizeof(un));

联合体的定义与初始化与结构体相似,主要是大小计算不同。


2.大小计算

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合体至少得有能力保存最大的那个成员)。

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是自己最大对齐数的整数倍的时候,就要对齐到自己最大对齐数的整数倍。
//联合类型的声明
union Un
{
	char c;
	int i;
};
//联合变量的定义
union Un un;
//计算两个变量的大小
printf("%d\n", sizeof(un));

根据规则可以计算联合体un大小为4,如下图所示:
在这里插入图片描述


3.成员访问和联合体传参

联合体的成员访问和结构体传参可以参考结构体,在这里需要注意是由于所有成员共享同一内存位置,只有最后一个被写入的成员能够被正确访问,访问其他成员会导致未定义的行为。


4.结构体和联合体的应用场合

结构体的应用场合

  • 组合相关数据
    表示实体的属性:结构体用于组合多个相关的不同类型的数据,以表示某一实体的属性。例如,在一个应用程序中,用户可以被表示为一个包含姓名、年龄、地址等字段的结构体。
  • 复杂数据类型
    封装复杂数据:当必须处理更复杂的数据类型或对象时,结构体提供了一种将不同类型的变量组合在一起的方式,便于数据的管理和使用。
  • 数据传递
    函数参数和返回值:结构体可以作为函数的参数或返回值,允许传递多个相关数据而不是传递多个单独的变量,这有助于提高代码的可读性和可维护性。
  • 数组和链表等数据结构
    实现动态数据结构:结构体常用于构建自定义数据结构,如链表、树、哈希表等。其中,结构体可以用于定义节点类型,包含指向其他节点的指针。
  • 表示数据库记录
    记录模型:在处理数据库或文件记录时,结构体可用于表示一条完整的记录,每个字段对应表中的一个列,便于数据的读取和处理。
  • 接口与API
    RPC和网络编程:在网络编程和远程过程调用(RPC)中,结构体用于定义数据交换格式,确保客户端和服务器之间的数据能够正确解析和传输。
  • 模拟对象
    面向对象程序设计的简单实现:虽然 C 语言不支持面向对象编程,但可以使用结构体和函数指针组合模拟简单的类和对象,从而实现某种程度的封装和功能。

联合体的应用场合

  • 节省内存
    内存紧张的场合:联合体能够显著节省内存,因为它的所有成员共享同一块内存。适用于内存受限的嵌入式系统、低功耗设备等。

  • 表示不同数据类型
    多类型数据表示:联合体可以用来表示一种数据对象的多种形式。例如,网络协议中,数据包的格式可能会根据类型不同而有所不同,可以使用联合体来表示这些不同的数据格式。

  • 处理不同的数据类型
    使用同一变量处理不同类型:在某些情况下,程序可能需要处理同一种数据,但类型不同的情况,比如整型、浮点型、字符型等。使用联合体可以在同一个变量内存中存储不同类型的数据,这特别适用于通信和数据解析场景。

  • 数据结构的扩展
    灵活的结构:在某些数据结构中(例如某些自定义的数据类型或实现一个简单的对象模型),可以利用联合体来增加结构的灵活性。例如,可定义一个 Shape 联合体,表示不同的几何形状(如圆形、矩形等),其数据依据形状的不同而变化。

  • 实现类型安全的枚举类型(type-safe enums)
    与枚举组合使用:联合体可以与枚举类型结合使用,创建一个类型安全的复合数据结构。这在需要明确指定数据类型的情况下特别有用。

  • 条件存储
    条件存储不同信息:如果只是在某个时刻使用其中一种类型,联合体非常适合这种情况。例如,表示一个不同状态的设备,可能在不同状态下需要保存不同类型的数据。


总结

结构体适合用于表示多种数据的组合,能够容纳多个成员且可同时使用;而联合体则适合用来处理几个可能的选择,能节省内存但一次只能使用一个成员。这是它们最主要的区别和特点。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值