自定义类型(结构体,位段,枚举,联合)

目录

一.结构体

1.什么是结构

2.结构的声明

 3.特殊的声明

4.结构体的自引用

5.结构体的定义和初始化

6.结构体的操作

7.结构体内存大小计算

1.四大规则

2.修改默认对齐数

8.结构体传参

二.位段

 1.位段的定义

 2.位段所占空间大小计算

 3.位段的具体运用

三.枚举

1.定义

2.枚举的优势

四.联合

1.联合的定义

2.联合的大小计算


一.结构体

1.什么是结构

我们已经了解很多种类型来声明定义不同种类的数据,有int,float,double等等,但是假如要刻画工厂的一颗螺丝钉,我们该用什么类型进行修饰呢?

仔细想想,即便用数组也不好刻画,因为数组中的元素都具有相同的类型,而想要刻画一个螺丝钉,需要不同的类型进行定义,需要它的编号(number),大小(size),现有数量(on_hand),名称(name).

拥有一些可以是不同类型,可以是相同类型的变量值(成员变量)的集合,我们称之为结构体.
在其它一些语言中,结构常被称为记录,其中的成员被称为字段.

2.结构的声明

我们可以根据这个定义我们螺丝钉的声明,它的struct tag(结构体类型名)是struct tag,类似int,double等等,它包含的member-list有4个(有size,on_hand,number,name).

然后可以选择创建或不创建variable list(变量列表),如果在声明时同时创建variable list,此时创建

的结构体变量是全局变量,而如果没有创建,只要有相应的struct tag,创建变量还不是手到擒来的

事.

 3.特殊的声明

每个结构代表一个新的作用域,每个结构都为它的成员变量分辟了相应的空间.

因此,上述两个结构体中number和name重复出现是没有问题的,两者分别有着它自己的空间.

但是,假如在声明的时候,没有加相应的标签(tag)

此时,编译器会把它理解为依旧是两个不同的结构体,即便它们所有的元素都相同.

假如下面紧跟着  p = & x ; 程序是会报错的.

4.结构体的自引用

假设我们想要构造一种新的数据结构——链表,它的实现是由一个结构体,指向另一个新的结构体.

 

我们写出这样的代码可行吗?

答案显然是否定的,假设存在这样一个结构体,那用sizeof计算它的大小,它的大小将会是无穷大,结点中又有另一个t同样的结点.

一个比较可行的方案是,放一个结构体指针,而这个指针可以指向新的结构体.

然后为了简便程序,不用创建变量,都需要输入struct Node,我们常常会用typedef进行重命名.

  

5.结构体的定义和初始化

我们可以直接创建一个全局的结构体变量s1,或者在主函数中创建临时结构体变量s2.

用一个中括号按顺序进行赋值即可.

如果不想按顺序进行赋值,可以指定初始化. 

指定初始化有以下两个优点:
1.易读且容易验证      2.可以不需要按顺序进行赋值

初始化中没有涉及到的成员默认为0.

假设一个结构体中包含另外一个结构体,初始化时,只需在中括号{}再加上另一个中括号{},初始化即可.

6.结构体的操作

访问结构体的成员变量,用.

假如是结构体指针,要想访问结构体中的成员变量,C语言提供了另一个操作符 —>

结构的另一种主要操作是赋值运算

虽然赋值操作仅适用于类型兼容的结构.但数组不能赋值操作,而结构体可以,属实是意外之喜.

除了memcpy与strcpy外,我们现在又知道另一种方法进行赋值操作,在结构体内放一个数组,则嵌在其中的数组在赋值操作的同时,也产生一个新的同样大小的数组空间.

7.结构体内存大小计算

1.四大规则

假设拿这样一个结构体做例子,对规则进行介绍.

(1)结构体的第一个成员直接对齐到相对于结构体变量起始地址为0的地方

(a所占大小为1个字节,从起始地址为0的地方开始存储)

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

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

(VS默认对齐数为8,Linux系统无对齐数)

(x占4个字节,比对齐数8要小,因此x的对齐数为4,偏移量为1,2,3的空间不是4的整数倍,会被浪费,然后从偏移量为4的地方开始存储x)

同样,y占8个字节,VS对齐数也为8,所以y的对齐数就是8,而刚好此时偏移量为8的整数倍,所以可以顺着继续存储.

 (3)如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
  (4)结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍.
比方说,按照类似上面的方法,我们得到了struct Point 这个结构体的大小为8个字节 
那example结构体的最大对齐数是8,现在从偏移量为0到偏移量为15 + 8 = 23,总共占了24字节的
空间,恰好是8的整数倍,因此example结构体所占空间大小就为24个字节.
 

2.修改默认对齐数

#pragma pack(num)   修改为默认对齐数为num
#pragma pack()          恢复默认对齐数

8.结构体传参

我们知道形参是实参的一份临时拷贝,假如结构体传参,大多数情况都是传函数体指针给函数就

好,否则假如结构体本身占据的空间很大,参数压栈的的系统开销比较大,容易导致性能的

下降.但现在有些编译器不断优化,有时候把结构体传过去,进行的操作是在原结构体上进行的.

二.位段

 1.位段的定义

  有些时候,我们定义一个变量a,为int 类型

  但是我们知道,虽然作为一个整型,但实际上变量a取到的范围根本不需要4个字节,仅仅只需要

3个bit位表示范围即可,为了应对这种情况,进一步节省空间,位段便孕育而生.

和结构体一样,位段有着相似的声明方式.

A便是一个位段,位段和结构体的区别,主要在以下两点

(1)位段的成员必须是int,unsigned int或者char类型

(2)位段中的位指的是二进制位,为了调整变量所占空间的大小,每个成员变量后面都要加一个:,

以及它所占的对应比特位(二进制位).

 2.位段所占空间大小计算

位段具体如何存储数据我们并不知道,但我们知道数据在vs2019编译器底下,是小端存储,即低位放在高地址,高位放在低地址.

所以我们可以画出下面这幅图:

  假设从低位往高位开始存储数据,7的二进制为1 1 1,刚好可以用3个比特位表示

 紧接着是3,0 0 1 1,可以用4个比特位准确表示,则继续存进去0 0 1 1

 这时候_c申请5个比特位,已经超过8个字节,这时候便又申请了一个字节,占6个比特位,注意剩余还有1个比特位,依旧占用

存进去的12的二进制表示1 1 0 0,继续接_d,5的二进制1 0 1存入.

0 0 0 1       1 1 1 1        1 0 1 0      0 1 1 0

对应的十六进制1f a6 00 00. 

 

而实际上位段存在跨平台问题,所以,我们在vs测试也只能大致猜想位段是如何存储的.

1. int 位段被当成有符号数还是无符号数是不确定的.
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题.
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义.
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的.(现在在vs2019是继续利用,但在vs2013是舍弃剩余位)

3.位段的具体运用

32位IP地址的划分利用.

三.枚举

1.定义

枚举顾名思义就是把可能的取值一一列举出来.

 

这些可能取值都是有值的,默认从0开始,一次递增1.

2.枚举的优势

1. 增加代码的可读性和可维护性
2. #define定义的标识符比较枚举有类型检查,可以调试,更加严谨.而且当define宏定义数量过多的时候,也不利于代码的简洁.
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量

四.联合

1.联合的定义

联合和结构体声明也是类似的

联合最大的特点在于这些成员公用同一块空间(所以联合也叫共用体).

union Un1
{
   char c [ 5 ];
   int i ;
};

2.联合的大小计算

1.联合的大小至少是最大成员的大小.
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值