【进阶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.rhkb.cn/news/146394.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wxWidgets(1):在Ubuntu 环境中搭建wxWidgets 库环境,安装库和CodeBlocks的IDE,可以运行demo界面了,继续学习中

1&#xff0c;选择使用 wxWidgets 框架 选择这个主要是因为完全的开源&#xff0c;不想折腾 Qt的库&#xff0c;而且打包的文件比较大。 网络上面有很多的对比&#xff0c;而且使用QT的人比较多。 但是我觉得wxwidgets 更加偏向 c 语法本身&#xff0c;也有助学习C。 没有太多…

RAID知识点总结

目录 RAID类型 RAID的数据组织及存取方式 RAID热备与重构 RAID逻辑卷 常见的RAID RAID0 RAID 1 RAID3 RAID 5 RAID 6 RAID组合 RAID 10 RAID 50 总结 RAID技术对比 RAID的应用场景 RAID2.0 使用RAID2.0的原因 RAID2.0的发展 RAID2.0技术&#xff1a;两层虚拟…

【深入探究人工智能】:历史、应用、技术与未来

深入探究人工智能 前言人工智能的历史人工智能的应用人工智能的技术人工智能的未来当代的人工智能产物结语&#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &am…

力扣-338.比特位计数

Idea 直接暴力做法&#xff1a;计算从0到n&#xff0c;每一位数的二进制中1的个数&#xff0c;遍历其二进制的每一位即可得到1的个数 AC Code class Solution { public:vector<int> countBits(int n) {vector<int> ans;ans.emplace_back(0);for(int i 1; i < …

数学建模之Matlab基础操作

作者由于后续课程也要学习Matlab&#xff0c;并且之前也进行了一些数学建模的练习&#xff08;虽然是论文手&#xff09;&#xff0c;所以花了几天零碎时间学习Matlab的基础操作&#xff0c;特此整理。 基本运算 a55 %加法&#xff0c;同理减法 b2^3 %立方 c5*2 %乘法 x 1; …

【C语言数据结构——————栈和队列4000字详解】

欢迎阅读新一期的c语言数据结构模块————栈和队列 ✒️个人主页&#xff1a;-_Joker_- &#x1f3f7;️专栏&#xff1a;C语言 &#x1f4dc;代码仓库&#xff1a;c_code &#x1f339;&#x1f339;欢迎大佬们的阅读和三连关注&#xff0c;顺着评论回访&#x1f339;&#…

Unity把UGUI再World模式下显示到相机最前方

Unity把UGUI再World模式下显示到相机最前方 通过脚本修改Shader 再VR里有时候要把3D的UI显示到相机最前方&#xff0c;加个UI相机会坏事&#xff0c;可以通过修改unity_GUIZTestMode来解决。 测试用例 测试用例如下&#xff1a; 场景包含一个红色的盒子&#xff0c;一个UI…

洛谷P1102 A-B 数对题解

目录 题目A-B 数对题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示传送门 代码解释亲测 题目 A-B 数对 题目背景 出题是一件痛苦的事情&#xff01; 相同的题目看多了也会有审美疲劳&#xff0c;于是我舍弃了大家所熟悉的 AB Problem&#xff0c;改用 …

使用WPS自动化转换办公文档: 将Word, PowerPoint和Excel文件转换为PDF

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【Linux】RPM包使用详解

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

WebSocket的那些事(6- RabbitMQ STOMP目的地详解)

目录 一、目的地类型二、Exchange类型目的地三、Queue类型目的地四、AMQ Queue类型目的地五、Topic类型目的地 一、目的地类型 在上节 WebSocket的那些事&#xff08;5-Spring STOMP支持之连接外部消息代理&#xff09;中我们已经简单介绍了各种目的地类型&#xff0c;如下图&…

分布式搜索引擎es-3

文章目录 数据聚合聚合的种类RestAPI实现聚合 自动补全自定义拼音分词器自动补全查询案例&#xff1a;实现酒店搜索框自动补全自动补全的javaAPI实现搜索框自动补全 口述自动补全数据同步集群集群的分布式存储集群分布式查询集群故障转移 数据聚合 什么是聚合&#xff1f; 聚合…

多网卡场景数据包接收时ip匹配规则

多网卡场景数据包接收时ip匹配规则 mac地址匹配规则 接收数据包时数据包中的目的mac地址匹配接收网卡的mac地址后&#xff0c;数据包才会继续被传递到网络层处理 ip地址匹配规则 图1&#xff1a; 参见&#xff1a;https://zhuanlan.zhihu.com/p/529160026?utm_id0 图2&am…

Vue中的数据分页与分页组件设计

Vue中的数据分页与分页组件设计 在前端开发中&#xff0c;数据分页是一个常见的需求&#xff0c;特别是当处理大量数据时。Vue作为一款流行的JavaScript框架&#xff0c;提供了强大的工具和生态系统来实现数据分页。本文将介绍如何在Vue中进行数据分页&#xff0c;以及如何设计…

【Django 笔记】第一个demo

1. pip 安装 2. django 指令 D:\software\python3\anconda3\Lib\site-packages\django\bin>django-adminType django-admin help <subcommand> for help on a specific subcommand.Available subcommands:[django]checkcompilemessagescreatecachetabledbshelldiff…

Vue中如何进行图表绘制

Vue中的图表绘制&#xff1a;数据可视化的艺术 数据可视化是现代Web应用程序的重要组成部分之一。Vue.js作为一种流行的JavaScript框架&#xff0c;提供了许多强大的工具和库&#xff0c;用于在前端应用程序中创建各种图表和数据可视化。本文将深入探讨在Vue中进行图表绘制的方…

WPF 实现点击按钮跳转页面功能

方法1. 配置环境 首先添加prism依赖项&#xff0c;配置好所有文件。需要配置的有两个文件&#xff1a;App.xaml.cs和App.xaml App.xaml.cs using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows;namespace PrismDemo {/// <summa…

R语言中更改R包安装路径

看到这些包下载到我的C盘&#xff0c;我蛮不爽的&#xff1a; 所以决定毫不犹豫的改到D盘&#xff1a; 首先&#xff0c;我们需要在RStudio中新建一个初始启动文件&#xff1a; file.edit(~/.Rprofile) 然后去你喜欢的环境新建一个文件夹存放安装的包的位置&#xff0c;我喜欢…

美容店预约小程序搭建流程

随着科技的不断发展&#xff0c;小程序已经成为了人们生活中不可或缺的一部分。对于美容店来说&#xff0c;搭建一个预约小程序不仅可以提高工作效率&#xff0c;还可以增加客户数量、提高服务质量。那么&#xff0c;如何搭建一个美容店预约小程序呢&#xff1f;本文将为你详细…

(七)Flask之路由转换器

引子&#xff1a; from flask import Flaskapp Flask(__name__)# 通过使用<int>转换器&#xff0c;可以捕获URL中的整数值&#xff0c;并将其作为参数传递给视图函数。 app.route(/index/<int:nid>, methods[GET, POST]) def index(nid):print(nid)return Indexi…