• 自定义类型--结构体(C语言)


    快速导航

    1.什么情况会使用到结构体?

    2.结构体的声明🛰

    3.特殊的声明🍺

    4.结构体自引用😊

    4.1在结构体中包含一个类型为该结构本身的成员是否可以?

    4.2正确的结构体自引用

    4.3 typedef对结构体重命名时的两种情况

    5.结构体变量的定义、初始化

    5.1结构体变量的定义

    5.2结构体变量的初始化 

    6.两种结构体成员访问方式的对比 

    7.结构体内存对齐🤡

    7.1为什么要有结构体内存对齐?

    7.2结构体内存对齐规则

    7.3  4个练习(画图理解)

    7.3.1 练习1

    7.3.2 练习2

    7.3.3 练习3

    7.3.4 练习4(嵌套结构体) 

     7.4修改默认对齐数

    👍【前言】👍在日常写代码的过程中,结构体可谓是见怪不怪,那么你是否真的理解结构体的内存对齐,结构体自引用,以及结构体的一些特殊声明,接下来就由博主带你们一起在知识的海洋中遨游吧!✈️

    1.什么情况会使用到结构体?

    C语言中的内置类型int,char,double等类型描述变量是有一定限制的,像学生,老师,书本等对象是无法使用内置类型进行描述的,所以我们需要用到结构体来对这些对象进行描述。

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

    1. struct Stu //描述一个学生对象
    2. {
    3. char name[20];//姓名
    4. int age; //年龄
    5. char id[12]; //学号
    6. };

    当使用内置类型无法描述一个对象时,就需要考虑使用自定义类型-结构体。

    2.结构体的声明🛰

    1. struct tag
    2. {
    3. member-list;//成员列表
    4. }variable-list; //变量列表

    下面以struct Book为例:

    1. struct Book
    2. {
    3. char name[20];//书名
    4. float price;//价格
    5. char id[20];//书号
    6. }; //一定能够要注意 ;不能丢

    3.特殊的声明🍺

    在声明结构体时,可以不完全声明。举个栗子:

    1. struct
    2. {
    3. int a;
    4. char b;
    5. float c;
    6. }x;//定义了一个匿名的变量x

     这种声明结构只能在声明的时候才能定义变量,之后无法再定义变量,适用于只定义一次的情况。

    这时候就会出现一个问题:如果两个匿名结构体中的成员相同,那么这两个匿名结构体是不是同一种类型?

    1. struct
    2. {
    3. int a;
    4. char b;
    5. float c;
    6. }*p; //相当于定义了一个结构体的指针
    1. //在上面代码的基础上,下面的代码合法吗?
    2. p = &x;

    注意:上面这种是不合法的;编译器会把两个匿名的结构体声明当成不同的两个类型,造成指针的非法访问。

    4.结构体自引用😊

    数据结构中的单链表为例:

    链表:物理空间上不一定连续,逻辑上一定连续,通过指针来链接。

    4.1在结构体中包含一个类型为该结构本身的成员是否可以?

    1. struct Node
    2. {
    3. int data;
    4. struct Node next;
    5. };

    Node结构体之中包含Node结构体……,就相当于无穷的递归下去,那么sizeof(Node)是多少呢?

    很显然,这种结构体自引用是不正确的。

    4.2正确的结构体自引用

    链表中数据域用来存储数据,指针域用来存储结构体指针。

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

    如果觉得结构体类型复杂,可以对结构体类型进行重命名,这也是数据结构书籍中普遍的一种用法:

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

    4.3 typedef对结构体重命名时的两种情况

    1.不存在结构体自引用

    1. typedef struct
    2. {
    3. int a;
    4. char b;
    5. float c;
    6. }St; //无论是匿名还是非匿名可以进行typedef

    2.存在结构体自引用 

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

     typedef:只有在非匿名的时候才能对结构体自引用的结构体进行重命名。

    1. typedef struct
    2. {
    3. int data;
    4. struct Node* next;//无法进行声明
    5. }Node;

    5.结构体变量的定义、初始化

    既然对结构体变量进行了声明,那么就要对变量进行定义和初始化:

    5.1结构体变量的定义

    1.在声明的时候定义变量

    1. struct Stu
    2. {
    3. char name[20];//姓名
    4. int age; //年龄
    5. char id[12]; //学号
    6. }s1, s2, s3;

    2.声明之后再定义 

    1. struct Stu
    2. {
    3. char name[20];//姓名
    4. int age; //年龄
    5. char id[12]; //学号
    6. };
    7. int main()
    8. {
    9. struct Stu s1;
    10. struct Stu s2;
    11. return 0;
    12. }

    5.2结构体变量的初始化 

    1.在声明的时候定义变量并且进行初始化

    1. struct Stu
    2. {
    3. char name[20];//姓名
    4. int age; //年龄
    5. char id[12]; //学号
    6. }s1 = {"阿飞", 19, "66666666"};

    2.定义时进行初始化 

    1. struct Stu
    2. {
    3. char name[20];//姓名
    4. int age; //年龄
    5. char id[12]; //学号
    6. };
    7. int main()
    8. {
    9. struct Stu s1 = { "阿飞", 19, "66666666" };
    10. return 0;
    11. }

    6.两种结构体成员访问方式的对比 

    1.传结构体

    1. void Print1(struct Stu s)
    2. {
    3. printf("%s, %d, %s\n", s.name, s.age, s.id);
    4. }
    5. int main()
    6. {
    7. struct Stu s = { "阿飞", 19, "66666666" };
    8. Print1(s);
    9. return 0;
    10. }

    2.传结构体指针 

    1. void Print2(struct Stu* ps)
    2. {
    3. printf("%s, %d, %s\n", ps->name, ps->age, ps->id);
    4. }
    5. int main()
    6. {
    7. struct Stu s = { "阿飞", 19, "66666666" };
    8. Print1(s);
    9. Print2(&s);
    10. return 0;
    11. }

     这两种访问方式都能访问结构体成员:

    但是传值的时候形参是实参的一份拷贝,如果结构体很大的话,会造成大量的内存消耗;

    首选使用传址来进行访问,因为没有额外的内存消耗。

    7.结构体内存对齐🤡

    1. struct S1
    2. {
    3. char c1;
    4. int i;
    5. char c2;
    6. };
    7. int main()
    8. {
    9. printf("%d\n", sizeof(s1));
    10. return 0;
    11. }

     VS下的运行结果:

    char类型1个字节,int类型4个字节,那么结果为什么不是6而是12呢?

    那是因为结构体内存对齐的缘故。具体在7.3中会详解;

    7.1为什么要有结构体内存对齐?

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

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

    2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

    7.2结构体内存对齐规则

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

    2.其他成员变量要对齐到对齐数的整数倍的地址处。

    对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。(VS中默认对齐数为8)

    3.结构体总大小为最大对齐数的整数倍处。

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

    7.3  4个练习(画图理解)

    7.3.1 练习1

    7.3.2 练习2

    7.3.3 练习3

    7.3.4 练习4(嵌套结构体) 

    1. struct S3
    2. {
    3. double d;
    4. char c;
    5. int i;
    6. };
    7. struct S4
    8. {
    9. char c1;
    10. struct S3 s3;
    11. double d;
    12. };

     

     7.4修改默认对齐数

    1. #pragma pack(8) //设置默认对齐数为8
    2. #pragma pack(4) //设置默认对齐数为4

    VS的默认对齐数为8,那接下来验证一下默认对齐数为4的时候结果是否正确。

    1. struct S
    2. {
    3. char c;
    4. double d;
    5. int i;
    6. };

     

    我们计算的结果和编译器打印的结果是相同的,说明默认对齐数确实修改了。 

  • 相关阅读:
    HTML易忽略的角落【目录】
    Visual Studio 删除行尾空格
    SpringCloud系列(7)--Eureka服务端的安装与配置
    Linux中设置git的代理
    软工非全研究生学习和工作总结-开题和第三辆捷安特
    计算机毕业设计 SSM+Vue线上医疗咨询管理系统 网上医疗问诊系统 线上医疗健康咨询系统Java Vue MySQL数据库 远程调试 代码讲解
    java spring cloud 工程企业管理软件-综合型项目管理软件-工程系统源码
    vue - 大文件分片上传之simple-uploader.js的使用
    C++设计模式-创建型设计模式:工厂模式
    c++基础知识-运算符与表达式1(详解)
  • 原文地址:https://blog.csdn.net/qq_63179783/article/details/123772869