【自定义类型】--- 位段、枚举、联合


  • 💓博客主页:江池俊的博客
  • ⏩收录专栏:C语言进阶之路
  • 👉专栏推荐:✅C语言初阶之路 ✅数据结构探索
  • 💻代码仓库:江池俊的代码仓库
  • 🎉欢迎大家点赞👍评论📝收藏⭐

在这里插入图片描述

文章目录

  • 一、✨结构体的那些事✨
    • 1.1 结构体的自引用
    • `1.2 结构体内存对齐`
      • 【结构体嵌套问题】
      • 为什么存在内存对齐?
    • 1.3 offsetof宏
  • 二、🍁位段(Bit-fields)🍁
    • 2.1 什么是位段?
    • 2.2 定义位段
    • 2.3 位段的内存分配
    • 2.4 位段的跨平台问题
    • 2.5 位段的用途
  • 三、👀枚举👀
    • 3.1 枚举类型的定义
    • 3.2 枚举的优点
    • 3.3 枚举的使用
  • 四、 💫联合(共用体)💫
    • 4.1 联合类型的定义
    • 4.2 联合的特点
    • 4.3 联合大小的计算

前言

上节【C语言】结构体解谜:拆解数据的力量!已经讲解了结构体这种自定义类型,那么接下来我将带着大家一起深入结构体以及其他自定义类型的学习。

一、✨结构体的那些事✨

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

1.1 结构体的自引用

在C语言中,结构体内部是不能包含直接指向自己类型的成员的,这会导致无法确定结构体的大小。这是因为一个结构体的大小必须是确定的,以便程序在内存中正确地分配空间。

如果需要在结构体内部包含指向相同类型的成员,可以使用指向结构体的指针来实现这个目的。

以下是一个正确的示例:

struct Node
{int data;struct Node* next; // 使用指针指向相同类型的结构体
};

在这个例子中,next 成员是一个指向 struct Node 类型的指针,而不是直接嵌套一个 struct Node 类型。

然而,对于以下代码:

struct Node
{int data;struct Node next; // 这种方式是错误的
};

这是不合法的,因为 struct Node 中包含了一个直接指向相同类型的成员 next,这将导致无法确定结构体的大小。

1.2 结构体内存对齐

我们已经掌握了结构体的基本使用了。
现在我们深入讨论一个问题:计算结构体的大小。
这也是一个特别热门的考点: 结构体内存对齐

在C语言中,结构体内存对齐是指编译器如何在内存中分配结构体的成员以保证存取效率和对齐要求。它的目的是为了优化内存访问的性能。

那么结构体大小我们将如何计算呢?

首先得掌握结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为 0 的地址处。

  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的值为8

  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

  4. 如果嵌套了结构体的情况,嵌套的结构体 对齐到 自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

  5. sizeof操作符sizeof 结构体会返回整个结构体的大小,包括填充字节。

  6. #pragma pack(n)#pragma pack 是一个预处理指令,可以用来改变默认的对齐方式。#pragma pack(n) 将指定对齐值为 n 字节。这个指令在一些特殊情况下可能会用到,但一般情况下不建议随意修改对齐方式。

例子:

struct Example {char a;    // 占用1字节int b;     // 在32位系统下通常占用4字节double c;  // 通常占用8字节
}; // 在32位系统下,这个结构体的大小为 16 字节(包括填充)

在这里插入图片描述

【结构体嵌套问题】

struct S3
{double d;char c;int i;
};
printf("%d\n", sizeof(struct S3)); //结果是 16 
//结构体嵌套问题
struct S4
{char c1;struct S3 s3;double d;
};
printf("%d\n", sizeof(struct S4)); //结果是 32

在这里插入图片描述
需要强调的是,具体的对齐规则和表现可能会因编译器、编译器版本和目标平台的不同而略有差异,因此在编写特定平台下对齐要求敏感的代码时,最好查阅相应的编译器文档或规范。

为什么存在内存对齐?

大部分的参考资料都是如是说的:

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

总体来说:

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

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

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

//例如:
struct S1
{char c1;int i;char c2;
};//大小为 12
struct S2
{char c1;char c2;int i;
};//大小为 8

S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。

1.3 offsetof宏

头文件: <stddef.h>
声明

offsetof(type, member-designator);

