自定义类型详解

目录

一、结构体类型

1.认识结构体

2.如何使用结构体类型

 2.1按顺序初始化结构体,并通过.符号访问

 ​2.2无序初始化结构体,并用.符号访问

 2.3通过地址的方式访问结构体变量成员

 2.4结构体的自引用

2.4.1错误的自引用

2.4.2正确的自引用

3.结构体内存对齐

3.1结构体对齐的规则

3.2练习

4.拓展:结构体实现的位段操作

4.1什么是位段

4.2位段有什么用?

4.3.位段的跨平台问题

二、枚举类型

1.枚举类型的定义

2.枚举的优点

三、联合体类型

1.联合体类型的定义

2.联合体类型的特点


一、结构体类型

1.认识结构体

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

如何创建一个结构体呢,如下代码便创建了一个结构体

struct abc//abc是这个结构体的名字
{int a;//这个a,b,c是结构体的成员列表char b;float c;
}abc1;
//abc1为创建的一个该结构体类型的变量,你也可以在别的地方定义这个变量,因为你已经创建了一个新的类型,一个新的结构体类型

2.如何使用结构体类型

创建完了一个结构体类型,那我们应该这样去使用这个结构体类型呢?

2.1按顺序初始化结构体,并通过.符号访问

#include<stdio.h>
struct abc//abc是这个结构体的名字
{int a;//这个a,b,c是结构体的成员列表char b;float c;
}abc1;
//abc1为创建的一个该结构体类型的变量,你也可以在别的地方定义这个变量,因为你已经创建了一个新的类型,一个新的结构体类型
int main()
{struct abc a1 = { 20,'b',5.2 };//按顺序初始化结构体//struct abc 此时就是声明类型可类比成intprintf("%d %c %f\n", a1.a, a1.b, a1.c);//将内容打印出来,用.这个符号直接访问结构体变量的成员
}

 2.2无序初始化结构体,并用.符号访问

#include<stdio.h>
struct abc//abc是这个结构体的名字
{int a;//这个a,b,c是结构体的成员列表char b;float c;
}abc1;
//abc1为创建的一个该结构体类型的变量,你也可以在别的地方定义这个变量,因为你已经创建了一个新的类型,一个新的结构体类型
int main()
{struct abc a2 = {.b='c',.a=10,.c=3.14};//通过.符号来无序的初始化结构体变量printf("%d %c %f\n", a2.a, a2.b, a2.c);//将内容打印出来
}

 

 2.3通过地址的方式访问结构体变量成员

#include<stdio.h>
struct abc//abc是这个结构体的名字
{int a;//这个a,b,c是结构体的成员列表char b;float c;
}abc1;
//abc1为创建的一个该结构体类型的变量,你也可以在别的地方定义这个变量,因为你已经创建了一个新的类型,一个新的结构体类型
int main()
{struct abc a3 = { .a = 60,.c = 9.9,.b = 'a' };//初始化结构体变量struct abc* address = &a3;//创建一个名字为address的struct abc类型的结构体指针来储存a3这个结构体变量的地址printf("%d %c %f\n", address->a,address->b,address->c);//通过->访问地址的方式将内容打印出来
}

 

 2.4结构体的自引用

2.4.1错误的自引用

#include<stdio.h>
struct abc
{int a;struct abc next;//结构体里存放一个结构体
};

小明觉得在结构体里存放一个结构体,这样子创建出多个结构体后,就能将多个结构体存放到一个结构体中,一环扣一环,通过一个结构体访问所有的结构体,这听上去好像还有几分道理,但你要明白,这个结构体里面,还有一个这个类型的结构体,而这个被包含的结构体里面也有,被包含的结构体里面的那个结构体也有......子子孙孙无穷尽也,可以类比成死递归,没有人知道什么时候穷尽,所以计算机该给它分配多少个字节的空间,计算机根本不知道,所以这很显然是错误的。

2.4.2正确的自引用

