• 23.0、C语言——结构体的剖析与使用


    23.0、C语言——结构体的剖析与使用

    结构体的声明:

    结构体的基础知识:

    结构是一些值的集合,这些值称为成员变量,结构的每个成员与数组不同,结构体的每个成员可以是不同的类型变量【但是成员变量类型不可以是自己的结构体类型】;

    比如声明一个学生结构体类型->

    1. struct Student {
    2. char name[20];
    3. char tele[12];
    4. char sex[10];
    5. short age;
    6. }

    在main()函数中定义的结构体变量都是局部变量;

    全局结构体变量,定义如下:

    1. struct Student {
    2. char name[20];
    3. char tele[12];
    4. char sex[10];
    5. short age;
    6. }s2,s3,s4;
    7. struct Student s5;

    特殊声明方式:

    匿名结构体类型 ->

    1. struct {
    2. int x;
    3. int y;
    4. int z;
    5. }a;

            1. 向上面代码描述的是匿名结构体类型,那么这种类型由于没有结构体名字,所以只能在声明结构体类型的后面直接定义结构体变量,在其他地方无法定义结构体变量;
            2. 当然这种声明的方式不建议大家去使用,这里只做了解即可;
            3. 结构体类型的指针只能存放自己结构体类型的对象地址;

    结构体的自引用方式 ->

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

            1. 其实就是数据结构中的链式存储方式;
            2. 结构体中一部分存储数据,一部分存储一个指向下一个结构体元素的指针;

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

    1. typedef struct Student {
    2. char name[20];
    3. char tele[12];
    4. char sex[10];
    5. short age;
    6. }Student;
    7. int main() {
    8. struct Student s1 = {"小澜","1355841253","男",18};
    9. printf("%s%s%s%d",s1.name,s1.tele,s1.sex,s1.age);
    10. return 0;
    11. }

    当然结构体类型中也可以包含其他的结构体类型:

    1. typedef struct Student {
    2. char name[20];
    3. char tele[12];
    4. char sex[10];
    5. short age;
    6. }Student;
    7. typedef struct People {
    8. Student s;
    9. int a;
    10. int b;
    11. int c;
    12. }People;
    13. int main() {
    14. People p = { {"小澜","1355841253","男",18},1,2,3 };
    15. printf("%s%s%s%d",p.s.name,p.s.tele,p.s.sex,p.s.age);
    16. return 0;
    17. }

    结构体内存对齐

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

    计算一下这两个结构占用内存空间的大小 ->

    考点 如何计算?首先得掌握结构体的对齐规则:

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

            2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;【对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值;】

            VS中默认的值为 8 ;【那么有些编译器没有默认对齐数(例如 gcc 编译器),这时候变量的大小就是每次的对齐数】

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

            4. 如果嵌套了结构体的情况,嵌套的 结构体a 对齐到自己结构体中变量的最大对齐数的整数倍处【比如说:该 结构体a 中定义了 int a; 且这个 a 的对齐数是该 结构体a 中最大的,那么该 结构体a 就按照 4 的倍数来算】,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍;

    计算s1的内存大小如下图所示: 

            1. s1 算出来的大小是 12;
            2. 那么结构体类型变量 s2 的大小计算方法也同理能算出来是 8;

            3. 可以看到 s1 和 s2 的成员变量都是一样的,只是定义的顺序不一样,最终导致结构体占用的内存空间大小不一样;
            4. 可以用 #pragma pack( int ) 去修改 默认的对齐数;如果int参数什么都不写就代表取消设置的默认对齐数;

    offsetof ( )  宏

    offsetof 其实是宏,这里先做了解即可,在之后宏的学习章节中再给大家详细介绍;

    size_t  offsetof ( structName , memberName );

            1. 返回该变量在该结构体中的偏移量;【使用的时候需要引入头文件 #include
            2. 第一个参数为 -> 结构体类型,第二个参数为 -> 该结构体的变量;
            3. 实例代码如下所示:

    1. struct S {
    2. char c;
    3. int i;
    4. double d;
    5. };
    6. int main() {
    7. printf("%d\n", offsetof(struct S ,c));
    8. printf("%d\n", offsetof(struct S, i));
    9. printf("%d\n", offsetof(struct S, d));
    10. return 0;
    11. }

    输出的结果为:0 , 4 , 8

    为什么存在内存对齐?

    大部分的参考资料都是这么说的:

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

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

    总的来说:

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

    结构体值传递

    1. struct S {
    2. int a;
    3. char b;
    4. short c;
    5. };
    6. void init(struct S tmp) {
    7. tmp.a = 1;
    8. tmp.b = 'a';
    9. tmp.c = 2;
    10. }
    11. int main() {
    12. struct S s = { 0 };
    13. init(s);
    14. printf("a=%d,b=%c,c=%d",s.a,s.b,s.c);
    15. return 0;
    16. }

            如上代码,将对象 s 传递给 init()函数 并且修改结构体对象 s 的值,但是由于这是值传递,所以 tmp 是一个临时拷贝的变量,所以修改的也只是 tmp 临时拷贝变量中的值,并没有真正的修改到 s 的值;

    结构体址传递 ->

    1. struct S {
    2. int a;
    3. char b;
    4. short c;
    5. };
    6. void init(struct S* p) {
    7. (*p).a = 1;
    8. (*p).b = 'a';
    9. (*p).c = 2;
    10. }
    11. int main() {
    12. struct S s = { 0 };
    13. init(&s);
    14. printf("a=%d,b=%c,c=%d",s.a,s.b,s.c);
    15. return 0;
    16. }

            1. 如上代码,传址方式将 s 的地址传递到 init() 函数中通过 s 的地址去修改 s 的值,是可以修改成功的;
            2. 如果我们要传递 结构体对象 给函数,尽量使用传址的方式,因为这样更加节省空间节省资源;
            3. 如果用传值的方式,当我们的变量很大的时候,会导致需要拷贝一个很大的临时变量,很占用栈的内存空间;

    总结:

            1. 函数传参的时候,参数是需要压栈,会有时间和空间上的开销;
            2. 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降; 
            3. 所以当结构体传参的时候要传结构体的地址;

  • 相关阅读:
    七星创客商业模式:享受优惠价格和丰厚奖励的新选择!
    如何提升设备投资回报率:预测性维护在制造业的应用
    大数据分布式计算工具Spark实战讲解(数据输入实战)
    springboot缓存篇之mybatis一级缓存和二级缓存
    13-Error接口错误处理以及go Module
    Router-view
    C++变量与基本类型
    IB 物理真题: 比潜热、理想气体
    制作linux系统内部yum源仓库
    JAVA经典百题之坐电梯
  • 原文地址:https://blog.csdn.net/m0_52433668/article/details/126886135