资讯专栏INFORMATION COLUMN

2021-09-05_user_defined_data_types(自定义数据类型)

liaosilzu2007 / 2272人阅读

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

user_defined_data_types(自定义数据类型)

1,结构体

结构体类型的声明

结构体自引用

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

结构体内存对齐

结构体传参

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


2,枚举

枚举类型的定义

枚举的优点

枚举的使用


3. 联合体

联合类型的定义

联合的特点

联合大小的计算


1,结构体

结构体类型的声明

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

接下来,让我们通过程序来进一步加深我们对结构体的认识

程序一:

#include//声明一个结构体类型//声明一个学生类型,是 想通过 学生类型 来创建  学生变量(对象)// 描述学生 : 属性 + 名字 + 性别 + 年龄 + 电话 struct student// struct  结构体 关键字  student 结构体 标签{	char name[10];	char sex[20];	int age;	char telephone[12];// 这四个变量 就是结构体的成员变量}s4,s5,s6; // 创建 结构体 全局变量// 以上所有 就是一个 结构体类型struct student s3; // 创建 结构体 全局变量int main(){	//创建 结构体变量 的方式	struct student s1;	struct student s2;  // 因为 这两个变量放在 main 函数里,所以是 局部变量	return 0;}

特殊的声明

程序一:

#includestruct          // 缺少一个标签(名字) 这种结构体类型 称为 匿名结构体类型     {             // 那么问题来了,没有名字 该 如何 创建 结构体 变量 ?	char name[10];	char sex[20];	int age;	char telephone[12];}x;// 只有一种方式在结构体末尾分号(;)前面   创建 结构体 全局变量struct          // 缺少一个标签(名字) 这种结构体类型 称为 匿名结构体类型     {             	char name[10];	char sex[20];	int age;	char telephone[12];}* px;//  在 匿名结构体 的 全部变量 px 前面加上 * ,该 匿名结构体类型 变成了 匿名结构体指针类型// 即 * px  是一个结构体指针int main(){	px = &x; // 经过 编译器 编译, 程序报警,该表达式 是 不合法的	//因为 编译器, 会把 它们 当做 2 种 不同类型 来处理	// 所以 两种 不同类型 的数据,是无法进行赋值	return 0;}

1,结构体

结构体自引用

程序一:

#includestruct node{	int data;	struct node* next;// 如果 没有 * 号,也就是数 next 是本身的结构体变量,会导致这个结构体所占内存无限大。    // 这里存地址, 存的是下一个数据的地址:    //结构体自引用 就是 结构体 用指针 找到 与自身同类型的  结构体变量	// 而不是说 结构体自己 包含 结构体自己 的 变量  : struct node next  (error)};int main(){	return 0;}


程序二:

#includetypedef struct node  // 这里的 node 是不能省略的, 要不然 下面 没有这 struct node* next  类型,只能写成 node* next{                    // 但是 node 是 [把 省略了 node 从而 变成 匿名结构体 的 重命名]。 是后有的,	                 //  也就是说 node 还没有生成, 就在结构体 调用它,	                 // 这种写法 是错误的	int data;	struct node* next;}node;   // node : typedef 把 结构体 struct node  简化成 nodeint main(){	node n;	return 0;}


1,结构体

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

程序一:

#includestruct s{	char c;	int a;	double d;	char arr[20];};int main(){	struct s s = { "c", 100, 3.14, "hellworld" };	printf("%c %d %lf %s/n", s.c, s.a, s.d, s.arr);	return 0;}


程序二:

#includestruct t{	double weight;	short age;};struct s{	char c;	int a;	double d;	char arr[20];	struct t st;};int main(){	struct s s = { "c", 100, 3.14, "hellworld", {55.6,30} };	printf("%c %d %lf %s %lf %d/n", s.c, s.a, s.d, s.arr, s.st.weight, s.st.age);	return 0;}

1,结构体

结构体内存对齐: 用来计算结构体 的 内存大小

结构体的对齐规则:

  1. 第一个成员 在与 结构体变量 偏移量为 0 的 地址处(第一个 结构体成员 存储的地址,即结构体第一个结构体变量 存储地址,地址为 0)。
  2. 其他成员 变量 要对齐某个数字(对齐数)的 整数倍 的 地址处

对齐数 = 编译器默认 的 一个对齐数 与 成员大小的 较小值
vs 中 默认的值为 8; gcc 没有默认对齐数(成员的大小,就是对齐数)