#include<stdio.h>
struct abc
{int a;struct abc* a1;
//结构体里存放一个结构体的地址,而被存放的结构体也能存放一个结构体的地址
//那么有一个结构体,便可链接所有这个结构体类型的变量
//实现一个访问所有
};

只要你是个地址你所占字节的大小不是4(32位)就是8(64位),因此计算机可以分配给它空间,这个结构体和上面那个错误的结构体最大的区别就是一个存放的是地址,一个存放的是内容

3.结构体内存对齐

话不多说,先上题目

#include<stdio.h>
struct abc
{char a;int b;
};
int main()
{printf("%d", sizeof(struct abc));
}

你不妨猜测一下打印出来的结果是多少,之前没遇到过这种题型的宝子们,答案应该就是5吧,char占一个字节,int占4个字节,瞧瞧多合理啊,但结果是

 

 是8,没想到吧,你的心里现在一定有个大大的疑惑,为什么呢?

这就要说到结构体对齐的问题了,首先我们要知道结构体对齐后长什么样才能够计算出结构体的正确大小

3.1结构体对齐的规则

(1) 第一个成员在与结构体变量偏移量为0的地址处。
(2) 其他成员变量要对齐到某个对齐数的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
VS中默认的值为8
(3) 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
(4) 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

有的小伙伴恐怕不知道什么是偏移量,针对结构体的情况,偏移量就是相对于你所创建的结构体首地址的距离,比方说第一天规则,第一个成员要在偏移量为0的地址处,偏移量为0,那么很显然,就是没偏移嘛,就是在首地址存放。所以第一条规则我们可以直接立即理解为第一个成员放首地址,非常的符合常识。

后面的三条讲解起来太抽象了,这里画个图给大家

假设这是我存放结构体的内存空间,图中的数字为偏移量

 按照第一条规则先把char a放进去,占一个字节的大小,那么我们接下来放int,根据第二条规则,int类型的大小为4,相比vs默认的8较小,所以它的对齐数应为4,故我们得找到偏移量为4的倍数的位置,并在那开始存储int b,因此4,5,6,7偏移量的位置就被int b所占据了,1,2,3则是被用来被浪费掉了,故打印出来的结果是8

3.2练习

#include<stdio.h>
struct abc
{char a;int b;char c;
};
int main()
{printf("%d", sizeof(struct abc));
}

第一步,将char a放进首地址即偏移量为0所在的空间,占了1,下一个空间的偏移量为1,因int型的对齐数为4,故对齐到偏移量为4时存储,存放4个字节,4,5,6,7的空间被占据,故最后char c会存放在偏移量为8的位置,空间大小好像是9个字节,但别忽略了第三条,结构体总大小为最大对齐数的整数倍,这里的最大对齐数,显然是4,故结构体大小应为12

 最后来一道复杂点的

#include<stdio.h>
struct abc
{char a;int b;char c;
};
struct xyz
{char x;float y;struct abc xyz;
};
int main()
{printf("%d", sizeof(struct xyz));
}

 首先将char x放进偏移量为0所在的空间,y的对齐数为4,故从偏移量为4的位置存储,偏移量为4,5,6,7的空间被float y使用,接着是结构体abc类型的存储,根据第四条,结构体在存储之前也得先对齐,观察abc,发现abc的最大对齐数为4,刚好我们此时的位置就是偏移量为8的位置,是4的倍数,故直接存储即可。先将char a存放进去,偏移量为8的空间被占据,再存放int b,此时,到了偏移量为9的空间,先对齐到偏移量为4的倍数的位置,即12,故12,13,14,15的空间被int b占据,偏移量为16的位置被char c占据,结构体xyz的空间似乎已经确实,为17,但根据第四条规则,结构体的大小,为所有最 大对齐数的整数倍,即4的倍数,为20

4.拓展:结构体实现的位段操作

4.1什么是位段

位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int(其实char也可以,因为字符型在内存中的存储使用的是ASCII码值的形式,可以这样理解,一个一个的字符,为一个一个的数)
2.位段的成员名后边有一个冒号和一个数字。

