【C进阶】⑧自定义类型:结构体、枚举、联合体 超详细的总结

一、结构体

1. 什么是结构体?

  • 结构体是一些值的集合,这些值被称为成员变量;结构体中的每个成员变量可以是不同类型的变量;
    例如:
  • 数组是一组相同类型的元素的集合;
    故:
  • 结构体也是一些值的集合,结构体的每个成员可以是不同类型;

2. 结构的声明

struct tag
{
	member -list	
}variable -list;
例如:生活中,对象是复杂的;
对于一本书而言:
一本书含有:书名+作者+出版社+定价+书号等等;
一个学生:学号+姓名+电话+性别等等;
这些,书和学生就是一种结构体;
struct Book
{
	char name[20];
	int price;
	char id[12];	
}b3,b4,b5;  //b3,b4,b5是一个全局的结构体变量
//使用结构体类型
int main()
{
	struct Book b1;
	struct Book b2;
//b1,b2是局部结构体变量;
	return 0;
}
  • b1,b2,b3,b4,b5都是结构体变量;不同点在于:b1,b2是局部结构体变量;b3,b4,b5是一个全局的结构体变量;

3.特殊的结构体声明

3.1在声明结构体的时候,可以不完全声明;
例如:
struct 
{
	char name[2] ;
	int price;	
}S;
//这表示匿名结构体类型,这声明了一个含有两个成员变量的结构体对象S;

4.结构体的自引用

struct Node
{
	int data;
	struct Node* next;	
},结构体内包含结构体;

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

//定义结构体Point,它含有两个成员变量;
//p1,p2,是结构体的对象,表示p1,p2有结构体Point的属性,即p1,p2都含有两个成员变量;
struct Point
{
	int data;
	int price;
}p1,p2;
int amin()
{
	struct Ponit p3 = {2,20};
	printf("%d %dn",p3.datd,p3.price);	
}

6.结构体内存对齐

规则:

  1. 结构体的第一个成员放在结构体变量在内存中存储位置的0偏移处开始;
  2. 从第2个成员往后的所有成员,都放在一个对齐数(成员变量大小和默认对齐数的较小值)的整数倍的地址处;
    注:vs的默认对齐数为8,Linux系统没有默认对齐数;
  3. 结构体的总大小是结构体的所有成员的对齐数中最大的那个对齐数的整数倍;
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含有嵌套结构体的对齐数)的整数倍;
  • 图解:
    在这里插入图片描述
猜猜看,下面的结构S的大小是多少?
struct S
{
	char c;
	int i;
	char b;
}s;
int main()
{
	struct S s = {0};
	printf("%d",sizeof(s));
	return 0;
}

在这里插入图片描述

答案是12;为什么呢?
结构体中:char占一个字节,int占四个字节,那么总共不应该是占六个字节吗?为什么是12个字节呢??
  • 图解:
    在这里插入图片描述
结构体S4,S5占多大空间呢?
struct S4
{
	double d;
	char c;
	int i;
};
struct S5
{
	char c1;
	struct S4 s4;
	double d;
};

在这里插入图片描述
答案是S4占16字节,S5占32字节;
图解:
在这里插入图片描述
在这里插入图片描述

7.为什么要内存对齐

大部分参考资料都是这样解释:
  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某写硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常;
  2. 性能原因:数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问;
  • 总结:结构体的内存对齐是拿空间来换取时间的做法;
    故:我们在设计结构体的时候,我们既要满足对齐,又要节省空间,所以:
    让占用空间小的成员尽量集中在一起;
如:
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};
这两个结构体成员变量都相同,但因为顺序不同,导致所占内存空间也不同;
前者占12字节,后者占8字节;

8.修改默认对齐数

#pragma pack(所修改的对齐数)
#pragma pake()恢复默认对齐数
如:
#pragma pack(2)
struct s
{
	char c1;
	int i;
	char c2;
};
#pragma pack()
int main()
{
	printf("%dn",sizeof(struct s));
	return 0;
}

9.百度笔试题:

写一个宏,计算结构体中某个变量相对于首地址的偏移,并给出说明;

考察:offsetof宏的实现
库函数:
offstetof(type,member)
之后在预处理这一章我将会详细解剖宏,欢迎来观看此处便不再叙述;

