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

news2024/11/28 2:36:46

  • 💓博客主页:江池俊的博客
  • ⏩收录专栏: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)); //结果是 8

	printf("S4 结构中的 c1 偏移 = %d 字节。\n",
		offsetof(struct S4, c1)); //结果是 0

	printf("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)); //结果为 4
	return 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");
	else
		printf("大端\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)); //结果为 8
	printf("%d\n", sizeof(union Un2)); //结果为 16
	return 0;
}

在这里插入图片描述


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

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

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

相关文章

Hypermesh联合LS-DYNA的子弹侵彻计算

Hypermesh凭借强大的网格划分和方便的求解设置功能而被广泛用于DYNA求解器的前处理。本文通过bullet穿透铝合金板侵彻计算来简单介绍Hypermesh和LS-DYNA的联合使用。 计算模型如图1所示&#xff0c;为1/4模型&#xff0c;bullet材料为钢材&#xff0c;被穿透的板为铝合金AL202…

python和java类的编写(属性私有化,方法公开化)

初始化类的属性的2种写法&#xff1a; 如下要注意python对文件名称、类、方法名的命名 方式一&#xff1a;原始的定义 class User1: # 初始化账号和密码 def __init__(self):# 账号和密码self.__username Noneself.__password Nonedef getnsername(self):return self.__us…

001 Python开发环境搭建

1、下载python 2023/10 python-3.11.5-amd64.exehttps://www.python.org/ftp/python/3.11.5/python-3.11.5-amd64.exe 2、下载Visual Studio Code 2023/10 VSCodeSetup-x64-1.82.2.exehttps://code.visualstudio.com/docs/?dvwin64 3、安装python 双击打开python-3.11.5-a…

【Linux】RPM包使用详解

&#x1f341; 博主 "开着拖拉机回家"带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——&#x1f390;开着拖拉机回家_大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341; 希望本文能够给您带来一定的帮助&#x1f338;文…

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

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

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

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

AI标注,怎么做才能省时省劲,提高效率?有何技巧?

AI标注是一种借助人工智能技术为数据集添加标签的方法&#xff0c;这一过程旨在使机器学习算法能够更好地识别和分类数据。与传统手工标注相比&#xff0c;AI标注具有高效、高准确性和低成本等优势。在本文中&#xff0c;我们将探讨如何实施有效的AI标注策略以提高工作效率。 …

如何使用ChatGPT来辅助写简历

How to ask ChatGPT for resume help https://www.producthunt.com/stories/how-to-ask-chatgpt-for-resume-help #MixCopilot 本文作者&#xff1a; 摘要&#xff1a; 本文介绍了如何使用ChatGPT来辅助写简历。通过ChatGPT&#xff0c;你可以改善简历的格式和结构&#xff0c;…

基于Java的实验室设备借用登记系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

Java生态系统的进化:从JDK 1.0到今天

文章目录 JDK 1.0&#xff1a;开启Java时代JDK 1.1&#xff1a;Swing和内部类JDK 1.2&#xff1a;Collections框架和JIT编译器JDK 1.5&#xff1a;引入泛型和枚举JDK 1.8&#xff1a;Lambda表达式和流JDK 11以后&#xff1a;模块化和新特性未来展望1. 云原生和容器化2. 更好的性…

SpringBoot 如何解决跨域问题

Spring Boot 中的跨域请求&#xff08;Cross-Origin Request&#xff09;问题与解决方案 跨域请求是指浏览器从一个域名的网页去请求另一个域名的资源&#xff0c;它是为了增强 Web 安全性而产生的限制。Spring Boot 应用程序通常会面临跨域请求的问题&#xff0c;本文将介绍跨…

Java栈的压入、弹出序列(详解)

目录 1.题目描述 2.题解 方法1 方法2 1.题目描述 输入两个整数序列&#xff0c;第一个序列表示栈的压入顺序&#xff0c;请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序&#xff0c;序列4,5,3,2,1是该压栈序…

初识动态内存分配

目录 为什么会存在动态内存分配&#xff1a; malloc: free&#xff1a; calloc&#xff1a; realloc&#xff1a; 注意事项&#xff1a; 攻破经典易错题&#xff1a; 题目一&#xff1a; 存在以下两种方式进行修改&#xff1a; 1.利用二级指针进行修改&#xff1a; 2.…

加拿大人工智能数据搜索平台【Secoda】完成1400万美元A轮融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于加拿大多伦多的人工智能数据搜索平台【Secoda】今日宣布已完成1400万美元A轮融资。 本轮融资由Craft Ventures领投&#xff0c;参与投资的投资机构有Abstract Ventures、现有投资者YCombi…

代码随想录算法训练营第五十天 |123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

一、123.买卖股票的最佳时机III 题目链接/文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;动态规划&#xff0c;股票至多买卖两次&#xff0c;怎么求&#xff1f; | LeetCode&#xff1a;123.买卖股票最佳时机III_哔哩哔哩_bilibili 思考&#xff1a; 至多买卖两次&…

【算法挨揍日记】day09——35. 搜索插入位置、69. x 的平方根

35. 搜索插入位置 35. 搜索插入位置 题目描述&#xff1a; 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 解题思…

德国云安全协作软件提供商【Rencore】完成800万美元融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于德国慕尼黑的云安全协作软件提供商Rencore今日宣布已完成800万美元融资。 本轮融资由UVC Partners领投。 该公司打算利用这笔资金进一步投资于其云协作治理产品的增长。 Rencore由Matthi…

机器学习7:逻辑回归

一、说明 逻辑回归模型是处理分类问题的最常见机器学习模型之一。二项式逻辑回归只是逻辑回归模型的一种类型。它指的是两个变量的分类&#xff0c;其中概率用于确定二元结果&#xff0c;因此“二项式”中的“bi”。结果为真或假 — 0 或 1。 二项式逻辑回归的一个例子是预测人…

公众号留言小程序有哪些?要免费的

为什么公众号没有留言功能&#xff1f;2018年2月12日之后直到现在&#xff0c;新注册公众号的运营者会发现一个问题&#xff1a;无论是个人还是企业的公众号&#xff0c;在后台都找不到留言功能了。这对公众号来说绝对是一个极差的体验&#xff0c;少了一个这么重要的功能&…