C语言详解——自定义类型:结构体、枚举、联合体

news2024/11/25 11:27:42

🌇个人主页:平凡的小苏

📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情

🛸C语言专栏:https://blog.csdn.net/vhhhbb/category_12174730.html

小苏希望大家能从这篇文章中收获到许多,如果大家觉得这篇文章对你有帮助,请给小苏点赞+收藏+评论

 

目录

1 结构体的声明 

1.1 结构的基础知识

1.2 结构的声明

1.3 特殊的声明

1.4 结构的自引用 

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

1.6 结构体内存对齐

1.7 修改默认对齐数 

2. 位段 

2.1 什么是位段

2.3 位段的跨平台问题 

 3. 枚举

3.1 枚举类型的定义

 3.2 枚举的优点

4. 联合(共用体)

4.1 联合类型的定义 

4.2 联合的特点

4.3 联合大小的计算 


1 结构体的声明 

1.1 结构的基础知识

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

1.2 结构的声明

struct tag
{
 member-list;//成员变量
}variable-list;//全局变量

struct Stu
{
    char name[20];
    int age;
}s1,s2;//这里的s1和s2是全局变量
//在局部创建变量就是全局变量


注意:如果在结构体前面加入typedef,那么在结构体后面加入的就不是变量了,
意思是将结构体重命名
typedef struct Stu
{
    char name[20];
}Stu;//这时候就将struct Stu重命名为Stu了     
例如描述一个学生:
struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}; //分号不能丢

1.3 特殊的声明

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

比如:
//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}a[20], *p;
上面的两个结构在声明的时候省略掉了结构体标签( tag )。
那么问题来了?
// 在上面代码的基础上,下面的代码合法吗?
p = & x ;
警告:
编译器会把上面的两个声明当成完全不同的两个类型。
所以是非法的。

1.4 结构的自引用 

在结构中包含一个类型为该结构本身的成员是否可以呢?答案是可以的
代码例子如下:
//代码2
struct Node
{
 int data;
 struct Node* next;
};

注意:

// 代码 3
typedef struct
{
int data ;
Node * next ;
} Node ;
// 这样写代码,可行否?
答案是不可以的,因为程序从上往下执行,编译器还不知道已经重命名为Node了。
// 解决方案:
typedef struct Node
{
int data ;
struct Node * next ;
} Node ;

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

有了结构体类型,那如何定义变量,其实很简单。
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};//乱序初始化
	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. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

练习例子:

//练习1
struct S3
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S3));

 

画图演示:

 

//练习2:结构体嵌套问题
#include<stdio.h>
struct S3
{
	double d;
	char c;
	int i;
};
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
int main()
{
	printf("%d\n", sizeof(struct S4));
}

 

 画图演示:

 

为什么存在内存对齐?

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

1. 平台原因 ( 移植原因 )
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因
数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。

 例如:


struct S1
{
 char c1;
 int i;
 char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};

 注:S1S2类型的成员一模一样,但是S1S2所占空间的大小有了一些区别。

1.7 修改默认对齐数 

之前我们见过了 #pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。
代码例子如下:
#include <stdio.h>
#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()//取消设置的默认对齐数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    return 0;
}
结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。

2. 位段 

结构体讲完就得讲讲结构体实现 位段 的能力。

2.1 什么是位段

位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号一个数字

比如:

A 就是一个位段类型。
那位段 A 的大小是多少?
struct A
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};
int main()
{
printf("%d\n", sizeof(struct A));
return 0;
}

 

注:一个int占32个比特位,而位段意思就是第一个int用了2个比特位,还剩30个,第二个int用5个比特位还剩25个,第三个int用10个比特位,还剩15个,而最后一个直接用一个int大小,,那么剩余15个比特位不够存了,他就会重新开辟4个字节来存放,并不会从剩余的开始存放 。(位段是没有结构体对齐的,不然就会冲突了)

 我们来看一个样例:看他在内存中是如何存放的

//VS2019上验证:位段的内存开辟和使用
#include<stdio.h>
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;
}

 

 注:vs中以小端字节序存放的,并且以十六进制存放,所以可以看出它用了三字节的空间

2.3 位段的跨平台问题 

1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

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

例如:

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

 3.2 枚举的优点

我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. #define 定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
注意:define编译器在预处理阶段就替换掉了define定义的常量,当程序运行起来编译的时候,define已经被替换了,所以想要调试是不能调试define定义的常量的。这里不作详细介绍,小编会在预处理那篇文章做详细讲解

4. 联合(共用体)

