自定义类型详解

1. 写在开始

外部客观世界纷繁多变,仅仅依靠编程语言提供的固有类型,难以详尽地描述刻画之;针对这一问题,C语言自身提供了自定义类型,供开发人员在实际的业务场景中,根据具体的需求,能够自定义数据类型,从而对现实问题进行抽象与代码实现。笔者今天的博客,主要是向大家介绍一些常用的自定义类型及使用(C语言)。内容如下:

2. 结构体类型

2.1 声明、定义与初始化

结构体的声明

  •  struct : 结构体声明关键字(由C语言提供)
  • Tag : 所定义的结构体命名
  • member_lists :  结构体内定义的各成员变量
  • variable_lists :  定义的结构体变量列表

如当我们需要描述一本书的时候,就可以定义一个‘书’的结构体类型:

struct Book
{
	char name[20]; // 书名
	float price; // 价格
	char id[10]; // 编号
};

结构体的定义与初始化

如上,我们已经声明了一个名为Book的结构体类型,当我们需要使用它时,就可以进行定义(实例化)并初始化:


typedef struct Book Book;  --- 1
int main()
{
	struct Book n1 = { "《高质量C/C++编程》", 54.5f,"B1002" }; --- 2
	printf("%-10s %.2f %-8s\n", n1.name, n1.price, n1.id); --- 3
	Book n2 = { "《C指针与陷阱》",66.7f, "B1006" }; --- 4
	Book* pn = &n2; --- 5
	printf("%-10s %.2f %-8s\n", pn->name, pn->price, pn->id); --- 6
	return 0;
}


解析:
1. 使用typedef关键字对struct Book类型关键字重命名为Book,方便之后的使用;
2. 我们定义了一个struct book类型的变量n1,并进行了初始化;
3. 第一个printf函数对结构体n1中各成员变量的内容进行了打印,访问各成员变量时,使用了.(结构体成员变量访问操作符)来进行访问内容;
4. 使用重命名的Book结构体关键字,定义了一个结构体变量n2,并进行了初始化;
5. 定义了一个Book* 类型的结构体指针pn,并将n2的地址赋值给了pn;
6. 第二个printf函数对结构体n2中各成员变量的内容进行了打印,访问各成员变量时,使用了->(结构体成员变量访问操作符)来进行访问内容。

  • 结构体变量的初始化: 使用 {}
  • 结构体变量的访问: 结构体变量.成员变量  /   结构体指针->成员变量

2.2 自引用

 当我们在使用结构体变量的过程中,想从一个结构体变量直接访问到另一个结构体变量的时候,我们就可以把该结构体的结构体指针类型定义添加到结构体成员中,从而能够通过结构体指针指向另一个结构体变量;我们将这种结构体声明的方式,称为结构体的自引用

Tips: 在数据将结构中,我们会经常使用结构体的自引用来定义结点。

2.3 内存对齐

我们在定义一个变量的时候,编译器会自动为该变量在内存中开辟一块空间;结构体变量也不例外,我们将结构体变量在内存开辟空间的方式、规则称为内存对齐。

内存对齐的规则

  1. 第一个成员变量从与结构体变量偏移量为0的地址处,往后开辟空间。
  2. 其他成员变量要从对齐到某个数字(对齐数)的整数倍的地址处,往后开辟空间。              (对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。)
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

Tips:  VS编译环境下,默认对齐数为 8。

举个例子

struct S3
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S3));

内存开辟情况

 运行结果

 再举个例子

// 嵌套结构体
struct S3
{
 double d;
 char c;
 int i;
};

struct S4
{
 char c1;
 struct S3 s3;
 double d;
};

printf("%d\n", sizeof(struct S4));

内存开辟情况

运行结果

为什么要进行内存对齐?

平台原因

并不是所有的硬件平台都能够访问任意地址上的任意数据的;某些硬件平台只能在某些地址处(如4的整数倍)取某些特定类型的数据,否则抛出硬件异常。

性能原因

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

究其本质:结构体的内存对齐实际就是一种拿空间来换取时间的做法。

2.4 传参

两种结构体传参方式

 然而,我们在结构体传参的时候: 应首选结构体地址传参

原因:

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

2.5 扩展: 位段

位段是结构体实现中的一种特殊类型,特殊在:

  • 位段成员类型必须是: unsigned  int  、  signed  int  或   int
  • 位段的成员名后边有一个冒号和一个数字

举个例子

struct A
{
     int _a:2;
     int _b:5;
     int _c:10;
     int _d:30;
};

值得关注的是:位段是以比特位为单位,根据成员变量后的数字,来为成员变量进行内存空间开辟

值得注意的是:跟结构体相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

位段存在跨平台的原因:

  • int 位段被当成有符号数还是无符号数是不确定的;
  • 位段中最大位的数目不能确定(int 在16位机器中占2个字节,在32位机器中占4个字节);
  • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义;
  • 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

3. 枚举类型

3.1 定义与特点

在现实世界中,有些事物的取值有限,且可以一一列举,如星期、月份、性别、三原色等;这个时候我们就可以定义枚举类型,对之进行刻画与描述。

举个例子

enum Day//星期
{
  Mon,
  Tues,
  Wed,
  Thur,
  Fri,
  Sat,
  Sun
};

enum Sex//性别
{
  MALE,
  FEMALE,
  SECRET
};

enum Color//颜色
{
  RED,
  GREEN,
  BLUE
};
  • enum:枚举定义关键字

  • {}中的内容: 枚举类型的可能取值,也称作枚举常量

值得注意的是: 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。

我们知道定义常量的时候,我们可以使用#define的方式,那么又为什么还要用到枚举呢?

枚举的优点

  • 增加代码的可读性和可维护性;
  • 便于调试;
  • 防止了命名污染(封装);
  • #define 定义的标识符比较枚举有类型检查,更加严谨。​

3.2 使用

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

enum Color c = RED  --- 1 // √
c = 5   // ×

解析:
1. 我们在定义好一个枚举类型的变量之后,只能通过枚举常量进行赋值。

4. 联合类型

4.1 定义与特点

联合是一种特殊的自定义类型,与结构体类型相似,也包含一系列的成员变量;但区别于结构体的是,这些成员变量共用同一块内存空间(所以联合也叫共用体)

举个例子

union Un
{
  char c;
  int i;
};
  • union : 联合定义关键字

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

究其本质:当我们使用联合时,只能使用其中的一个成员变量;因为各成员变量共用一块内存空间,当重复使用时,将造成数据的覆盖;因此,在实际的开发过程中,当某个事物可以有多个选项,但在特定的条件下,其只能选择其中的一个时,在该场景中,我们就可以使用联合。

4.2 计算大小

联合大小的计算

  • 联合的大小至少是最大成员的大小;
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

5. 小结

这篇文章,主要是对C语言中常用自定义类型的介绍与总结,重点要掌握结构体的内存对齐规则,以及了解枚举与联合的特性与使用细节。自定义类型,是我们在实际开发过程中,必须使用的且强大的工具之一,因此大家一定要好好掌握,熟练运用;最后,希望今天的博客内容,能够对你有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值