浅谈C语言中的自定义类型


前言

这篇文章我们来说说关于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.位段存在的问题


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

上面的的一些我是摘录从网上的,很明确!
我们着重说一下3.4点
就上面的例子而言,我们看到他是8个字节的空间,但是这8个字节怎么使用,在标准上是没有定义的。先使用高地址还是低地址?
第四点,有朋友一定看到了,_a两个比特位,_b五个比特位,他们两个占用一个字节就可以了,但是剩下的这1个比特位是否会使用在_c上这个就是我们不得而知的了

还有一个问题就是,假如说我们给_a给了两个比特位,但是如果向其中存储一个大小超过两个位的数据,他是会溢出截断的,会存储截断之后的数据

3.使用场景

这里我就提一个把:在嵌入式系统中,对寄存器的操作就可以使用到位段的技术。

总结

以上就是我们常见的一些自定义的数据类型啦,第一次写文章,难免有不够完美的地方,难免有出错的地方,希望朋友们可以一起为文章斧正指点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值