自定义类型:结构体,位段,枚举,联合体1

简介: 自定义类型:结构体,位段,枚举,联合体

😊前言😊

初阶C语言中 ,我们已经接触过一些简单的结构体,让大家对结构体有了初步的了解,今天我们来深入研究一下结构体、枚举和联合体。希望大家会有多多少少的收获


🤔结构体🤔

结构体是什么?

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


结构体声明

结构体定义需要用到关键字 struct 来定义 。

例如我们需要描述一个学生,就要有学生姓名、年龄、性别、学号等等。

我们试着把一个学生的信息用结构体表示出来>

struct Stu
{
  char name[20];//姓名
  int age;//年龄
  char sex[5];//性别
  char id[20];//学号
}s1,s2;//全局变量
int main()
{
  struct Stu s3, s4;//局部变量
  return 0;
}

也可以这样写>

typedef struct Stu
{
  char name[20];//姓名
  int age;//年龄
  char sex[5];//性别
  char id[20];//学号
}Stu;//typedef之后重定义的名称
Stu s1, s2;//全局变量s1,s2
int main()
{
  Stu s3, s4;//局部变量s3,s4
  return 0;
}


typedef 重定义将结构体名称定义为Stu,这样一来我们创建结构体变量就会方便很多了,直接Stu后面加上变量名称,就可以很好的创建出一个结构体变量。


匿名结构体

匿名结构体,顾名思义就是没有名字,他必须在定义的时候完成变量的创建,而且只能使用这一次,后面无法再次创建新的变量。

struct //不写标签
{
  int arr[10];
  int size;
}str;//str是这个结构体的变量


当然这种结构体我们用的会比较少,一般是这个结构体只是自己用而且只用一次的情况下,我们可以考虑使用匿名结构体。


结构体的自引用

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

这里我们先给大家介绍一下链表的结构:


f5b3faa999144b1db4a6f91791aeff79.png

链表的每一个节点有两个成员,一个是数值域data,另一个存放的是可以指向链表下一个节点的指针域。

那么了解了链表的这种结构,我们该如何去定义这种结构体呢?

先来看看这样子定义行不行?


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

结构体里面有一个data,还有下一个结构体,ok很完美,但是回头我们再来想一想


sizeof(struct Node)是多少呢? 一个int是四个字节,在一个struct Node类型的next,里面又包含了一个int类型和一个struct Node,这样无限循环下去,无限套娃,它所占的字节数也是无限大的。


显然这样定义是不可行的。


那我们试试另一种定义方式>

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

一个结构体里面一个data,还有一个指向下一个结构体类型的结构体指针。

这样一来他所占的字节数就是可控的了。这种定义方式就是结构体的自引用的正确方式。

那么如何使用typedef重命名呢?


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


切忌:不能将结构体中的struct Node*next写成Node*next;

结构体变量的初始化


struct Point
{
  int x;
  int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {1,2};
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 };//结构体嵌套初始化


结构体内存的对齐

到这里我们已经掌握了结构体的基本使用方法

我们再来探讨一个更加深入的问题:计算一个结构体的大小。

这也是一个非常热门的考点:结构体的内存对齐

我们先来看看这些代码>


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;
}

s1和s2里面的变量个数相同 ,那么好他们两个所占的字节数也一定一样咯。我们来运行看看>


41904bb8f717483b8a4e89ad22acc964.png


咦为什么一个是12一个是8呢?他俩明明变量都一模一样,只不过就是顺序不一样而已,为什么所占的字节数就不一样了呢。而且就算把他们里面的变量单独加起来也就6个字节,那12和8是怎么来的呢?

到这里就不得不提出结构体内存对齐了。那又该怎样去对齐呢?我们一起来研究一下>



c7dccc11ba8c4caf94d5490385a57e01.png





这样我们就可以理解为什么s1会是12个字节了。我们再来看看s2>



5574a1771e2f4b8c8b4c83e3d3d6af0b.png


我们再来练习一道s3>

struct S3
{
  double d;
  char c;
  int i;
};


69c885dc6756458f8791095c64606105.png


答案是不是16呢我们来验证一下>


b91ce6f4665b4bf39a03863cab6be203.png



学到这里,反过来问一下,内存中国为啥那么会存在对齐呢?他又有什么意义呢?


对于这个问题大部分参考资料是这么说的:


1. 平台原因(移植原因):

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


2. 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。


总体来说:


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


那我们在设计结构体的时候,我们既要满足对齐,又要节省空间要怎么做到呢?


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


就好比如上面的s1、s2他们的成员都是一样的顺序不同他们之间所占字节就相差了4字节。


那这个默认对齐数可以修改吗还是只能是编译器自己规定的呢? 答案是可以修改


相关文章
|
监控 关系型数据库 MySQL
MySQL 5.7在高并发下性能劣化问题的详细剖析
TL;DR MySQL 5.7高并发读写混合场景下rt飙升,业务系统大量超时报错。本文总结了阿里业务场景下遇到的坑,剖析问题背后的原因,帮助读者更好的理解MySQL内核原理,降低升级MySQL 5.7的风险。
10069 0
|
Java 关系型数据库 中间件
分库分表(3)——ShardingJDBC实践
分库分表(3)——ShardingJDBC实践
981 0
分库分表(3)——ShardingJDBC实践
|
JavaScript API
本地开发环境请求服务器接口跨域的问题(vue的问题)
本地开发环境请求服务器接口跨域的问题(vue的问题)
490 0
|
5月前
|
数据采集 编解码 JavaScript
视觉爬虫开发:通过Puppeteer截图+CV定位动态元素坐标
本文是关于“视觉爬虫开发”的速查指南,重点介绍如何使用 Puppeteer 和 OpenCV 在小红书上实现视频截图与评论采集。内容包括代理 IP 接入、Cookie 与 User-Agent 设置、动态元素坐标获取及评论采集的代码示例。提供功能点列表、常用代码片段、配置建议和快速测试方式,帮助开发者快速掌握核心技术和实践方法。通过 Puppeteer 截图结合 OpenCV 模板匹配,精准定位动态元素坐标,提升爬虫稳定性与效率。
154 2
视觉爬虫开发:通过Puppeteer截图+CV定位动态元素坐标
|
9月前
|
机器学习/深度学习 存储 人工智能
《量子计算硬件:关键指标对人工智能应用性能的影响》
量子计算硬件的关键技术指标对人工智能性能至关重要。量子比特数量决定信息处理规模,更多量子比特可加速机器学习、提升模型精度;相干时间保障量子态稳定,延长其能提高计算可靠性;门保真度确保操作准确,高保真度增强计算精度与容错能力。其他如耦合强度、噪声水平等也协同作用,共同影响性能。优化这些指标将推动AI发展。
319 66
|
网络安全 数据安全/隐私保护 Docker
百度搜索:蓝易云【SSH远程直连Docker容器教程。】
希望以上教程能够帮助你通过SSH远程直连Docker容器。如有更多疑问,请参考相关的文档和资源进行进一步学习。
195 0
|
设计模式 Java 微服务
你一定要知道业务开发最常用的两种设计模式
文章介绍了业务开发中最常用的两种设计模式:策略模式和异步形式的责任链模式,通过具体案例展示了它们在代码解耦、扩展性增强以及提升响应速度方面的应用,并强调了设计模式在提升代码质量和开发效率中的重要性。
|
数据挖掘 Go 数据库
Go语言map这么查询 才是高手风范
Go语言map这么查询 才是高手风范
430 0
|
缓存 API 调度
Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架
🔥 介绍 本文通过OkHttp+Retrofit+Rxjava+Hilt实现一个网络请求框。
878 0
Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架