【自定义类型】带你走进结构体、枚举、联合

news2024/11/17 3:33:10

欢迎来到小王学代码的博客

在字符型函数之后,我们接下来要学习的是自定义类型中的结构体、枚举、联合

目录

前言

一、结构体

1.1结构体的声明

1.2 特殊声明和结构自引用

1.4结构体的自引用

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

1.6 结构体内存对齐

1.7修改默认对齐数

1.8结构体传参

二、枚举

1.枚举的定义

2.枚举的优点

3.枚举的使用

 三、联合(共用体)

1.联合的定义

2.联合的特点

3.联合大小的计算

4.练习题

总结


前言

我们前面知道了C语言中有内置数据类型,也有自定义类型,详情参考【C语言进阶】深度剖析数据在内存中的存储_小王学代码的博客-CSDN博客

接下来我们详细来介绍一下自定义类型中的结构体、枚举、联合是什么,如何用?

一、结构体

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

1.1结构体的声明

 实操代码演示:

struct Stu {//学生结构体
	char name[20];//姓名
	int age;//年龄
	char id[20];//学号
};//分号是自带的,不能丢失,
int main()
{
	struct Stu stu = { "name",12,"123456" };
	return 0;
}

1.2 特殊声明和结构自引用

在声明结构的时候,可以用不完全声明,这个时候是可以的,但是一定要在分号之前设置全局变量,否则无法找到这个结构体(匿名结构体)

演示:

struct {
	int age;
	char name[20];
}x;
//是可以这样没有标签的,但是前提是创建x这样的全局变量,否则之后就找不到了
//因为是匿名结构体
struct {
	int age;
	char name[20];
}a[10],*p;
int main()
{
	
	scanf("%s", x.name);
	printf("%s", x.name);
	p = &x;
//非法的,因为p和x不是指向同一个结构体类型的,虽然成员变量一样,但是实际上类型是不同的
	return 0;
}

接下来我们思考一下,上面代码的第一个和第二个结构体是否一样,是否可以  p=&x 

图示:

上面说明了,编译器会把上面两个结构体的声明当成完全不同的两个类型,所以是非法的

1.4结构体的自引用

结构体的自引用多用于数据结构那边,比如顺序表、链表等,接下来我们介绍一下,声明叫做结构体的自引用

结构体的自引用,顾名思义在这个结构体内部使用该结构体类型的成员变量

图示:

代码演示:

//正确的自引用方式   有*号
struct Node
{
int data;
struct Node* next;
};
//错误的方式   没有*号
struct Node
{
int data;
struct Node next;
};

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

上面我们介绍了如何声明,接下来我们来看看如何使用结构体,对其初始化和定义

代码演示:

struct Point {
	int x;
	int y;
}p1;			//声明类型的同时进行定义变量p1  这个是全局变量

struct Point p2;	//这也是定义结构体变量,在结构体外面,函数外面,这也是全局变量

//初始化,定义变量的同时进行赋初值
struct Point p3 = { 1,2 };

struct Stu {
	char name[20];//姓名
	int age;//年龄
};

struct Stu s = { "why",20 };//初始化,这也是全局变量

struct Node {
	int date;
	struct Point p;
	struct Node* next;
}n1={10,{1,2},NULL};	//结构体嵌套初始化

struct Node n2 = { 10,{1,2},NULL };//结构体嵌套初始化

剩下的如何赋值,如何初始化:

struct Point
{
	int x;
	int y;
}p1 = {10, 20};

struct Point p2 = {0,0};

struct S
{
	int num;
	char ch;
	struct Point p;
	float d;
};

int main()
{
	struct Point p3 = {1,2};
	struct S s = { 100, 'w', {2,5}, 3.14f};
//这些属于基础的部分,赋值需要使用大括号,内部如果有其他类型的结构体,也要进行使用大括号
	struct S s2 = {.d=1.2f, .p.x=3,.p.y=5, .ch = 'q', .num=200};
	//创建一个变量之后,可以使用 .d=1.2f 这样的形式进行赋值,可以不用考虑结构体成员变量的顺序

	printf("%d %c %d %d %f\n", s.num, s.ch, s.p.x, s.p.y, s.d);
	printf("%d %c %d %d %f\n", s2.num, s2.ch, s2.p.x, s2.p.y, s2.d);

	return 0;
}

1.6 结构体内存对齐

上文。我们已经基本了解了结构体的基本使用。

