自定义数据类型:结构体,枚举,联合

news2024/9/29 9:28:55

之前我们已经了解过结构体,这篇文章再来深入学习的一下,然后再学习其他自定义数据类型,枚举和联合

目录

1.结构体

1.1 结构体类型的声明

1.2 结构体的自引用

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

1.4 结构体内存对齐

1.5 结构体传参

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

2.枚举

2.1 枚举类型的定义

2.2 枚举的优点和使用

3.联合

3.1 联合类型的定义

3.2 联合的特点

3.3 联合大小的计算


1.结构体

1.1 结构体类型的声明

结构体的基础知识:

内置数据类型有char short int long long float double 等,而C语言也允许我们自己创建类型。

数组是一组相同类型元素的集合,而结构体可以是不同类型成员的集合。

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

结构的声明

struct tag//tag为结构体名称,可以自己设置
{
	member-list;//成员列表
}variable-list;//变量列表

例如声明一个学生结构体

struct Student//结构体名称是Student, 可以自己起不同的名称
{
	//成员变量
	char name[20];//姓名
	int age;//名字
	float weight;//体重
};//最后要加分号

特殊的声明:匿名结构体类型

//匿名结构体类型
struct
{
	char c;
	int a;
	double d;
}s1,s2;//只能在这里创建变量,后面不能再使用,相当于一次性

struct
{
	char c;
	int a;
	double d;
}*ps;//结构体指针

int main()
{
	ps = &s1;
	//虽然*ps的类型与s1的类型内容相同
	//但他们都是匿名结构体变量,编译器会认为它们不同
	//会有警告
	return 0;
}

警告:编译器会认为两个匿名结构体是完全不同的两个类型。

1.2 结构体的自引用

在结构体中包含一个类型为该结构本身的成员是否可以?

错误的示范
struct Node1
{
	int data;
	struct Node1 n;
};
结构体内部有自己,那它的大小是什么,自己大小+data大小,自己大小会无限嵌套

正确的自引用方法

struct Node
{
	int data;//4
	struct Node* next;//指针 4/8
};

int main()
{
	struct Node n1;
	struct Node n2;
	n1.next = &n2;//n1的下一个数据是n2
	return 0;
}

通过这个结构体指针,我们就可以找到下一个结构体单元,使数据链接起来。

typedef类型重命名

typedef struct
{
	int data;
	char c;
}S;//有typedef这里S是类型,不是结构体变量

typedef struct
{
	int data;
	Node* next;//这样是错误的,还没有定义Node 报错
}Node;

解决方法:
typedef struct Node
{
    int data;
    struct Node* next;
}Node;

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

结构体变量的定义:

有了结构体类型,我们来定义一下结构体变量

struct S
{
	int a;
	char c;
}s1;//1.声明时创建,是全局变量

struct S s2;//2.通过类型创建,全局变量

int main()
{
	struct S s3;//3.通过类型创建,是局部变量
	return 0;
}

结构体变量的初始化

#include<stdio.h>
struct S
{
	int a;
	char c;
}s1;

struct B
{
	float f;
	struct S s;
};

int main()
{
	//创建变量时初始化
	struct S s2 = { 100,'a'};//1.按顺序初始化
	struct S s3 = { .c = 'r', .a = 100 };//2.自己指定顺序初始化

	struct B sb = { 3.14f,{200,'w'} };

	printf("%f %d %c\n", sb.f, sb.s.a, sb.s.c);

	return 0;
}

1.4 结构体内存对齐

这是结构体知识中比较重要的内容

我们已经掌握了结构体的基本使用,现在我们深入讨论一个问题,如何计算结构体的大小

#include<stdio.h>
struct S1
{
	int a;
	char c;
};
struct S2
{
	char c1;
	int a;
	char c2;
};

struct S3
{
	char c1;
	int a;
	char c2;
	char c3;
};


int main()
{
	printf("%d\n", sizeof(struct S1));//8
	printf("%d\n", sizeof(struct S2));//12
	printf("%d\n", sizeof(struct S3));//12

	return 0;
}

看到这个结果,大家肯定会有疑惑,结构体的大小究竟是怎么算的。

如果计算大小,首先要知道结构体对齐规则

1.结构体的第一个成员永远都放在0偏移处

2.从第二个成员开始,以后的每个成员都要对齐到某个对齐数的整数倍数。

  这个对齐数时:成员自身大小和默认对齐数的较小值

  备注:VS环境下默认对其数是8

        gcc环境下没有默认对齐数,没有默认对齐数就是成员自身的大小

3.当成员全部存放进去后,

  结构体的总大小必须是:所有成员的对齐数中最大对齐数的整数倍 如果不够,则浪费空间对齐

