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

Posted 林慢慢i

tags:

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

前言:本章主要内容是C语言自定义类型中的结构体、枚举和联合。

文章目录

结构体

结构

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

结构的声明

struct tag
{
	member - list;
}variable-list;

例如描述一个学生:

struct stu
{
	char name[30];//名字
	int age;//年龄
	char sex[5];//性别
};

特殊的声明(声明结构也可以不完全声明)

例如(省略结构体标签tag):
struct
{
	int a;
	char b;
	float c;
}a;
struct
{
	int a;
	char b;
	float c;
}a[30],*p;

注意:编译器会把上面两个声明当成完全不同的两个类型,所以使用 p=&x 非法。

结构的自引用

例如:
struct Node
{
	int data;
	struct Node* next;
};

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

1.声明类型的同时定义变量p1
struct Point
{
	int x;
	int y;
}p1;
2.定义结构体变量p2
struct Point p2;
3.初始化:定义变量的同时赋初值。
3.1
struct Point p3 = { x , y };
3.2
struct Stu
{
	char name[20];
	int age;
};
struct Stu s = { "zhangsan",20 };
4.结构体嵌套初始化
4.1
struct Node
{
	int data;
	struct Point p;
	struct Node* nest;
}n1 = { 10,{4,5},NULL };
4.2
struct Node n2 = { 20,{5,6},NULL };

结构体内存对齐

掌握了结构体的基本使用后,接下来介绍下如何计算结构体的大小,这就涉及到了一个热门考点:结构体内存对齐。

如何计算?首先得掌握结构体的对齐规则:

1.第一个成员在与结构体变量偏移量为0的地址处。

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数=编译器默认的一个对齐数与该成员大小的较小值。

VS中的默认值是8

3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

例题,计算下列结构体的大小。
struct S
{
	char c1;
	int i;
	char c2;
};

struct S2
{
	char c1;
	int i;
	double d;//8
};


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

struct S4
{
	double d;
	char c;
	int i;
};
struct S5
{
	char c1;
	struct S4 s4;
	double d;
};
#include <stdio.h>
int main()
{
	struct S s = {0};
	struct S2 s2 = { 0 };
	struct S3 s3 = { 0 };
	struct S4 s4 = { 0 };
	struct S5 s5 = { 0 };
	printf("%d\\n", sizeof(s5));//32
	printf("%d\\n", sizeof(s));//12
	printf("%d\\n", sizeof(s2));//16
	printf("%d\\n", sizeof(s3));//8
	printf("%d\\n", sizeof(s4));//16
	return 0;
}

在这里插入图片描述

注释:

以S为例,画下S的图示。其余类似,大家可以自己动手画图分析一波!

在这里插入图片描述

S的c1占用一个字节,浪费三个字节,int从下标4(第五个)开始,占用四个字节,c2占用一个字节,浪费3个字节,累计1+3+4+1+3=12;

S2的c1占用一个字节,浪费三个字节,int从下标4(第五个)开始,占用四个字节,d占用8个字节,累计1+3+4+8=16;

S3的c1占用一个字节,c2占用一个字节,然后浪费两个字节,int从下标4(第五个)开始,占用四个字节,累计1+1+2+4=8;

S4的d占用8个字节,c占用一个字节,然后浪费三个字节,int从下标12(第13个)开始,占用四个字节,累计8+1+3+4=16;

S5的c1占用1个字节,然后浪费7个字节,struct s4从下标8(第九个)开始(此时默认对齐数是8),占用16个字节,double d从下标24(第25个)开始,占用8个字节,累计1+7+16+8=32;

为什么存在内存对齐?

在这里插入图片描述

总结:结构体的内存对齐是拿空间换取时间

那在设计结构体的时候,我们既要满足内存对齐,又想节省空间,如何做到?

答:让占用空间小的成员尽量集中在一起

例题:
struct s1
{
	char c1;
	int i;
	char c2;
};
struct s2
{
	char c1;
	char c2;
	int i;
};

注释:上例中s1和s2类型的成员一模一样,但是s1占了12字节,s2占8字节,s2更具备优势。

修改默认对齐数

1.设置默认对齐数
#pragma pack(n)//默认对齐数为n
2.取消设置默认对齐数,还原为默认
#pragma pack()//取消默认对齐数
例子
#pragma pack(2)//默认对齐数为2
struct S
{
	char c1;//0
	int i;//
	char c2;//
};
#pragma pack()//取消默认对齐数

#pragma pack(1)//默认对齐数为1
struct S
{
	char c1;//1 1 1
	int i;//4 1 1
	char c2;//1 1 1
};
#pragma pack()//取消默认对齐数

int main()
{
	printf("%d\\n", sizeof(struct S));
	return 0;
}

注释:结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。

结构体传参

例题(结构体传参和结构体地址传参)
struct S
{
	int data[1000];
	int num;
};

struct S s = { {1,2,3,4},1000 };

void print1(struct S s)//结构体传参
{
	printf("%d\\n", s.num);
}
void print2(struct S* ps)//结构体地址传参
{
	printf("%d\\n", ps->num);
}
int main()
{
	print1(s);
	print2(&s);
	return 0;
}

注释:对比上面的print1和print2函数,print1的缺点很明显:在函数传参过程中,参数需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能下降。

总的来说,结构体传参的时候,传结构体的地址(指针大小固定4/8)。