我们都知道数据类型都会在内存中占据空间,我们来深入了解计算一下结构体的大小

这是一个很重要的知识点:结构体内存对齐

什么叫做内存对齐呢?是如何对齐呢?有以下几条规则

1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
    VS中默认的值为8
3. 结构体总大小最大对齐数(每个成员变量都有一个对齐数)的整数倍
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的      整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

代码演示:


struct s1 {
	char c1;
	int i;
	char c2;
};
struct s2 {
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\n", sizeof(struct s1));
	printf("%d\n", sizeof(struct s2));
	return 0;
}

图示分析讲解:

 为什么要存在内存对齐呢?

解释:

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

所以在以后的结构体的设计中,我们尽量要满足对齐,又要节省空间,所以我们应该

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

1.7修改默认对齐数

我们可以使用 #pragma 这个预处理指令,改变默认对齐数(默认对齐数在不同的编译器中有所不同)

#pragma pack(num)        修改默认偏移量为num

#pragma pack ()              表示,取消之前设置的默认偏移量,回到编译器默认偏移量

代码演示:

#pragma pack(4)//设置默认对齐数为4

struct s3 {
	char c1;
	int i;
	char c2;
	double c;
};
#pragma pack()//再次使用这个预处理宏,会取消设置的默认对齐数

#pragma pack(1)//设置最大对齐数为1
struct s4 {
	char c1;
	int i;
	char c2;
	double c;
};

int main()
{
	printf("%d ", sizeof(struct s3));
	printf("%d", sizeof(struct s4));

	return 0;
}

所以说在学会使用#pragma pack(),就可以在结构对齐方式不合适时,自己更改默认对齐数

1.8结构体传参

我们有两种方式可以传参,第一传结构体,第二传地址,哪一个更好呢?

代码演示:

//传参:1.传递结构体,2.传递地址
struct s3 {
	int data;
};
struct s3 s = { 1000 };

void print1(struct s3 s)//传递结构体
{
	printf("%d  ", s.data);
}
void print2(struct s3* s) {//传递地址
	printf("%d", s->data);
}
int main() {
	print1(s);//传递结构体
	print2(&s);//传递地址
}

两种方法,由于传递地址,不会在创建形参的时候,再次申请空间,而且结构体的内存一般比较大,所以为了更好的节约开销,我们比较倾向于传地址

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

二、枚举

1.枚举的定义

枚举就是一一列举各种可能性

把可能取值都一一列举出来,比如一周七天,所以可以列举出来星期一、星期二....

代码演示 :

enum Day//星期
{
Mon,
Tues,   //枚举成员之间使用逗号隔开
Wed,
Thur,
Fri,
Sat,
Sun   //且枚举的成员的取值是从0开始的,逐渐+1
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
int main()
{
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	printf("%d\n", Thur);
	printf("%d\n", Fri);
	printf("%d\n", Sat);
	printf("%d\n", Sun);
	return 0;
}

图文演示: 

 以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。
 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值

enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};

2.枚举的优点

为什么要使用枚举,出现枚举enum这样一个类似与结构体的东西?

有些小伙伴看出来了 枚举不就是一个对于 #define 定义常量的集合吗,那么创建枚举的意思是?

好处是什么?

枚举的优点:

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

3.枚举的使用

我们到现在,了解认识了枚举的定义,什么是枚举,枚举的优点是什么?最后来认识一下,如何使用枚举,怎么正确使用?

图文演示:

 三、联合(共用体)

1.联合的定义

联合是一种特殊的自定义类型

这种类型定义的变量,也是和结构体类似,有一系列的成员,特征是这些成员公共使用同一块空间

比如:

联合其实就是,共同使用一个空间,改变一个成员变量的时候,另一个成员变量也可能会改变,这个比较适合,一个使用,其他不适用的情况。

2.联合的特点

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

那么就来看看,联合里面的成员的首地址是什么?是不是一样的?

代码演示:

3.联合大小的计算

上文我们了解到了,结构体内存大小计算是有很多项规定的,是否感觉计算起来确实有点麻烦,那么联合就很简单啦。只有俩个点

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

第一种情况: 

