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

news2024/11/26 12:36:59

目录

文章目录

前言

结构体 

 结构体类型的声明

结构体的自引用

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

结构体变量定义

 初始化

 结构体大小

结构体传参

位段

 什么是位段

枚举

枚举的定义

枚举的优点 

 枚举的使用

联合(共用体)

联合类型的定义 

联合大小的计算 

总结


前言

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


结构体 

结构的基础知识

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

 结构体类型的声明

结构体格式:

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的大小。

为什么存在内存对齐?

平台原因(移植原因): 

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

性能原因:

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

总体来说:

  • 结构体的内存对齐是拿空间来换取时间的做法

 除此之外我们还可以修改默认对齐数

 #pragma 这个预处理指令,这里我们使用这个指令,可以改变我们的默认对齐数。

 例如:

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

 结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。

结构体传参

结构体传参有两种方式直接上代码:

struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};

//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}

//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}

int main()
{
print1(s);  //传结构体
print2(&s); //传地址
return 0;
}

 这两种方式哪个更好呢?

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

位段

 什么是位段

位段的声明和结构是类似的,有两个不同:

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

 例如:

struct A
{
int a:2;
int b:5;
int c:10;
int d:30;
};

 A就是一个位段类型。
那位段A的大小是多少?

printf("%d\n", sizeof(struct A));//8

 A的大小是8个字节,这是怎么回事呢?变量:后边的数字表示该变量所需的比特位(二进制位)。

位段的内存分配

  1.  位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

例如: 

 

 a占2个二进制位,b占5个二进制位,c占10个二进制位,那么系统在分配空间时就会分配一个整形空间给它们。

而d是30个二进制位,1个整形还剩余15个二进制位不够,那么此时系统会再次开辟一个整形空间,至于d是继续沿用剩余的15位空间,还是直接在新开的空间里存储,这里就涉及了很多不确定因素,编译器不同位段开辟的空间及使用的方式也会各不相同,所有位段是不跨平台的。

 这里及涉及到位段跨平台问题,主要因素:

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

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

位段的应用:

 

 可以尽可能的节省空间。

枚举

枚举就是把可能的取值一一列举。

在我们的日常生活中也有很多例子:如性别,月份,星期等。

枚举的定义

enum Color//颜色
{
RED,
GREEN,
BLUE
};

enum Color 就是是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。

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

enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
int main()
{
enum Color c=RED;
return 0;
}

枚举的优点 

那枚举有什么用呢?我们可以使用 #define 定义常量,为什么非要使用枚举? 枚举的优点:

  1.  增加代码的可读性和可维护性
  2.  和#define定义的标识符比较枚举有类型检查,更加严谨。
  3.  便于调试
  4. 使用方便,一次可以定义多个常量

 枚举的使用

enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。

 那这样是否可行呢?clr = 5,答案是不可行,枚举有类型检查,在c++编译时会出现报错,显示整形不能用于初始化枚举类型。

联合(共用体)

联合类型的定义 

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

 例如:

//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));//4

 c和i公用同一块空间。

通过调试我们也可以观察到:

 它们的地址相同。由此我们就可以推断出内存分布:

 使用c时就访问蓝色空间,使用i时就访问整块空间。我们通过程序感受一下:

union Un
{
	char c;
	int i;
};

union Un un;
int main()
{
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);//11223355
	return 0;
}

 i正常情况下应该是11223344,而使用联合,将c赋值0x55,由于它们共用同一块空间,所以在c赋值时就自动覆盖了i的后两位(程序运行在小端机器上)。

联合大小的计算 

规则如下:

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

 例如:

union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));//8
printf("%d\n", sizeof(union Un2));//16

总结

希望本文能够帮助你更好地理解自定义类型,并且能够在你的编程之路上起到一定的帮助作用。最后,感谢阅读! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/764669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Nginx的web集群项目

目录 nginx介绍代理集群 安装配置文件http 使用master和worker升级问题 基于域名的虚拟主机隐藏nginx的版本信息供别人下载的网站统计的信息的页面pv介绍 ngixn续nginx认证nginx的allow和denynginx限制并发数nginx限速限速的算法 nginx 限制请求数nginx 的 locationnginx 的 lo…

代码随想录算法训练营day4 | 24. 两两交换链表中的节点,19. 删除链表的倒数第 N 个结点,面试题 02.07. 链表相交,142. 环形链表 II

