【C语言进阶】5. 自定义类型详解

1 结构体

【C语言初阶】中我们学习了结构体相关的基本概念(包括初始化,声明,传参),在这里进一步深入了解。

1.1 特殊的声明

结构体在声明的时候可以不完全声明,例如:匿名结构体
在这里插入图片描述
匿名结构体类型只能使用1次
在这里插入图片描述
p是结构体类型的指针,x是结构体变量 虽然这两个结构体成员变量一致,但是从编译器的角度认为 这二者还是两个不同的结构体,会报警告

1.2 结构体自引用

在结构体中包含一个类型为该结构本身的成员是否可以?
在这里插入图片描述
在结构体当中再包含一个同样的结构体,struct Node 的字节空间也就是内存大小不可知,编译器报错显示 struct Node next 未定义
而且Node中可再包含1个Node ,不断套娃下去
在这里插入图片描述
将struct Node* 变成指针的形式(指针的字节大小是确定的),数据结构中的链表采用的就是数据域,指针域
在这里插入图片描述
将匿名结构体typedef 重命名为Node ,但是这样是不可行的。
是因为匿名结构体当中就已经存在Node * 类型的指针 但是在typedef Node之前并不存在Node类型的变量,(类似于先有鸡还是先有蛋)
在这里插入图片描述
正确的typedef形式
在typedef 之后 struct Node n1 和Node n1 (创建结构体变量)一致
linklist 指的是结构体指针

1.5 结构体变量的定义和初始化

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};//结构体嵌套初始化

1.6 结构体内存对齐(重点)

在这里插入图片描述
两个结构体成员变量相同,只是顺序不一样,所占空间不同
这就涉及到结构体内存对齐
在这里插入图片描述
vs编译器的默认对齐数是8,gcc等其他编译器没有默认对齐数,就是成员变量自身大小
s1,s2分别对应的内存空间布局
在这里插入图片描述
offsetof (宏) 返回的是结构体成员变量在结构体当中的偏移量在这里插入图片描述
说明结构体内存对齐是存在的。
在这里插入图片描述
在这里插入图片描述
s3和s4的大小分别是16和32
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
是s1和s2结构体的成员变量相同,所占空间却不同,是因为s2中所占空间小的成员存放在一起

1.7 修改默认对齐数

#pragma 预处理指令 改变默认对齐数

#pragma pack(8)//设置默认对齐数为8
struct S1
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

1.8 结构体传参


在传址调用的时候,为了防止指针所指向的内容被修改可以加个const修饰
在这里插入图片描述
结论
结构体传参要选择传结构体地址

2 位段

2.1 什么是位段

位段的声明与结构体类似,有两个不同点:
1.位段的成员必须是整型家族的,例如:int,char
2.位段的成员名后面有1个冒号和数字(冒号后面的数字表示的是该元素的字节数)
不能超过类型的二进制位 例如char 类型不能超过8位 int类型不能超过32位…
位段中通常放置的都是同类型的元素(位段本身不太稳定,不适合太复杂)
在这里插入图片描述

2.2 位段的内存分配

在这里插入图片描述
位段是1个字节或者4个字节开辟的,不存在大小端字节序的问题。 大小端字节序是发生在多个字节的情况下
(vs编译器下位段的内存分配)字节的使用情况,从右向左使用(先使用低位空间),如果内存空间不够,再重新开辟一个,之前多余的内存空间直接舍弃。元素也不宜过大,若存放不下舍弃高位
在这里插入图片描述

2.3 位段的跨平台问题

在这里插入图片描述
在vs编译器上可以运行的代码,在16位机器上会出现问题,所以使用位段要针对不同平台编译不同代码
总结
跟结构体相比,位段可以达到同样的效果,一定程度上可以节省空间,但是存在跨平台的问题

2.4 位段的应用

在这里插入图片描述
IP数据报 位段在网络中应用广泛,很好的节省数据的大小,方便网络上的传输

3 枚举

3.1 枚举类型的定义

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

enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};
enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
}enum Color//颜色
{
 RED,
 GREEN,
 BLUE
};

枚举enum Color… 是枚举类型,类似于int,char
{ }中的内容是枚举类型的可能取值,也叫 枚举常量
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};

3.2 枚举类型的优点

为什么使用枚举?通过 #define 定义常量,为什么非要使用枚举?下面来介绍一下枚举的优点

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

在实现通讯录的过程中switch case 1 ,case 2 跟函数的实现不搭配,完全是自己定义的
可以通过枚举的方式优化

// 枚举类型
enum Option
{
 	//枚举的下标默认从0开始,0表示退出EXIT
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};
switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			SaveContact(&con);
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}

通过枚举将case 选项与要实现的具体函数对应起来,更加符合逻辑

优点2:和#define定义的标识符比较枚举有类型检查,更加严谨

#define Mon 1
#define Tues 2
....

#define 定义的标识符是没有类型的,而enum枚举是有类型的(枚举类型)变量
当然这一点在C语言当中很难体现出来,在C++可以很好的显示出问题(C++对语法、类型的检查更为严格)
在这里插入图片描述
C++认为5是int类型 而d是Day枚举类型,无法将5赋值给Day(但是C语言中可以实现)

优点3:防止命名污染(封装)

将EXIT,ADD,DEL,SEARCH,MODIFY,SHOW,SORT进行封装

优点4:便于调试

在这里插入图片描述

优点5:使用方便,一次可以定义多个常量

#define 一次只能定义一个,而枚举可以定义多个

4 联合

4.1 联合的定义

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
在这里插入图片描述
联合体中的c和i公用一块内存空间
在这里插入图片描述
根据联合的特点可以实现计算机大小端的判断
在这里插入图片描述

4.2 联合大小的计算

1、联合的大小至少是最大成员的大小。
2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。(vs的默认对齐数是8)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值