自定义类型:结构体、枚举、联合体(C语言系列19)

目录

前言:

1.内置类型

2.自定义类型

3.结构体      

3.1结构体定义

3.2结构体的声明

3.3结构体不完全声明

3.4结构体的自引用

3.4.1链表

3.5结构体变量的定义和初始化

3.6结构体的内存对齐 

3.6.1结构体内存对齐的规则

3.6.2为什么会存在内存对齐数

3.6.3修改默认对齐数

3.7结构体传参 

4.位段

4.1什么是位段

4.2位段的内存分配

4.3位段的跨平台问题

4.4位段的应用

5.枚举

5.1枚举类型的定义

5.2枚举的优点

6.联合体(共用体)

6.1联合体的定义

6.2联合体的特点

6.3联合体大小的计算

结束语:


前言:

之前我们学习了有关于结构体的一些知识,今天小编想与大家分享一些结构体的更深层次的知识,以及枚举和联合体方面的一些知识。

1.内置类型

之前我们学习了一些内置类型我们先来回忆一下,char、short、int、long long、float、double、bool。

2.自定义类型

结构体struct
枚举enum
联合体union

3.结构体      

3.1结构体定义

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

3.2结构体的声明

struct tag
{
    member - list;//成员变量
}variable-list;//全局变量

3.3结构体不完全声明

struct
{
    char book_name[20];
    char unthor[20];
    int price;
    char id[15];
}s1,s2;//匿名结构体类型

注意:如果是匿名结构体类型则只能只用一次。

两个匿名结构体即使成员均相同,但是在编译器看来还是不相同的。如下例子所示:

#define _CRT_SECURE_NO_WARNINGS 1
struct
{
	char book_name[20];
	char author[20];
	int price;
	char id[15];
}sb1;
struct
{
	char book_name[20];
	char author[20];
	int price;
	char id[15];
}*ps;//结构体指针变量
int main()
{
	ps = &sb1;//是非法的
	return 0;
}

 如果是相同的,那么ps = &sb1就可以赋值成功。但是编辑器是认为两个结构体是不相同的,故赋值是不成功的,是非法的。

3.4结构体的自引用

简单来讲就是自己里面包含了自己。

3.4.1链表

在此之前我们先来说一下链表,解释如下图所示:

 在链表中结构体是不可以自引用的:

如下所示:

struct Node
{
    int date;
    struct Node n;//不可以直接自引用
};

正确引用方式:
struct Node
{
    int data;
    struct Node* next;
};

3.5结构体变量的定义和初始化

如下代码中所示:

#define _CRT_SECURE_NO_WARNINGS 1
struct Book
{
	char book_name[20];
	char author[20];
	int price;
	char id[15];
}sb3 = {"C语言","谭浩强","88","PG1000111"},sb4;//全局变量的初始化
struct Book sb5 = { "C语言","谭浩强","88","PG1000111" };//全局变量的初始化
int main()
{
	struct Book sb1 = { "计算机操作系统","汤小丹","66","PG1001110" };//局部变量初始化
	return 0;
}

上面是正常顺序的初始化,我们初次之外还可以使用乱序初始化,如下代码所示:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S
{
	char c;
	int a;
	float f;
};
int main()
{
	struct S s = { .a = 10,.c = 'w',.f = 4.4f };
	printf("%c %d %lf", s.c, s.a, s.f);
	return 0;
}

结果如下所示:

3.6结构体的内存对齐 

3.6.1结构体内存对齐的规则

  • 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处。
  • 从第二个成员开始,要对齐到某一个【对齐数】的整数倍的偏移处。
  • 对齐数:选择结构体成员自身大小和默认对齐数的较小值。
  • 结构体的总大小,必须是最大对齐数,其中最大的对齐数就是最大对齐数。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

其中默认对齐数

  • 在vs编译器环境中:为8。
  • 在Linux环境中:默认不设对齐数(对齐数是结构体成员的自身大小)。 

例子:

代码如下所示: 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	printf("%d", sizeof(struct S1));
	return 0;
}

结果如下所示:
 

解析如下所示: 

3.6.2为什么会存在内存对齐数

平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

性能原因:

数据结构(尤其是栈)应该尽可能的在自然边界上对齐,原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问。

总体来说:
结构体的内存对齐是拿空间来换取时间的做法。

如果不对齐的话:

那么在设计结构体的时候,我们既要考虑满足对齐,又要节省空间,我们就让占用空间最小的成员尽量集中在一起。 

如下所示相同的成员变量,而所占用的空间却不相同。

代码如下所示:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	struct S1 s1;
	printf("%d\n", sizeof(struct S1));
	struct S2 s2;
	printf("%d\n", sizeof(struct S2));
	return 0;
}


结果如下所示:

明显第二个结构体成员变量集中后,空间就比第一个结构体节省了很多!!!

3.6.3修改默认对齐数

设置默认对齐数:
#pragma pack(4

设置默认对齐数为4,其中这个默认对齐数最好是2的倍数。

恢复默认对齐数:
#pragma pack()

