• C语言自定义类型:结构体


    结构体

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

    结构体和其他类型基础数据类型一样,例如 int类型、char类型
    只不过结构体可以自定义数据类型进行复杂对象的描述就会使用到结构体

    结构体声明

    结构体声明的语法形式:
    方法一:

    struct book
    {
       char a[20];
       char b[15];  成员列表
       int c;
    };
    int main()
    {
        struct book t;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 这里struct 是一个结构体关键字,book 是结构体标签,可以更改
    • 里面的就是成员变量,可以定义不同类型
    • 最后 { } 后还有 :
    • struct book 就是结构体类型
      t 就是用结构体类型创建的变量

    方法二:

    struct book
    {
       char name[20];
       char id[15];  成员列表
       int price;
    }s1,s2,s3;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 在创建结构体类型的同时,创建变量
    • 方法二 创建的变量是全局变量,
      方法一 的是局部变量

    特殊的结构体声明

    struct    
    {
       int a;
       char b;
       float c; 
    }s;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 声明结构体时,可以不完全的声明,类似于数组的不完全初始化
      省略掉了结构体的名称(标签)
    • 这种结构体声明叫 匿名结构类型但是只能用一次

    例题:
    如果二种匿名结构类型的成员相同,类型是否相同?

    struct
    {   
       int a;
       char b;
       float c;
    }s;
    struct
    {   
       int a;
       char b;
       float c;
    }*pd;3
    
    int main()
    {
       *pd = s;
       二种类型是否相等?
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 上面的成员类型虽然都是一样的
      如果强行使用,不会出现太大的问题
      但是编译器会认为这是二种类型,所以是非法的

    结构体自引用

    • 一个结构体类型里是可以包含另一过结构体类型作为它的成员,但是应该正确的使用

    下面是错误的示范

    struct G
    {
       int f;
       struct G;
    };
    int main()
    {
      struct G s;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这种想想都知道是不可以运行的了,无限循环,因为如果要计算结构体的大小,那大小是多少

    正确的自引用方式:

    struct Node
     {  
         struct Node* next;
     };
    
    • 1
    • 2
    • 3
    • 4
    • 正确的自引用是包含同类型的结构体指针
    • 一般是用来实现链表(不太懂没关系,我也没学到)

    是否可以用匿名结构体进行结构体自引用?

    • 结构体类型是无法通过结构体自身变量进行自引用,只能通过自身类型进行引用
    • 因为匿名结构体只能用一次

    是否可以用 typedef 进行类型重命名后,进行结构体自引用?

    typedef struct
    {
     int data;
     Node* next;
    }Node;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    把匿名结构体,用 typedef 重新命名为 Node,然后进行结构体自引用

    • 也是不行的,因为要产生 Node ,就要先有这个结构体类型,才能进行 typedef 类型重命名
      就跟是先有蛋还是先有鸡的问题

    正确的写法:

    typedef struct Node
    {
     int data;
     struct Node* next;
    }Node;
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    有了上面的结构体类型,接下来就是定义和初始化了

    struct K
    {
    	int a;
    	char b;
    }; 
    struct H
    {
    	int a;
    	char b;
    }pd;  //这个是全局变量
    int main()
    {   // 定义变量的同时,赋初始值
    	struct K b = { 6,'G'};
    	struct H pd = { 66,'A'};
    	printf("%d %c\n", b.a, b.b); //打印
    	printf("%d %c\n", pd.a, pd.b); //打印
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 第一种定义的是局部结构体变量,在赋初始值后打印
    • 第二种定义的是全局结构体变量,也是在赋初始值后打印

    结构体嵌套初始化

    结构体嵌套:指的是结构体里包含结构体

    struct KG
    {
    	int a;
    	char b;
    };
    struct H
    {
    	int a;
    	char b;
    	struct KG q;  // 结构体嵌套
    };
    int main()
    {
    	struct H pd = { 66,'A',4,'J'};//初始化
    
    	printf("%d %c %d %c\n", pd.a, pd.b,pd.q.a,pd.q.b); //打印
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 结构体 H 中包含了 结构体 KG,这种叫结构体嵌套
    • 初始化是通过结构体成员找到结构体的方式

    结构体内存对齐(计算结构体大小)

    这里我们来探讨一下结构体是怎么计算大小?

    struct S
    {
    	char a;
    	int b;
    	char c;
    };
    int main()
    {
    	struct S pd = { 0 };
    	printf("%d\n", sizeof(s));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 正常计算变量大小:6个字节
      char a 1字节
      int b 4 字节
      char c 1 字节

    结果:
    在这里插入图片描述
    为什么结果是 12 呢?
    如果想要正确的计算,首先得掌握结构体的对齐规则:

    • 1 第一个成员在与结构体变量偏移量为0的地址处。
    • 2 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
      对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
    • 3 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
    • 4 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

    图解:
    我们先前算的 6 为什么不正确呢?
    反而结果是 12
    在这里插入图片描述

    在这里插入图片描述

    计算结构体大小练习题

    练习题

    struct S2
    {
     char c1;
     char c2;
     int i;
    };
    printf("%d\n", sizeof(struct S2))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    图解:
    在这里插入图片描述

    练习题 1

    struct S
    {
       double c1;
       char c2;
       int i;
    };
    printf("%d\n",sizeof(struct S));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    图解:
    在这里插入图片描述

    练习题 2
    结构体的嵌套的计算方式

    struct S
    {
    	double c1;
    	char c2;
    	int i;
    };
    struct S3
    {
    	char c1;
    	struct S s1;
    	double d;
    };
    int main()
    {
    	struct S3 s3 = { 0 };
    	printf("%d\n", sizeof(s3));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    图解:
    在这里插入图片描述

    • 结构体嵌套的最大数并不是16
      16是结构体嵌套成员的大小,S里的最大对齐数是 8
      要对比的是对齐数,不是所占的空间

    内存对齐总是会浪费空间,为什么存在内存对齐?
    原因有二个:

    1. 平台原因(移植原因):
      不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
      定类型的数据,否则抛出硬件异常
    2. 性能原因:
      数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
      原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
      问。
    • 总结一句话
      结构体的内存对齐是拿空间来换取时间的做法

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

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

    例如:
    S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别

    struct S1
    {
     char c1;
     int i;
     char c2;
    };       s1 的大小是 12 个字节
    ------------------------------------
    struct S2
    {
     char c1;
     char c2;
     int i;
    };       s2 的大小是 8 个字节
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    总结:

    计算结构体的大小的小技巧

    1. 先算出所有结构体成员加起来的大小
    2. 找出结构体成员大小中最大的那个数
    3. 所有结构体成员加起来的大小是否是结构体成员大小中最大的那个数的倍数
      如果不是,那结果就是最大的那个数的下一个倍数

    修改默认对齐数

    计算结构体大小的默认对齐数是可以修改的
    #pragma 这个预处理指令,可以改变我们的默认对齐数

    #pragma pack(2)//设置默认对齐数为2
    struct S1
    {
     char c1;
     int i;
     char c2;
    };
    #pragma pack()//取消设置的默认对齐数,还原为默认
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 这个结构体大小就是 8 了
    • 如果修改默认对齐数后,最大的对齐数不会超过修改后的对齐数
      上面最大的对齐数是 2,并不是 4
    #pragma pack(1)//设置默认对齐数为1
    struct S2
    {
     char c1;
     int i;
     char c2;
    };
    #pragma pack()//取消设置的默认对齐数,还原为默认
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 这个结构体大小是 6
    • 正常来说设置默认对齐数都是 2的几次方,不会设置成奇数

    这个的意义在于:结构在对齐方式不合适的时候,我可以自己更改默认对齐数

    offsetof 宏 计算偏移量

    offsetof 是计算某个结构体成员离最开始的地址(0)的偏差是多少
    offsetof 的头文件是:《stddef.h》

    offsetof 的使用:

    #include<stddef.h>
    struct S
    {
    	char c1;
    	int i;
    	char c2;
    };
    int main()
    {
    	printf("%d\n", offsetof(struct S, i));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结果:
    之前计算过,i 的偏移量是 4
    在这里插入图片描述

    结构体传参

    结构体传参有二种方法
    1.形参
    打印时用的是.操作符
    如果是传的是形参的方式,传的是整个数结构体的大小,比较占空间

    struct peo
    {
    	char name[20];
    	char srx[10];
    	int tele[12];
    };
    print(struct peo str)
    {
    	printf("%s %s %d\n", str.name, str.srx, str.tele);
    }
    int main()
    {
    	struct peo s = { "小明","男","15846092751" };
    	print1(s);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.实参以指针的方式
    打印时用的是->操作符
    这种方式传的是地址,最大也就是4个或8个字节

    struct peo
    {
    	char name[20];
    	char srx[10];
    	int tele[12];
    };
    print1(struct peo* str)
    {
    	printf("%s %s %d\n", str->name, str->srx, str->tele);
    }
    int main()
    {
    	struct peo s = { "小明","男","15846092751" };
    	print2(&s);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 如果选择结构体传参时首选以指针的方式
      因为如果选择以形参的函数传参的时候,参数是需要压栈的。
      如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的
      下降。
    • 总结:
      结构体传参的时候,要传结构体的地址
  • 相关阅读:
    查看nginx当前并发连接数 - 修改最高并发数
    【搭建NextCloud私有云盘服务】采用docker在linux上进行部署,内含nextCloud移植(迁移服务器)方法
    安泰电压放大器在水下主动电场中的应用
    如何用golang写一个自己的后端框架
    正则表达式入门级别详细教程
    java毕业设计基于ssm框架的生鲜超市进销存管理系统
    imedicallis命令的背后
    Vue第1天:特性概览
    mysql存储过程和函数
    全网最牛自动化测试框架系列之pytest(10)-常用执行参数说明
  • 原文地址:https://blog.csdn.net/m0_66483195/article/details/125494938