( 就是说谁最大,就用谁,没有数组,结构体的时候,只有基本内置类型的时候

这样大小就是其中最大的类型 的字节大小

第二种情况:

代码演示:

union un1 {
	char c[5];//5个char类型为5字节
	int i;//;两种类型int char 所以最后结果为4 的倍数
	//5>4  所以最后为8
};
union un2 {
	short c[7];//相当于7个short类型连着  这样是14个字节
	int i;//un2 中 类型一共两种  int short  所以int为最大类型,所以最后得到的数据应该是4的倍数
	//因为是联合 所以14是这两个联合起来其中最大的一个, 再取4的倍数 ,得到16
};

int main()
{
	printf("%d\n", sizeof(union un1));//8
	printf("%d\n", sizeof(union un2));//16

	return 0;
}

4.练习题

如何检验当前编译器是大小端问题

之前我们学指针的时候,学到了第一种方法,请参考大小端【C语言进阶】深度剖析数据在内存中的存储_小王学代码的博客-CSDN博客

接下来我们来看用联合如何测试?

代码如下:

 所以得到,在VS环境下为小端。


总结

我们在这一章节学到了结构体的定义,如何声明,自引用,结构体变量的定义和初始化,结构体内存对齐,还有如何修改默认对齐数。接着对于枚举的认识,和使用、最后是联合的定义特点以及如何计算大小问题,以一道面试题来结束本文。

希望大家在看完这篇文章的时候,会对你有所启发,虽然枚举、联合现阶段用的很少,但是我们一定要了解,并认识,结构体的内容是很重要的,自引用的合理使用,会在数据结构的时候频繁使用!!

最后你的留言,你的点赞收藏都是小王前进的动力,欢迎大佬指点,有所不足,请您指出,谢谢!!!

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

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

相关文章

【杂烩】Latex中的一些技巧备忘录

1. subfigure 和 minipage 环境的运用 首先是多张图组合到一起,左侧和下方备注列标题和行标题。 使用的时候需要的包: minipage不需要 \usepackage{subfigure} \usepackage{graphicx}代码1: \begin{figure*}[htbp]\centering%第一行图片展…

微信小程序怎么实现拍照功能,以及授权,拍完照保存到本地。

写微信小程序就是要不停的翻阅官方文档查阅所需要的需求。API的使用说明wx.getSetting 获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限 scope.camera相机wx.authorize 提前向用户发起授权请求。调用后会立刻弹窗询问用户是否同意授权小程序使用某项功能或获…

复盘!!指针 ,地址 ,数组之间的联系

文章目录前言一、直接看题二、直接上代码总结前言 工作摸鱼 闲来无事 一、直接看题 二、直接上代码 所以,这个题目答案是5. 我来盘给你:int a[5] {1,2,3,4,5}; 1数组名本身就是表示数组首元素地址 2对数组名取地址:表示的是整个数组的地址…

Android抓包Charles入门教程

Android抓包工具有很多,有轻量的如httpCanary直接安装在手机上通过代理进行抓包,有功能强大的WireShark PC 端的抓包工具(也要设置代理啦),还有老牌抓包工具Finder和Charles以及mitmproxy 。 手机上的抓包工具远离基本差不多&…

MAC(m1)-CentOS8 Docker设置MySQL数据持久化

Docker安装部署Mysql8的过程(以作数据持久化)_docker_脚本之家 Docker安装Mysql并映射_小小小果子的博客-CSDN博客_docker mysql 映射 Docker中安装mysql并把配置等挂载在外面(开启多个mysql)_遗忘de神话的博客-CSDN博客_dock中安装了mysql,还能在外部再安装一个mysql吗 mys…

Windows实时运动控制软核(五):LOCAL高速接口测试之VC6.0

今天,正运动小助手给大家分享一下MotionRT7的安装和使用,以及使用VC6.0对MotionRT7开发的前期准备。 01 MotionRT7简介 MotionRT7是深圳市正运动技术推出的跨平台运动控制实时内核,也是国内首家完全自主自研, 自主可控的Windows…

电脑怎么录制屏幕视频,3种方法,轻松弄懂

在日常生活当中,电脑录屏的使用场景也变得越来越多了起来,网课录制,微课录制,直播录制,会议录制等等。那么问题来了,电脑怎么录制屏幕视频呢?今天小编就向大家分享3个电脑录制屏幕视频的方法&am…

osg fbo(一),生成颜色缓冲区图片

由于工作需要&#xff0c;重新捡了下shader。很明显&#xff0c;fbo是重中之重。好记性不如烂笔头&#xff0c;先记录下 1&#xff0c;生成一个颜色纹理(为了省事&#xff0c;可以将纹理宽高屏幕宽高) osg::ref_ptr<osg::Texture2D> tex createFloatRectangleTexture(…

Linux安装Docker详细教程

文章目录Docker架构环境说明安装步骤阿里云镜像加速Docker底层原理Docker架构 镜像&#xff08;image&#xff09;: Docker 镜像&#xff08;Image&#xff09;就是一个只读的模板。镜像可以用来创建 Docker 容器&#xff0c;一个镜像可以创建很多容器。 就好似 Java 中的 类和…

Day857.高性能限流器Guava RateLimiter -Java 并发编程实战

高性能限流器Guava RateLimiter Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于高性能限流器Guava RateLimiter的内容。 Guava RateLimiter 是如何解决高并发场景下的限流问题的。 Guava 是 Google 开源的 Java 类库&#xff0c;提供了一个工具类 RateLimiter。先…

2022智能制造世界与中国十大科技进展内容小结

2022智能制造世界与中国十大科技进展内容小结1. 全断面隧道掘进装备行业工业互联网平台2. 大型复杂构件机器人原位高效高质量铣削加工技术及装备3. 西门子SNC—原生数字化工厂4. 亚马逊 数字孪生服务IoT TwinMaker5. Nvidia 新一代智能移动机器人仿真平台6. 北京奔驰的基于大数…

Excel特殊符号及用法

一、公式中常用符号 &#xff1a; 表示一个单元格区域&#xff0c;如A1:B10 - * / 加减乘除运算符> 大于号> 大于等于号< 小于号< 小于等于号<> 不等于号&#xff0c;如IF(A1<>"销售部",,)^数字 乘方运算&#xff0c;如…

第二章Mybatis进阶操作学习

文章目录特殊SQL的执行模糊查询批量操作动态设置表名添加功能获取自增的主键自定义映射resultMap处理字段和属性的映射关系在SQL语句中使用别名使用核心配置文件中的驼峰对应方法使用resultMap自定义映射处理一对一映射处理级联方式处理association标签分步查询分步查询的优点&…

【网安神器篇】——Sqlmap详解

作者名&#xff1a;白昼安全主页面链接&#xff1a;主页传送门创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右铭&a…

C语言 自定义类型 之 【联合体(共用体)】

文章目录前言联合体(UNION)类型的定义联合体的特点联合体大小的计算联合体使用的典型例题 — 判断机器的大小端写在最后前言 联合体&#xff08;union&#xff09;在C语言中是不常用的&#xff0c;不过存在就必然有其有用之处&#xff0c;这里不深入探讨联合体用在何处有什么用…

R语言实现牛顿插值

文章目录4 差商与牛顿插值差商牛顿插值4 差商与牛顿插值 差商 如果采取间隔不等的采样&#xff0c;差商会变得稍显复杂&#xff0c;对于x0,x1,…,xnx_0,x_1,\ldots,x_nx0​,x1​,…,xn​&#xff0c;若与y0,y1,…,yny_0,y_1,\ldots,y_ny0​,y1​,…,yn​通过映射fff一一对应&…

ASEMI整流桥KBP310电路设计注意事项

编辑-Z 型号&#xff1a;KBP310 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;1000V 最大平均正向整流输出电流&#xff08;IF&#xff09;&#xff1a;3.0A 峰值正向浪涌电流&#xff08;IFSM&#xff09;&#xff1a;60A 每个元件的典型热阻&#xff0…

MySQL LOAD VS DM8 dmfldr

MySQL LOAD VS DM8 dmfldr 背景 某业务系统从MySQL迁移至达梦后&#xff0c;有导入业务文件的功能使用MySQL的LOAD方式将csv文件导入到指定的表中。迁移到达梦后&#xff0c;该功能需要进行对应的调整&#xff08;因为达梦没有LOAD功能&#xff09;&#xff0c;但达梦提供了dm…

【自定义类型】-结构体,枚举,联合

&#x1f387;作者&#xff1a;小树苗渴望变成参天大树 &#x1f496;作者宣言&#xff1a;认真写好每一篇博客 &#x1f9e8;作者gitee:link 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; &#x1f38a;自定义类型&#x1f389;前言&a…

osg fbo(二),将颜色缓冲区图片铺在全屏

其实&#xff0c;需要两个摄像机&#xff0c;一个是采样摄像机&#xff0c;一个是最终显示的摄像机。 osg fbo(一&#xff09;中&#xff0c;passRoot直接加的面片&#xff0c;现在通过最终显示摄像机来代替&#xff0c;passRoot加上这两个摄像机即可。 //passRoot->addCh…