比如 结构体里 有一个 成员(变量) 为 整形 int 类型 为 4byte
// 而 vs 中 默认值为 8, 4 < 8, 取 4,
// 那么 该成员的 对齐数 为 4

  1. 结构体总大小为 最大 对齐数(每个成员 的 变量 都有 一个对齐数)的整数倍。
  2. 如果嵌套了结构体的情况,嵌套的结构体 对齐 到自己的 最大 对齐数 的整数倍处,
    // 结构体 的 整体大小 就是 所有 最大对齐数(含嵌套结构体的对齐数)的整数倍。

    程序一:
struct s{	char c1;//  第一个成员 在与 结构体变量 偏移量为 0 的 地址处 (内存所占 1 字节)	int a; //  a 对齐数 是 4 ,因为 其他成员 变量 要对齐 对齐数(4) 的 整数倍 的 地址处(地址4)	// 从 c1(0 地址) 后面开始(从地址 4 开始) 地址 4 处 存放 a,就是说 c1  与 a 之间 隔了 3 个 地址(1,2,3) -> 3 字节	// 此时 a 末尾地址 为 地址8 (因为 a 的存储 需要 4 byte 空间) 	char c2;//  c2 对齐数 1 ; 其他成员 变量 要对齐 对齐数(1)的 整数倍(倍数为 1) 的 地址处  也就是紧跟 a 后面的 地址8,(char 1 byte)存完之后,末尾地址指向 9;	                                                                                            // 无论是地址几,都是 对齐数 1 的倍数	// 至此,1+3+4+1 为 9 字节	// 结构体 总大小 为9 字节,但此时地址,不是 成员中 最大 对齐数(4) 的整数倍,	//  12 满足	// 所以 结构体的大小 最后 为 12 byte};struct s2{	char c1;//  第一个成员 在与 结构体变量 偏移量为 0 的 地址处 (1字节)	char c2;// 对齐数为 1 -> 其他成员 变量 要对齐 对齐数的 整数倍( 倍数为 1 ) 的 地址处(地址1) ,c2 的存储地址 紧跟在 c1 的后面(2 字节)	int a;// 对齐数 4   -> 其他成员 变量 要对齐 对齐数的 整数倍( 倍数为 4 ) 的 地址处(地址4),(c1 是 0 地址,c2 是1 地址,浪费 2,3地址)	//   即 来到 地址4, 也就是 a 的地址,也就是说 a 与 c2  隔了 2 个地址(浪费了2字节空间),a 的 存储  也要 占 4 字节	//  1 + 1 + 2 + 4 == 8 字节 	//   成员中 最大对齐数(4) 的整数倍	//  因为 8 == 2*4  > 6 满足条件	// 所以最后 结构体总大小 为 8};int main(){	struct s s = { 0 };	struct s2 s2 = { 0 };	printf("%d/n", sizeof(s));// 12	printf("%d/n", sizeof(s2));// 8	return 0;}

struct s1附图 :


12 byte ,满足成员中 最大 对齐数(4) 的整数倍, 即 结构体大小 为 12 byte

struct 附图2:




程序二:

#includestruct s3{	double d;// 第一个成员 在 与 结构体 偏移量为 0 的地址处(double 8字节,此时地址 指向地址7)	char c;// 对齐数 1 地址8(9字节)	int i;// 对齐数 4   c 后面的 是 地址9,不满足倍数条件,地址 12 满足(浪费 9,10,11地址,即 3字节空间),即 i 的地址 是 地址12	// i 占 4 字节,	// 8 + 1 + 3 +4 == 16	//  16 满足成员中 最大对齐数(8)的整数倍};int main(){	printf("%d/n", sizeof(struct s3));// 16}

附图:




程序二:

#includestruct s3{	double d;// 对齐数 8  地址 7	char c;// 对齐数 1  地址 8	int i;// 对齐数 4  地址 12    i 存储 需要 4byte ,地址 16	//  结构体大小 16 byte 满足 最大 对齐数(8) 的整数倍	// 故结构体 真正大小 为 16字节};struct s4{	char c1; // 地址0, 对齐数 1	// 嵌套了结构体的情况,嵌套的结构体 对齐 到自己的 最大 对齐数(8) 的整数倍处	struct s3 s3; // 对齐地址 8   然后 结构体 s3 内存大小 为 16 byte	             // 存完16byte之后,地址 24	double d; // 对齐数 8 ,地址24  满足 对齐数 整数倍 地址 	           // 地址 24 到 地址 32 	           //  32 满足 最大 对齐数(8)的整数倍	           //  结构体 变量 s4  内存大小 32 字节 };int main(){	printf("%d/n", sizeof(struct s4));// 32}

附图:




为什么存在内存对齐 : 空间(浪费的) 换取 时间

1. 平台原因(移植原因):不是所有的硬件平台都能访问地址上的任意数据的,

某些硬件平台智能在某些地址处取某些特定类型的数据。否则抛出硬件异常。

2.性能原因:数据结构(尤其是栈)应尽可能地在自然边界sang对齐,原因在于,为了访问 未对齐 的内存,

处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问


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

struct s{	char c1;//  第一个成员 在与 结构体变量 偏移量为 0 的 地址处 (内存所占 1 字节)	int a; //  a 对齐数 是 4 ,因为 其他成员 变量 要对齐 对齐数(4) 的 整数倍 的 地址处(地址4)	// 从 c1(0 地址) 后面开始(从地址 4 开始) 地址 4 处 存放 a,就是说 c1  与 a 之间 隔了 3 个 地址(1,2,3) -> 3 字节	// 此时 a 末尾地址 为 地址8 (因为 a 的存储 需要 4 byte 空间) 	char c2;//  c2 对齐数 1 ; 其他成员 变量 要对齐 对齐数(1)的 整数倍(倍数为 1) 的 地址处  也就是紧跟 a 后面的 地址8,(char 1 byte)存完之后,末尾地址指向 9;	                                                                                            // 无论是地址几,都是 对齐数 1 的倍数	// 至此,1+3+4+1 为 9 字节	// 结构体 总大小 为9 字节,但此时地址,不是 成员中 最大 对齐数(4) 的整数倍,	//  12 满足	// 所以 结构体的大小 最后 为 12 byte};struct s2{	char c1;//  第一个成员 在与 结构体变量 偏移量为 0 的 地址处 (1字节)	char c2;// 对齐数为 1 -> 其他成员 变量 要对齐 对齐数的 整数倍( 倍数为 1 ) 的 地址处(地址1) ,c2 的存储地址 紧跟在 c1 的后面(2 字节)	int a;// 对齐数 4   -> 其他成员 变量 要对齐 对齐数的 整数倍( 倍数为 4 ) 的 地址处(地址4),(c1 是 0 地址,c2 是1 地址,浪费 2,3地址)	//   即 来到 地址4, 也就是 a 的地址,也就是说 a 与 c2  隔了 2 个地址(浪费了2字节空间),a 的 存储  也要 占 4 字节	//  1 + 1 + 2 + 4 == 8 字节 	//   成员中 最大对齐数(4) 的整数倍	//  因为 8 == 2*4  > 6 满足条件	// 所以最后 结构体总大小 为 8};

根据 剖析, 发现 结构体成员相同,但是 让 占用空间小的的成员尽量集中在一起 ,节省很多的空间(也就是不至于浪费很多空间)


修改默认对齐数

之前我们见过了 #pragma 这个预处理指令,这里让我们再次使用,可以改变我们的默认对齐数

程序一:

#include#pragma pack(4)   // 设计 默认对齐数 为 4struct s{	char c1;// 1		// 浪费 3 个字节(原本要浪费 7 个 字节,现在只需 3 个字节)	double d; // 8 byte 对齐 为 对齐数 4,【4与8 选择较小的】 的整数倍 的地址	// 存储 d  需要 8 个字节 加上前面浪费 3 个 和 c1 1个 字节	// 1+3+8 == 12 字节  	// 这样写法,帮我们 避免 了 4 字节 的 空间浪费};#pragma pack() // 取消设置的 默认 对齐数


程序二:

