自定义类型详解

您所在的位置:网站首页 c语言中结构体的定义和使用 自定义类型详解

自定义类型详解

2023-06-15 00:21| 来源: 网络整理| 查看: 265

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 内存对齐

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

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

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3