4.如果嵌套了结构体,嵌套的结构体要对齐到自己成员的最大对齐数的整数倍,整个结构体的大小,必须是最大对齐数的整数倍,最大对齐数包含嵌套的结构体成员中的对齐数

我们拿这个结构体来举例计算一下

struct S3
{
	char c1;
	int a;
	char c2;
	char c3;
};

 c1是第一个成员放在0偏移处,a的大小是4个字节,与默认对齐数相比,它的对齐数是4,放在4的倍数处,也就是4偏移量处,c2和c3大小都是1,偏移量都是1的倍数,所以直接在后面存放。

这个结构体的大小就是 最大对齐数4 的 整数倍 ,12

可以再做些练习

#include<stdio.h>

struct S2
{
	char c1;
	char c2;
	int a;
};
struct S3
{
	double d;
	char c;
	int i;
};
struct S4
{
	char c1;
	struct S3 s3;//16
	double d;
};

int main()
{
	printf("%d\n", sizeof(struct S2));//8
	printf("%d\n", sizeof(struct S3));//16
	printf("%d\n", sizeof(struct S4));//32
	return 0; 
}

为什么存在内存对齐

大部分的参考资料都是这样说的

1.平台原因(移植原因)∶不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
⒉性能原因︰数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说︰

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

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

就是让占用空间小的成员尽量集中在一起。

 修改默认参数 #pragma pack 预处理指令

#include<stdio.h>
#pragma pack(1)//把默认对齐数改为1
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)
struct S
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	printf("%d\n", sizeof(struct S));//6
	printf("%d\n", sizeof(struct S1));//12

	return 0;
}

1.5 结构体传参

结构体传参有两种方式

#include<stdio.h>
struct S
{
	int data[1000];
	int num;
};

struct S s1 = { {1,2},100 };

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

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

int main()
{
	//传结构体变量
	print1(s1);

	//传结构体指针
	print2(&s1);
}

上面的print1和print2函数哪个好些?

答案是∶首选print2函数。原因︰

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。就是再拷贝一份数据。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

结论结构体传参的时候,要传结构体的地址。
 

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

什么是位段

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

1.位段的成员是int , unsigned int ,signed int 或其他整形家组char...
2.位段的成员后边有一个冒号和一个数字

#include<stdio.h>

//位段   二进制位

struct A
{
	//_只是成员变量的名称中的下划线,没有什么特殊意义
	int _a : 2;//存放两个二进制位就能表示的数字  00 或 01 或 10 或 11
	int _b : 5;
	int _c : 10;
	int _d : 30;
};//47个bit位

//一个字节8个bit位

int main()
{
	struct A sa = { 0 };
	printf("%d\n", sizeof(sa));//8 个字节,相比16个字节已经节省空间了
	printf
	return 0;
}

上述代码中A就是一个位段类型,它的大小为sizeof(struct A)是8个字节。

为什么是8个字节,这里我们学习一下位段的内存分配

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

 在VS平台上,段位数据的内存分配方式如下:

1.分配到的内存中的比特位是由右向左使用的,高地址到低地址
2.分配的内存剩余的比特位不够使用时,浪费掉,开辟新的空间

 我们来验证一下

#include<stdio.h>

struct S
{
	char _a : 3;
	char _b : 4;
	char _c : 5;
	char _d : 4;
};

int main()
{
	struct S s = { 0 };
	s._a = 10;//1010  截断 010
	s._b = 12;//1100
	s._c = 3;//00011
	s._d = 4;//0100
	return 0;
}

 空间开辟过程:

 调试验证:

 位段的跨平台问题

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

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

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

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

总结︰

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

2.枚举

枚举顾名思义就是一一列举。

把可能的取值一一列举。

比如我们现实生活中︰

一个的星期一到星期日是有限的7天,可以——列举。

性别有:男、女、保密,也可以——列举。

月份有12个月,也可以——列举

这里就可以使用枚举了。

2.1 枚举类型的定义

enum Sex
{
	//枚举的可能取值,默认是从0开始,递增1的
	MALE,
	FEMALE=5,
	SECRET
};

2.2 枚举的优点和使用

 枚举的优点

为什么使用枚举?

我们可以使用#define定义常量,为什么非要使用枚举﹖枚举的优点︰

1.增加代码的可读性和可维护性
⒉和#define定义的标识符比较枚举有类型检查,更加严谨。

3.防止了命名污染(封装)
4.便于调试
5.使用方便,一次可以定义多个常量

 枚举的使用:

enum Sex
{
	//枚举的可能取值,默认是从0开始,递增1的
	MALE,
	FEMALE=5,
	SECRET
};

int main()
{
	enum Sex s = MALE;//把可能的确实赋给这样的变量 
	printf("%d\n", MALE);//0
	printf("%d\n", FEMALE);//5
	printf("%d\n", SECRET);//6
	return 0;
}

 也可以给枚举变量直接赋值相应的数字,但这种方法在C++中是不行的