 那 我们把 默认对齐数 设置为 1 呢?#include#pragma pack(1)   // 设计 默认对齐数 为 1struct s{	char c1;// 1	// 一个字节的都不会浪费	double d; // 8 byte 对齐 为(对齐数 1,【1与8 选择较小的】) 的整数倍 的地址	// 存储 d  需要 8 个字节 	// 1+8 == 9 字节  (9 是 最大 对齐数(1)的 整数倍)	// 这样写法,帮我们 避免 了 7 字节 的 空间浪费};#pragma pack() // 取消设置的 默认 对齐数// #pragma pack() 一般设置 默认 对齐数 为 2,4,8, 16   (2的次方数)

百度面试题

写一个宏,计算结构体中某变量 相对于首地址的偏移,并给出说明 (考察 offsetof 宏的实现)

#include#includestruct s{	char c;	int i;	double d;};int main(){	// offsetof 其实是一个宏,用来表示   成员 相对于 结构体 的 偏移量	printf("%d/n", offsetof(struct s, c));// 0	printf("%d/n", offsetof(struct s, i));// 4	printf("%d/n", offsetof(struct s, d));// 8	return 0;      //而且 offsetof 的 参数 传的是 一个类型,更加说了 offsetof 是一个宏 }


1,结构体

结构体传参

#includestruct s{	int a;	char c;	double d;};void init(struct s *tmp){	tmp->a = 100;	tmp->c = "w";	tmp->d = 3.14;}void print(struct s tmp)// 传值:如果传递的结构体对象的时候,结构体过大(空间过大),容易导致 参数压栈 的 系统 开销比较大,从而导致系统性能下降。{	printf("%d %c %lf/n", tmp.a, tmp.c, tmp.d);}void print2(const struct s* tmp) // 传址 : 最好使用这种方法(节省空间),一个地址在操作系统不改变的情况,永远都是4个字节大小,{       // const 是为了防止 意外改变 结构体变量地址 指向的 值	printf("%d %c %lf/n", tmp->a, tmp->c, tmp->d);}int main() {	struct s s = { 0 };	init(&s);	print(s);	print2(&s);	return 0;}


1,结构体

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

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

1. 位段 的 成员 必须是 int,unsigned int 或 signed int(还可以是 char )

2. 位段 的 成员 后边 有 一个冒号 和一个数字( 数字 <= 32 [有多少位操作系统决定] )

位段 - 位:二进制位

位段的内存分配:

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

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

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


程序一:

#includestruct a  // 按照以下写法写结构体成员, a 已经不是个结构体类型了,而是 一个 位段 类型了// 位段 看见成员 都是 int 类型 ,所以,它 一开始 就创建了 4 byte的 空间{	int a : 2;// 2 这里的意思是: a只需要 2 个比特位(bit)	int b : 5;// 5 这里的意思是: b只需要 5 个比特位(bit)	int c : 10;// 10 这里的意思是:  c只需要 10 个比特位(bit)	int d : 30;// 30 这里的意思是: d只需要 30 个比特位(bit)};// 一共 47 个 bit 位,由已经创建的 4byte 空间来分配空间, 很明显 空间不够 大,只能存入 a,b,c,4 byte 空间 还剩 15 bit   d 放不下,怎么办呢?  在vs 环境中 系统 会舍弃(浪费)剩余的 15 bit 空间 然后, 它再向 空间申请 4 byte(32bit) 空间, 存储 d 需要 30 bt 空间,这 30 bit 的数据 就存入 这个向系统第二次申请 4 byte 的空间里 至此,数据全部存完,剩余的空间就浪费掉了,也就是说 这 位段 的 内存大小为 8 byteint main(){	struct a a;	printf("%d/n", sizeof(a));// 8 byte	return 0;}

附图:


&ensp;

位段的意义:

经过上程序,我们发现 原本 a 需要占 4 byte空间,但是 a 只需要 2 bit 的空间, 这样就形成巨大的空间浪费。

所以 我们通过 位段 这种方式 大大减小了 空间上浪费的问题

(虽然位段也有浪费,但是相比 结构体 浪费的空间要少, b c d 也是同理)



程序一:

#include// vs 环境struct s // 因为下面 成员类型 为 char 类型,所以它 一开始 就准备 了 1 byte 空间{	char a : 3;//  a 要个 3 bit 位, 由提前准备 1 byte 空间 来分配,还剩 5 bit	// 	char b : 4;// b 要 4 bit 位,由提前准备 1 byte 空间 来分配,还剩 1 bit 空间	char c : 5;// c 要 5 bit 位,由提前准备 1 byte 空间 来分配(剩余 1 bit),不够大,	// 舍弃(浪费)掉,再向内存申请 1 byte 的空间 来 存储 c (剩余 3 bit 空间)  	char d : 4;// d 需要 4 bit 位,剩余内存空间(3 bit)不够,把 3 bit 浪费掉(舍弃)	// 再向内存申请 1 byte 空间,来存储 d,(剩余 4 bit 空间)	// 至此 数据全部 存储完毕,剩余的空间 舍弃掉(浪费了)	//  一共向内存 申请了  3 byte 的空间};int main(){	struct s s = { 0 };	s.a = 10;// 1010  因为 a 只有 3 bit 位  所以存入的是 010	s.b = 20;// 10100 因为 b 只有 4 bit 位  所以存入的是 0100	s.c = 3;//  0011  因为 c 只有 5 bit 位  所以存入的是 00011	s.d = 4;//  0100  因为 d 只有 4 bit 位  所以存入的是 0100	return 0;}

附图:

化作 16 进制 0x 22 03 04 (可以通过调试 -> 内存 -> &s 来观察 )


位段 的 跨平台问题

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

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

3. 位段中 的 成员 在内存中 从左向右分配,还是从右向左 分配标准 尚未定义(由编译器决定)。

4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳与 第一个 位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的(由编译器决定)。

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

位段 不能 跨平台 使用




2,枚举: 顾名思义就是 -> 列举(把可能的取值 一 一 列举)

举几个现实生活的例子

1. 一周 的 星期一 到 星期日 是 有限的 7天,可以 一 一 列举

2. 性别有:男、女、保密,也可以 一 一 列举

3.月份有 12 个月,也可以 一 一 列举


枚举类型的定义

程序一:

enum sex// 性别{	// 枚举的可能取值(枚举常量)	male,// 0	female,// 1	secret// 2};enum color // 颜色{	// 枚举的可能取值(枚举常量)	red,// 0	green,// 1	blue// 2};int main(){	enum day d = mon;//枚举赋的值,只能是 枚举的 可能取值	enum sex s = male;//枚举赋的值,只能是 枚举的 可能取值	enum color c = blue;//枚举赋的值,只能是 枚举的 可能取值  blue 在枚举的里面 是 2, 那我们 可不可以这样写 enum color c = 2  ?  答案是不行,右边 2int 类型, 左边的 c 是 enum color 类型	printf("%d %d %d/n", male, female, secret);// 0 1 2	printf("%d %d %d/n", red, green, blue);// 0 1 2  printf("%d/n",sizeof(s)); // 枚举大小为 4 byte ,为什么呢?                               因为 枚举常量 的类型是 整形,                              那 s 就是整形变量,所以输出为 4	return 0;}



程序二:


            
                     
             
               

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/119303.html

相关文章

