从头开始:自定义类型入门指南(结构体、位段、枚举、联合)(一)

简介: 从头开始:自定义类型入门指南(结构体、位段、枚举、联合)(一)

前言

在编程中,数据类型是非常重要的。然而,有时候标准的数据类型可能无法满足我们的需求。在这种情况下,自定义类型可以帮助我们更好地组织和表示数据。


结构体

结构的基础知识

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

结构体类型的声明

结构体格式:

struct 结构体标签名
{
  member-list;//  成员列表
}variable-list;// 变量列表;

我们举个例子:

例如:我们使用结构体描述一个学生的基本信息

struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s1;

name[20],age,sex[5],id[20],都是结构体成员,s1是结构体变量,指代某个学生,如学生1、学生2……

在结构体声明时还存在特殊的声明:在声明结构的时候,可以不完全的声明

例如:

struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;

上述的两个结构在声明的时候省略掉了结构体标签(tag)

那么这里思考一下,p是第二个匿名结构体的指针,两个匿名结构体结构体成员相同,那么,p等于&x是否合法?

虽然两个匿名结构体几乎相同,但编译器会把上面的两个声明,当成完全不同的两个类型。 所以是非法的。

注意:匿名结构体是一种方便的定义结构体变量的方式,但需要注意作用域和命名冲突等问题。

  • 匿名结构体只能在定义它的作用域内使用。这意味着如果需要在其他函数或文件中使用该结构体,就需要定义一个具有名称的结构体类型。
  • 匿名结构体只能在定义它的作用域内使用。这意味着如果需要在其他函数或文件中使用该结构体,就需要定义一个具有名称的结构体类型。
  • 匿名结构体不能被继承。由于没有名称,因此无法通过其他结构体继承它的成员。
  • 匿名结构体的定义通常用于临时变量或局部变量。如果需要定义一个全局变量或持久化变量,最好还是使用具有名称的结构体类型。
  • 如果在一个结构体中包含多个匿名结构体,那么它们之间的成员不能重名。否则会导致编译错误。

匿名结构体在使用时是一次性的,使用一次之后就无法被使用,所以在使用时慎用。

结构体的自引用

结构体的自引用是指结构体中的一个成员引用了结构体本身。接下来我们来看以下代码:

struct Node
{
int data;
struct Node next;
};

这样是否可行?如果可行那sizeof(struct Node)又是多少呢?

这样其实是不可行的,为什么呢?

自引用的结构体在编译阶段是无法确定大小的,因为结构体的大小取决于其成员的大小,而成员又依赖于结构体的大小。这样会导致一个无限循环的问题。

为了解决这个问题,可以使用指针或引用来间接引用结构体本身。这样可以避免结构体大小的无限循环问题。

正确的解引用方式为:

struct Node
{
int data;
struct Node* next;
};

那么接下来我们再来看看这段代码:

typedef struct
{
int data;
Node* next;
}Node;

typedef可以对结构体进行重命名。这样是否可行?

答案是不行,在结构体定义中,使用Node* next来声明成员是不允许的,因为在结构体定义中,Node尚未被定义,编译器无法确定Node的大小。这将导致编译错误。

为了解决这个问题,可以使用结构体的前向声明来声明成员。可以将结构体的定义和typedef分开,先声明结构体的名称,然后再定义结构体的成员。

我们可以这样定义:

typedef struct Node Node;
struct Node {
    int data;
    Node* next;
};
typedef struct Node
{
int data;
struct Node* next;
}Node;

这两种形式都是可以的。

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

结构体变量定义

结构体的变量有两种方式:

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

结构体大小

我们已经了解了结构体的基本使用,那么结构体如何计算大小呢?

这里就要引进新的内容——结构体内存对齐

这也是特别热门的考点。

首先我们要先掌握结构体的对齐规则:

  • 第一个成员在与结构体变量偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
  • 对齐数 是 编译器默认的一个对齐数 与 该成员大小的较小值。

VS中默认的值为8 ,gcc中无默认值。对齐数就是结构体成员的自身大小。

  •  结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

这些规则怎么理解呢?如下图:

第一个成员在与结构体变量偏移量为0的地址处,例如结构体struct S1,第一个成员c1为char类型,在偏移量为0的位置,大小占一个字节。

对齐数 是 编译器默认的一个对齐数 与 该成员大小的较小值。第二个结构体变量为int类型,大小是4个字节,而我所使用的vs默认对齐数8,选择较小值,所以i的对齐数应为4。

