what the 自定义数据类型

学习自定义类型:结构体,联合体,枚举

文章目录


前言

在前面我们学习一些了C语言中的一些基本的数据类型,它们是C语言一开始就设计好的,咱们拿去使就好了。今天咱们再来学习一些自定义的数据类型,顾名思义就是咱们自己定义一个数据类型,然后再去使用它们。


一、结构体

1.结构体的创建和初始化

在此之前,我们学习了数组,它可以将许多相同类型的数据存储在一起,而我们今天所学习结构体(struct)是C语言中用于将不同类型的数据组合在一起的一种数据结构。它允许你将多个数据项(成员)组织成一个单独的复合数据类型。每个成员可以是不同的数据类型。如下代码是结构体的声明。

//结构体声明
struct tag        //tag为结构体标签
{
	member_list;  //结构体成员列表

}varible_list;    //变量列表,就是我们所定义的结构体的名字,与函数名同理

这是结构体实例展示,我们平时在写结构体代码时就可以按照上面的格式书写。

接下来我们再来学习一下如何初始化结构体,这其实和我们之前初始化数组有相像之处,但也有略微区别:一种是根据创建结构体变量时的顺序来初始化,另一种是不按顺序初始化

2.结构体的特殊声明

在声明结构体时,我们有时也会不完全的声明,其中就有一种声明方式就是——匿名结构体声明。

这种结构体是匿名的,咱们在创建的时候就没有声明它的类型,因此我们在使用它之前要注意一些问题:

答案是不能的。因为在我们创建结构体的时候,我们没有声明结构体的类型,编译器会把两个结构体当做两个完全不同的类型,是非法的。

匿名的结构体类型,如果没有对其进行重命名的话,基本上只使用一次。

3.结构体的自引用

自引用就是在结构体中再放一个结构体,但是真的这么简单嘛?其实不然,如果我们直接将一个结构体放在它本身的结构体中会发生什么呢?

这时候我们再想想之前所学的内容——指针,我们可以利用指针将下一个数据的地址存放在结构体中,那样就能够节省许多空间了。

而这也是结构体的正确自引用方式。

我们有时候是不是觉得使用结构体的时候要写一大堆是不是很麻烦,那么咱们可以利用之前所学过的重命名(typedef)的方法简化一下

这时候我们就将struct Node 重命名为Node了,但是在重命名过程中我们要注意一些问题:我们在重命名过程中struct Node 仍然是struct Node,咱们不可以将结构体变量中的struct Node* next改为Node* next.

最后,还要注意一点:不要对匿名结构体进行重命名操作,因为我们不知道匿名结构体的结构体标签,在重命名时无法操作。

4.结构体内存对齐   ⭐⭐⭐

在我们要计算一个结构体大小时,就不像计算数组大小用sizeof那么简单了,计算结构体大小时要掌握      结构体对齐规则

1.结构体变量的第一个成员要对齐到结构体变量起始位置偏移量为0的地址处;

2.其他成员要对齐到某个数字(对齐数)的整数倍地址处;

     对齐数=编译器默认的一个对齐数与该成员变量大小的较小值

    VS中默认的对齐数是8

     Linux中gcc没有默认对齐数,对齐数就是其成员变量的大小

3.结构体的总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,其中最大的一个对齐数)的整数倍

4.如果嵌套了结构体的情况,嵌套的结构体要对齐到自己成员中的最大对齐数的整数倍的地址处,结构体的大小(包括嵌套的结构体中的成员变量)为所有对齐数中的最大对齐数的整数倍

接下来介绍几个例子来看看如何使用对齐规则

其实咱们要进行内存对齐,还是为了提高效率,我们要知道内存中是一个连续的空间,空间中的一个字节占8个bit位,如果我们不进行内存对齐就将其存入结构体中,那么就会导致数据分散到了多个字节中去了,我们可能要进行好几次访问,如果我们进行了内存对齐,那么可能就能将数据存放到尽可能少字节数的内存中,那么我们访问的次数也少不少,效率也就上去了

