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

本节内容大致目录如下:

1.结构体

2.位段

3.枚举

4.联合(共用体)

以上都是C语言中的自定义类型,可以根据我们的需要去定义。

一、结构体

一些基础知识在初阶C语言的时候已经介绍过,在这里粗略概括;重点介绍前面没有提到过的。

1.结构体的声明

声明其实就是需要自己创造一个结构体(类型)。后面再拿这个结构体(类型)去创造变量。

(1)简单声明

(2*)特殊的声明

struct
{int a;char b;float c;
}x;//x为结构体创造出来的名字
struct
{int a;char b;float c;
}a[20], * p;

1.这种在结构体关键字前省略了名字,这种声明方式的结构体称为:匿名结构体类型。

2.因为省略了名字,后续没法再进行变量的创造,所以只能使用一次

3.两个相同的匿名结构体,属于两种不同的类型

(3*)结构体的自引用

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

1.在结构体内部,可以用自身结构体类型来创建的指针,称为结构体的自引用。

2.一般用来数据结构的链表中,因为需要类型相同。

2.结构体变量的创建和初始化


在前面的时候,我们有介绍过在声明的时候创造的全局变量,接下来都一起介绍了。

第一种创造方式:

#include<stdio.h>
struct Stu
{char c;int arr[10];
};
int main()
{struct Stu A;//结构体变量Astruct Stu B;//结构体变量Breturn 0;
}


这里创造的变量A和B都是局部变量。

第二种创造方式:

#include<stdio.h>
typedef struct Stu
{char c;int arr[10];
}Stu;//对结构体重命名
int main()
{//struct Stu A;//结构体变量A//struct Stu B;//结构体变量BStu C;//结构体变量Creturn 0;
}


第三种方式:上面提到过的创造全局变量

#include<stdio.h>
struct Stu
{char c;int arr[10];
}D;//全局变量D
int main()
{//struct Stu A;//结构体变量A//struct Stu B;//结构体变量B//Stu C;//结构体变量Creturn 0;
}


在创建后变量后,就该对变量进行初始化了

初始化:

#include<stdio.h>
struct Stu
{char name[20];int age;double height;
};
int main()
{struct Stu s1 = {"zhangsan",20,182.8};//顺序初始化struct Stu s2 = {.age=18,.height=188.5};//指定成员初始化return 0;
}


在创建变量的时候就初始化:

struct Stu
{char name[20];int age;double height;
}s3 = {"lisi",19,150.6};//创造的全局变量并初始化

3.结构体的内存对齐(*)

这是本节的重点,所谓的内存对齐,就是要知道结构体类型的内存大小专门来的,我们该怎么计算。

(1)引例

我们先观察两个大体相同的结构体,为什么相同的变量不同的顺序,内存大小却不一样。

struct s1
{char c1;char c2;int i;
};
struct s2
{char c1;int i;char c2;
};
#include<stdio.h>
int main()
{printf("%zd\n", sizeof(struct s1));printf("%zd\n",sizeof(struct s2));return 0;
}

运行结果:

第一个结构体类型s1的内存是8字节

第二个结构体类型s2的内存是12字节

造成这种原因是:在结构体中,存在内存对齐这一规则。

(2)结构体内存对齐规则

 1)第一条规则:第一个成员在,与结构体变量偏移量为0的地址处

什么是偏移量:

把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为“有效地址或偏移量”。

图解:

第一个成员就从0位置开始存放,内存多大就占几个格子(字节)。

2)第二条规则:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处(偏移量处)

 什么是对齐数:

对齐数=编译器默认的一个对齐数与该成员的内存大小的较小值

(vs的默认值为8)

图解:

 上述成员在内存中一共占据了8个字节的空间

第二个结构体:

 这些成员在内存中所占的字节大小就是结构体的最终内存大小了吗?还没完,还需要根据第三条规则来计算。

3)第三条规则:结构体的总大小为最大对齐数(每个成员变量都有一个对齐数) 的整数倍