4.1 联合类型的定义 

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
比如:
//联合类型的声明
union Un
{
 char c;
 int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));

 注:从这里可以看出,联合体的成员是共用一块内存的,共用的是联合体成员中占字节最大的一个。所以计算联合体的大小是4.(注意:联合体也是有内存对齐的)。

4.2 联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。
#include<stdio.h>
union Un
{
	int i;
	char c;
};
union Un un;
int main()
{
	printf("%d\n", &(un.i));
	printf("%d\n", &(un.c));
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);
	return 0;
}

 

 从这里可以看出:联合体的成员共用一块内存的话,那么它的成员的起始地址是一样的。并且un.i存入的是11223344,un.c存入的是55,因为它共用一块空间,那么当我们以%x打印的时候,打印出来的自然就是11223355了。

4.3 联合大小的计算 

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
  • 比如:
union Un1
{
 char c[5];
 int i;
};
union Un2
{
 short c[7];
 int i;
};
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));

 注:根据联合体共用的性质和内存对齐的性质,算出来的自然就是8和16了。

好了小编的分享到这里就结束了,如果有什么不足的地方请大佬多多指教!!! 

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

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

相关文章

【Pytorch】稀疏矩阵的表示与加减运算

前提知识&#xff1a;LongTensor()是64位整数型向量&#xff0c;FloatTensor()是32位浮点数向量。 有大量0元素的时候&#xff0c;我们可以使用坐标形式存储稀疏矩阵。 一个3*3的矩阵&#xff0c;但是只有坐标&#xff08;0,0&#xff09;处有值&#xff08;值为1&#xff09;…

量子计算(二十二):Grover算法

文章目录 Grover算法 一、什么是搜索算法 二、怎么实现Grover搜索算法 Grover算法 一、什么是搜索算法 举一个简单的例子&#xff0c;在下班的高峰期&#xff0c;要从公司回到家里&#xff0c;开车走怎样的路线才能够耗时最短呢&#xff1f;最简单的想法&#xff0c;当然…

Radius vector (半径矢量)

Radius vector引言定义引言 今天给大家介绍一下什么是Radius vector。 定义 从圆心到当前位置的矢量r⃗\vec{r}r。它也被叫做位置向量。 其中&#xff0c;O表示圆心&#xff0c;这里我们称之为原点位置&#xff0c;A表示当前位置。因此矢量OA⃗\vec{OA}OA是一个Radius vec…

VsCode简单使用,配置c/c++编译环境,个人感受

整体感觉 本质就是一个加强的文本编译器&#xff0c;增加了终端和控制台&#xff0c;可以程序交互&#xff0c;相当于一个IDE的外部框架。和sublime有一定相似性&#xff0c;但界面和插件安装方面远远优于sublime。应该说这是一种为编译语言设计的显示框架&#xff0c;真正起作…

代码随想录算法训练营第二十天 二叉树 java : 654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树

LeetCode 654.最大二叉树 题目讲解 思路 二叉树的根是最大值左子树最大值左边部分构造的最大二叉树右子树最大值是右边部分构造的最大二叉树 AC代码 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode ri…

可视化监控告警-grafana

需求背景 根据我们的业务背景&#xff0c;需要监控3个纬度。 Iaas层&#xff1a;即腾讯云的基础设施CVMPaas层&#xff1a;即使用的腾讯云Paas服务&#xff0c;各种中间件Saas层&#xff1a;即我们本身的服务指标监控。 腾讯云监控&#xff0c;可以覆盖1&#xff0c;2场景, …

HarmonyOS/OpenHarmony应用开发-ArkTS的声明式开发范式

基于ArkTS的声明式开发范式的方舟开发框架是一套开发极简、高性能、跨设备应用的UI开发框架&#xff0c;支持开发者高效的构建跨设备应用UI界面。 基础能力 使用基于ArkTS的声明式开发范式的方舟开发框架&#xff0c;采用更接近自然语义的编程方式&#xff0c;让开发者可以直…

「自控原理」2.2 控制系统的复域数学模型

本节引入控制模型的复域模型——传递函数&#xff0c;并介绍其构建和使用方法。 本节介绍了几种常见环节的传递函数 文章目录传递函数的定义传递函数的标准形式首1标准型尾1标准型传递函数的性质传递函数的局限性典型环节的传递函数控制系统的复域数学模型是传递函数。拉普拉斯…

深入浅出java并发编程(Thread)

快速了解 package java.lang;public class Thread implements Runnable {}Thread是lang包下的一个类&#xff0c;实现了Runnable接口。源码如下 FunctionalInterface public interface Runnable {public abstract void run(); }Runnable是一个函数式接口不会抛出异常没有返回…