其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,i的对齐数是4,所以i要对齐到4的整数倍地址处,也就是4地址处,这是c1与i之间就会浪费3个字节的空间(这3个空间不存储数据)。

结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。第三个变量是c2,char类型占一个字节,对齐到1的整数倍地址处,也就紧挨着i,此时三个变量占了9个字节,但结构体的大小必须是结构体成员最大对齐数的整数倍,这里的9显然不是4的整数倍,所以系统会继续向后扩展“浪费”三个字节的空间到12个字节。

所以结构体struct S1占12个字节。

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

体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};

我们来看一下它们两个的结构体大小。

 

结构体struct S3第一个成员d大小是8,默认对齐数是8,选较小的数作为对齐数,相等就选8作为对齐数,在偏移量为0的位置向后8个字节都是d的空间范围。

第二个成员c,char类型对齐数是1,对齐位置也是1的倍数,所以紧挨着d,

第三个变量i,int类型占4个字节,对齐数是4,对齐位置需要是4的倍数开始,d+c占了9个字节的空间,i的对齐位置必须是4的倍数,所以向后扩展3个字节后i开始对齐。

d+c+i总共占了16个字节,刚好所有成员对齐数最大值8的整数倍,所以16就是struct S1的大小。

我们继续看struct S4,第一个成员c1占一个字节对齐数是1,在偏移量为0的位置。

 

第二个成员是结构体变量,类型为struct S3,前边我们计算出struct S3占16个字节,struct S3中最大的对齐数是8,嵌套的结构体对齐到自己的最大对齐数的整数倍处,也就是偏移量为8的位置处开始向后的16个字节的范围都是s3所占空间。

第三个成员d,类型为double类型占8个字节,对齐数是8,所以对齐位置要是8的倍数,也就是紧挨着s3的位置,向后8个字节的范围。

c1+s3+d总共占了32个字节的空间,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。所有对齐数最大的是8,所以32就是struct S4的大小。

相关文章
|
缓存 Kubernetes 开发者
Gitlab Runner的分布式缓存实战
配置兼容S3的分布式缓存minio,在k8s环境支持Gitlab CI脚本的缓存语法
358 1
Gitlab Runner的分布式缓存实战
|
存储 运维 持续交付
Nacos架构与原理 - 寻址机制
Nacos架构与原理 - 寻址机制
230 0
|
缓存 监控 网络性能优化
从内核的视角观测容器——SysOM 容器监控
从内核的视角观测容器——SysOM 容器监控
|
存储 安全 信息无障碍
可信计算平台与安全芯片扫盲文
可信计算平台与安全芯片扫盲文
567 0
|
自然语言处理
统一transformer与diffusion!Meta融合新方法剑指下一代多模态王者
【9月更文挑战第22天】该研究由Meta、Waymo及南加大团队合作完成,提出了一种名为Transfusion的新多模态模型,巧妙融合了语言模型与扩散模型的优点,实现了单一模型下的文本与图像生成和理解。Transfusion通过结合下一个token预测与扩散模型,在混合模态序列上训练单个Transformer,能够无缝处理离散和连续数据。实验表明,该模型在图像生成、文本生成以及图像-文本生成任务上表现出色,超越了DALL-E 2和SDXL等模型。不过,Transfusion仍面临计算成本高和图像理解能力有限等挑战,并且尚未涵盖音频和视频等其他模态。
320 2
|
域名解析 网络协议 数据库
|
JavaScript 前端开发 安全
XSS Challenges 通关解析
XSS Challenges 通关解析
|
Java 开发者
Java文档注解中@link与@see的使用详解
Java文档注解中@link与@see的使用详解
1840 0
|
SQL 机器学习/深度学习 开发框架
【网安AIGC专题10.25】8 CoLeFunDa华为团队:静默漏洞检测(识别+多分类)+数据增强、样本扩充+对比学习+微调+结果分析(降维空间,分类错误样本归纳,应用场景优势,有效性威胁分析)
【网安AIGC专题10.25】8 CoLeFunDa华为团队:静默漏洞检测(识别+多分类)+数据增强、样本扩充+对比学习+微调+结果分析(降维空间,分类错误样本归纳,应用场景优势,有效性威胁分析)
736 0
|
网络协议 网络虚拟化
VLAN高级技术
VLAN高级技术

热门文章

最新文章