参数

  • type - - - 这是一个 class 类型,其中,member-designator 是一个有效的成员指示器。
  • member-designator - - - 这是一个 class 类型的成员指示器。

返回值

  • 该宏返回类型为 size_t 的值,表示 type 中成员的偏移量。

C 库宏 offsetof(type, member-designator) 会生成一个类型为 size_t 的整型常量,它是一个结构成员相对于结构开头的字节偏移量。成员是由 member-designator 给定的,结构的名称是在 type 中给定的。

【实例】:


#include<stdio.h>
#include<stddef.h>
struct S3
{double d;char c;int i;
}; //大小为16
//结构体嵌套问题
struct S4
{char c1;struct S3 s3;double d;
}; //大小为32
int main()
{printf("aS3 结构中的 c 偏移 = %d 字节。\n",offsetof(struct S3, c)); //结果是 8printf("S4 结构中的 c1 偏移 = %d 字节。\n",offsetof(struct S4, c1)); //结果是 0printf("S4 结构中的 s3 偏移 = %d 字节。\n",offsetof(struct S4, s3)); //结果是 8 return 0;
}

在这里插入图片描述
在这里插入图片描述


二、🍁位段(Bit-fields)🍁

在C语言中,位段(Bit-fields)是一种非常有用的数据结构,允许你定义数据成员占用的位数,从而有效地利用存储空间。位段特别适用于需要精细控制内存占用和性能的情况。在本文中,我们将深入研究C语言中的位段,包括如何定义、使用和优化它们。

2.1 什么是位段?

位段是一种结构体成员,它允许你定义成员占用的位数。这样,你可以在一个字节(或更大的内存单元)中将不同的位用于不同的数据,而不是整个字节。这有助于节省内存,并在某些情况下提高性能。

2.2 定义位段

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

  1. 位段的成员必须是 intunsigned intsigned int
  2. 位段的成员名后边有一个冒号和一个数字。

在结构体中定义位段的语法如下:

struct MyStruct {type memberName : numberOfBits;
};
  • type 是成员的数据类型,通常是 intunsigned int 或其他整数类型。
  • memberName 是成员的名称。
  • numberOfBits 指定了成员占用的位数。

2.3 位段的内存分配

使用位段时需要注意以下事项:

  1. 位段的成员可以是 intunsigned intsigned int 或者是 char (属于整形家族)类型
  2. 位段的总位数不能超过成员的类型的总位数。
  3. 位段成员不能取地址,也不能作为函数参数传递。
  4. 位段的空间上是按照需要以 4个字节( int ) 或者 1个字节( char ) 的方式来开辟的。
  5. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
//一个例子
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?

在这里插入图片描述

2.4 位段的跨平台问题

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

总结:

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

2.5 位段的用途

  1. 节省内存: 位段允许你精确地控制每个成员占用的位数,从而节省内存。这对于嵌入式系统和大规模数据结构特别有用。

  2. 提高性能: 在某些情况下,位段可以提高内存访问性能,因为它们可以减少数据传输的时间。

  3. 代码可读性: 通过使用位段,你可以更清晰地表示特定的标志或位字段,从而提高代码的可读性。

在这里插入图片描述


三、👀枚举👀

枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举

这里就可以使用枚举了。

3.1 枚举类型的定义

enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
enum Sex//性别
{MALE,FEMALE,SECRET
};
enum Color//颜色
{RED,GREEN,BLUE
};

以上定义的 enum Dayenum Sexenum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量

这些可能取值都是有值的,默认从0开始一次递增1,当然在定义的时候也可以赋初值。
例如:

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

3.2 枚举的优点

为什么使用枚举?

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

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

3.3 枚举的使用

enum Color//颜色
{RED = 1,GREEN = 2,BLUE = 4
};
int main()
{enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。clr = 5; //赋值号左边是枚举类型,右边是整型,若在c++中是不能赋值的return 0;
}

四、 💫联合(共用体)💫

联合体(Union)是一种特殊的数据类型,允许在相同的内存位置存储不同类型的数据。它类似于结构体,但是不同的是,联合体的成员共享同一块内存空间。
关键字:union

4.1 联合类型的定义

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