如:char的大小为1,1就是对齐数;像int大小为4,4就是对齐数;还有一个编译器默认对齐数,需要变量与其对比得出。

对齐数图解:

现在我们来计算结构体的大小

第一个:

第二个:

 该结构体在内存中占9个字节,不是4的整数倍,需要增大(增大到离9最近的数字且是4的整数倍),所以该结构体的内存大小为12字节。

如果结构体嵌套又如何计算,我们看第四条规则

4)第四条规则:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

存放嵌套的结构体时,它也有自己的最大对齐数和内存大小,所以只需要把它存放到是它自己的最大对齐数的整数倍处即可。

 图解:

变量C在右图所占的内存大小为16个字节,该结构体的最大对齐数为4,所以16为最终的内存大小。

struct s1
{char c1;char c2;int i;
};
struct s2
{char c1;int i;char c2;
};
struct s3
{char c1;struct s2 c2;
};
#include<stdio.h>
int main()
{printf("%zd\n", sizeof(struct s1));printf("%zd\n", sizeof(struct s2));printf("%zd\n",sizeof(struct s3));return 0;
}

 

(3)内存对齐的原因

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

2)性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐;原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问(拿空间换时间的一种做法)

3)做法:既可以节省空间又能节约时间

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

例如:

struct s1//已集中
{char c1;char c2;int i;
};
struct s2//未集中
{char c1;int i;char c2;
};

像struct s1的结构体就做到了上述的要求,内存只占8,而struct s2却占了12

(4)修改默认对齐数

我们可以通过修改默认对齐数,使结构体有更好的对齐方式,这里用#pragma这个预处理指令来修改。

#pragma pack(1)//设置默认对齐数为1
struct s1
{char c1;char c2;int i;
};
#pragma pack()//恢复默认对齐数
struct s2
{char c1;char c2;int i;
};
#include<stdio.h>
int main()
{printf("%zd\n", sizeof(struct s1));printf("%zd\n", sizeof(struct s2));return 0;
}

1.一样的数据类型和排列方式,所占内存却是不一样

2.结构在对齐方式不合适的时候,我们可以自己更改默认对齐数

3.默认对齐数,修改的结果一般要求为2^n,n>=1;不可以为符合或奇数(1除外)。

二、位段

位段是基于结构体的基础上的,位段是一种特殊的结构体--也是为了节省空间

1.位段的定义

位段的声明和结构是类似的

(1)位段的成员必须是:int,unsigned int、char或signed int(c99之后也可以有其他的类型)

(2)位段的成员名后边有一个冒号和数字

(3)举例:

struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};

1. _a、_b这些只是为了更好知道这是位段才加的,也可以选择不加。

2.后面的冒号和数字才是位段的语法要求。

3.位段的“位”表示二进制位的意思,冒号后面的数字就是代表有多少二进制位。

4.数字表明该成员变量最大的二进制位,如_a:2,_a只有两个二进制位,能表示的二进制数字只有:00,01,10和11,范围就是0-3。

(4)位段的作用

 我们根据预知的数据内存,可以设置合理的二进制位,就可以达到节约空间的目的

2.位段的内存分配

(1)内存的计算

1.位段也是一种结构体,所以位段也遵守内存对齐的方式。

2.位段是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

如:

下面的内存大小是多少呢?

#include<stdio.h>
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{printf("%zd\n",sizeof(struct A));return 0;
}

我们通过代码的运行结果可知:该位段的内存大小为8个字节

 我们接上面第二点:如:该位段都是int,一上来会先分配一个字节的空间(一字节==32bit),我们前面的2+5+10刚好存放在第一个字节的空间里面,而_d需要30bit,所以只能再开辟一个字节的空间,32bit拿出30比特刚好存放_d这个数据。

(2)位段的内存分配规则

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

 比如上述第一个字节中的bit没有被用完,后续是否还用这是不确定的

(3)内存分配实例

看一段代码:

#include<stdio.h>
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s = {0};//初始化printf("%zd\n", sizeof(struct S));return 0;
}

先简单看这个位段会消耗多少字节?

