自定义类型讲解

 

💕痛苦难道是白忍受的吗?💕

作者:Mylvzi 

 文章主要内容:自定义类型讲解 

一.结构体

定义:

数组:多组相同类型元素的集合

结构体:多组不同类型元素的集合-->管理多组不同类型数据的集合体,结构体中的数据也叫做结构体成员。

例如:管理学生的基本信息,需要的数据有学生的年龄,性别,身高等等

结构体关键字:struct

结构体的声明:

struct Stu//创建了一个结构体类型-->struct Stu-->整体是一种数据类型
{
	int age;
	float height;
	char name[20];
}s1,s2;//可直接在末尾添加你所需要的变量名

struct Stu s1, s2;//使用类型创建变量  类型+变量名  int a;

一种特殊的声明:匿名声明(忽略掉tag标签)

//特殊的声明-->匿名声明-->不告诉你具体名字
struct {
	int a;
	float b;
}x;//匿名定义了一个结构体变量x
//缺点:只能使用一次,无法对其修改
//优点:安全性高

//注意:未知tag,保证了其使用的唯一性
struct
{
	int a;
	char c;
	float f;
}x;

struct 
{
	int a;
	char c;
	float f;
}* p;

int main()
{
	p = &x;//err
	//尽管成员列表相同,但都是匿名结构体变量,未知类型,会发生类型转换报错
	return 0;
}

结构体的自引用:

//结构体的成员列表不能存在一个类型和该结构体一样的结构体
//套娃是非法的;无法计算具体的大小

但可以有和原结构体类型相同的结构体指针变量,指向下一个结构体;(链表中常使用)


//通过结构体访问下一个结构体
struct Node
{
	int data;
	struct Node next;//err
	//sizeof(struct Node)是多少?无法计算
};

//改进
struct Node
{
	int data;
	struct Node* next;//存放下一个结构体的地址
};

int main()
{
	printf("%zd\n", sizeof(struct Node));
	return 0;
}

//错误的命名方式
typedef struct
{
	int data;
	Node* next;
}Node;
//先typedef为Node后才能使用Node,不能直接在成员列表内使用

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

尽量不要使用匿名的方式声明结构体,可能声明错误; 

结构体定义和初始化:

注意:使用.操作符初始化结构体时,可以不按照顺序初始化;否则,一定要严格按照结构体成员顺序进行初始化 

struct SN
{
	char c;
	int i;
}sn1 = { 'q', 100 }, sn2 = {.i=200, .c='w'};//全局变量

结构体的内存对齐(重要): 

先来计算两个结构体的大小:

再来看成员相较于结构体初始地址的偏移量(利用到offsetof宏) 

  

通过以上两个现象,我们知道,结构体成员在内存中存储时并不是连续存储的,且其大小也不能简单的通过成员大小加和的方式得到;实际上,结构体在内存中的存储以及其大小是有一定的规则,这个规则叫做结构体内存对齐 

结构体内存对齐规则:

1.结构体第一个成员的起始地址总是位于结构体偏移量为0的地址处;

2.从第二个成员开始,剩下的成员在内存中存放时要对齐到其对齐数的整数倍处;

对齐数:默认对齐数和成员自身大小的较小值,vs的默认对齐数8,Linux中无默认对齐数,对齐数是成员大小本身

3.结构体内存大小:必须是最大对齐数的整数倍

4.嵌套结构体:如果一个结构体嵌套了一个结构体,嵌套的结构体在内存对齐时对齐到其最大对齐数的整数倍处,整个结构体的内存大小是最大对齐数的整数倍(含嵌套的结构体的对齐数) 

 利用内存对齐规则分析上述两个结构体的内存分布及内存大小:

为什么要进行内存对齐呢?

有两个原因:
1.平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。硬件不同,读取数据的方式不同,读取到的内容也就不同,通过内存对齐可以实现跨硬件读取数据;

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

总而言之,结构体内存对齐是一种拿空间换时间的做法,尽管浪费掉了一些内存空间,但我们访问数据的速度大大提升;

 但是,我们也可以做到结构体空间的最优化-->将内存空间小的数据集中在一起,比如s1,s2

修改默认对齐数:

#pragma预处理指令

#pragma pack(16)//修改默认对齐数为16
struct Stu
{
	int i;
	char c1;
	char c2;
};
#pragma pack()//恢复默认对齐数

结构体传参: 

传递结构体时,尽量传递结构体地址(使用结构体指针接收)

struct S
{
	int data[1000];
	int num;
};

void print1(struct S p)//传递结构体本身
{
	printf("%d\n", p.num);//形参是实参的临时拷贝,传递结构体本身会重新开辟一块儿内存空间
}

void print2(struct S* p)//传递结构体地址  //如果不希望p所指向的内容被改变,添加const修饰
{                                       //const struct S* p
	printf("%d\n", p->num);
}
//print2效率更高,减少了空间的开辟。提高效率;
int main()
{
	struct S s1;
	print1(s1);
	print2(&s1);
	return 0;
}

