0
点赞
收藏
分享

微信扫一扫

自定义类型:结构体,枚举, 联合

佃成成成成 2022-04-27 阅读 141
c语言c++

本章重点:

目录

本章重点:

一:结构体

1,1 结构体的基础知识:

1,2 结构的声明

1.3 特殊的声明

1.4 结构的自引用

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

1.6结构体内存对齐

1.7 修改默认对齐数

1,8结构体传参

二,位段

2,1  什么是位段

2,2 位段的内存分配。

2,3位段的跨平台问题 

2.4 位段的应用

三,枚举(enum)

3,1 枚举类型的定义

3,2 枚举的优点

3,3 枚举的使用

四,联合(共用体)(union)

4,1 联合的定义

4,2 联合体的特点

4,3 联合大小的计算

联合体也是存在对齐地。


一:结构体

1,1 结构体的基础知识:

1,2 结构的声明

例如描述一个学生:

struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}s1,s2; //分号不能丢
//这里的s1,s2 值结构体类型的变量(全局的),后期可以直接使用
//也可以不写。也可以写多个。

1.3 特殊的声明

在声明结构的时候,可以不完全的声明。

//匿名结构体
//如果一个结构体,只想用一次就可以使用匿名结构体来定义。
struct
{
     int a;
     char b;
     float c;
}x;//匿名结构体必须加结构体变量的。
   //在使用的时候直接用变量就行。
   //注意只能使用一次,就只能使用x这一个变量。


struct
{
     int a;
     char b;
     float c;
}a[20], *p;

//如果这里让p = &x; 是不行的,类型不一样。

//匿名结构体的成员一样的时候,但是在编译器看来也是不同的类型的结构体

1.4 结构的自引用

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

struct Node
{
     int data;
     struct Node* next;
};
//通过指针的形式 ,链接下一个节点。





//注意:错误实例:
struct Node
{
 int data;
 struct Node next;
};
//这里不知道sizeof(struct Node) 占多大内存

拓展:

typedef正常使用实例:

typedef struct stu
{
	int data;//数据
	struct stu* next;//指针
}stu;
//把struct stu  重新命名为stu 
//这样在以后的书写的时候就会方便一点

int main()
{
	struct stu n1;
	stu n2;
	//这里的 n1 和 n2 类型是一样的。
	return 0;
}

对于一个匿名结构体也可使用typedef

typedef struct
{
	int a;
	char b;

}stu;
//这样重命名一个匿名结构体后,有了名字就不只是可以使用一次
//可以多次使用了



//上面和下面的是一样的东西。

typedef struct stu
{
	int a;
	char b;

}stu;

注意:

typedef struct
{
	int data;//数据
	stu* next;//指针
}stu;
//这个写法是错误的啊,
//这个就是先有蛋还是先有鸡的问题了



//如果结构体里面有自引用的时候就不能上面方法了
//应该用下面的方法

typedef struct stu
{
	int data;//数据
	struct stu* next;//指针
}stu;

还有一种用法:


typedef struct stu
{
	int a;
	char b;

}stu ,*pstu;
//这里的*pstu 是--> struct stu* 类型的
//意思就是对(struct stu*)结构体指针类型重命名为pstu*

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

有了结构体类型,那如何定义变量,其实很简单

第一种:

struct book
{
	char name;
	float price;
	char id;
}s1, s2, s3={“C语言”,55.5f,"zxf001"};
//这里的s1 ,s2,s3 就是在定义的时候就创建的变量
//这个的s1,s2 ,s3都是全局变量
//也可以在创建的同时给他初始化。

第二种:


struct book
{
	char name;
	float price;
	char id;
};


int main()
{
	struct book s1 = { "C语言", 66.6f, "zxd002" };
	struct book s2;
	struct book s3;
	//可以在使用的时候在去创建变量也可以。


	return 0;
}

结构体嵌套


struct book
{
	char name[100];
	float price;
	char id[100];
};

struct test
{
	struct book a;
	struct test* next;
};