位段

什么是位段?

位段的声明和结构是类似的,注意两点不同:

1.位段的成员必须是int、unsigned int或signed int。

2.位段的成员名后边有一个冒号和一个数字。

比如:
struct A
{
	//4个字节 - 32bit
	int _a : 2;//_a 成员占2个bit位
	int _b : 5;//_b 成员占5个bit位
	int _c : 10;//_c 成员占10个bit位
	//剩余15个bit
	//4个字节 - 32bit
	int _d : 30;//_b 成员占30个bit位
};
int main()
{
	printf("%d\\n", sizeof(struct A));//8

	return 0;
}

注释:A就是一个位段类型,大小是8字节。

位段的内存分配原则

1.位段的成员可以是int、unsigned int和signed int或者是char(属于整形家族)类型。

2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟。

3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

例子:
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;
}
思考下上例中空间是如何开辟的?

答:
在这里插入图片描述

位段的跨平台问题

1.int位段被当成有符号数还是无符号数是不确定的。

2.位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)。

3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余位段还是继续利用剩余位段,这点不确定。

总结:

跟结构相比,位段可以达到同样的效果,还可以很好的节省空间,但是存在跨平台的问题。

位段的应用

在计算机网络的信息传输过程中,减小数据包的大小。

在这里插入图片描述

枚举

顾名思义就是把可能的取值一一列举。

比如:

月份有12个月,可以一一列举。

一周有七天,可以一一列举。

性别有男、女,可以一一列举。

颜色也可以一一列举。

这些例子里就可以使用枚举。

枚举类型的定义

星期、性别、颜色枚举类型举例

enum Day
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
enum Sex
{
	MALE,
	FEMALE,
	SECRET
};
enum Color
{
	RED,
	GREEN,
	BLUE
};

注释:以上定义的enmu Day,enum Sex,enum Color都是枚举类型。{}中的内容是枚举类型的可能取值,也叫枚举常量。

枚举常量都是有值得,默认从0开始,一次递增1,当然也可以在定义的时候赋初值。例如:

enum Color
{
	RED=1,
	GREEN=2,
	BLUE=4
};

枚举的优点(明明已经有#define定义常量,为什么非要使用枚举?)

1.增加代码的可读性和可维护性

2.和#define定义的标识符相比枚举有类型检查,更严谨

3.防止命名污染(封装)

4.便于调试

5.使用方便,一次可以定义多个变量

枚举的使用

例子1:这样给clr赋值5可以吗?
enum Color
{
	RED=1,
	GREEN=2,
	BLUE=4
};
enum Color clr = GREEN;
clr = 5;

答:不可以,枚举变量一经过定义赋值,不可再改变。

例子2:更加直观的显示所要实现算法的功能,而非简单的1、2、3、4.
void menu()
{
	printf("*****************************\\n");
	printf("****  1. add    2. sub  *****\\n");
	printf("****  3. mul    4. div  *****\\n");
	printf("****  0. exit          *****\\n");
	printf("*****************************\\n");
}

enum Option
{
	EXIT,//0
	ADD,//1
	SUB,//2
	MUL,//3
	DIV//4
};

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			break;
		case SUB:
			break;
		case MUL:
			break;
		case DIV:
			break;
		case EXIT:
			break;
		default:
			break;
		}
	} while (input);


	return 0;
}

联合(共用体)

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

联合类型的声明、定义和联合类型变量大小计算:

union Un
{
	short s[7];
	int n;
};
Union Un un;
int main()
{
  printf("%d\\n", sizeof(union Un));
  return 0;
}

注释:结构体向int对齐,7个short一共是14字节,对齐后是16字节。n是单独的4字节,由于是union,所以n与s共用空间,只取最长的元素,故占用16字节。

联合的特点

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

判断大小端存储

例题:若位顺序类似小端,低地址在低处,所以39是低地址,在低位,38在高位,结果就是3839;若类似大端存储,则结果是3938
int main()
{
	union
	{
		short k;
		char i[2];
	}*s, a;
	s = &a;
	s->i[0] = 0x39;
	s->i[1] = 0x38;
	printf("%x\\n", a.k);
	return 0;
}

在这里插入图片描述

注释:由输出结果可知位顺序类似小端,低地址在低处,所以39是低地址,在低位,38在高位,所以是3839。

联合大小的计算

1.联合的大小至少是最大成员的大小。

2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

例题:
int main()
{
	union Un1
	{
		char c[5];
		int i;
	};
	union Un2
	{
		short c[7];
		int i;
	};
	printf("%d\\n", sizeof(union Un1));
	printf("%d\\n", sizeof(union Un2));
	return 0;
}

在这里插入图片描述

注释:Un1中,char c[5]至少占用6个字节,最大对齐数4,取最大对齐数的整数倍,结果为8;Un2中,short c[7]至少占用16个字节,最大对齐数4,取最大对齐数的整数倍,恰好结果为16.

自定义类型(结构体+枚举+联合)部分到此介绍结束了,感谢您的阅读!!!如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。

以上是关于自定义类型详解(结构体+枚举+联合)C进阶的主要内容,如果未能解决你的问题,请参考以下文章

C语言自定义数据类型中的结构体,枚举,联合详解

自定义类型详解(结构体,枚举,联合体)

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

自定义类型~结构体~位段~枚举~联合~超详解~一遍就会

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

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