如:

enum Sex s = 0;//MALE

 枚举类型和变量的大小:

枚举类型成员都是int类型,大小都是4个字节

#include<stdio.h>

enum Sex
{
	MALE,
	FEMALE=5,
	SECRET
};

int main()
{
	enum Sex s = MALE;

	printf("%d\n",sizeof(enum Sex));//4
	printf("%d\n", sizeof(MALE));//4
	printf("%d\n", sizeof(s));//4
	return 0;
}

3.联合

3.1 联合类型的定义

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

#include<stdio.h>

//一个时间段只能使用一个值
union Un
{
	char c;
	int i;
};

int main()
{
	union Un u;
	printf("%d\n", sizeof(u));//4

	//下面三个都相等
	printf("%p\n", &u);
	printf("%p\n", &u.i);
	printf("%p\n", &u.c);
	return 0; 
}

3.2 联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。

 可以利用这一点判断是大端存储还是小端存储

#include<stdio.h>

union Un
{
	char c;
	int i;
};

int main()
 {
	union Un u;
	u.i = 1;
	if (u.c == 1)
	{
		printf("小端存储\n");
	}
	else
	{
		printf("大端存储\n");
	}
}

3.3 联合大小的计算

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
#include<stdio.h>

union Un
{
	char arr[5];
	int n;
};

int main()
{
	printf("%d\n", sizeof(union Un));//8
	return 0;
}

这里输出结果是8,是因为在联合体中也存在对齐。数组的对齐数是按类型算,最大对齐数是4,最大对齐数的整数倍是8。

本篇结束

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

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

相关文章

【Shermo学习】使用shermo批量读入ORCA频率计算结果文件,并批量输出热力学校正数据

使用shermo批量读入ORCA频率计算结果文件&#xff0c;并批量输出热力学校正数据 安装与运行简单任务示例批量输出热力学校正数据 Shermo是北京科音自然科学研究中心卢天老师开发的一个程序&#xff0c;可以用来处理量子化学计算过程中的热力学数据。本文基于Shermo程序&#xf…

数据库设计篇-范式与反范式

概述 一般地&#xff0c;在进行数据库设计时&#xff0c;应遵循三大原则&#xff0c;也就是我们通常说的三大范式&#xff0c;即第一范式要求确保表中每列的原子性&#xff0c;也就是不可拆分&#xff1b;第二范式要求确保表中每列与主键相关&#xff0c;而不能只与主键的某部…

抖音林客生活服务商平台

抖音林客生活服务服务商平台是为了方便服务商管理自己的服务和订单而设计的平台。以下是其主要功能&#xff1a; 服务管理&#xff1a;服务商可以在平台上添加自己提供的服务&#xff0c;并设置服务的价格、规格等信息&#xff1b; 订单管理&#xff1a;服务商可以查看…

【Git总结】

第三章Git常用命令 Git注意首次 安装必须设置一下用户签名&#xff0c;否则无法提交代码。 vim 文件名&#xff08;hellow.txt&#xff09;//进入编辑模式 cat 文件名&#xff08;hellow.txt&#xff09;//查看文件内容 i进入编辑模式&#xff0c;(Esc):wq保存退出 &#…

掌握imgproc组件:opencv-图像处理

图像处理 1.线性滤波&#xff1a;方框滤波、均值滤波、高斯滤波1.1 平滑处理1.2 图像滤波与滤波器1.3 线性滤波器的简介1.4 滤波和模糊1.5 邻域算子与线性邻域滤波1.6 方框滤波1.7 均值滤波1.8 高斯滤波1.9 线性滤波综合案例 2.非线性滤波&#xff1a;中值滤波、双边滤波2.1 中…

python自定义序列类深入学习

一&#xff1a;自定义序列类 1、序列类型的分类 容器序列&#xff1a; list 、 tuple、deque 扁平序列&#xff1a; str 、bytes、bytearray、arry.array 可变序列&#xff1a; list 、deque、bytearray、array 不可变&#xff1a; str、tuple、bytes 容器序列表示可以放置任意…

计算机中CPU、内存、缓存的关系

CPU&#xff08;Central Processing Unit&#xff0c;中央处理器&#xff09; 内存&#xff08;Random Access Memory&#xff0c;随机存取存储器&#xff09; 缓存&#xff08;Cache&#xff09; CPU、内存和缓存之间有着密切的关系&#xff0c;它们共同构成了计算机系统的核…

软考高级系统架构设计师(八) 基于中间件的开发实际项目

