• 自定义类型:结构体


    目录

    一、结构体的声明

    1.结构的基础知识

    2.结构的声明

     3.特殊的声明(不完全的声明)

    4.结构的自引用

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

    6.结构体内存对齐(非常重要!!!!!!!!!)

    6.1考点(规则)

    7.修改默认对齐数

     8.结构体传参


     

    引言自定义类型相对的是内置类型

    6e8f57761b484805bf1b3c56075f5e2c.pngee172f99a22e4c5f902ac14ef29f34ad.png

    一、结构体的声明

    1.结构的基础知识

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

    2.结构的声明

    1. struct tag
    2. {
    3. member-list;
    4. }variable-list;

    关键字:struct (不能缺,不能改)

    tag:结构体标签(根据自己实际需求写)

    member-list:成员列表(可以是一个,也可以是多个)

    variable-list:变量列表

    例如描述一本书:书名+作者+定价+书号

    1. struct Book
    2. {
    3. char book_name[20];//书名
    4. char author[20];//作者
    5. int price;//价格
    6. char id[15];//书号
    7. };

    1b106143e9264f5299917d9ab84011d2.png
    上图可选项:

    1. struct Book
    2. {
    3. char book_name[20];
    4. char author[20];
    5. int price;
    6. char id[15];
    7. }sb3, sb4;
    8. //sb3, sb4 也是struct Book类型的结构体变量
    9. //是全局变量
    10. int main()
    11. {
    12. struct Book sb1;//局部变量
    13. struct Book sb2;//局部变量
    14. struct Stu ss1;
    15. struct Stu ss2;
    16. return 0;
    17. }

     3.特殊的声明(不完全的声明)

    不完全的声明——标签省略不写(省略后,还通过这种类型传递变量,只能在sb1、sb2处跟着传递变量,否则不能传递)

    此匿名结构体类型只能用一次,用完之后不能再用

    1. struct
    2. {
    3. char book_name[20];
    4. char author[20];
    5. int price;
    6. char id[15];
    7. }sb1, sb2;//匿名结构体类型
    8. int main()
    9. {
    10. return 0;
    11. }

    4.结构的自引用

    数据结构——数据在内存中存储的结构

    线性数据结构:顺序表、链表

    树形数据结构:二叉树

    b093babf10e34ab08daa1933ff35267f.png

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

    1. //代码1
    2. struct Node
    3. {
    4. int data;
    5. struct Node next;
    6. };
    7. //可行否?
    8. 如果可以,那sizeof(struct Node)是多少?

    正确的自引用方式:

    1. //代码2
    2. struct Node
    3. {
    4. int data;
    5. struct Node* next;
    6. };

    注意:

    1. //代码3
    2. typedef struct
    3. {
    4. int data;
    5. Node* next;
    6. }Node;
    7. //这样写代码,可行否?
    8. //解决方案:
    9. typedef struct Node
    10. {
    11. int data;
    12. struct Node* next;
    13. }Node; 

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

    1. struct Point
    2. {
    3. int x;
    4. int y;
    5. }p1; //声明类型的同时定义变量p1
    6. struct Point p2; //定义结构体变量p2
    7. //初始化:定义变量的同时赋初值。
    8. struct Point p3 = {x, y};
    9. struct Stu     //类型声明
    10. {
    11. char name[15];//名字
    12. int age;    //年龄
    13. };
    14. struct Stu s = {"zhangsan", 20};//初始化
    15. struct Node
    16. {
    17. int data;
    18. struct Point p;
    19. struct Node* next;
    20. }n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
    21. struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

    6.结构体内存对齐非常重要!!!!!!!!!

    深入讨论一个问题:计算结构体的大小

    这也是一个特别热门的考点: 结构体内存对齐

    我们先来看一个代码:

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

    运行结果是什么呢

    dc86f340b23541dbb68052db7629ea2c.png

     答案是   12   8

    首先补充一个知识点:

    offsetof——宏:用来计算结构成员相对于起始位置的偏移量

    接下来我们看一下S1:

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

    0def38891aa84e1cb947930cb29ed432.png

    ad326c32ca694aa789cbed429420a183.png

    我们再来看一下S2:

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

    f23b25229a2e457b8353e5f8e2b5879f.png

     d7a641875b2e4394a36f7f8b33b137d6.png

    6.1考点(规则)

    如何计算? 

    首先得掌握结构体的对齐规则:
    1. 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处。
    2. 从第二个成员开始,要对齐到某个
    对齐数的整数倍的偏移处
    对齐数 :结构体成员自身大小和默认对齐数的较小值
    VS:8

    Linux环境默认不设置对齐数(对齐数是结构体成员的自身大小)
    3.
    结构体总大小必须是最大对齐数的整数倍。

    每个成员变量都有一个对齐数,其中最大的对齐数就是最大对齐数
    4. 如果嵌套了结构体的情况

    嵌套的结构体对齐到自己的最大对齐数的整数倍处,

    结构体的整体大小就是所有最大对齐数

    含嵌套结构体的对齐数)的整数倍。

    c88355db0c804c6e90756e162db4735d.png

    0f7a3cc40b604d25ad0e2dd271ace3fb.png

    94e8f8e88c5946f68aa76cb8121ee586.png

    为什么存在内存对齐?

    1. 平台原因(移植原因):
           
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

    2. 性能原因:
            数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
            原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

    假设:

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

    0308fba344554ee5b127eefd4523bd66.png

     总体来说:
    结构体的内存对齐是拿空间来换取时间的做法

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

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

    1. //例如:
    2. struct S1
    3. {
    4. char c1;
    5. int i;
    6. char c2;
    7. };
    8. struct S2
    9. {
    10. char c1;
    11. char c2;
    12. int i;
    13. };

    7.修改默认对齐数

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

    1. 设置默认对齐数
    2. #pragma pack(1)
    3. struct S1
    4. {
    5. char c1;
    6. int i;
    7. char c2;
    8. };//
    9. //恢复默认对齐数
    10. #pragma pack()
    11. int main()
    12. {
    13. printf("%d\n", sizeof(struct S1));
    14. return 0;
    15. }

    52789f440ec94f8da09faa0b5b8ccf4e.png 

    结论:
    结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。 

     8.结构体传参

    1. struct S
    2. {
    3. int data[1000];
    4. int num;
    5. };
    6. struct S s = {{1,2,3,4}, 1000};
    7. //结构体传参
    8. void print1(struct S s)
    9. {
    10. printf("%d\n", s.num);
    11. }
    12. //结构体地址传参
    13. void print2(struct S* ps)
    14. {
    15. printf("%d\n", ps->num);
    16. }
    17. int main()
    18. {
    19. print1(s);  //传结构体
    20. print2(&s); //传地址
    21. return 0;
    22. }

    上面的 print1 和 print2 函数哪个好些?
    答案是:首选print2函数。
    原因:
    函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
    如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

    结论:
    结构体传参的时候,要传结构体的地址。

     

     

     

     

     

     

  • 相关阅读:
    软件测试入门到高级,5k-50k,你属于哪个阶段?
    【python基础】python的继承与多态,子类中调用父类方法和属性
    以http协议实现onvif协议并完成对IPC摄像头的监控
    uni-app项目总结
    数据赋能(121)——体系:数据清洗——实施过程、应用特点
    js 取整,保留2位小数
    Vue3组件计算属性的缓存
    进程的概念,组成和特征(PCB)
    从文字到视频:借助ChatGPT与剪映轻松生成高质量视频(文末送书)
    深度学习500问——Chapter03:深度学习基础(1)
  • 原文地址:https://blog.csdn.net/m0_72161237/article/details/126907118