总的来说:结构体的内存对齐是拿空间换时间的做法。

那么我们在设计结构体的时候,我们既要对齐又要节省空间那么我们就要让内存空间小的变量尽量放在一起

有时候我们觉得对齐方式不合适的时候,我们可以试着修改默认对齐数,#pragma 这个预处理指令,可以改变编译器的默认对齐数。

5.结构体传参

我们在调用函数的时候需要用到传参,现在我们来看看结构体在函数调用的时候如何来进行传参

综上代码所述:结构体传参的时候,要传结构体的地址。

6.位段

位段的声明和结构体的类似,但有两个不同:
1.位段的成员必须是 int ,signed int 或 unsigned int,在C99中位段成员也可以是其他数据类型

2.位段成员的后面加一个:和一个数字。

位段和结构有相同的效果,并且还能很好的节省空间,但是存在一些跨平台的问题:
1.int 位段被当成符号位还是无符号位是不确定的;

2.位段中的最大数目也是不能确定的。(16位机器最大16,32位机器最大32,如果位段为27,那么在16位机器中就会出现问题);

3.位段成员中的内存分配是从左向右还是从右往左分配的标准都是不确定的;

4.当一个结构包含两个位段,第二个位段比较大,无法容纳第一个位段,那么剩余的空间是否浪费是不确定的。

最后再来说一下位段使用的注意事项:

位段中会有几个成员公用一个字节,这样有些位段的起始位置就不是某个字节的起始位置,那么这些位段就是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。因此我们是不能直接对位段的成员使用&操作符的,这样就不能使用scanf给位段的成员输入值,我们只能先输入放在一个变量中,然后再将这个变量赋值给位段成员。

二、联合体

1.联合体的声明和初始化

联合体(union)是C语言中的一种数据结构,它允许多个不同类型的数据共享同一块内存区域。联合体中的所有成员都存储在同一内存位置,但同时只能使用其中一个成员。联合体也叫:共用体 。给联合体的其中的一个成员赋值,其他成员的值也会发生变化。

//联合体声明
union Un  //联合体标签(Un)
{
	member_list;   //联合变量
}variable_list;

上述代码就是联合体实例展示

2.联合体的特点

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

上面这个代码可以证明联合体的成员公用一个内存空间。

3.联合体大小的计算

联合体的大小计算要遵循两个规则:

1.联合体的大小至少是最大成员的大小;

2.当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

4.联合体的小妙用

在之前我们学习了数据在内存中存储是存在顺序的,即大小端字序存储。今天咱们来使用联合体的方法来判读大小端。

三、枚举

1.枚举的声明

在C语言中,枚举(enumeration)是一种用户定义的数据类型,用于为一组具名的整数值赋予符号名称。枚举常用于程序中需要表示一组相关常量或状态的情况下,使代码更易读和维护。

//枚举的声明
enum 枚举类型名 
{
    标识符1,
    标识符2,
    ...      //注意:最后一个枚举成员后面不用加任何符号
};

其中,枚举类型名是定义的整个枚举类型的名称,标识符1、标识符2等被称为枚举常量,它们默认被赋予整数值,第一个常量的默认值为0,后续的常量值递增1。也可以在定义时为枚举常量手动赋值。

上述代码就是枚举的实例展示

2.枚举的优点及使用

枚举的优点:

1.增加代码的可读性和可维护性;

2.和#define定义的标识符比较枚举有类型检查,更具有严谨性;

3.便于调试,预处理阶段会删除#define定义的符号;

4.使用方便,一次可以定义多个变量;

5.枚举变量是遵循作用域规则的,枚举如果声明在函数内,那么只能在函数内使用。

枚举的使用:

这里我们要注意一下,在C语言中可以拿整数给枚举变量赋值,而在C++中确是不可以的,因为C++中的类型检查比较严格。


总结

这次,我们学习了C语言中的自定义类型,这对于我们后面学习数据结构也会有很大作用,希望我们能够好好理解消化这节知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值