栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言进阶篇 四.自定义类型详解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言进阶篇 四.自定义类型详解

目录

结构体

结构体类型的声明

结构的自引用

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

结构体内存对齐

结构体传参

结构体实现位段(位段的填充&可移植性)

枚举

枚举的优点

枚举的使用

联合

联合类型的定义

联合大小的计算

联合的特点


结构体

结构体类型的声明

结构体的各种数据类型的集合,这些数据被称为结构体成员。创建结构体变量时,每个成员各自占有空间。

声明格式:

struct tag { member - list ; } variable - list ; 或 声明匿名结构体 struct { int a ; char b ; float c ; } a [ 20 ], * p ; 在声明时同时创建结构体指针变量p等,便于使用。

结构的自引用 struct Node { int data ; struct Node next ; }; 结构体引用自身作为成员是错误的,原因是结构体的大小将是未知的 正确的做法是将结构体指针作为成员,如: struct Node { int data ; struct Node* next ; }; 注意typedef后的类型在结构体中还不能使用,例如 typedef struct { int data ; Node * next ;//编译器还不认识Node } Node ; 正确的做法是: typedef struct Node { int data ; struct Node * next ; } Node ;

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

定义结构体变量有多种方式,例如

struct Point{ int x; int y; }p1;//定义结构体变量p1 或者 struct Point p2;//定义结构体变量p2 初始化结构体变量 struct Point p3={3,5}; 倘若是这样定义的结构体,还需嵌套初始化 struct Node { int data ; struct Point p ; struct Node * next ; } n1 = { 10 , { 4 , 5 }, NULL }; // 结构体嵌套初始化n1,用{}初始化结构体成员结构体变量 struct Node n2 = { 20 , { 5 , 6 }, NULL }; // 结构体嵌套初始化n2

结构体内存对齐

在计算结构体大小是需要考虑结构体内存对齐,结构体内存对齐存在的原因如下:

1.平台原因:某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.性能原因:在访问未对齐的内存,处理器需要访问两次,而对齐的内存只要访问一次

故数据结构(栈)应该尽可能在自然边界上对齐。

内存对齐规则:

1.第一个成员对齐到与结构体变量偏移为0的位置

2.其他成员对齐到各自对齐数的整数倍(对齐数是编译器默认对齐数与成员大小(字节数)的较小值)

3.结构体总大小为各个成员最大对齐数的整数倍

4.如果嵌套结构体,嵌套结构体对齐到自己成员最大对齐数的整数倍,结构体总大小是所有成员最大对齐数(含嵌套结构体的成员)的整数倍

VS2022编译器默认的对齐数是8,用#pragma pack(4)可以修改编译器的默认对齐数为4

再用#pragma pack()可以还原编译器的默认对齐数 用宏offsetof可以求结构体成员变量相对结构体首地址的偏移量 offsetof宏的实现 #define offsetof(StructName,MemberName) (size_t)&(((StructName*)0)->MemberName)

结构体传参

结构体传参通常传结构体的地址,即结构体指针,这样做节省内存的开销,因为结构体传参参数需要压栈,结构体较大时系统时间和空间开销大。

结构体实现位段(位段的填充&可移植性)

位段是结构体中的一种特殊的类型,(位段不具跨平台性,注重可移植程序避免使用)位段声明规则如下:

1.位段的成员必须是整形(int,char)。 2.位段的成员名后边有一个冒号和一个数字(数字表示开辟的bit位)。 位段的内存分配: 位段的空间上是按需以 4 个字节( int )或者 1 个字节( char )的方式来开辟的(空间不够则直接重新开辟字节) 例如:
struct S {
 char a:3;
 char b:4;
 char c:5;
 char d:4;
};
struct S s = {0};
s.a = 5; s.b = 12; s.c = 3; s.d = 4;

在VS下,在内存中布局如下(图有点草,c=3上面是00011打错了):

可见位段在字节中是从右向左开辟,不够位段成员大小则重新在更高地址开辟一个字节

在编译器下查看结构体s的内存内容发现确实如此: 

注意:位段的跨平台问题(基于以下原因,位段不支持跨平台性)

1.int 位段不知道被当成signed int还是unsigned int。 2. 位段中最大位的数目不确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题) 3. 位段中的成员在内存中分配顺序标准未定义。(VS2022是从右向左) 4. 当后一个位段成员无法容纳于第一个位段成员剩余的位时,不确定是否舍弃剩余位(VS2022下是舍弃的)

枚举

枚举是指将可能取值一一列举的类型,例如

enum Color { RED , GREEN , BLUE }; {} 中的内容是枚举类型的可能取值,也叫 枚举常量 。 这些可能取值都是有值的,默认从 0 开始,一次递增 1 ,当然在定义的时候也可以赋初值,未被赋值的变量的值将是上一个枚举常量+1(第一个枚举常量是0)。

枚举的优点

相比define定义的常量,枚举有类型检查更加的严谨。

定义枚举类型一次定义多个常量快捷方便,枚举的常量有各自的名称且是同一个类型,增加了代码的可读性便于维护。

枚举的使用

enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
像clr=4;这样赋值是不合理的因为enum color类型和int 类型不同。

虽然enum Color和int类型不同,但编译器却能正常运作,求枚举类型的变量的大小发现是4个字节

 

联合 联合也是一种特殊的自定义类型,这种类型定义的变量包含一系列的成员,与结构体不同的是,这些成员公用一块空间,所以联合也叫共用体。

联合类型的定义 联合类型的声明 union Un { char c ; int i ; }; 定义联合变量 union Un un ;

联合大小的计算

因为联合的成员是共用同一块内存空间的,这样一个联合体类型变量的大小,至少是最大成员的大小,但这不是联合体类型变量的最终大小,因为联合体也存在内存对齐

规则是,联合体大小至少是最大成员的大小;若其不是联合体成员最大对齐数的整数倍,则还要对齐到整数倍。(数组对齐数是数组成员类型的对齐数)

例如:

 首先VS2022下编译器的默认对齐数是8,c的对齐数是1,i的对齐数是4,数组a的对齐数是1,

这样联合un的大小至少是5,然后对齐到4的整数倍8

为什么至少是5而不是4呢,数组a算一个成员还是数组中各个成员都算做联合体的一个成员?

下面的例子说明了:

可见整个数组a是联合体的成员,否则结果就是8而不是12了。 

联合的特点

因为联合体成员公用一块空间,联合的特点自然是内存的存储上与结构体有所不同。 

例如:

i的大小刚好是整个联合体的大小,c则是一部分,

故i=0x22334455(4字节)时,c就等于0x55(1字节),从内存中也很好理解。

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/982640.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号