自定义类型————结构体、位段、枚举类型、联合体

目录

结构体

结构体内存对齐

位段

枚举类型

联合体


在C语言中,除了标准规定的内置的整型、浮点型...等等这些类型外,还有一些特殊的类型:结构体类型、位段、枚举类型以及联合体,下面让我们一起来看一下它们的具体形式。

结构体

像int、char、double 这些类型只能表示单一的属性,像int整型,能表示人的年龄这种单一的属性,但如果想表示一个人的信息,就需要用到结构体类型了。一个结构体里面可以设计多个元素,比如表示一个的信息,可以设计姓名、年龄、身份证号等等元素。

具体表示形式:

struct man //这里的struct man是结构体类型名
{
	char name[20];
	int age;
	char ID[20];
}s;  //这里的s 是结构体变量名

如果s放在main函数外,创建的就是结构体全局变量,放在main函数里面,就是局部变量。

struct man 
{
	char name[20];
	int age;
	char ID[20];
};
int main()
{
  struct man s;
  return 0;
}

特殊声明的结构体类型

匿名结构体类型:不写明结构体类型名,只能使用一次。

struct  //没写出结合体类型名
{
	char name[20];
	int age;
	char ID[20];
}s; 

注意:匿名结构体没有类型名,如果有两个内容完全一样的结构体,其实它们是不同的。

编译器会把它们当成完全不一样的两个结构体,如下图:

 

 结构体的自引用

链表的实现就需要用到结构体自引用,也就是在结构体内部引用自身结构体指针。

struct man
{
	char name[20];
	int age;
	char ID[20];
	struct man* next;
};

 为了简便,通常我们使用typedef重命名结构体类型,这样以后struct man就可以写成man了。

typedef struct man
{
	char name[20];
	int age;
	char ID[20];
	struct man* next;
}man;

但注意不能写成如下形式:

typedef struct 
{
	char name[20];
	int age;
	char ID[20];
	man* next;
}man;

这里省略了结构体类型名,将其重定义为man,但在重定义之前又在结构体里面自引用了man,编译器此时会报错,不认识 " man"。

结构体嵌套初始化

结构体是可以嵌套使用的,那么如何初始化呢?

struct score
{
	double max;
	double avg;
	double min;
}p;
struct man
{
	char name[20];
	int age;
	char ID[20];
	struct score p;
}s;
int main()
{
	struct man s = { "zhangsan",20,"123",{100.0,60.0,80.0} };
	return 0;
}

 这struct man中嵌套了struct score,初始化时要再用个 {  } 对其初始化。

结构体内存对齐

结构体如何计算其大小,这就涉及到了内存对齐,我们来看一下规则:

 这里我们采用VS2022环境测试,看几个例子帮助理解:

实例一:

首先解释一下规则里的相关术语:

偏移量:结构体类型在内存中开辟空间,开辟的这块空间是连续的,每个大小为1字节,第一个字节相对于起始位置偏移量是0,第二个是1..... 依次往下。

对齐数:编译器默认的一个对齐数(VS底下是8,其他编译器可能是4,也有的就是结构体成员的大小)和该结构体成员的大小的最小值。

  S1中,首先是char类型的c1,按照上述规则第一个结构体成员放在偏移量为0处;

第二个int类型的 i 要从偏移量为4的地方放起(规则二),往下放4个字节,所以char s1和int i

占了8个字节,而这里就有3个字节浪费了,char c2从偏移量为8的位置放起,占1个字节,这时整个结构体占了9个字节,但规定三说:结构体总大小为最大对齐数(4)的整数倍,也就是12,所以整个结构体大小为12,浪费6个字节。

 同样的,struct s2里面:

实例二:

计算sizeof(S4)

 这里嵌套了结构体,要用到规则四。 struct S3:

 struct S4:

struct S4大小是32字节。

既然结构体内存对齐存在浪费,为什么还会有这种规定呢?

 解释一下性能原因:比如:

struct S
{
  char a;
  int b;
};

 在32位平台下,一次处理32个比特位,也就是4个字节,如果没有内存对齐,先处理char a,处理了1个字节,剩下int b 还有4个字节就要分两次处理,而内存对齐的情况下就会第二次再处理int b,只需要处理一次,保证一次处理完整的数据。所以内存对齐其实是一种用空间换时间的方法。

