自定义类型

Posted L_add

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义类型相关的知识,希望对你有一定的参考价值。

结构体

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

结构体声明

struct Book
{
	//成员变量
	char name[20];
	char author[20];
	short price;
};
//typedef 相当于定义了一个小名
typedef struct nub
{
	//成员变量
	char name[20];
	char author[20];
	short price;
}nub;
int main()
{
	struct Book b1 = { "C语言程序设计", "谭浩强", 45 };
	nub n1 = { "hah", "llll", 90 };

	return 0;
}

匿名结构体,特点:只能用一次


struct
{
	char c;
	int a;
	short s;
}S;
struct
{
	char c;
	int a;
	short s;
}*ps;

结构体自引用

在结构中包含一个类型为该结构体本身的成员

struct Node
{
	int data;
	struct Node * n;
};

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

struct S
{
	int a;
	int b;
	double d;
};
struct B
{
	char c;
	struct S s;
	short ss;
};
int main()
{
	struct B  b1= { 'a', { 100, 200, 3.14 }, 5 };
	printf("%lf\\n", b1.s.d);
}

结构体内存对齐

结构体在计算大小时,会发生内存对齐
结构体对齐规则:
1、第一个成员在与结构体偏移量为0的地址处
2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数=编译器默认的一个对齐数与该成员大小的较小值
vs中默认是8,Linux默认对齐数是4

//结构体在计算大小时,会发生内存对齐
//offsetof 计算的是结构体成员相对于结构体起始位置的偏移量
#include <stddef.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	struct S1 s1;
	//printf("%d\\n", sizeof(s1));//12
	printf("%d\\n", offsetof(struct S1, c1));//1
	printf("%d\\n", offsetof(struct S1, i));//4
	printf("%d\\n", offsetof(struct S1, c2));//8
	return 0;
}

在这里插入图片描述
3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
4、如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大的对齐数(含嵌套结构体的对齐数)的整数倍

struct S3//16
{
	double d;//8
	char c;//1
	int i;//4
};
struct S4
{
	char c1;//1
	struct S3 s3;//16
	double d1;//8
};
int main()
{
	struct S4 s4;
	printf("%d\\n", sizeof(s4));//32
	return 0;
}

为什么存在内存对齐?
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台上只能在某些地址处取某些特定类型的数据,否则会出现抛出硬件异常
2、性能原因:数据结构(尤其是栈)应尽可能的在自然边界上对齐,原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问只需要一次。
总体来说:
结构体的内存对齐是拿空间换时间
如何在满足对齐时又节省空间?
1、让占用空间小的成员尽量集中在一起

struct S1
{
	char c1;
	char c2;
	int i;
};
int main()
{
	struct S1 s1;
	printf("%d\\n", sizeof(s1));//8
	return 0;

在这里插入图片描述

2、修改默认对齐数

#pragma pack(1)//设置默认对齐数
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//恢复默认对齐数
int main()
{
	printf("%d\\n", sizeof(struct S1));//6
	return 0;
}

结构体传参

struct S
{
	int data[20];
	int num;
};
void Print(struct S s){}
void Print2(struct S* ps){}
int main()
{
	Print(s);//传结构体
	Print2(&s);//传地址
	return 0;
}

函数传参时,参数需要压栈,会有时间和空间上的系统开销。如果传递结构体对象,结构体过大,参数压栈的系统开销比较大,会导致性能的下降
所以,结构体传参是要传地址

结构体实现位段(位端的填充&可移植性)

位段的声明和结构体类似,但有两个不同:
1、位段的成员必须是 int,unsigned int,signed int
2、位段的成员名后面有一个冒号和一个数字

/位段 - 二进制位
//位段是在一定程度上节省空间
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	struct A a1;
	//一次开辟一个整形4个字节
	//4  -》32bit
	//_a - 2
	//_b - 5
	//_c - 10
	//4  ->32bit
	//_d - 30
	printf("%d\\n", sizeof(struct A));//4+4 = 8
	return 0;
}

位段的内存分配
1、位段成员可以是int,unsigned int,signed int ,char(属于整形家族)类型
2、位段的空间上是按需要以4个字节(int)或是1个字节(char)的方式来开辟的
3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
位段的跨平台问题
1、int位段被当成有符号还是无符号是不确定的
2、位段中最大位数不能确定(16为机器最大16,32位是32,写成27,在16位机器会出现问题)
3、位段中成员在内存中从左向右分配,还是从右向左分配标准尚未定义
4、当一个结构体包含两个位段,第二个位段较大,无法容纳于第一个位段剩余的位时,是舍弃还是利用是不确定的
总结:跟结构体相比,位段可以达到同样的效果,可以节省空间,但是有跨平台的问题存在

枚举

枚举—列举,把可能的值一一列举

枚举类型的定义

取值默认从0开始,也可以赋值,一次递增1;

enum RGB
{
	red,
	green,
	blue
};
int main()
{
	printf("%d\\n", Tues);
	printf("%d\\n", red);//0
	printf("%d\\n", green);//1
	printf("%d\\n", blue);//2
	return 0;
}
enum Day
{
	//枚举类型可能取值
	Mon = 3,
	Tues,
	Wed,
	Thus,
	Fri,
	Sat,
	Sun

};
int main()
{
	printf("%d\\n", Tues);//4
	return 0;
}

枚举的优点

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

联合

联合类型的定义

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

union Un//联合类型的声明
{
	char c;
	int i;
};
int main()
{
	union Un u;//联合类型的定义
	return 0;
}

联合的特点

联合的成员是共用一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小

union Un
{
	char c;
	int i;
};
int main()
{
	union Un u;//联合类型的定义
	printf("%d\\n", sizeof(u));//4
	printf("%p\\n", &u);
	//c和i会公用一块空间
	printf("%p\\n", &u.c);
	printf("%p\\n", &u.i);
	return 0;
}

在这里插入图片描述
在同一时间内,联合体内的成员只能使用一个

union Un
{
	char c;
	int i;
};
int main()
{
	union Un u;//联合类型的定义
	u.i = 0x11223344;
	u.c = 0x55;
	printf("%x\\n", u.i);//0x1122334455
	return 0;
}

其中一个成员改变,其他的成员也改变

判断计算机是大端还是小端

int check_sys()
{
	union Un
	{
		char c;
		int i;
	}u;
	u.i = 1;
	return u.c;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\\n");
	else
		printf("大端\\n");
	return 0;
}

联合大小的计算

  • 联合的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍的时候,就对齐到最大对齐数的整数倍
union U
{
	char arr[6];//6
	int i;//4
};
int main()
{
	union U u;
	printf("%d\\n", sizeof(u));//8
	return 0;
}
union U
{
	short arr[7];//14
	int i;//4
};
int main()
{
	union U u;
	printf("%d\\n", sizeof(u));//16
	return 0;
}

以上是关于自定义类型的主要内容,如果未能解决你的问题,请参考以下文章

vs code 自定义代码片段

在片段中创建自定义列表视图时出错所需活动,找到片段

VSCode创建自定义用户片段

在python 3.6中处理自定义编码时遇到类型错误

VSCode自定义代码片段——CSS选择器

将vscode打造成无敌的IDE添加自定义的snippet