自定义类型目录
本文重点(前言)
自定义类型包括结构体,枚举和联合,他们都是自己构造的类型,对于这篇文章我们了解的就是这些自定义类型的用法,以及在内存中占用多少内存。以及这些特点和对我们在编写代码时的帮助。
(1) 对于结构体知识的详细介绍 <1>结构体类型的声明在C语言中我们为了区分不一样的数据,C语言中自己定义了内置类型
int,char,short,long, long long,float,double,有了这些我们在一看到这些类型我们就可以知道这是什么数据,让我们一目了然。
但是如果定义一个比较抽象的东西的时候我们怎么办呢。
比如:
在定义一本书,要介绍书的价钱,作者,书号,出版社,书名等等
在定义一个学生,也要介绍学生的年龄,学号,姓名,身高,等等
但是我们C语言中也没有定义这些东西,这些东西太复杂了,对于编译器来说不太现实,所以我们需要自定义类型,用这些来表示这些复杂对象。
这个就是相当于一个链表的存在,画图解释
我们已经掌握了结构体的基本使用了。现在我们深入讨论一个问题:计算结构体的大小。
计算结构体的大小(内存对齐) 先看一道例题答案:12,8 。现在你心里肯定有点疑问,为什们明明结构体的内容都一样,就是顺序不一样,那为什么这个答案还不一样呢,有些没有基础的小白可能想char是一个字节,int是4个字节,那们加起来不应该是6个字节吗?这个就涉及到结构体的内存对齐。等看完下面内存对齐我相信你一定对上面的题目豁然开朗。
内存对齐解释首先我们先要了解一个函数(),能够反应偏移量的参数,所谓偏移量就是计算机汇编语言,是指把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为“有效地址或偏移量”。
我们先用这个函数看一下第一个结构体每个数据的偏移量然后再画图解释。
(1)第一个成员在与结构体变量偏移量为0的地址处。
(2)其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
vs的对齐数:8
linux环境默认不设对齐数(对齐数是结构体成员的自身大小)
(3)结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
(4)如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
请看下图
我相信大家应该能举一反三了吧,那么s2就自己动手算算吧,下来给大家算一道结构体嵌套的题目。用一下规则4.
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)); return 0; }我相信大家明白了结构体的内存对齐,那为什么要内存对齐呢?
为什么存在内存对齐?
平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
之前我们见过了 #pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。
#include <stdio.h> #pragma pack(1)//设置默认对齐数为8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 int main() { printf("%d\n", sizeof(struct S1)); return 0; }需要解释一下,位段的位是什么意思,其实是二进制位,看完下面代码就知道了
struct S { int a1; int b1; int c1; int d1; }; struct A { int a2 : 2; //位段的位就是二进制位,2表示int a2占2个字节 int b2 : 5; int c2 : 10; int d2 : 30; }; int main() { printf("%d\n", sizeof(struct S));//16 printf("%d\n", sizeof(struct A));//8 //对于为什们是8,我们就看下面的字段内存介绍 return 0; } (2) 位段的内存分配 规则 (1). 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型 (2). 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。 (3). 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。在使用的位段的时候有好多不确定性,就上上面他应该将剩余的空间用完,还是再开辟一个空间用呢。这都是不确定性的,我们看看再vs中他的内存分配。
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; return 0; }int 位段被当成有符号数还是无符号数是不确定的。
位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题。
位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的
所以字段是不确定性的,就像一句话高风险高回报,
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在
通讯录的实现,请看我的下一篇文章《用C实现通讯录》
枚举 1.什么叫枚举枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:一周的星期一到星期日是有限的7天,可以一一列举。 性别有:男、女、保密,也可以一一列举。月份有12个月,也可以一一列举
注意,枚举定义的是常量,不可被修改。
我们可以看到在修改枚举变量时,是不可修改的,说明他是一个常量。
3.枚举的优点 为什么使用枚举?我们可以使用 #define 定义常量,为什么非要使用枚举? 枚举的优点:enum color { red, blue, green, };可能有的人认为这是12个字节,因为这是3整型,那么这就是大错特错了,枚举的定义是什么,是将所有可能的值列出来,但是最后的结果只有一个呀,所以他的大小就是4个字节,一个整型的大小。
联合体(共同体) (1)联合类型的定义联合也是一种特殊的自定义类型,他的关键字是union,这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
如果像下面这个代码,c,i,d的第一个二进制位都是一样的。看图。union Un
{
char c;
int i;
double;
};
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。
因为其共同使用一个空间可以用这个特点判断大小端
如果不知道什么是大小端,请点击这个,是我写的大小端的解释。
例子:
对于这个联合体来说,c的对齐数应该是1,int的对齐数是4,
我们知道c[5],占5个字节,而int只占4个字节,但是最大成员大小是最大对齐数的整数倍
所以应该是8个字节。
对于这些知识点,关键的还是怎么使用,不是你看一遍你就可以知道怎么用了,你需要多多的练习,掌握他的用法,更加熟悉在内存中的存储,我相信大家一定可以克服难关,将这些问题都解决,一起加油!!!😊😊