C语言自定义类型重难点总结(结构体位段枚举联合)

Posted 燕麦冲冲冲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言自定义类型重难点总结(结构体位段枚举联合)相关的知识,希望对你有一定的参考价值。

一、结构体

1.1 结构体的特殊声明(匿名结构体类型)

struct
{
	int a;
	char b;
}x;

struct
{
	int a;
	char b;
}y;

上面的例子中省略了结构体标签

x = y;//将y中的成员赋值给x中的成员

尽管两个匿名结构体有相同的成员,但是编译器仍然将它们视为不同的两个构造体类型,因为它们是匿名的。

匿名结构体类型只可以用一次,并且无法用其再次创建对象,因为没有标签。

1.2 结构体的自引用

问题引入:一个结构体中是否能包含一个由自身创建的对象?

struct Person
{
	int age;
	struct Person relative;//创建一个亲戚对象
};

上述例子显然不对,可以巧妙地反证。

sizeof(struct Person)

假如成立,那么上述式子是无法计算出这个结构体的大小。

但是我偏要结构体中包含一个由自身所创建的对象,即结构体的自引用,那么该如何实现呢?

struct Person
{
	int age;
	struct Person* relative;
};

区别就在于,这个对象变成了结构体指针。
如若还不能理解,可以参照链表的相关知识进行深入理解。

如果再升一点难度,将结构体自引用与匿名结构体联系起来

typedef struct
{
	int data;
	Node* next;
}Node;

上述代码的意思是:首先创建一个匿名结构体,这个结构体包含一些成员,再为这个匿名结构体重定义,命名为Node。
但是这样写是错误的,因为对于一个结构体是先有成员后再有重定义的名字Node,所以其中的一个成员Node* next是非法的,Node还没有被重定义就被使用了。

解决方案

typedef struct Node
{
	int data;
	struct Node* next;
}Node;

1.3 结构体的内存对齐

问题引入:对于一个结构体,我们如何计算它所占空间的大小。

这个当中就涉及了近几年特别热门的知识点:结构体的内存对齐。

struct s1
{
	char c1;
	int i;
	char c2;
};

问:求这个结构体所占空间的大小?
利用画图软件进行内存分析:

问:为什么存在内存对齐呢?
答:内存对齐可以实现牺牲空间来换取时间利用效率。

1.4 修改默认对齐数

#pragma pack(8)

上述代码意思是设置默认对齐数为8,一般放置于结构体之前。

#pragma pack()

上述代码表示:取消设置的默认对齐数,还原为默认。
一般设置于结构体之后,与设置默认对齐数的代码共同使用。

1.5 结构体传参

传参分为传值和传址两种,传址更适合于结构体传参,因为可以避免因为结构体过大,参数压栈开销大(临时拷贝),所导致的性能降低。

二、位段

2.1 枯燥但不完全枯燥的基本概念

问:什么是位段?

  1. 位段的成员必须是整形家族。
  2. 位段的成员名后有冒号和数字,表示该成员所占比特位大小。
  3. 位段的形式和结构体十分相似,唯一的区别就是成员后面有冒号和数字。
struct A
{
	int a:2;
	int b:5;
	int c:10;
	int d:30;
}

需要注意的是:冒号后的数字16位机器最大16,32位机器最大32

2.2 位段的内存分配

问:上述的结构体A该如何在内存中分配空间呢?

答:位段的空间是按照成员数据类型来开辟的,如果是int就以4个字节,如果是char就以1个字节的方式来开辟的。
那么上述代码,都为int类型,那么先开辟4个字节,a、b、c三者加起来占17个bit,那么还剩15个bit,不够存放d的30个bit,那么这时候就直接舍掉这15个剩下的bit,再开辟4个字节用于存放d,所以这个位段的大小就是8个字节。

2.3 位段的优势与劣势

优势:可以节省空间。
比如用二进制表示性别时,只有三种可能:男、女、保密,那么就只用开辟两个bit位。

劣势:跨平台时可能出现不兼容的问题。
比如万一有的编译器偏要把开辟空间中剩余的比特位也利用起来,而不是舍去。

三、枚举

3.1 枚举的优点

enum Color
{
	RED,
	GREEN,
	BLUE
}

上述代码中RED默认值位0,如果不为其它枚举常量赋予初值的话,就依次往下递增1

问:能用#define定义常量,为什么要使用枚举?

答:好处一:可增加代码的可读性和可维护性。
比如在switch分支中,case所对应的选项可以运用枚举常量,而不是利用单纯的数字或者字符,利用有意义的单词可以让代码通俗易懂,同时也便于调试,还能一次性定义多个常量。
好处二:相较于#denfine定义的常量,枚举常量增加了类型检查

enum Color c = BLUE;
c = 2;//ok?

例如上述情况,第一行代码代表创建一个枚举对象,并为其赋值上BLUE(注意:枚举和结构体不同,枚举常量仅代表其创建的对象的可能取值),BLUE的值在上面的枚举中是2,所以给c赋值上2正确吗?

答:不正确。这样在cpp编译器下会出现类型不匹配的错误,提示为Color类型转换为了int类型。
而#define定义的常量不会出现这样的情况,所以不太利于程序的安全。

好处三:便于调试

好处四:相较于#define可以定义多个常量

四、联合(共用体)

4.1 基本概念

联合的声明举例

union Un
{
	char c;
	int i;
};

联合变量的定义

union Un un;

联合变量的大小计算

sizeof(un);//4

问题引出:联合变量是如何利用内存空间的?

答:联合的成员是共用一块内存空间的,比如上述例子中,c和i共用四个字节。
一个联合变量的大小,至少最大成员的大小。

printf("%p\\n", &(un.i));
printf("%p\\n", &(un.c));

由于这两个成员共用一块内存空间,所以它们所在的地址是相同的,那么上述代码的结果是相同的。

un.i = 0x11223344;
un.c = 0x55;
printf("%x\\n", un.i);

上述代码又涉及到了内存的分布了,所以还是画个图来展示。

4.2 联合大小的计算

  1. 联合的大小至少是最大成员的大小。
  2. 当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
union Un1
{
	char c[5];
	int i;
};
printf("%d\\n", sizeof(union Un1)); 

上述代码的结果就应该是8,而不应该是5,因为最大对齐数是4,而5不是4的倍数。

以上是关于C语言自定义类型重难点总结(结构体位段枚举联合)的主要内容,如果未能解决你的问题,请参考以下文章

c语言篇 +自定义类型(枚举联合结构体)以及位段

C语言学习笔记(15)自定义类型:结构体,枚举,联合

C语言☀️自定义类型(结构体+位段+枚举+联合体)建议收藏

C语言进阶自定义类型详解(结构体+枚举+联合)

C语言用例子一次性讲清楚结构体和联合体区别,xdm看过来!

C语言自定义数据类型:结构体,枚举,联合