#include<stdio.h>
//联合类型的声明
union Un
{char c;int i;
};int main()
{//联合变量的定义union Un un;//计算连个变量的大小printf("%d\n", sizeof(un)); //结果为 4return 0;
}

在这里插入图片描述

4.2 联合的特点

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

面试题:

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

//判断当前计算机的大小端存储
#include<stdio.h>check_sys()
{union Un{char c;int i;}u;u.i = 1;return u.c;//返回1表示小端,返回0表示大端
}int main()
{if (check_sys())printf("小端\n");elseprintf("大端\n");return 0;
} //vs2022下输出结果为:小端

在这里插入图片描述

4.3 联合大小的计算

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

比如:

#include<stdio.h>
union Un1
{char c[5];int i;
};
union Un2
{short c[7];int i;
};
int main()
{//下面输出的结果是什么?printf("%d\n", sizeof(union Un1)); //结果为 8printf("%d\n", sizeof(union Un2)); //结果为 16return 0;
}

在这里插入图片描述


今天的分享就到这里了,感谢你的阅读,如果你有任何疑问或者想分享你的经验,请在下方留言,我会非常乐意与你讨论。

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

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

相关文章

OCI 发布了容器运行时和镜像规范!

7 月 19 日是开放容器计划Open Container Initiative&#xff08;OCI&#xff09;的一个重要里程碑&#xff0c;OCI 发布了容器运行时和镜像规范的 1.0 版本&#xff0c;而 Docker 在这过去两年中一直充当着推动和引领的核心角色。 我们的目标是为社区、客户以及更广泛的容器行…

问答区混赏金的集合贴

此贴专记录CSDN问答社区里面&#xff0c;一些回答者在临近结题时胡乱回答&#xff0c;只为分取结题赏金的人。 为了截图方便&#xff0c;给回答者点赞和点踩不是对其回答的认可和不认可&#xff0c;只是为了方便截图而已 文章目录 第一位——夜深人静的哝玛 (PS:与本人的头像和…

为什么都说NFS读写性能差,如何进行优化?

使用基于NFS协议存储系统的同学经常遇到的问题是在小文件比较多的情况下性能会比较差。小文件访问性能差本身是可以理解的,但是NFS确实是太差了。不知大家是否深层次分析过,为什么NFS访问小文件性能会这么差? NFS文件系统与本地文件系统的差异在于多了一个网络传输的过程。…

基于or-tools的人员排班问题建模求解(JavaAPI)

使用Java调用or-tools实现了阿里mindopt求解器的案例&#xff08;https://opt.aliyun.com/platform/case&#xff09;人员排班问题。 这里写目录标题 人员排班问题问题描述数学建模编程求解&#xff08;ortoolsJavaAPI&#xff09;求解结果 人员排班问题 随着现在产业的发展&…

OpenGL之光照贴图

我们需要拓展之前的系统,引入漫反射和镜面光贴图(Map)。这允许我们对物体的漫反射分量和镜面光分量有着更精确的控制。 漫反射贴图 我们希望通过某种方式对物体的每个片段单独设置漫反射颜色。我们仅仅是对同样的原理使用了不同的名字:其实都是使用一张覆盖物体的图像,让我…

通讯网关软件013——利用CommGate X2ORACLE实现Modbus RTU数据转储ORACLE

本文介绍利用CommGate X2ORACLE实现从Modbus RTU设备读取数据并转储至ORACLE数据库。CommGate X2ORACLE是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现从Modbus RTU设备读取数据并转储至ORACL…

洛谷P5732 【深基5.习7】杨辉三角题解

目录 题目【深基5.习7】杨辉三角题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1传送门 代码解释亲测 题目 【深基5.习7】杨辉三角 题目描述 给出 n ( n ≤ 20 ) n(n\le20) n(n≤20)&#xff0c;输出杨辉三角的前 n n n 行。 如果你不知道什么是杨辉三角&#xf…

Android Jetpack组件架构:ViewModel的原理

Android Jetpack组件架构&#xff1a;ViewModel的原理 导言 本篇文章是关于介绍ViewModel的&#xff0c;由于ViewModel的使用还是挺简单的&#xff0c;这里就不再介绍其的基本应用&#xff0c;我们主要来分析ViewModel的原理。 ViewModel的生命周期 众所周知&#xff0c;一般…