二.位段 

位段的定义及内存分配

讲完结构体就需要讲一下结构体实现位段的能力

位段-->给成员分配具体大小内存空间的结构体

注意:

1.声明和结构体相同

2.成员必须是int,unsigned int,char类型

3.设计格式:成员类型 成员名:具体大小

4.位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

 

 观察下列位段在内存中的分配:

位段的跨平台问题:

1. int 位段被当成有符号数还是无符号数是不确定的。

2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。

 位段的应用: 

不难发现,位段是一种对空间极度优化的结构体,往往应用到空间利用率高的数据存储,或者大量开关信息的存储;

举例:信息的传递(ip数据包的传递)

​​​​​​​

三.枚举

定义:

枚举也是一种存储数据的自定义类型,顾名思义,如果取值能够被一一枚举,那么我们就可以使用枚举来存储相应的数据

枚举关键字:enum

enum Sex//性别
{
	MALE,
	FEMALE,
	SECRET
};
enum Color//颜色
{
	//都有默认取值
	RED,//0
	GREEN,//1 GREEN = 5;也可以人为赋值
	BLUE//2
};
//enum Color,enum Sex都是枚举类型

优点: 

1. 增加代码的可读性和可维护性

        更加规范,代码量少;便于维护

2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

        只能使用枚举类型的数据进行赋值,否则会报错

3. 便于调试

4. 使用方便,一次可以定义多个常量

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

枚举的应用(来源于chatgpt)

四.联合体(共用体)

定义:

联合体也是一种存储多种数据的自定义类型,其特点是所有的成员共用同一块内存(所以也叫共用体)

联合体关键字:union

//联合体
union Un
{
	int a;
	char b;
};
int main()
{
	printf("%d\n", sizeof(Un));//4
	return 0;
}

特点:

所有成员共用同一块儿空间

 

联合体大小计算:

1.内存大小至少是最大成员的内存大小(必须能够保存该数据)

2.且最终大小要是最大对齐数的整数倍

 利用联合体检验当前计算机存储方式(大小端的检验)

//大小端的检验
//之前写法
//检查首地址元素的值
int check_sys(int* p)
{
	int b = *(char*)p;//得到首地址
	return b;
}
int main()
{
	int a = 1;
	int ret = check_sys(&a);
	if (ret == 1)
		printf("小端");
	else
		printf("大端");
	return 0;
}

int check_sys()
{
	union
	{
		int i;
		char c;
	}un = {un.i=1};
	return un.c;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端");
	else
		printf("大端");
	return 0;
}

 

 五.总结

  这篇文章详细介绍了四种自定义类型,结构体(struct),位段(指定成员大小的结构体),枚举类型(enum),联合体(union);要掌握他们的声明,定义方式,在内存中的存储方式,以及内存大小的计算;尽管在目前学习中,自定义类型的应用场景较少,但在之后的学习中会大量使用

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Rust vs Go:常用语法对比(十三)

题图来自 Go vs. Rust: The Ultimate Performance Battle 241. Yield priority to other threads Explicitly decrease the priority of the current process, so that other execution threads have a better chance to execute now. Then resume normal execution and call f…

kettle 学习笔记

kettle 学习笔记 个人理解下载 / 安装kettle及测试环境准备kattle下载安装JDK安装配置MySQL安装配置 使用练习创建数据库连接转换练习 个人理解 ETL工具的一种,作用是将数据进行抽取,转换,应该是数据中心类型的项目用的比较多,将…

用html+javascript打造公文一键排版系统8:附件及标题排版

最近工作有点忙,所 以没能及时完善公文一键排版系统,现在只好熬夜更新一下。 有时公文有包括附件,招照公文排版规范: 附件应当另面编排,并在版记之前,与公文正文一起装订。“附件”二字及附件顺序号用3号黑…

网络层中一些零碎且易忘的知识点

异构网络:指传输介质、数据编码方式、链路控制协议以及数据单元格式和转发机制不同,异构即物理层和数据链路层均不同RIP、OSPF、BGP分别是哪一层的协议: -RIPOSPFBGP所属层次应用层网络层应用层封装在什么协议中UDPIPTCP 一个主机可以有多个I…

【2023年11月第四版教材】《第1章-信息化发展之<1信息与信息化>》

第01章-信息化发展 1 信息与信息化 大部分为新增内容,预计选择题考4分,案例和论文不考。本章与第三版相同内容将斜体表示。 1 信息与信息化 1、信息是物质、能量及其属性的标示的集合,是确定性的增加。 2、控制论的创始人维纳认为:信息就是信…

【大数据趋势】7月30日 汇率,恒指期货的大数据趋势概率分析。

1. 数据源头之一 : 汇率变化 从程序模拟趋势来看,美元在持续弱势状态,周线上正在构建一个新的下跌趋势,而且正在反抽过程中,即将完成,如果没有外部干预,会顺势往下。从月线来看,高点逐步降低&a…