int main()
{
	struct book s1 = { "C语言1", 55.5f,"zxf001" };//一般结构体初始化
	struct test n = { {"C语言2", 66.6f, "zxf002"}, NULL };//结构体嵌套初始化


	return 0;
}

注意事项:



struct book
{
	char name[100];
	float price;
	char id[100];
}s1, s2, s3 = { "C语言1", 55.5f, "zxf003"};

int main()
{
	//s2 = { "C语言2", 66.6f,"zxf002" };
	//注意这里不能用这样的方式来初始化结构体。
	//结构体应该在创建的时候,直接定义。不能先声明再初始化
	//应该在声明的时候顺便初始化

	s2.name = "C语言2";//注意这样定义结构体也是错误的
	//name和id这里都是数组名 数组名隐式转化为指针,这里的指针是指针常量
	//指针常量的指向是不能发生改变的,所以这样初始化数组的时候会会报错
	//说表达式必须有可修改的左值
	s2.price = 66.6f;
	s2.id = "zxd002";//这里id也是同样的

	//正确代码
	strcpy(s2.name, "C语言2");
	s2.price = 66.6f;
	strcpy(s2.id, "zxf002");

	printf("%s\n", s2.name);
	printf("%f\n", s2.price);
	printf("%s\n", s2.id);

	return 0;

}

1.6结构体内存对齐

我们在深入讨论结构体内存大小的时候,就会涉及待结构体内存对齐。

引例:


typedef struct 
{
	char b;
	int a;
	char name;
}s1;
typedef struct
{
	
	int a;
	char b;
	char name;
}s2;

int main()
{
	s1 s;
	printf("%d\n", sizeof(s1));//12

	printf("%d\n", sizeof(s2));//8



	return 0;
}

结构体内存对齐规则

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

2,其他成员变量要对齐到与偏移量等于(对齐数)的整数倍的地址处。

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

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

拓展:

1,offsetof - 宏

练习:

struct S1
{
	char c1;
	int i;
	char c2;
};
printf("%d\n", sizeof(struct S1));//12


struct S2
{
	char c1;
	char c2;
	int i;
};
printf("%d\n", sizeof(struct S2));//8


struct S3
{
	double d;
	char c;
	int i;
};
printf("%d\n", sizeof(struct S3));//16


struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
printf("%d\n", sizeof(struct s4));//32

为什么会存在内粗对齐呢?

1. 平台原因(移植原因):

2. 性能原因:

总体来说:

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

练习:


struct S3//8
{
	char c;//对齐数1
	int i;//对齐数4
};

#include<stdio.h>
struct S4
{
	char c1;//对齐数1
	struct S3 s;//对齐数4(max)
	char d;//对齐数1
};


int main()
{
	printf("%d\n", sizeof(struct S3));//8

	printf("%d\n", sizeof(struct S4));//16
	return 0;
}

struct S3//8
{
	char c;//对齐数1
	int i;//对齐数4
};

#include<stdio.h>
struct S4
{
	char c1;//对齐数1
	struct S3 s;//对齐数4
	double d;//对齐数8
};


int main()
{
	printf("%d\n", sizeof(struct S3));//8

	printf("%d\n", sizeof(struct S4));//24
	return 0;
}

1.7 修改默认对齐数

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

举例:

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认


//#pragma pack(0)
//设置默认对齐数为0,是没有意义的。


#pragma pack(1)//设置默认对齐数为1
//也就是不对齐。
struct S2
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));//12
    printf("%d\n", sizeof(struct S2));//6
    
    return 0;
}

结论:

1,8结构体传参

直接上代码:

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

首选:print2的传参方式(传地址)

结论:



二,位段

结构体讲完就得讲讲结构体实现位段的能力。

2,1  什么是位段

例如:

#include<stdio.h>


struct A
{
	int _a : 2;//代表_a这个成员只占2个bit位
	int _b : 5;//_b这个成员只占5个bit位
	int _c : 10;
	int _d : 30;
};//:数字代表变量所占的bit位的意思。
//位段是通过结构体实现的。
//一共47位  8个字节就可以放得下
//内存对齐是牺牲空间保证效率。
//位段是牺牲效率节省空间的。


