当前位置 博文首页 > dongbo_aaa的博客:自定义类型:结构体、枚举与联合

    dongbo_aaa的博客:自定义类型:结构体、枚举与联合

    作者:[db:作者] 时间:2021-08-07 12:59

    结构体

    1、结构体的声明

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

    结构体也是一种类型。

    结构的声明:

    struct tag   //struct 关键字     tag 名称(标签)(可以但不建议省略)
    {
    	member_list;     //成员不能为空(C语言中)
    }variable_list;      //可以省略

    举个例子:

    struct Stu     //描述一个学生
    {
    	char name[20];
    	int age;
    	char sex[5];
    	char id[20];
    };  //注意:分号不能丢

    特殊的声明:

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

    比如:

    struct
    {
    	int a; 
    	char b;
    	float c;
    }x;
    struct
    {
    	int a;
    	char b;
    	float c;
    }a[20],*p;     //这两个都为匿名结构体类型,省略了结构体标签(tag)
    但若在上面的基础上,
    p=&x;

    合法吗?

    答案是非法的,,,因为编译器会把上面的两个声明当成是完全不同的两个类型。

    2、结构的成员

    结构的成员可以是标量、数组、指针、甚至是其他结构体。

    结构体成员的访问:

    结构体变量访问成员是通过点操作符(.)访问的。

    如上述的结构体:

    struct S s;
    strcpy(s.name,"zhangsan");
    s.age = 20;

    但有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针,那么则应该这样访问:

    struct S
    {
    	char name[20];
    	int age;
    }s;
    
    void print(struct S* ps)
    {
    	printf("name=%s  age=%d\n", (*ps).name, (*ps).age);
    	printf("name=%s  age=%d\n", ps->name, ps->age);
    }

    3.结构的自引用

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

    例如:

    typedef struct Node
    {
    	int date;
    	struct Node* next;
    }Node;

    结构的不完整声明:

    若两个结构体互相包含,且要正常使用,则需要这样:

    struct B;
    struct A
    {
    	int _a;
    	struct B* pb;
    };
    struct B
    {
    	int _b;
    	struct A* pa;
    };

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

    定义的时候有两种方式:

    ①声明类型的同时定义变量; ?②直接定义结构体变量;

    struct Point     //①
    {
    	int x;
    	int y;
    }p1;
    struct Point p2;    //②

    初始化:定义变量的同时赋初值

    struct Point p3 = { x, y };
    
    struct Stu    //类型声明
    {
    	char name[15];
    	int age;
    };
    struct Stu s = { "zhangsan", 20 }; //初始化

    结构体还可以嵌套初始化:

    struct Node
    {
    	int date;
    	struct Point p;
    	struct Node* next;
    }n1 = { 10, { 4, 5 }, NULL };
    
    struct Node n2 = { 20, { 5, 6 }, NULL };

    5.结构体内存对齐

    首先我们要知道为什么要存在内存对齐?

    ①平台原因(移植原因):

    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

    ②性能原因:

    数据结构(尤其是栈)应该尽可能的在自然边界上对齐。

    原因是要访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问只需要一次。

    结构体的对齐规则(重要):

    1.第一个成员在与结构体变量偏移量为0的地址处(所以结构体成员的第一个元素你需要内存对齐,默认对齐);

    2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 ? ? 对齐数:编译器默认的一个对齐数与该成员大小得到较小值; ? ? ? ? ? ? ? ? ? ? ? ?VS中默认为8 ? ? ? Linux中默认为4

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

    4.如果嵌套了结构体,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小为所有最大对齐数的整数倍。

    当我们了解了结构体的内存对齐后,就要来计算结构体的大小了:

    来看几个例子:

    //练习1
    	struct S1
    	{
    		char c1;   //偏移量为1		
    		int i;     //大小为4,所以需要对齐,所以总大小为1+3+4		
    		char c2;   //大小为1,不需要对齐   1+3+4+1
    	};             //所以总大小为最大对齐数的倍数: 12	
    		printf("%d\n", sizeof(struct S1));
    //练习2
    	struct S2
    	{
    		char c1;
    		char c2;
    		int i;
    	};                //这次的结果是8      只是变换了一下成员的位置,结果就不一样,正好说明了结构体的对齐规则
    	printf("%d\n", sizeof(struct S2));     
    //练习3
    	struct S3
    	{
    		double d;
    		char c;
    		int i;
    	};               //按照规则,结果为16
    	printf("%d\n", sizeof(struct S3));

    下面来一个复杂的,结构体嵌套:

    //练习4
    	struct S4
    	{
    		char c1;         //1+3
    		struct S3 s3;    //由上可知   16
    		double d;        //8
    	};                       //∴  1+3+16+8=28    但要满足总大小为最大对齐数的倍数  ∴结构体总大小为32
    	printf("%d\n", sizeof(struct S4));

    总的来说:结构体的内存对齐就是用空间来换取时间。

    扩展:

    #pragma pack()

    这个可以用来修改编译器默认的对齐数;

    括号中只能是1、2、4、8; 若括号中什么都不写,表示恢复默认。

    结构体在传参的时候不发生降维;且尽量不要直接传结构体变量,而应该传结构体指针。

    位段

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

    ①位段的成员必须是int,unsigned int,signed int。

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

    如:

    struct A
    	{
    		int _a : 2;
    		int _b : 5;
    		int _c : 10;
    		int _d : 30;
    	};     //因为它的单位是比特位,所以它的大小是8

    位段的内存分配:

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

    ②位段的空间上是按照需要以4个字节(int)或者一个字节(char)的方式来开辟的;

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

    位段的跨平台问题:

    ①int位段被当成是有符号数还是无符号数是不确定的;

    ②位段中最大位的数目不能确定;

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

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

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

    枚举

    枚举其实就是把可能的取值一一列举;

    这里就不介绍枚举了,我们来看看它的优点吧:

    ①增加代码的可读性和可维护性;

    ②和#define定义的标识符比较枚举有类型检查,更加严谨;

    ③防止了命名污染(封装)

    ④便于调试;

    ⑤使用方便,一次可以定义多个常量。

    联合(共用体)

    联合其实也是一种特殊的自定义类型,它定义的变量也包含一系列的成员,

    最大的特点就是这些成员共用同一块空间;?

    那么一个联合变量的大小,就至少是最大成员的大小;

    联合大小的计算:

    ①联合的大小至少是最大成员的大小;

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

    这里我们看一个联合和结构体的巧妙使用:

    union ip_addr
    	{
    		unsigned long addr;
    		struct
    		{
    			unsigned char c1;
    			unsigned char c2;
    			unsigned char c3;
    			unsigned char c4;
    		}ip;
    	};
    	union ip_addr my_ip;
    	my_ip.addr = 176238749;
    	printf("%d.%d.%d.%d\n", my_ip.ip.c4, my_ip.ip.c3, my_ip.ip.c2, my_ip.ip.c1);
    可以将long类型的IP地址转化为点分十进制的表示形式。


    cs