【C语言】自定义类型:结构体,枚举,联合

news2024/11/26 4:22:13

目录

  • 前言:
    • 一.结构体
      • 1.结构体的声明
      • 2.结构体特殊的声明
      • 3.结构体的自引用
      • 4.结构体变量的定义和初始化
      • 5.结构体内存对齐
      • 6.修改默认对齐数
      • 7.结构体传参
    • 二.位段
      • 1.什么是位段
      • 2.位段的内存分配
    • 三.枚举
      • 1.枚举的定义
      • 2.枚举的优点
    • 四.联合(共用体)
      • 1. 联合类型的定义
      • 2. 联合的特点
      • 3. 联合大小的计算

前言:

之前学过结构体的初阶知识,在原来的基础上会深入了解结构体的自引用、内存对齐和结构体实现位段;同时在初识C语言时,稍微了解了一点枚举的相关知识,在这里将会更深入学习;还有学习全新的知识—联合
——————————————————————————

一.结构体

先回忆下什么是结构体?
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.结构体的声明

struct tag
{
member-list;
}variable-list;

struct:结构体关键字
tag:自定义名字
member-list:成员列表
variable-list:变量列表

比如描述一个学生,有名字、年龄、性别和身份证号码。

struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}; //分号不能丢

创建结构体变量时可以这样写:

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}s1, s2, s3;

也可以这样写:

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};
struct Stu s1, s2, s3;

2.结构体特殊的声明

在声明结构的时候,可以不完全的声明。
比如:

struct
{
 int a;
 char b;
 float c;
}x;

这叫匿名结构体类型,去掉了结构体的名字。这种写法有个缺点,就是创建的变量只能使用一次(变量x)
再创建一个结构体指针:

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

那么能不能写成:

p = &x;

答案是不行,p和x里面的成员虽然一样,但是编译器会把上面的两个声明当成完全不同的两个类型,所以这是非法的。

总结:匿名结构体类型的变量只能使用一次,不常用

3.结构体的自引用

结构体的自引用其实是结构体自己包含自己(同类型),但是要注意,包含的必须是结构体指针,这样才能找到结构体的地址。

struct Node
{
	int data;//数据域
	struct Node* next;//指针域
};

如果用typedef给结构体类型重新起名字为Node,前面学过匿名可以省略Node,那么这样写是否可行?

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

答案是不行,因为typedef对类型重命名后,在花括号里面提前使用Node了,然后才命名Node,所以顺序混乱了。
正确的写法:

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

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

结构体变量的定义有两种方式,一种是直接顺着类型定义结构体变量;另一种是有了类型之后单独创建结构体变量。

struct SN
{
	char c;
	int a;
}sn1, sn2;//全局变量
int main()
{
	struct SN sn3, sn4;//局部变量
	return 0;
}

结构体变量的初始化:

struct SN
{
	char c;
	int a;
}sn1 = { 'w',2 }, sn2 = { .a = 5,.c = 't' };
int main()
{
	printf("%c %d\n", sn1.c, sn1.a);
	printf("%c %d\n", sn2.c, sn2.a);
	return 0;
}

sn2 = { .a = 5,.c = ‘t’ };用点可以找到你想赋值的成员,不需要按顺序来初始化。

5.结构体内存对齐

结构体内存对齐其实是计算结构体大小
我们先看一段代码:

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

刚开始的思路:
c1一个字节,i 4个字节,c2一个字节,加起来S1一共6个字节,S2同理

结果是:
在这里插入图片描述
在这里插入图片描述
我们可以使用宏offsetof计算结构体成员相较于结构体起始位置的偏移量

#include <stddef.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\n", offsetof(struct S1, c1));
	printf("%d\n", offsetof(struct S1, i));
	printf("%d\n", offsetof(struct S1, c2));
	return 0;
}

打印出来的结果:
在这里插入图片描述

在这里插入图片描述
我们发现结构成员不是按照顺序在内存中连续存放的,而是有一定的对齐规则

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

知道了规则后,我们再来分析下struct S1

在这里插入图片描述
为什么存在内存对齐?

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

所以,在设计结构体的时候,我们既要满足对齐,又要节省空间(让占用空间小的成员尽量集中在一起)

6.修改默认对齐数

#pragma 这个预处理指令可以改变默认对齐数

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

结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。一般设置默认对齐数都是2的次方,便于使用。

7.结构体传参

struct S
{
 int data[100];
 int num;
};
struct S s = {{1,2,3,4}, 100};
//结构体数值传参
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;
}

两种传参方式选择传地址更好

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

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

二.位段

位段与结构体相关联,接下来了解位段

1.什么是位段

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

1.位段的成员必须是 int、unsigned int 或signed int (后来引用了char)
2.位段的成员名后边有一个冒号和一个数字

比如:

struct A//A就是一个位段类型。
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

位段A的大小:

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

分析:
位段其实是二进制位,也就是说:
int _a:2:占2个比特位;
int _b:5:占5个比特位;
int _c:10:占10个比特位;
int _d:30:占30个比特位。
假设结构体有一个成员用2个比特位就够了,那么就没必要用分配一个整型,这样就可以节省空间