#include<stdio.h>
struct abc
{int a : 3;
//冒号后面的数字代表的二进制位的数量,这里可看作用3个二进制位存储整型aint b : 20;int c : 20;
};
int main()
{printf("%d", sizeof(struct abc));
}

试想一下,这里打印出来的结果是多少,存储int a我们用了3个二进制位,存储int b用了20个二进制位,存储int c也用了20个二进制位,那么一共用了43个二进制位(bit位),而一个字节为8个bit位,那么是不是就用了6个字节来存储这个结构体呢?

 在vs上,a用了3个二进制存储,b用了20个二进制位存储,此时还没达到32bit位,但是当把c用20个二进制位存储的时候就会发现,超出一个整型的大小了,vs采用的方式便是再开辟一个整型的空间给你存放这个c,之前剩下的就不要了。但由于c语言对于位段粗糙的定义,导致在不同的编译器有不同的实现,有的编译器秉承着不浪费的原则,先用完之前剩下的空间再开辟,所以位段的使用尽量不要跨平台。要注意的一点:先位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟。在vs上只有char时按1个字节开辟,当既有char又有int或者只有int的时候按4个字节开辟。

4.2位段有什么用?

聪明的小伙伴恐怕早就想到了,3这个数字,我用2个bit位就能够存放,但是我创建了一个整型,我就用了32个bit位去存放,因此,位段可以很好的节省空间。

4.3.位段的跨平台问题

(1)int 位段被当成有符号数还是无符号数是不确定的。
(2) 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题。
(3) 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
(4) 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的。

二、枚举类型

1.枚举类型的定义

#include<stdio.h>
enum sex
{male,female,no
};//这里就定义了一个关于性别的枚举类型,它会按照从0开始的顺序给male,female,no赋值
int main()
{int arr1[no] = {9,7};//值得再次强调的一点,它定义出来的是常量(而非常变量),可以用在数组中printf("%d %d %d", male, female, no);
}

 当然你也可以自己给它们赋值,不用根据默认的来

#include<stdio.h>
enum sex
{male=3,female,no=99
};//这里就定义了一个关于性别的枚举类型,它会按照从0开始的顺序给male,female,no赋值
int main()
{int arr1[no] = {9,7};//值得再次强调的一点,它定义出来的是常量(而非常变量),可以用在数组中printf("%d %d %d", male, female, no);
}

2.枚举的优点

1. 增加代码的可读性和可维护性
2. #define 定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量

三、联合体类型

1.联合体类型的定义

这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

union abc
{int a;int b;char c;
};
int main()
{union abc x;x.a = 20;x.b = 10;x.c = 'a';printf("%d %d %c\n",x.a,x.b,x.c);printf("%d\n", x.a);printf("%d\n", x.b);
}

由此可知,改变联合体上的一个数就会导致牵一发而动全身的效果 

2.联合体类型的特点

(1)牵一发而动全身

(2)所有的数据都从同一个地址开始存储

union abc
{int a;int b;char c;
};
int main()
{union abc x;x.a = 20;x.b = 10;x.c = 'a';printf("%p %p %p\n", &(x.a),&(x.b),&(x.c));
}

 

今天的分享到这里就结束了,感谢各位友友的来访,祝各位友友前程似锦O(∩_∩)O

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

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

相关文章

Ansible 自动化运维工具(完善版)

目录 Ansible概述 Ansible特点 Ansible应用 1、使用者 2、Ansible工具集合 3、作用对象 Ansible的搭建 环境 ansible主机 1、ansible 2、Ansible-doc Ansible模块 1.command模块 2.shell模块 3.raw模块 Ansible概述 Ansible是最近非常火的一款开源运维自动化工具…

最全的ubuntu20.04上安装nvidia和cuda

文章目录 一、环境要求二、查询推荐安装的驱动版本三、安装470四、查看显卡驱动是否成功五、安装对应版本的cuda 官方路径 一、环境要求 ubuntu20.04如果之前有过驱动应该先卸载 sudo apt-get --purge remove nvidia* sudo apt autoremove# 卸载cuda sudo apt-get --purge r…