当然,其实两种方式各有优劣,不同情形下用不同的处理方式。

为了尽量减少内存对齐的空间浪费,我们一般将小的、零碎的空间放在前面。

修改最大对齐数

 #pragma pack(n)

#pragma pack( )

可以修改最大对齐数,n就表示修改的数值。

位段

位段是用结构体来实现的一种类型,它是为了节省空间而设计的。

(例如有些变量只可能是1或0,不需要那么多空间)

struct s
{
	int _a : 4;
	unsigned int _b : 1;
	char _c : 2;
};

位段的成员必须是整型家族的成员,如:int 、char 、unsigned char......

这里的s就是一个位段类型,int _a:4 代表大小是4个比特位(不是字节)。

计算位段大小

由2知:struct s里面的成员都是int类型的,所以每次开辟4个字节的空间,a要2个比特位,b要5个比特位,c要10个,加起来17个,还没满32个比特位(4个字节),要是加上d的30个就超过了32个比特位,所以d新开辟4个字节的空间进行存储,所以一共开辟了8个字节的空间。

注意无符号整型没有符号位,而是转化为计数位,所以unsigned int 要4个字节的空间,_a 和 _c加起来是4个字节,_c是一个字节,但是结构体内存对齐适用所有场景,按规定三,结构体总大小是最大对齐数的整数倍,所以大小是8个字节。

位段在内存中的存储

 按照上面位段的计算,S大小应该是3个字节。

a用了3个比特位,但是存放数字10需要4个二进制位(比特位),因为10的二进制数为:1010

这里类似截断,截取最后3位,010 ;类似的,b截取 1100;c本来是011,但给了5个比特位,所以是00011,;d:0100 .

在一个字节内部,我们从低位向高位使用。

第一个字节里: 先放a,  010   再放b   1100

第二个字节里:放c  00011

第三个字节里:放d 0100

        

 转化为十六进制就是620300,这就是位段的存储方式。

位段的跨平台问题

位段的应用

 位段在网络里面应用是比较多的,因为网上信息传输存在各种各样或大或小的数据包,如果数据包太大太多,容易造成丢包,影响网络质量。这时可以用位段节省空间,减小数据包的大小,以此来加快网络流畅度。

枚举类型

枚举与穷举不同,穷举是列举出所有可能,枚举是列举某样可能性不算太多的事物的所有可能情况

比如:人的性别、一周的星期。

enum Sex
{
	Man,
	WOman,
	Secruit
};
int main()
{
	enum Sex a = Man;
	return 0;
}

枚举的都是常量,第一个默认为0,第二个为1.......也可以在定义时修改。

 解释一下第4条:便于调试, define定义的常量在调试时是看不出来的,因为#define是预处理指令

是在程序编译之前进行的,所以调试的时候也看不出来,在编译前就替换了。但是枚举可以。

只能拿枚举常量给枚举变量赋值,像上面的代码:enum Sex a=Man这是可以的,但是不能enum Sex a=0这是错误的。

联合体

联合体是共用开辟的内存空间的,但不能同时使用,一个在使用另一个就不能使用。

 这里sizeof(u)的大小是4个字节,因为a 和c共用一块空间,按大的算。

所以下面打印的三个地址是一样的。

 观察这段代码,帮助你更好的理解

union s
{
	int a;
	char b;
}u;

int main()
{
	u.a = 0x00112233;
	u.b = 0;
	return 0;
}

 我们看u在内存中的变化:

 

 可以观察到给u.b赋值改变的是u.a的第一个字节。

联合体大小的计算

 联合体在计算大小时也存在对齐。

 sizeof(union Un)大小是8,最大对齐数是4,本来Un大小是5,因为存在内存对齐,所以是8.

 有人会问,这里的char  arr[5],最大对齐数不应该是5吗?

不是的,char arr[5]可以看做是char s1;char s2.....char s5,最大对齐数是1.

可以这么理解,但是这两个绝对是不等价的!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值