Minecraft 1.19.2 Fabric模组开发 05.动画效果物品

我们本次在Fabric中实现一个具有动画效果的物品&#xff0c;本次演示的模型代码均在文末给出 效果演示效果演示效果演示 首先&#xff0c;请确保你的开发包中引入了geckolib依赖&#xff0c;相关教程请参考:Minecraft 1.19.2 Fabric模组开发 03.动画生物实体 1.首先我们要使用…

nvidia显卡编码并发session限制破解

对于服务器型显卡&#xff08;比如&#xff1a;Tesla T4、NVIDIA A100等&#xff09;&#xff0c;nvidia显卡编码并发session路数没有限制&#xff0c;对于消费型显卡&#xff08;比如&#xff1a;GeForce RTX 3070、GeForce RTX 3080等&#xff09;,nvidia显卡编码并发session…

Odoo 16 企业版手册 - 财务管理之会计仪表板

会计仪表板 财务管理是任何类型业务不可避免的一部分。无论您经营的业务规模或类型如何&#xff0c;如果财务流程没有得到适当的管理和监控&#xff0c;您将在未来面临严重的财务失败。手动管理所有会计操作不是一个好主意&#xff0c;因为它需要大量的时间和精力。在人工会计管…

esp32 Python开发快速入门--环境配置以及点亮LED灯

esp32 Python开发快速入门--环境配置以及点亮LED灯1. 环境配置2. 固件的烧录3 下面开始点灯1. 环境配置 需要配置两个环境&#xff0c; 开发的IDE Thonny 链接如下 https://thonny.org/ 安装串口助手&#xff0c;芯片是通过串口来实现通信的&#xff0c;需要驱动&#xff0c;…

Openresty宏观概述笔记

最近由于项目需要学习了安全代理的相关知识&#xff0c;其实刚开始的时候是非常需要一个入门的介绍&#xff0c;大概说明下这个到底是个什么东西&#xff0c;能干啥&#xff0c;简单的原理是什么&#xff0c;为此我记录下我看完用完的心得&#xff0c;记录成笔记。 一般我们代码…

Nginx基础01:安装和基本使用

背景Nginx是一个高性能的Web服务器&#xff0c;几乎所有的Web服务都需要使用Nginx。关于Nginx的功能特性这里不再赘述&#xff0c;让我们从0开始&#xff0c;了解Nginx的基本用法&#xff0c;学习它在Web服务中都有哪些应用。本文主要介绍Nginx的安装以及基础的控制命令。Nginx…

讨论| 电视行业已是落日夕阳?

如今&#xff0c;随着移动设备、互联网技术的发展&#xff0c;电视似乎逐渐淡出人们的视野。近期&#xff0c;与电视相关的热门讨论似乎都是对于各大视频平台的会员投屏制度&#xff0c;这似乎更加减少了大众对于电视的使用欲望。那么电视领域是否真的趋于淘汰了呢&#xff1f;…

PASCAL VOC 数据集的标注格式

PASCAL VOC 数据集的标注格式 PASCAL VOC 挑战赛 &#xff08; **The PASCAL Visual Object Classes &#xff09;是一个世界级的计算机视觉挑战赛。 PASCAL的全称是Pattern Analysis, Statistical Modelling and Computational Learning&#xff0c;很多优秀的计算机视觉模型…

区块链知识系列 - 系统学习EVM(一)

EVM有一个基于栈的架构&#xff0c;在一个栈中保存了所有内存数值。EVM的数据处理单位被定义为256位的“字”&#xff08;这主要是为了方便处理哈希运算和椭圆曲线运算操作&#xff09; 这里所说的内存数值是指那些EVM字节码运行所需要的输入、输出参数数据和智能合约程序运行中…

拉伯证券|锂离子动力电池有哪些优缺点?锂离子电池的优缺点详解

锂离子动力电池是20世纪开发成功的新型高能电池。这种电池的负极是石墨等资料&#xff0c;正极用磷酸铁锂、钴酸锂、钛酸锂等。70年代进入实用化。因其具有能量高、电池电压高、工作温度规模宽、贮存寿命长等优点&#xff0c;已广泛应用于军事和民用小型电器中。 锂离子动力电池…

jvm垃圾回收笔记

JVM基础知识笔记 1. 垃圾回收相关算法 标记清除-标记整理-复制 这三个看上面的文章 1.1 分代收集算法 将不同生命周期的对象采用不同的收集方式&#xff0c;以便提高回收效率&#xff0c;一般是将Java堆分为新生代和老年代&#xff0c;这样可以根据各个年代的特点使用不同的…