2.位段的内存分配

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

位段是如何开辟的?

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%d\n", sizeof(s));
	return 0;
}

在这里插入图片描述
打开调试查看内存:
在这里插入图片描述
位段的跨平台问题

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

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

位段的应用类型于包装的作用,既方便也节省了空间
在这里插入图片描述

三.枚举

所谓枚举就是一一列举
比如说星期一到星期五、颜色和月份

1.枚举的定义

列举颜色:

enum Color
{
	RED,
	GREEN,
	BLUE
};
int main()
{
	//枚举变量的创建
	enum Color c = RED;
	//       定义   初始化
	return 0;
}
};

这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。

	printf("%d\n", RED);//0
	printf("%d\n", GREEN);//1
	printf("%d\n", BLUE);//2

自己赋值:

enum Color
{
	RED=4,
	GREEN=10,
	BLUE=2
};

在这里插入图片描述

2.枚举的优点

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

四.联合(共用体)

1. 联合类型的定义

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

union Un
{
	char c;
	int i;
};
int main()
{
	union Un un;
	printf("%d\n", sizeof(un));
	printf("%p\n", &un);
	printf("%p\n", &(un.i));
	printf("%p\n", &(un.c));
	return 0;
}

打印出来的结果:
在这里插入图片描述
在这里插入图片描述

2. 联合的特点

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

判断当前计算机的大小端存储

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

第二种写法:(以函数的方式)

int test()
{
	union //匿名只能使用一次
	{
		int i;
		char c;
	}un = { .i = 1 };
	return un.c;
}
int main()
{
	int ret = test();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

在这里插入图片描述

3. 联合大小的计算

  • 联合的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un1
{
	char c[5];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));// 8
	return 0;
}

char 类型自身大小是1,默认是8,所以它的对齐数是1,又因为它是数组,所以是5(最大成员是5);
int 类型自身大小是4,默认是8,所以它的对齐数是4;
5是最大成员,但不是最大对齐数的倍数,所以又要补充3个字节到8,因此结果为8。

~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ 感谢你的观看~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
在这里插入图片描述
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

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

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

相关文章

php使用PDO_sqlsrv

php拓展下载&#xff1a;Microsoft Drivers for PHP 发行说明 - PHP drivers for SQL Server | Microsoft Learn 参考文章&#xff1a;php7.3.4 pdo方式连接sqlserver 设置方法_pdo sqlserver_黑贝是条狗的博客-CSDN博客 php5.6.9安装sqlsrv扩展&#xff08;windows&#xff0…

BEVDet 论文解读

BEVDet: High-Performance Multi-Camera 3D Object Detection in Bird-Eye-View 作者单位 PhiGent Robotics 目的 2D 的视觉感知在过去的几年里有了急速的发展&#xff0c;涌现出一些优秀的范式工作&#xff0c;这些工作有较高的性能&#xff0c;可扩展性&#xff0c;以及多…

【前端设计】使用Verdi查看波形时鼠标遮住了parameter值怎么整

盆友&#xff0c;你们在使用Verdi的时候&#xff0c;有没有遇到过鼠标遮挡着了parameter数值的场景&#xff1f;就跟下面这个示意图一样&#xff1a; 最可恨的是这个参数值他会跟着你的鼠标走&#xff0c;你想把鼠标移开看看看这个例化值到底是多大吧&#xff0c;这个数他跟着你…

云原生基础设施实践:NebulaGraph 的 KubeBlocks 集成故事

像是 NebulaGraph 这类基础设施上云&#xff0c;通用的方法一般是将线下物理机替换成云端的虚拟资源&#xff0c;依托各大云服务厂商实现“服务上云”。但还有一种选择&#xff0c;就是依托云数据基础设施&#xff0c;将数据库产品变成为云生态的一环&#xff0c;不只是提供自身…

直播回顾 | SDS 容灾方案,让制品数据更安全

7 月 18 日&#xff0c;腾讯云 CODING 与 XSKY星辰天合联合举办了主题为“SDS 容灾方案&#xff0c;让制品数据更安全”的线上研讨会。 来自腾讯云 CODING 的高级解决方案架构师陈钧桐和 XSKY星辰天合金融行业解决方案专家战策&#xff0c;分享了制品管理的困境与需求、腾讯云…

【数据挖掘】如何修复时序分析缺少的日期

一、说明 我撰写本文的目的是通过引导您完成一个示例来帮助您了解 TVF 以及如何使用它们&#xff0c;该示例解决了时间序列分析中常见的缺失日期问题。 我们将介绍&#xff1a; 如何生成日期以填补数据中缺失的空白如何创建 TVF 和参数的使用如何呼叫 TVF我们将考虑扩展我们的日…

Less知识点整理学习笔记