目录 24. 两两交换链表中的节点 19. 删除链表的倒数第 N 个结点 面试题 02.07. 链表相交 142. 环形链表 II 24. 两两交换链表中的节点 24. 两两交换链表中的节点 难度:medium 类型:链表 思路: 代码: class Solution {pub…

【目标检测】ROI Polling和ROI Align

ROI Pooling和ROI Align都是为了解决目标检测RPN任务后得到的一系列proposals大小不一致的问题。 ✨ 1 基本思想 🌊 1.1 ROI Pooling 假设有一张特征图大小为8x8(原图大小sxs),一个bbox坐标(0, 3, 7, 8),我们目标是获得大小为2x2的特征图作…

PostgreSQL 考试认证指南:考前准备和考试概述

下面是关于考前准备和考试概述的指南: 考前准备: 1.确定考试内容:详细了解考试的内容范围和考试要求。可以查阅PostgreSQL官方网站或认证考试指南,以获取相关信息。 2.学习和实践:系统地学习和掌握与PostgreSQL相关…

Animboat Application Framework

SpringBoot的服务将部署在云端 管理云端数据和处理分布式的业务请求 本地基础服务将作为云端和终端中间媒介, 与局域网内其它dcc 插件或者app运行实例进行通信, 同时本地基础服务将负责本地数据的管理。 每个AppInstance都会有自己的FlaskSvr用于与Loc…

前端学习记录~2023.7.16~CSS杂记 Day8

前言一、正常布局流二、弹性盒子1、为什么是弹性盒子2、指定元素的布局为flexible3、flex 模型说明4、列还是行?5、换行6、flex-flow 缩写7、flex 项的动态尺寸8、flex:缩写与全写9、水平和垂直对齐(1)align-items 属性&#xff0…

[极客大挑战 2019]PHP(反序列化)

介绍说明&#xff0c;有备份的习惯&#xff0c;找常见的备份文件后缀名 使用dirsearch进行扫描 dirsearch -u http://f64378a5-a3e0-4dbb-83a3-990bb9e19901.node4.buuoj.cn:81/ -e php-e 指定网站语言 扫描出现&#xff0c;www.zip文件 查看index.php <?php include c…

C\C++ 使用socket判断ip是否能连通

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan 简介&#xff1a; 使用socket判断ip是否能联通 效果&#xff1a; 代码&#xff1a; #include <iostream> #include <cstdlib> #include <cstdio> #include &…

Openlayers实战:加载GPX文件

在OPenlayers的交互中,经常性的我们要加载一些数据,在这个实战中,演示的是加载GPX文件。 GPX(GPS eXchange Format,GPS交换格式)是一个XML格式,为应用软件设计的通用GPS数据格式。它可以用来描述路点、轨迹、路程。这个格式是免费的,可以在不需要付任何许可费用的前提…

字体反爬破解

1、通过 f12 查看网页相关信息① 搜索“python”相关岗位&#xff0c;想爬取下来作为分析&#xff0c;但是看到html源码为特殊字符&#xff0c;而不是页面上直观能看到的文字信息②点击对应的css样式查看css源码&#xff0c;通过源码解析字体加密过程 2、通过 DomainURI 获取到…

npm如何发包、测试以及删除发布包?

发包&#xff1a; 先在 npm 官网创个号 https://www.npmjs.com/ 2.创好了之后就先创建自己的文件夹&#xff0c;我用的vscode&#xff0c;也可以自己在命令行里面敲&#xff0c;比如我在F:// 前端学习-VUE项目 创建 my_firs_npm npm init -y创建之后&#xff0c;你能得到一个…

使用nginx部署前后端分离项目,处理跨域问题(共享cookie)

1.唠嗑 踩坑了&#xff0c;花费一天时间&#xff0c;开始对nginx配置不懂&#xff0c;老是弄错了配置文件&#xff0c;之前装的nginx ,cofnig有两个&#xff0c;nginx.config和nginx.config.def &#xff0c;开始配置我在nginx.config中配置的&#xff0c;后面一直在改def&…

scala学习手册

1. case class学习 样例类模式匹配 1.1 样例类&#xff08;case class&#xff09;适合用于不可变的数据。它是一种特殊的类&#xff0c;能够被优化以用于模式匹配。 case class MetaData(userId: String)case class Book(name: String) {def printBookName(): Unit {printl…

修改conda默认的环境安装位置

修改conda默认的环境安装位置

go语言终端交叉编译的事项windows编译其它平台软件包

交叉编译的终极版本[以此为准]&#xff1a; windows编译窗口目前分为cmd窗口&#xff0c;powershell窗口&#xff0c;这两个里面运行的命令不一样。 1.cmd窗口编译&#xff1b; 在windows10之前的系统版本上使用cmd命令行可以使用命令 CMD命令行中 在CMD命令行中编译&#…

C++-string类的模拟实现

本博客基于C官方文档当中给出的string类当中的主要功能实现&#xff0c;来作为参照&#xff0c;简单模拟实现 My-string 。 对于C当中的string类的介绍&#xff0c;在之前的几篇博客当中有说明&#xff0c;如有问题&#xff0c;请参照一下两个博客文章进行参考&#xff1a; (2…

CTF 1和0

一、 对于此类型数据&#xff0c;将其复制到excel中&#xff0c;将0所在位置背景色设置为白色&#xff0c;将1所在的位置设置为黑色 如图所示 二、添加定位符 对于定位符来说&#xff0c;同样可以在excel表中添加1&#xff0c;且1所在单元格为黑色表示定位符&#xff0c;如下…

07 - 线性表的类型定义 - 循环单向链表

前两节我们已经学习了单链表和双链表的概念以及具体的实现过程,其中有一个问题,值得注意,这样的链表有一个共同特征,就是尾节点指向 null,可以说是一次性的,像是糖葫芦。想象一下,如果此时,糖葫芦变成了手串,也就意味着头尾相连,形成闭环,这就是单向链表的另外一种形…

提高工作效率的文件管理软件实践方法

在现代社会中&#xff0c;高效的工作效率是保持竞争力的关键。随着信息技术的不断发展&#xff0c;文件管理软件成为提高工作效率的重要工具之一。 一个好的文件管理软件需要具备直观友好的用户界面。用户界面的清晰易操作让用户更便捷地找到所需文件。在软件开发的初期&#…

KMP 算法推演总结

title: KMP 算法推演总结 date: 2023-07-17 16:07:13 tags: 算法 categories:数据结构与算法 cover: https://cover.png feature: false KMP 算法推演 可先见 Fan’s Web 字符串匹配的 BF 算法、RK 算法部分&#xff0c;后面的 BM 算法及 KMP 算法可以搭配一起看 KMP 算法的…