int main()
{
	printf("%zd", sizeof(struct A));//8
}

这里a就是一位段,位段可以在满足实际的情况下节省不少空间。但是效率会比结构体低一些

2,2 位段的内存分配。

举例:(以下演示的vs环境下的情况)

 练习:

struct S
{
 char a:3;
 char b:4;
 char c:5;
 char d:4;
};

struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

解析:

通过查看内存也是这样。

2,3位段的跨平台问题 

总结:

2.4 位段的应用

 上面的 4位版本号,4位首部长度------。等等数据是可以用位段来实现的,这样可以大大的节省空间。网络上数据包过大是非常影响这个传输速率的。使用位段效率会高一些。



三,枚举(enum)

枚举顾名思义就是一一列举。

把可能的取值一一列举

生活中:

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

月份有12个月,可以一一列举 这里就可以使用枚举了

这些东西可以使用枚举来列举。

3,1 枚举类型的定义

举例:

enum Sex//性别
{
	male,//男
	female,//女
	secret//保密
};
//枚举常量都是有值的。
//默认数值是012---一直向后排。

int main()
{
	enum Sex s1 = male;
	enum Sex s2 = female;
	enum Sex s3 = secret;

	printf("%d", s1);//0
	printf("%d", s2);//1
	printf("%d", s3);//2

	//枚举变量s1,s2,s3只能是以上列举的值不能是别的值。

	return 0;
}

以上定义的  enum Sex 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。

例如:

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};
//这里就是规定的数值。1 2 4



enum Sex
{
	male = 1,
	female = 3,
	secret 
};
//这里赋值前两个,后面默认加一  1 3 4



enum Sex
{
	male = 1,
	female ,
	sec
};
// 1 2 3

3,2 枚举的优点

为什么使用枚举?我们可以使用 #define 定义常量,枚举的优点?

所以在定义常量的时候如果可以的话建议使用枚举来实现。

3,3 枚举的使用


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

enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;//这种情况会出现强制类型转换,5是int 类型的。
//而clr是enum color 类型的。

四,联合(共用体)(union)

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

4,1 联合的定义

定义:

//联合类型的声明
union Un
{
	char c;
	int i;
};
//可以在;前面直接创建变量。和结构体相同。

int main()
{
	//联合变量的定义
	union Un u;
	//计算一个变量的大小
	printf("%d\n", sizeof(u));//4

	printf("%p\n", &u);
	printf("%p\n", &(u.c));
	printf("%p\n", &(u.i));
	//这三个都是一个地址。

    //说明c和i 共用了第一个字节。
    //为了不造成访问紊乱。
    //在同一时间只会用其中的一个变量。

	return 0;
}

在实际中就会有这样的场景。例如我们在描述学校里面一个人的身份的时候。人有教授,讲师。他是讲师就不是教授,是教授就不可能是讲师。在使用的时候就只能用其中的一个。

4,2 联合体的特点

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

举例:(判断当前机器的大小端存储)

以前写的


int main()
{
	int a = 1;
	if (1 == *(char*)&a)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
    return 0;
}

改进版:


union Un
{
	int a;
	char b;
};

int main()
{
	union Un u;
	u.a = 1;
	if (1 == u.b)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}

	return 0;

}
//这里就是运用了 a 和 b 共用了同用了第一个字节。
//这里可以 typetef  重命名 简化类型。


4,3 联合大小的计算

联合体也是存在对齐地。

举例:


union Un1 //大小为8
{
	char arr[5];//这是最大成员 5 不是最大对数4的整数倍、
	//所以要对齐到4的整数倍 8
	 //数组的对齐数是数组成员地对齐数。
	//这个联合体成员地对齐数就1

	int i;//对齐数是4

};


union Un2  //大小为16
{
	short arr[7];//这是最大成员 14 不是最大对数4的整数倍
	//所以要对齐到4的整数倍 16
	//数组的对齐数是数组成员地对齐数。
	//这个联合体成员地对齐数就2

	int i;//对齐数是4

};

举报

相关推荐

0 条评论