学习记录——Octave Convolution、LSK

Octave Convolution 2019 ICCV 自然世界中的图像存在高低频,卷积层的输出特征图以及输入通道,也都存在高、低频分量。 低频分量支撑的是整体轮廓,高频分量则关注细节,显然,低频分量是存在冗余的,在编码过程…

区块链学习笔记

区块链技术与应用 数组 列表 二叉树 哈希函数 BTC中的密码学原理 cryptographic hash function collsion resistance(碰撞抵抗) 碰撞指的是找到两个不同的输入值,使得它们的哈希值相同。也就是说,如果存在任意两个输入x和y,满足x ≠ y…

AC+FIT(瘦AP)配置浅谈

FIT ensp实验材料 :pc、路由器、三层交换机、二层交换机、ac、ap 保证连通性: 根据ac与ap设计好的ip配置,使之可以通讯 ac与ap可以实现跨网段管理 1、设置三层交换机的vlan 与vlanif信息 dhcp enable //开启dhcp ip pool forap //…

WEB:unseping

背景知识 php序列化和反序列化 命令执行绕过方式 题目 进行代码审计 可知为反序列化 整体是创建case类,可接受post传来的ctf值 _consturuct函数,是在函数调动前启用,构造了$method和$args两个变量。 _dexstruct函数在变量摧毁的时使用,所…

SQL 执行计划管理(SPM)

一、SPM 需求背景 任何数据库应用程序的性能在很大程度上都依赖于查询执行,尽管优化器无需用户干预就可以评估最佳计划,但是 SQL 语句的执行计划仍可能由于以下多种原因发生意外更改:版本升级、重新收集优化器统计信息、改变优化器参数或模式…

IT技术面试中常见的问题及解答技巧

在IT技术面试中,面试官常常会问到一些常见的问题,针对这些问题,我们可以充分准备和提前准备一些解答技巧。下面我将分享一些我个人的经验和观察,希望对大家有所帮助。 请介绍一下你的项目经验。 在回答这个问题时,我们…

Linux命令大全

目录 第一章、系统命令1.1)系统命令1.2)目录结构1.3)编辑命令vi/vim 第二章、文件操作命令(区分大小写)2.1)查看查找文件和文件信息,切换目录2.2)新建/删除/复制/移动修改文件和文件…

go 如何知道一个对象是分配在栈上还是堆上?

如何判断变量是分配在栈(stack)上还是堆(heap)上? Go和C不同,Go局部变量会进行逃逸分析。如果变量离开作用域后没有被引用,则优先分配到栈上,否则分配到堆上。判断语句:…

苍穹外卖day10——订单状态定时处理(Spring Task)、来单提醒和客户催单(WebSocket)

预期效果 对于超时没处理的需要定时程序处理。基于SpringTask实现。 来单提醒和客户催单。基于WebSocket实现。 Spring Task 介绍 Cron表达式 周几通常不能和日一起指定。 cron表达式在线生成器 在线Cron表达式生成器 入门案例 创建定时任务类 /*** 定义定时任务类*/ Slf4j…

HCIA实验四

一.实验要求: 1、R4为ISP,其上只能配置IP地址;R4与其他所有直连设备间均使用共有IP; 2、R3 - R5/6/7为MGRE环境,R3为中心站点; 3、整个网络配置OSPF环境,IP基于172.16.0.0/16网段划分&#x…

Hexo+GithubPages免费搭建个人博客网站

HexoGithubPages免费搭建个人博客网站 目录 一、前言二、Github配置 新建同名仓库配置Pages 三、安装Hexo四、配置hexo-deployer-git五、访问六、发布文章七、安装主题 一、前言 我之前开了好几年的云服务器了,实际上使用场景并不是很多,感觉有点浪费…

01|Oracle学习(监听程序、管理工具、PL/SQL Developer、本地网络服务介绍)

基础概念 监听程序:运行在Oracle服务器端用于侦听客户端请求的程序。 相当于保安,你来找人,他会拦你,问你找谁。他去帮你叫人过来。 配置监听程序应用场景 Oracle数据库软件安装之后没有监听程序(服务)…

Leetcode | Binary search | 22. 74. 162. 33. 34. 153.

22. Generate Parentheses 要意识到只要还有左括号,就可以放到path里。只要右括号数量小于左括号,也可以放进去。就是valid的组合。recurse两次 74. Search a 2D Matrix 看成sorted list就好。直接用m*n表示最后一位的index,并且每次只需要 …

软件测试员的非技术必备技能

成为软件测试人员所需的技能 非技术技能 以下技能对于成为优秀的软件测试人员至关重要。 将您的技能组合与以下清单进行比较,以确定软件测试是否适合您 - 分析技能:优秀的软件测试人员应具备敏锐的分析能力。 分析技能将有助于将复杂的软件系统分解为…