该结构体共消耗3字节。他们所占的二进制有3+4+5+4=16位,不应该是只占2字节吗?

图解:

现在我们已经知道了内存的分配,接下来了解数据是怎么存入的。

代码:

#include<stdio.h>
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s = {0};//初始化printf("%zd\n", sizeof(struct S));//赋值s.a = 10;s.b = 12;s.c = 3;s.d = 4;return 0;
}

 内存分配图解:

这就是这些数据存入内存中的二进制形式,然后转化成16进制就是在调试窗口的展示形式,让我们看看是不是这样子呢?

可以清楚看到三个字节中数据的存储方式,因为只能存储有限位的bit,所以需要控制数据的大小范围,否则会造成数据的丢失。

3.位段的跨平台问题

(1)int被当成有符号数还是无符号数是不确定的

如上述代码的结果:

(2)位段中最大位的数目不能确定。(16位机器最大位16,32位机器最大32位,写成27,在16位平台的机器上会出现问题)

(3)位段中的成员在内存中是从左向右分配,还是从右向左分配标准尚未定义(大小端存储字节序)

(4)当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

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

三、枚举

所谓枚举,就是一一列举

1.枚举的定义

枚举,全名又称枚举常量,属于常量中的一种。定义枚举常量的时候需要用到enum的关键字。

(1)定义格式

enum Sex//性别常量
{Man,//男Woman,//女Sercet,//保密
};

1.enum Sex称为枚举类型,Man、Woman和Sercet称为枚举常量

2.格式与结构体相似,但是每个枚举常量后面是逗号。

3.枚举常量的常量值默认从0开始。类似#define定义的常量一样,在后续的使用中他就是一个数字。

4.枚举常量的名字一般首字母大写或者全部大写,便于识别。

(2)打印枚举常量

#include<stdio.h>
enum Sex//性别常量
{Man,//男Woman,//女Sercet,//保密
};
int main()
{printf("%d\n", Man);printf("%d\n", Woman);printf("%d\n",Sercet);return 0;
}

结果:

(3)修改默认值

第一种:修改起始值,后续的值会依次递增

#include<stdio.h>
enum Sex//性别常量
{Man=5,Woman,Sercet,
};
int main()
{printf("%d\n", Man);printf("%d\n", Woman);printf("%d\n",Sercet);return 0;
}

第二种:任意赋值

#include<stdio.h>
enum Sex//性别常量
{Man=5,Woman=3,Sercet=100,
};
int main()
{printf("%d\n", Man);printf("%d\n", Woman);printf("%d\n",Sercet);return 0;
}

注意:只能在定义的时候赋值,但是不能在后续的步骤中修改

2.枚举的的优点

(1) 增加代码的可读性和可维护性


(2)和#define定义的标识符比较枚举有类型检查,更加严谨。

枚举常量是有类型的(枚举类型),而#define定义的却没有
(3)防止了命名污染(封装)


(4)便于调试


(5)使用方便,一次可以定义多个常量

四、联合(共用体)

1.联合体的定义

(1)联合体是一种特殊的自定义类型,特征是这些变量公用一块空间。定义需要用到联合关键字union。

(2)代码格式

union Un
{char c;int i;
};
与结构体格式相同,但是关键字不一样。

联合变量的创建:

#include<stdio.h>
union Un
{char c;int i;
};
int main()
{union Un u1;//联合变量printf("%zd",sizeof(u1));//计算联合变量的大小return 0;
}

2.联合体的特点

(1)特点

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

#include<stdio.h>
union Un
{char c;int i;
};
int main()
{union Un u1;printf("%p\n", &u1);printf("%p\n", &(u1.c));printf("%p\n",&(u1.i));return 0;
}

运行结果:

这三个的起始地址都一样,那在内存中是什么样的呢?

他们是公用一块内存的,如果修改其中一个,另一个是很有可能就被修改了。

(2)利用其特点解决的问题

利用联合体判断当前机器是小端还是大端字节序?