代码如下所示:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#pragma pack(2)//设置默认对齐数为2
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};
#pragma pack()//用完后要恢复默认对齐数
int main()
{
	struct S1 s1;
	printf("%d\n", sizeof(struct S1));
	struct S2 s2;
	printf("%d\n", sizeof(struct S2));
	return 0;
}

结果如下所示:

3.7结构体传参 

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。

所以结构体传参的时候,要传结构体的地址。

代码如下所示:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S
{
	int data[1000];
	int num;
};
void print1(struct S s)
{
	printf("%d %d %d %d\n", s.data[0], s.data[1], s.data[2], s.num);
}
void print2(const struct S* ps)
{
	printf("%d %d %d %d\n", (*ps).data[0], (*ps).data[1], (*ps).data[2], (*ps).num);
}
int main()
{
	struct S ss = { {1,2,3},100 };
	print1(ss);
	print2(&ss);
	return 0;
}

结果如下所示:

4.位段

在位段中为了节省空间,不考虑对齐。

4.1什么是位段

位段的声明和结构体是类似的,有两个不同:

  1. 位段的成员必须是int、 unsigned int、signed int或char。
  2. 位段的成员名后边有一个冒号和一个数字。

代码如下所示:

struct A
{
	int _a : 2;//2个字节
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

 其中A就是一个位段类型。

解析:

 在位段中存放数据时存放的是二进制的数据。

代码如下所示:

#define _CRT_SECURE_NO_WARNINGS 1
//位段中如何存放数据
#include<stdio.h>
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;
}

 解析:

4.2位段的内存分配

  • 位段的成员可以是int、unsigned int、signed int或者是char (属于整形家族)类型。
  • 位段的空间是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
  • 位段涉及很多不确定的因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

4.3位段的跨平台问题

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

总结:与结构体相比,位段可以达到相同的效果,但是可以很好的节省空间,但是有跨平台的问题存在。 

4.4位段的应用

会应用于网络通信中,当A给B发消息时此刻内容会被打包成一个数据包。在此过程中就会应用到位段。

5.枚举

5.1枚举类型的定义

枚举顾名思义就是一一列举,把所有可能的取值都一一列举出来。

如下所示:

enum DAY
{
	Mon,
	Tus,
	Wed,
	Thur,
	Fir,
	Sat,
	Sum
};


这些可能取值都是有值的,默认从0开始,一次增加一,当然也可以给赋初始值。

代码如下所示:

#define _CRT_SECURE_NO_WARNINGS 1
//枚举
#include<stdio.h>
enum DAY
{
	Mon,
	Tus,
	Wed,
	Thur,
	Fir,
	Sat,
	Sum
};
int main()
{
	printf("%d\n", Mon);
	printf("%d\n", Tus);
	printf("%d\n", Wed);
	printf("%d\n", Thur);
	printf("%d\n", Fir);
	printf("%d\n", Sat);
	printf("%d\n", Sum);
	return 0;
}

 

结果如下所示:

当给赋初始值时:
代码如下所示:

#define _CRT_SECURE_NO_WARNINGS 1
//枚举赋初始值
#include<stdio.h>
enum DAY
{
	Mon = 2,
	Tus,
	Wed,
	Thur = 7,
	Fir,
	Sat,
	Sum
};
int main()
{
	printf("%d\n", Mon);
	printf("%d\n", Tus);
	printf("%d\n", Wed);
	printf("%d\n", Thur);
	printf("%d\n", Fir);
	printf("%d\n", Sat);
	printf("%d\n", Sum);
	return 0;
}


结果如下所示:

5.2枚举的优点

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

6.联合体(共用体)

6.1联合体的定义

关键字:union

联合体也是一种特殊的自定义类型。

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

union Un
{
	char c;
	int i;
	double d;
};

6.2联合体的特点

联合体的成员是共用同一块空间的,这样一个联合体变量的大小,至少是最大成员的大小(因为联合体至少得有能力保存最大的那个成员)。

代码展示:

#define _CRT_SECURE_NO_WARNINGS 1
//联合体
#include<stdio.h>
union Un
{
	char c;
	int i;
	double d;
};
int main()
{
	union Un un;
	printf("%p\n", &un);
	printf("%p\n", &(un.c));
	printf("%p\n", &(un.d));
	printf("%p\n", &(un.i));

	printf("%d\n", sizeof(union Un));
	printf("%d\n", sizeof(un));
	return 0;
}


结果如下所示:

 

6.3联合体大小的计算

  • 联合体的大小至少是最大成员的大小。
  • 当最大成员不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

代码如下所示:

#define _CRT_SECURE_NO_WARNINGS 1
//联合体的大小
#include<stdio.h>
union Un
{
	char arr[5];//5
	int i;//4
};
int main()
{
	printf("%d\n", sizeof(union Un));//最大是5,但是不是最大对齐数的整数倍,故应该对齐到8
	return 0;
}

结果如下所示:

结束语:

这次小编给大家分享了自定义类型中的结构体,枚举,联合体还有怎么算对齐数和位段的概念,希望对大家有所帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!) 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力敲代码的小白✧٩(ˊωˋ*)و✧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值