  • 行业报告 | 2021.10.06 | 研报目录更新

    大家好,我们每天全网搜集各行各业的研究报告,了解一个行业从阅读这个行业的研报开始,今日分享目录如下: 20211006分享目录: 2021抖音电商商家经营方法论白皮书-34页.pdf 2021中国数据智能产业发展研究报告-50页.pdf 2021公益数字化转型-56页.pdf 2021年中国一线城市出行平台调研报告-77页.pdf 2021年中国内容机构(MCN)行业发展研究报告-66页.pd...

    dinfer 评论0 收藏0
  • 《安富莱嵌入式周报》第228期:2021.08.30--2021.09.05

    摘要:论坛下载由于库是不带中值滤波器的,需要自己实现,所以花了点时间制作了一个章节。红色线是波形高斯白噪声均匀白噪声。第版教程发布中文显示章节论坛下载可以直接运行界面效果,也可以使用可以直接编译运行。上位机已经整合主机,下一版发布 往期周报汇总地址:http://www.armbbs.cn/for...

    刘东 评论0 收藏0
  • 行业报告 | 2021.09.06 | 研报目录更新

    摘要:参一江湖只作为内容整理方,仅供学习使用。更多相关报告请查看参一江湖星球。 大家好,我们每天全网搜集各行各业的研究报告,了解一个行业从阅读这个行业的研报开始,今日分享目录如下: 20210906分享目录: 2021中国车险科技创新服务研究报告-45页.pdf 2021年中国家装行业数字化研...

    junfeng777 评论0 收藏0
  • 行业报告 | 2021.09.28 | 研报目录更新

    大家好,我们每天全网搜集各行各业的研究报告,了解一个行业从阅读这个行业的研报开始,今日分享目录如下: 20210928分享目录: 休闲娱乐行业主题研究:本地出行,众彩纷呈-35页.pdf 休闲服务行业海南折扣观察第一期:SKU较少的GDF折扣小幅加大,其他公司相对稳定-14页.pdf 传媒行业2021年中期策略报告:从流量到留量,抓住年轻人、拥抱视频化、提升专业化-30页.pdf 传媒行业深度...

    LancerComet 评论0 收藏0

发表评论

0条评论

liaosilzu2007

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<