#include<stdio.h>
int check_sys()
{union Un{char c;int i;}u;u.i = 1;return u.c;
}
int main()
{int ret = check_sys();if (ret == 1)printf("小端\n");elseprintf("大端\n");return 0;
}

原理:

返回的u.c,拿到的是i的第一个字节;如果小端存储。第一个字节(低字节)就会存储在低地址处,也就是起始位置,反之一样。

 3.联合大小的计算

(1)联合的大小至少是最大成员的大小。(不一定就是最大的)
(2)当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。(蹲守结构体的内存对齐规则)

#include<stdio.h>
union Un
{char a[5];int i;
};
int main()
{printf("%zd\n",sizeof(union Un));return 0;
}

联合体一般用于有公用部分的时候,如果商场的购物系统,很多商品都有共同的属性,如:价格等。


本章完 

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

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

相关文章

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…

IO流之File类

File类 File 对应的硬盘上的文件或者文件夹 位于java.io包下 File对文件/文件夹进行操作&#xff0c;但是无法对文件内容进行操作&#xff0c;读取/写入不可以操作&#xff0c;但是可以创文件夹/读取文件路径,IO流才可以进行操作 文件/文件夹的路径&#xff1a;linux使用/作为文…

【红外与可见光图像融合】离散平稳小波变换域中基于离散余弦变换和局部空间频率的红外与视觉图像融合方法(Matlab代码实现)

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

音频编辑软件Steinberg SpectraLayers Pro mac中文软件介绍

Steinberg SpectraLayers Pro mac是一款专业的音频编辑软件&#xff0c;旨在帮助音频专业人士进行精细的音频编辑和声音处理。它提供了强大的频谱编辑功能&#xff0c;可以对音频文件进行深入的频谱分析和编辑。 Steinberg SpectraLayers Pro mac软件特点 1. 频谱编辑&#xff…

基于SpringBoot的知识管理系统

目录 前言 一、技术栈 二、系统功能介绍 用户管理 文章分类 资料分类 文章信息 论坛交流 资料下载 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息互联网信息的飞速发展&#xff0c;无纸化作业变成了一种趋势&#xff0c;针对这个问题开发一个…

Windows历史版本下载

1、微PE工具箱&#xff08;非广告本人常用&#xff09; 常用安装Windows系统的微PE工具 地址&#xff1a;https://www.wepe.com.cn/download.html 2、Windows系统下载地址&#xff08;非微软官方&#xff09; 地址&#xff1a;MSDN, 我告诉你 - 做一个安静的工具站 下载&…

SpringMVC的请求映射:路由请求的精准导航

SpringMVC的请求映射&#xff1a;路由请求的精准导航 SpringMVC是一个用于构建Web应用程序的强大框架&#xff0c;它提供了众多的特性和组件来简化开发过程。其中&#xff0c;请求映射是SpringMVC中的一个关键特性&#xff0c;用于将HTTP请求映射到具体的处理方法。本文将深入…

RocketMQ Dashboard说解

RocketMQ Dashboard 是 RocketMQ 的管控利器&#xff0c;为用户提供客户端和应用程序的各种事件、性能的统计信息&#xff0c;支持以可视化工具代替 Topic 配置、Broker 管理等命令行操作。 介绍​ 功能概览​ 面板功能运维修改nameserver 地址; 选用 VIPChannel驾驶舱查看 …

基于SpringBoot的高校学科竞赛平台

目录 前言 一、技术栈 二、系统功能介绍 竞赛题库管理 竞赛信息管理 晋级名单管理 往年成绩管理 参赛申请管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步…

【信创】麒麟v10(arm)-mysql8-mongo-redis-oceanbase

Win10/Win11 借助qume模拟器安装arm64麒麟v10 前言 近两年的国产化进程一直在推进&#xff0c;基于arm架构的国产系统也在积极发展&#xff0c;这里记录一下基于麒麟v10arm版安装常见数据库的方案。 麒麟软件介绍: 银河麒麟高级服务器操作系统V10 - 国产操作系统、银河麒麟、中…