目录 中间件的用途 中间件技术 中间件的特点 中间件的十大优越性 企业应用集成 轻量级架构 Struts框架 spring Hibernate 实际项目举例 产品逻辑大图 gRPC的接口规范 关键中间件交互 整体架构设计 大数据素材底层处理 业务交互大图 底层数据素材加工大图 中间…

这些年你走了多少弯路?接口性能测试你真的懂了?进阶测试...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 性能测试关注 响…

java + opencv对比图片不同

1&#xff0c;去官网下载opencv&#xff0c;下载的时候需要注册一个 Oracle 账户&#xff0c;分分钟就能注册。然后安装。我下的是4.7的。 2&#xff0c;找到jar包放进项目里 3&#xff0c;项目结构&#xff0c;比较简单 4&#xff0c;把下载的文件放进C盘 5&#xff0c;主类代…

未来独角兽!安全狗入选2023年福建省数字经济核心产业领域创新企业名单

近日&#xff0c;福建省数字福建建设领导小组办公室公布了入选2023年全省数字经济核心产业领域创新企业名单。 作为国内云原生安全领导厂商&#xff0c;安全狗凭借综合安全能力入选名单&#xff0c;荣膺“未来独角兽”称号。 厦门服云信息科技有限公司&#xff08;品牌名&#…

7DGroup性能实施项目日记4

经过了一个十一假期&#xff0c;我们的日记虽然没有更新&#xff0c;但我们的项目并没有停止。 虽然这个项目是一个依托性能培训的项目&#xff0c;但对我来说&#xff0c;这和真实的项目并无二致。我们花了几万&#xff08;根据一期的培训&#xff0c;估计在3万-4万左右&…

高速电路设计系列分享-基本概念

目录 概要 整体架构流程 技术名词解释 1.带宽的理解 2.了解转换器的精度 技术细节 小结 概要 提示&#xff1a;这里可以添加技术概要 本文主要熟悉一些基本概念。随笔&#xff0c;加一些网上用语&#xff0c;只做学习之用&#xff0c;不用深入分析。 整体架构流程 提…

最佳的SCADA软件推荐

前言 发现优化工业运营效率并最大化投资回报率的最佳“监控和数据采集 &#xff08;SCADA&#xff09;、工业物联网 &#xff08;IIoT&#xff09;、人工智能 &#xff08;AI&#xff09; 等”数字化转型技术&#xff0c;使商业组织能够提高运营效率并促进公用事业管理。当配备…

浏览器插件开发(一)入门之自制屏蔽某度广告的插件

一&#xff1a;浏览器扩展介绍&#xff1a; 浏览器扩展是一个小软件定义模块的网页浏览器&#xff0c;通常称为浏览器插件&#xff0c;大部分浏览器允许安装拓展&#xff0c;其作用有用户界面修改&#xff0c;广告拦截和Cookie管理等 包含基本平台信息的应用程序清单JSON文件 …

第十章 总结【编译原理】

第十章 总结【编译原理】 前言推荐第十章 总结10.1 概述10.2 局部优化10.2.1基本块及流图10.2.2基本块的DAG表示及其应用 10.3 循环优化*10.4数据流分析 最后 前言 2023-6-26 18:54:31 以下内容源自《【编译原理】》 仅供学习交流使用 推荐 第九章 总结及作业&#xff08;4…

态路小课堂丨三种实现光模块更高传输速率的技术你知道吗?

TARLUZ态路 随着云计算、大数据的快速兴起&#xff0c;数据中心以及电信运营商对光模块的传输速率要求越来越高。从1998年发展至今&#xff0c;光模块一直朝着更高的速率、更小的封装不断升级。光模块一般采用增加波长数、增加信号传输通道数量和提高单通道速率技术方案实现光模…

公众号内添加投票链接制作投票的软件网络投票器

手机互联网给所有人都带来不同程度的便利&#xff0c;而微信已经成为国民的系统级别的应用。 现在很多人都会在微信群或朋友圈里转发投票&#xff0c;对于运营及推广来说找一个合适的投票小程序能够提高工作效率&#xff0c;提高活动的影响力。 那么微信做投票的小程序哪个比较…

.vm文件发邮件时js未生效,无法控制显示隐藏

需求起因 最近在做一个发邮件的功能&#xff0c;是后端发邮件&#xff0c;不过邮件内容是由前端来写。 邮件内容包括姓名、手机号、邮箱&#xff0c;这三个参数都是不一定有的&#xff0c;如果没有某个参数时&#xff0c;那一行内容就不显示。 写法没错&#xff0c;但就是js…

npm详解

1.npm支持多个源&#xff08;没列全&#xff0c;有兴趣的可以自己去了解一下其他源&#xff09; &#xff08;1&#xff09;官方源&#xff1a;https://registry.npmjs.org &#xff08;2&#xff09;淘宝源&#xff1a;https://registry.npm.taobao.org &#xff08;3&#x…