【进阶C语言】自定义类型

本节内容大致目录如下&#xff1a; 1.结构体 2.位段 3.枚举 4.联合&#xff08;共用体&#xff09; 以上都是C语言中的自定义类型&#xff0c;可以根据我们的需要去定义。 一、结构体 一些基础知识在初阶C语言的时候已经介绍过&#xff0c;在这里粗略概括&#xff1b;重…

C++17中std::filesystem::directory_entry的使用

C17引入了std::filesystem库(文件系统库, filesystem library)。这里整理下std::filesystem::directory_entry的使用。 std::filesystem::directory_entry&#xff0c;目录项&#xff0c;获取文件属性。此directory_entry类主要用法包括&#xff1a; (1).构造函数、…

Flutter笔记:滚动之-无限滚动与动态加载的实现(GetX简单状态管理版)

Flutter笔记 无限滚动与动态加载的实现&#xff08;GeX简单状态管理版&#xff09; 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq…

阿里云RDS关系型数据库详细介绍_多版本数据库说明

阿里云RDS关系型数据库大全&#xff0c;关系型数据库包括MySQL版、PolarDB、PostgreSQL、SQL Server和MariaDB等&#xff0c;NoSQL数据库如Redis、Tair、Lindorm和MongoDB&#xff0c;阿里云百科分享阿里云RDS关系型数据库大全&#xff1a; 目录 阿里云RDS关系型数据库大全 …

通过containerd部署k8s集群环境及初始化时部分报错解决

目录 一.基础环境配置&#xff08;每个节点都做&#xff09; 1.hosts解析 2.防火墙和selinux 3.安装基本软件并配置时间同步 4.禁用swap分区 5.更改内核参数 6.配置ipvs 7.k8s下载 &#xff08;1&#xff09;配置镜像下载相关软件 &#xff08;2&#xff09;配置kube…

【视频去噪】基于全变异正则化最小二乘反卷积是最标准的图像处理、视频去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

stl格式-3D三角形

文章目录 什么是stl文件?格式首选stl的语法1.这是一个stl格式的文件:(ASCII码)2.下面先举个例子(难度略微提示)补充:关于\<\<我试了一下:这个法线你随便写好像也没问题\>> 3.来个立方体4.最后再写一个由三个直角形组成的立方体(直棱锥)5.amend 修正(右手定则,法线…

毛玻璃态计算器

效果展示 页面结构组成 从上述的效果可以看出&#xff0c;计算机的页面比较规整&#xff0c;适合grid布局。 CSS3 知识点 grid 布局 实现计算机布局 <div class"container"><form class"calculator" name"calc"><input type…

EasyEdge 智能边缘控制台通过sdk发布应用

离线部署SDK生成 模型部署完成后会出现下载SDK的按钮&#xff0c;点击按钮下载SDK并保存好SDK。 进入EasyDL官网的技术文档 安装智能边缘控制台 跟着教程&#xff0c;完成安装&#xff1a;点此链接 树莓派4b是Linux arm64的架构&#xff0c;点击对应的链接进行下载。 下载完成…

金融帝国实验室(CapLab)官方更新_V9.1.15版本(2023年第64次)

〖金融帝国实验室〗&#xff08;Capitalism Lab&#xff09;游戏更新记录&#xff08;2023年度&#xff09; ————————————— ◎游戏开发&#xff1a;Enlight Software Ltd.&#xff08;微启软件有限公司&#xff09; ◎官方网站&#xff1a;https://www.capitalis…

阿里云通义千问14B模型开源!性能超越Llama2等同等尺寸模型

9月25日&#xff0c;阿里云开源通义千问140亿参数模型Qwen-14B及其对话模型Qwen-14B-Chat,免费可商用。Qwen-14B在多个权威评测中超越同等规模模型&#xff0c;部分指标甚至接近Llama2-70B。阿里云此前开源了70亿参数模型Qwen-7B等&#xff0c;一个多月下载量破100万&#xff0…

【C++】笔试训练(三)

目录 一、选择题二、编程题1、字符串中找出连续最长的数字串2、数组中出现次数超过一半的数字 一、选择题 1、以下程序的输出结果是&#xff08;&#xff09; #include <stdio.h> int main() {char a[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }, * p;int i;i 8;p a i;p…