文章目录
前言
这篇文章我们来说说关于C语言中的一些自定义类型,但是呢一些过于基础的东西咋们就不说了,主要说一些我们在学习过程中忽略和容易忽略的点
一、结构体
自定义类型之所以叫做自定义类型,就是因为在C语言中一些自带的类型没有办法满足我们日常中一个个体的描述,比如说我们想要描述一位学生,一本书,我们使用C语言中的自带的数据类型就没有办法对其进行定义了!那我们现在就开始吧!结构体应该是我们最前接触到的自定义类型。结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
1.结构体的定义和特殊定义
下面我就是我们最普遍的结构体的定义。
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
那我们接下来就说一个点吧,就是在定义的时候,我们可以对其进行不完全的声明。
//匿名结构体类型
struct
{
int a;
char b;
float c;
}a;
在这个地方就有一个要注意的点了,假如说我们用同样的结构体类型来定义一个指针,如下:
struct
{
int a;
char b;
float c;
}*ptr;
我们可以看到,我们上面声明的两个结构体其中的类型是完全一致的,但是如果我们这样
ptr = &a;
这样的操作是不可以嗷,编译器会认为这两种结构体类型是完全不一样的两种类型,就会报警告。
2.结构体的自引用
所谓的自引用就是:在结构中包含一个类型为该结构本身的成员,大家看到这里可以思考一下这样可以吗?
struct Node
{
int data;
struct Node next;
};
答案是否定!不可以!
我们来一下为什么不可以,在这个地方我们使用反证法,就是假设他是成立的,看是否能推翻他!
假设我们认为上面这样定义是可以的,那我们请想想
sizeof(struct Node);
这条语句的运算结果是什么呢(不考虑内存对其)?
我们可以看到一个有一个int类型的变量,大小是4个字节,之后有一个struct Node 类型的变量,他的空间是…,在这里我们就发现了,我们想要计算struct Node类型的大小,但是计算他的大小我们有需要知道他的大小,在这里就看到,这是不合理的。
那么我们就说,难道没有别的办法了吗?如果我们真的向要同类型的结构体作为自身的成员变量该怎么办呢?
解决办法如下:
struct Node
{
int data;
struct Node* next;
};
我们可以看到,上面的定义其实就是链表节点元素的定义方法了。因为将其换成一个指针,指针变量的大小是确定的,4个字节或者8个字节
再看一个比较有想象力的问题,我们知道,typedef可以简化结构体类型,加上之前的匿名结构体,就有了下面定义:
typedef struct
{
int data;
Node* next;
}Node;
看一下这样可以吗?我们将这个匿名的结构体变量重命名为Node,之后再使用,用其定义变量。
确实是很有想象力,但其实他是不行的,冷静一想也是很合理的,你本来就是要匿名,但是你有为他重新取一个名字,这样就不合理了嗷!
3.结构体内存对其
结构体的内存对其,我想为其单独写一篇博客,我认为那个还是比较重要的,并且会涉及到一些C++中和类的大小相关的知识,之后文章写完,我会将链接贴在下面,这里我就不细说了。
4.结构体作为函数参数
这个地方其实我觉得只需要我们记住一个结论就行了,结构体在传参的时候,尽量传地址,因为一个结构体如果很大的话,在内存函数栈帧上的开销是比较大的,函数栈帧之后我也会出一篇文章,大家可以看看。
二、枚举
枚举,顾名思义,就是一一列举,出现它是因为我们生活中有一些东西就是可以一一列举的,比如说:血型、性别等等
1.枚举的定义
enum Sex//性别
{
MALE,
FEMALE,
};
枚举的定义和结构体的定义十分相似,但也有一些不同,就是在枚举中,我们是使用 , 对其进行分割的
这样定义的类型叫做“枚举类型”,枚举类型的取值,我们叫做“枚举常量”
这里向大家提一个问题:我们既然可以使用#define 来定义常量,哪为什么还要使用枚举来定义呢?他的有点在哪呢?
这里我们就来说一下吧:
1.#define 定义的常量,在预处理阶段就被替换掉了,而我们的枚举常量是不会被替换的,这样就方便了我们在今后的调试程序
2.他可以提高代码的可读性和降低程序的维护成本
3.他很方便,他可以同时定义多个常量
2.枚举的初始化
enum Sex//性别
{
MALE = 1,
FEMALE = 3,
};
我们在定义枚举的时候,其实是可以为其初始化一个值的如上,这一步是可以没有的,如果没有的话,枚举中的第一个枚举常量就是0,其后的常量每一依次增加1。但我们也是可以向上面为其初始化一个值。
在使用的时候,也有一个要注意的点:
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;
我们只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。不然的话就会报警告了。
三、联合
联合,就是不同的变量,使用同一块空间。
1.定义
不多说~
2.特点
这里我们就看一段代码吧!
union Un
{
int i;
char c;
};
union Un un;
int main()
{
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
//下面输出的结果是什么?
un.i = 0x12345678;
un.c = 0x11;
printf("%x\n", un.i);
}
我们能看到un,i 和 un.c 的地址是相同的,只是在操作的时候,就像指针解引用时候可以控制的空间是一样的。
之后
un.i = 0x12345678;
un.c = 0x11;
这两行,对结构体进行操作,我们可以看到输出的是12345611,就可以更好的理解到操作的是同一块空间了。这个地方为什么是12345611,而不是11345678呢?这个地方就存在一个大小端字节序的问题了,这里我们就不细说了,可以见以后的博文。
3.大小
联合体的大小,我们很容易能想到,他至少也是一个最大成员的大小,但是其中也是有一些小细节。
比如:当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。来个例子:
union Un1
{
char c[5];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
return 0;
}
这里我们看他的最小空间也应该是5个字节,之后再和最大对齐数对齐一下就是4的整数倍,8
这里有一个疑问,为什么最大对齐数任然是4,而不是5呢?其实啊,如果联合体的成员是一个数组,那么这个数组的最大对齐数是其中元素的最大对齐数,也就是1。
四、位段
这个可以是说相比之前的三种类型是最不常用的了,但是很重要,我们也要谈谈
1.定义
位段这个东西,他其实是依附于结构体的,只有用结构体才能实现位段。
他其中的成员是必须是int、unsigned int、 signed int类型
看代码:
struct A
{
int _a:2;
int _b:5;
int _c:30;
};
这个地方,_a是成员变量的名称, 后面的 2、5、30是这个变量的大小,单位是bit,那我们可以看看这个位段的大小是多少?
这个地方呀,其实位段开辟空间是一个字节一个字节的开辟的,并且在不同的编译器下,空间的使用也是不同的,并且他任然是会遵从对齐原则的。
_a _b一个字节、_c四个字节一共五个字节,之后对齐一下就是八个字节了!
2.位段存在的问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
上面的的一些我是摘录从网上的,很明确!
我们着重说一下3.4点
就上面的例子而言,我们看到他是8个字节的空间,但是这8个字节怎么使用,在标准上是没有定义的。先使用高地址还是低地址?
第四点,有朋友一定看到了,_a两个比特位,_b五个比特位,他们两个占用一个字节就可以了,但是剩下的这1个比特位是否会使用在_c上这个就是我们不得而知的了
还有一个问题就是,假如说我们给_a给了两个比特位,但是如果向其中存储一个大小超过两个位的数据,他是会溢出截断的,会存储截断之后的数据
3.使用场景
这里我就提一个把:在嵌入式系统中,对寄存器的操作就可以使用到位段的技术。
总结
以上就是我们常见的一些自定义的数据类型啦,第一次写文章,难免有不够完美的地方,难免有出错的地方,希望朋友们可以一起为文章斧正指点。