10.结构体传参

struct S
{
	int data[1000];
	int num;
};
//结构体传参
void printf1(struct S s)
{
	printf("%dn",s.num);
}
void pritf2(struct S* ps)
{
	printf("%dn",ps->num);
}
int main()
{
	struct S s= {{1,3,2,4},1000};
	printf1(s);//传结构体
	printf2(&s);//传结构体地址	
	return 0; 
}

注:

  • 当结构体非常大的时候,我们传结构体地址时就比较节省内存,所以我们一般都传递结构体的地址;
  • 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销;
    如果传递的一个结构体对象的时候,结构体过大MC俺叔压栈的系统开销比较大,所以会导致性能的下降;
  • 总结:结构体传参,传结构体的地址

11.位段

11.1 什么是结构体位段

位段的声明和结构是类似的,有两个不同:
	1.位段成员必须是int, unsigned int, 或signed int;
	2.位段的成员名后边有一个冒号和一个数字;
如:
struct A
{
	int _a:2;
	int _b:5;
	int _c:10;
	int _d:30;
} ;//A是一个位段类型
int main()
{
	printf("%dn",sizepf(struct A));
	return 0;
}

struct A
{
		//首先内存开辟一个int大小的内存空间
	int _a:2;  //表示_a成员占2个bit位
	int _b:5;  //表示_b成员占5个bit位
	int _c:10;  //表示_c成员占10个bit位
		//内存空间只剩下15bit,不够保存_d成员;
		//内存继续又开辟一个int类型大小的空间来存放_d成员;
		//共计开辟了8字节的内存空间;
	int _d:30;  //表示_d成员占30个bit位
} ;//A是一个位段类型

12.位段的内存分配

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

13.位段的跨平台问题

  1. int 位段被当成有符号数和无符号数是不确定的;
  2. 位段中的最大位的数目不能确定;(16位的机器最大是16bit,32位机器最大是32bit,写成27,在16位机器上会出现问题)
  3. 位段中的成员在内存中从左向有分配,还是从右向左分配标准未定义;
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的bit位还是利用,这是不确定的;
  • 总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在;

14.位段的应用

应用与网络传输数据时,对数据进行加工形成数据包;
可以使用位段来节省内存大小;

在这里插入图片描述

二、枚举

1. 概念:

枚举顾名思义就是一一列举;把可能的取值一一列举;
如:
//声明枚举类型:枚举类型可能取到的值
enmu Color
{
   RED,         //值为0
   GREEN,   //值为1
   BLUE       //值为2
};
int main()
{
   enum Color c = BLUE;
   return 0;
}

2.枚举的优点

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

三、联合(共用体)

1. 概念:

联合也是一种特殊的自定义类型;
这种类型定义的变量也包含一系列的成员;
特征是这些成员共用同一块空间(所以联合也叫共用体);
如:
union Un
{
	char c;
	int i;
};
int main()
{
	union Un u;
	printf("%pn",&u);
	printf("%pn",&(u.c));
	printf("%pn",&(u.i));
	return 0;
}

由此可知他们共用一个内存空间;
在这里插入图片描述

2.联合的特点

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

3.两种方法判断系统大小端模式

3.1方法一:

int check_sys()
{
	int a = 1;
	int ret = *(char*)&a;
	return ret;
}

3.2方法二:

(使用联合体共用空间的特性)

int check_sys()
{
	union U
	{
			char c;
			int i;
	}u;
		u.i=1;
		return u.c;
	//	返回1 就是小端
	//返回0 就是大端
}
int main(0
{
	int ret =check_sys();
	if(ret == 1)
	{
		printf("小端n");
	}
	else
	{
		printf("大端n");
	}
	return 0;
}

4.联合体的大小计算

规则:
	1.联合体的大小至少是最大成员的大小;
	2.当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍;
如:
union Un
{
	char a[5];
	int i;
};
int main()
{
	union Un u;
	printf("%dn",sizeof(u));
	return 0;
}

在这里插入图片描述

解析:
	a数组的所占内存为5,对齐数为1;
	变量i所占内存为4字节,对齐数为4;
	所以最后所占内存大小为4的最小整数倍,8;

四、最后

在这里插入图片描述

感谢大家的支持?!我们下次见!

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>