文章目录 1. Less介绍2. 安装2.1 部署node.js环境2.2 安装Less2.3 WebStorm配置Less 3. Less语法3.1 变量3.2 嵌套3.3 运算 1. Less介绍 Less是CSS预处理语言&#xff0c;可以使用变量、嵌套、运算等&#xff0c;便于维护项目CSS样式代码。 2. 安装 2.1 部署node.js环境 官…

Python爬虫学习笔记(十二)————scrapy案例

目录 1.yield 2.案例&#xff1a;当当网 3.案例&#xff1a;电影天堂 1.yield &#xff08;1&#xff09;带有 yield 的函数不再是一个普通函数&#xff0c;而是一个生成器generator&#xff0c;可用于迭代 &#xff08;2&#xff09; yield 是一个类似 return 的关键字&am…

《数据分析-JiMuReport07》JiMuReport报表开发-下拉框条数参数调整

JimuReport报表下拉框条数参数调整 {selectSearchPageSize:n} 1.下拉框条数限制 下拉框默认只显示10条记录&#xff0c;如果想要显示更多条数可以通过添加参数实现。 2.参数 selectSearchPageSize参数&#xff0c;设置参数大小 3.效果 可以看到设置的下拉框条数20条已经实现

细说小程序底部标签---【浅入深出系列006】

浅入深出系列总目录在000集 如何0元学微信小程序–【浅入深出系列000】 文章目录 本系列校训学习资源的选择 学习语法的前提底部标签的总概鹅厂的自定义标签官方说明&#xff1a; 先来了解app.json文件tabBar 位于app.json哪里 使用流程要注意的是&#xff1a;配套资源作业&a…

el-popover在原生table中,弹出多个以及内部取消按钮无效问题

问题&#xff1a;当el-popover和原生table同时使用的时候会失效&#xff08;不是el-table) <el-popover placement"bottom" width"500" trigger"click" :key"popover-${item.id}"></el-popover> 解决&#xff1a; :key…

虚拟数字人——NeRF实现实时对话数字人

前言 1.这是一个能实时对话的虚拟数字人demo,使用的是NeRF&#xff08;Neural Radiance Fields&#xff09;&#xff0c;训练方式可以看看我前面的博客。 2.文本转语音是用了VITS语音合成&#xff0c;项目git:https://github.com/jaywalnut310/vits . 3.语言模型是用了新开…

Jenkins从配置到实战(一) - 实现C/C++项目自动化编译

前言 本文章主要介绍了&#xff0c;如何去安装和部署Jenkins&#xff0c;并实现自动拉取项目代码&#xff0c;自动化编译流程。 网站 官网中文网站 下载安装 可以下载这个 安装jenkins前先安装java yum search java|grep jdkyum install java-1.8.0-openjdk 安装jenkins j…

NE555 PWM输出

NE555是一种集成电路&#xff08;IC&#xff09;&#xff0c;通常用于电子电路的各种目的&#xff0c;包括计时器、振荡器等等。 本文介绍搭建NE555电路输出PWM信号&#xff0c;电路如图下&#xff1a; 使用该电路可以输出PWM占空比≥50%波形&#xff0c;仿真波形如下图&#…

20230723在win10的命令行下显示文本文件的内容type

20230723在win10的命令行下显示文本文件的内容type 2023/7/23 20:35 百度搜索&#xff1a;WINDOWS 命令行 打开文本文件 windows命令行读取文件命令-WinFrom控件库|.net开源控件库... 2023年7月14日 linux下,可能会用到cat或都是more命令,windows下可以使用type或more命令 type…

VMware Fusion 14 Tech Preview - 适用于 Arm 的 Windows 11 上的全面 3D 加速

VMware Fusion 14 Tech Preview - 适用于 Arm 的 Windows 11 上的全面 3D 加速 VMware Fusion Tech Preview 2023 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-fusion-14/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;…

求解包含约束的最优化问题:罚函数法

文章目录 外点罚函数法内点罚函数法罚函数法 vs 拉格朗日乘子法 外点罚函数法 针对包含约束条件的最优化问题&#xff0c;此前介绍的拉格朗日乘子法和KKT条件已经提供一种有效的解决方案。但由于我是从智能优化算法入门运筹优化行业的&#xff0c;所以在遇到这类问题时&#x…

day35-Image Carousel(图片轮播图简易版)

50 天学习 50 个项目 - HTMLCSS and JavaScript day35-Image Carousel&#xff08;图片轮播图简易版&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport…

93、简述kafka架构设计

kafka架构设计 Consumer Group&#xff1a;消费者组&#xff0c;消费者组内每个消费者负责消费不同分区的数据&#xff0c;提高消费能力。逻辑上的一个订阅者。Topic: 可以理解为一个队列&#xff0c;Topic 将消息分类&#xff0c;生产者和消费者面向的是同一个 Topic。Partiti…

netty组件详解-中

接着之前的博客netty组件详解-上&#xff0c;我们继续深入到源码层面&#xff0c;来探究netty的各个组件和其设计思想&#xff1a; netty内置的通讯模式 我们在编写netty代码时&#xff0c;经常使用NioServerSocketChannel 作为通讯模式。 例如下面的简单netty客户端示例: pri…