Oracle通过函数调用dblink同步表数据方案(全量/增量)

创建对应的包&#xff0c;以方便触发调用 /*包声明*/ CREATE OR REPLACE PACKAGE yjb.pkg_scene_job AS /*创建同步任务*/FUNCTION F_SYNC_DRUG_STOCK RETURN NUMBER;/*同步*/PROCEDURE PRC_SYNC_DRUG_STOCK(RUNJOB VARCHAR2) ; END pkg_scene_job; /*包体*/ CREATE OR REPL…

Hudi基础知识讲解

Hudi概述 Hudi是一种数据湖的存储格式&#xff0c;在Hadoop文件系统之上提供了更新数据和删除数据的能力以及消费变化数据的能力。支持多种计算引擎&#xff0c;提供IUD接口&#xff0c;在 HDFS的数据集上提供了插入更新和增量拉取的流原语。 基础架构图 Hudi特性 ACID事务能…

用户案例 | Apache DolphinScheduler 离线调度在自如多业务场景下的应用与实践

用户案例 | 自如 随着自如业务的快速发展&#xff0c;不断增长的调度任务和历史逾万的存量任务对平台稳定性提出了更高的要求。同时&#xff0c;众多非专业开发人员也需要一种更为“亲民”的调度平台使用体验。 如何满足这些日渐凸显的需求对自如大数据平台的开发团队来说&am…

2023年最全最新的学习课程合集

WEB前端入门&#xff1a;从零开始做网站。 完成所有课堂练习就可以做出自己的作品&#xff0c;并掌握数据库和了解开源项目。 这些课程涵盖了HTML、CSS和JavaScript等前端技术&#xff0c;以及与之相关的设计原则和最佳实践。 前端课程的目标是培养学生在网页开发方面的技能…

冒泡排序模拟实现qsort()函数

冒泡排序模拟实现qsort函数 前言1. 分析2. 解决一&#xff0c;如何接受不同数据3. 解决二&#xff0c;如何实现不同数据的比较4. 解决三&#xff0c;如何实现不同数据交换5. 模拟bubble_sort&#xff08;&#xff09;函数排序整型所有代码实现6. 结构体排序实现7. 结尾 前言 要…

应急管理大屏助力暴雨天气下的水灾防范

随着气候变化和城市化进程的加剧&#xff0c;暴雨天气引发的水灾风险日益凸显。在面对这种自然灾害时&#xff0c;如何高效、及时地应对、减轻损失成为了当务之急。水灾应急管理平台的可视化大屏为相关部门和决策者提供了实时、全面的信息展示和决策支持&#xff0c;大大提升了…

逻辑(css)-背景网格制作(linear-gradient)

目录 linear-gradient需求实现 linear-gradient 语法&#xff1a;linear-gradient([direction], color-stop1, color-stop2, ...) 第一个参数为(可选)方向参数&#xff0c;可以是度数也可以是方位名词,方向与读书的关系如下&#xff1a; 角度方位文字说明示例0degto top从下…

左神算法之中级提升(2)

目录 [案例1】 【题目描述】 【思路解析1】 【思路解析2】 【代码实现】 【案例2】 【题目描述】 【思路解析】 【代码实现】 【案例3】 【题目描述】 【思路解析】 【代码实现】 【案例4】 【题目描述】今日头条2018面试题 第四题 【输入描述】 【思路解析】 【…

零拷贝是如何实现的

零拷贝是如何实现的 零拷贝&#xff08;Zero-copy&#xff09;是一种优化技术&#xff0c;用于在数据传输过程中减少数据的拷贝次数&#xff0c;从而提高数据传输的效率和性能。传统的数据传输涉及多次内存拷贝操作&#xff0c;而零拷贝通过减少或避免这些拷贝操作来实现性能优…

​LeetCode解法汇总​979. 在二叉树中分配硬币

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣 描述&#xff1a; 给定一个有 N 个结点的二叉树的根结点 root&#xff0c;树中的每个结点上都对应…