C/C++之自定义类型(结构体,位段,联合体,枚举)详解

个人主页点我进入主页

专栏分类:C语言初阶      C语言程序设计————KTV       C语言小游戏     C语言进阶

C语言刷题

欢迎大家点赞,评论,收藏。

一起努力,一起奔赴大厂。

目录

个人主页:点我进入主页

 

1.前言

2.结构体

2.1结构体声明

2.2结构体初始化

2.3结构体的自引用

2,4结构体的内存对齐

 3.位段

3.1什么是位段

3.2位段的内存分配

3.3位段的跨平台性

4.枚举 

4.1枚举声明

4.2枚举的优点

4.3枚举的使用

5.联合体

5,1联合体的声明

5.2联合体的大小

5.3联合体的使用


1.前言

        随着我们深入学习C语言,我们发现单纯的int,char,double,float类型已经不能满足我们的需要了,那C语言是否还有其他的类型呢,事实上还有一类那就是结构体,结构体是我们自己创造的一种类型,它可以包含C语言的所有类型,结构体是什么呢?结构体如何创建?结构体如何初始化?等问题我会给大家详细解析

2.结构体

2.1结构体声明

        对于结构体如何声明,例如我们想创建一个关于学生的信息,包括名字和学号我们可以如下操作:

struct student{
    int num;
    cahr name[50];
};

2.2结构体初始化

        对于结构体的初始化我们可以看如下代码:

#include <stdio.h>
struct student {
	int num;
	char name[50];
};
int main()
{
	struct student s[3] = { {1,"zhansan"},{2,"lisi"} };
	int i;
	for (i = 0; i < 2; i++)
	{
		printf("%d %s\n", s[i].num, s[i].name);
	}
	return 0;
}

        对于结构体的访问我们需要用到“.”或者"->"进行访问“.”就是让面的操作对于“->”就是传址也就是指针我们可以进行如下操作,代码如下:

#include <stdio.h>
struct student {
	int num;
	char name[50];
};
int main()
{
	struct student s[3] = { {1,"zhansan"},{2,"lisi"} },*p=s;
	int i;

	for (i = 0; i < 2; i++)
	{
		printf("%d %s\n", p->num ,p->name );
		p++;
	}
	return 0;
}

2.3结构体的自引用

        对于结构体,还有一种操作就是结构体的自引用,我们还可以理解为结构体嵌套结构体具体的代码如下:

struct student {
	int num;
	char name[50];
};
struct Std {
	struct student std[3];
	int gard;
};

        对于striuct Std类型的变量初始化和struct student类型的相似只是多次操作即可例如s.std[0].num=1;

2,4结构体的内存对齐

        结构体中有一个很有意思的现象,代码如下:

#include <stdio.h>
struct student1 {
	char ch1;
	char ch2;
	int i;
};
struct student2 {
	char ch1;
	int i;
	char ch2;
};
int main()
{
	printf("%d\n", sizeof(struct student1));
	printf("%d\n", sizeof(struct student2));
}

代码输出的结果为

f62c784685694e7c95111a7c38acef0a.png

        问什么会这样呢?我们一般的理解是char占用1个字节,int占4个字节,共占6个字节,这就和结构体的内存对齐有关了 ,首先得掌握结构体的对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么存在内存对齐?

我们可以理解为第一个占0位置,对齐数就是编译器的默认值和成员大小的较小值,偏移量的初始位置为对齐数的倍数,最后所占的字节为最大成员的倍数。 

例如我们第一个结构体进行画图讲解:

 ce1799fbd4da4589b25022f8d8580b5f.png

         ch1占0的位置,ch2的对齐数是1占1的位置,num的对齐数是4占4的位置,共占8个8是4的倍数故占8个字节。

对于对齐数的默认值我们可以用#pragma pack()进行修改,例如#pragma pack(8);

大部分的参考资料都是如是说的:

1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。

总体来说:

结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。

 3.位段

3.1什么是位段

        位段和结构体类似,它的成员是int,unsigned int ,signed int,它的形式是类型 +变量名+: 字节数,它的详细代码可以理解为:

struct num {
	int a : 2;
	int b : 3;
	int i : 30;
};

3.2位段的内存分配

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

 例如如下代码的内存分配:

#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;
}

我们可以进行画图理解:

        由于位段的不确定性所以我我们在一个字节中不知道是占高位还是低位,我们正常思维是占低位,在占低位时可以理解为

0f0d17f8581c4e8a85d6bf95ce61d081.png

        这样第一个字节为01100010为62,第二个字节为00000011为03,第三个字节为00000100为04,真实的储存是不是我们理解的呢?我们进入调试看一看内存

571504a1a14f4bc383e6a33a4b49827d.png

        于是这样就形成了位段,对于位段占几个字节我们可以利用sizeof()进行操作得到它占用几个字节 。

3.3位段的跨平台性

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

位段在信息传输时有很重要i的作用,在这里不做讲解。

4.枚举 

4.1枚举声明

enum s {
	blue,
	red,
	back
};

        枚举和#define一样在上面的代码中blue相当于#define blue 0,red相当于 #define red 1,back相当于#define back 2。难道只能从0开始吗?显然是不可能的,我们应该如何修改?如下:

enum s {
	blue=3,
	red=2,
	back=10
};

4.2枚举的优点

我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量

4.3枚举的使用

枚举的使用主要就是switch case语句中例如

enum s {
	blue,
	red,
	back
};
int main()
{
	int a = 1;
	switch (a)
	{
		case blue:
			; break;
		case red:
			; break;
		case back:
			; break;
	}
	return 0;
}

5.联合体

5,1联合体的声明

联合体声明如下:

union Un1
{
	char c[5];
	int i;
};

5.2联合体的大小

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

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

对于联合体的成员共同占用一个空间,我们可以做一个测试,代码如下:

#include <stdio.h>
union Un1
{
	char c[5];
	int i;
};

int main()
{
	union Un1 u1;
	printf("%p\n", &u1.c);
	printf("%p\n", &u1.i);
	printf("%p\n", &u1);
}

我们运行结果如下:

cb3aa2523a2c448a981860bdaf716658.png

因此我们可以得到联合体存储的方式

6a359a82e91c4669a91410d2317d0e9b.png

 对于如何计算联合体的大小,我们可以看一下代码:

#include <stdio.h>
union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
}

对于Un1我们可以画成a726c0b001d242fd9f0614e7d9ae6a79.png

        对于c占5个字节,i占4个字节,但是c是char类型是1个字节,成员最大的为4,由于需要占最大成员的倍数 故占8个字节。Un2也是同样的操作,short占2个字节,共14个字节,int占4个字节,共占用16个字节。

5.3联合体的使用

        我们知道联合体是一种节省空间存储方式,我们可以把它用在多个不共同使用的多i个结构体创建上大致可以理解为

struct num{

        union u1{

               结构体1;

               结构体2;

                ......

        };

};

今天的内容就结束了,欢迎大家来三连。 

猜你喜欢

转载自blog.csdn.net/Infernal_Puppet/article/details/133720624