【进阶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.mzph.cn/news/93558.shtml

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

相关文章

代码随想录算法训练营第五十五天 | 动态规划 part 12 | 300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

目录 300.最长递增子序列思路代码 674. 最长连续递增序列思路代码 718. 最长重复子数组思路代码 300.最长递增子序列 Leetcode 思路 dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度递推公式&#xff1a;if (nums[i] > nums[j]) dp[i] max(dp[i], dp[j] 1)初…

面试总结之Spring篇

一、AOP 1、什么是AOP 1.1、概述 AOP&#xff08;Aspect-Oriented Programming&#xff09;&#xff1a;面向切面编程&#xff0c;即把一些业务逻辑中的相同代码抽取出来&#xff0c;让业务逻辑更加简练清爽 如果要CRUD写一堆业务&#xff0c;可如何实现业务代码前后进行打印…

计算机竞赛 深度学习驾驶行为状态检测系统(疲劳 抽烟 喝水 玩手机) - opencv python

文章目录 1 前言1 课题背景2 相关技术2.1 Dlib人脸识别库2.2 疲劳检测算法2.3 YOLOV5算法 3 效果展示3.1 眨眼3.2 打哈欠3.3 使用手机检测3.4 抽烟检测3.5 喝水检测 4 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的驾…

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

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

【强化算法专题一】双指针算法

【强化算法专题一】双指针算法 1.双指针算法--移动零2.双指针算法--复写零3.双指针算法--快乐数4.双指针算法--盛水最多的容器5.双指针算法--有效三角形的个数6.双指针算法--和为s的两个数7.双指针算法--三数之和8.双指针算法--四数之和 1.双指针算法–移动零 算法原理解析----…

【JavaScript】读取本地json文件并绘制表格

本文为避免跨域问题&#xff0c;使用了改造过的本地json文件的方法实现读取json数据并绘制表格。 如果发起http请求获取本地 json文件中数据&#xff0c;需要架设本地服务器&#xff0c;本文不做阐述。 概述 1、json在本地&#xff0c;并不需要从服务器下载。 2、采用jquery…

国庆作业day5

应用层&#xff1a;提供用户与网络应用程序之间的接口。表示层&#xff1a;负责数据的格式转换、加密和解密。会话层&#xff1a;负责建立、管理和终止会话。它提供会话控制和同步&#xff0c;允许应用程序之间建立连接和交换数据。传输层&#xff1a;提供端到端的连接。网络层…

postgresql-管理数据表

postgresql-管理数据表 创建表数据类型字段约束表级约束模式搜索路径 修改表添加字段删除字段添加约束删除约束修改字段默认值修改字段数据类型重命名字段重命名表 删除表 创建表 在 PostgreSQL 中&#xff0c;使用 CREATE TABLE 语句创建一个新表&#xff1a; CREATE TABLE …

专业PDF编辑阅读工具PDF Expert mac中文特点介绍

PDF Expert mac是一款专业的PDF编辑和阅读工具。它可以帮助用户在Mac、iPad和iPhone等设备上查看、注释、编辑、填写和签署PDF文档。 PDF Expert mac软件特点 PDF编辑&#xff1a;PDF Expert提供了丰富的PDF编辑功能&#xff0c;包括添加、删除、移动、旋转、缩放、裁剪等操作…

树莓派4B与STM32串口通信

目录 2上篇文章的补充 2.1 树莓派通信设置 3树莓派与STM32通信 3.1接线准备 3.2代码 3.2.1 STM32代码&#xff1a; 3.2.2树莓派代码&#xff1a; 2上篇文章的补充 2.1 树莓派通信设置 在上篇文章的基础上&#xff0c;进一步的设置 终端输入&#xff1a;sudo minicom …

从 0 到 1 ,手把手教你编写《消息队列》项目(Java实现) —— 核心类持久化存储

文章目录 一、持久化存储的方式与路径二、公共模块序列化 / 反序列化异常规定 三、持久化存储数据库数据管理文件数据管理读写规定新增 /删除规定内存中 Message 的规定存储规定代码编写 硬盘数据管理 一、持久化存储的方式与路径 交换机,队列,绑定关系,这些我们使用数据库来管…

四、浏览器渲染过程,DOM,CSSDOM,渲染,布局,绘制详细介绍

知识点&#xff1a; 1、为什么不能先执行 js文件&#xff1f;&#xff1f; 我们不能先执行JS文件&#xff0c;必须等到CSSOM构建完成了才能执行JS文件&#xff0c;因为前面已经说过渲染树是需要DOM和CSSOM构建完成了以后才能构建&#xff0c;而且JS是可以操控CSS样式的&#…

springboot的配置文件(properties和yml/yaml)

springboot的配置文件有两种格式分别是properties和yml/yaml 创建配置文件 在创建springboot项目时候&#xff0c;会默认生成application.properties这种格式 书写风格 端口 application.propertis server.port8080 application.yml server:port: 8080 连接数据库 applica…

<Xcode> Xcode IOS无开发者账号打包和分发

关于flutter我们前边聊到的初入门、数据解析、适配、安卓打包、ios端的开发和黑苹果环境部署&#xff0c;但是对于苹果的打包和分发&#xff0c;我只是给大家了一个链接&#xff0c;作为一个顶级好男人&#xff0c;我认为这样是对大家的不负责任&#xff0c;那么这篇就主要是针…

【计算机网络黑皮书】应用层

【事先声明】 这是对于中科大的计算机网络的网课的学习笔记&#xff0c;感谢郑烇老师的无偿分享 书籍是《计算机网络&#xff08;自顶向下方法 第6版&#xff09;》 需要的可以私信我&#xff0c;无偿分享&#xff0c;课程简介下也有 课程连接 目录 应用层网络应用的原理应用架…

作业 day4

完成父子进程通信

Socket通信

优质博文IT-BLOG-CN 一、简介 Socket套接字&#xff1a;描述了计算机的IP地址和端口&#xff0c;运行在计算机中的程序之间采用socket进行数据通信。通信的两端都有socket&#xff0c;它是一个通道&#xff0c;数据在两个socket之间进行传输。socket把复杂的TCP/IP协议族隐藏在…

Linux基本指令(二)

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; C&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大…

[spring] spring core - 配置注入及其他内容补充

[spring] spring core - 配置注入及其他内容补充 上篇 [sping] spring core - 依赖注入 这里主要补一些 core 相关内容补充&#xff0c;同时添加了 java config bean 的方法 java config bean 是除了 XML、java 注解之外另一给实现 DI 的方法 java config bean 这个方法不…

Tomcat报404问题的原因分析

1.未配置环境变量 按照需求重新配置即可。 2.IIs访问权限问题 注意:这个问题有的博主也写了,但是这个问题可有可无,意思是正常情况下,有没有都是可以访问滴放心 3.端口占用问题 端口占用可能会出现这个问题,因为tomcat的默认端口号是8080,如果在是运行tomcat时计算机的…