北冰洋以北 发表于 2025-4-16 09:49:20

自定义范例之结构体

https://i-blog.csdnimg.cn/direct/7beea27c86fe40c18a4e356d9dfe8bdb.jpeg



1.结构体范例概述

        结构体范例是一种用户自定义的数据范例,用于将不同范例的数据组合成一个整体。在C语言中,结构体利用struct关键字定义,由一系列具有相同范例或不同范例的数据构成的数据聚集,也称为结构。结构体中的数据在逻辑上是相互关联的,每个数据称为结构体的成员,成员可以有不同的数据范例,而且成员一般通过名字举行访问
2.结构体范例的特点

        每个成员具有独立的数据范例:这意味着可以在一个结构体内混淆不同范例的数据,如整数、浮点数、字符数组等。
        定义结构体时,成员数量必须固定:一旦结构体范例被定义,其成员的数量和范例就不能改变。
        成员名固定唯一且不可与结构体范例相同:每个成员必须有一个唯一的名称,且这个名称不能与结构体范例名相同。


[*]结构体范例的声明
[*]结构体范例的自引用
[*]结构体范例变量的定义、初始化和访问
[*]结构体内存对齐
[*]结构体传参
3. 结构体范例的声明

#define_CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

//方法一
struct Stu
{
        char name;
        float score;
        int age;
}s1,s2;//全局变量

int main()
{
        //方法二
        struct Stu s3, s4;//全局变量
        return 0;
} 在C语言中,结构体(struct)是一种用户自定义的数据范例,它答应将多个不同范例的变量组合在一起。当你频繁地利用结构体时,每次都输入struct可能会显得繁琐。为相识决这个题目,C语言提供了typedef关键字,可以对结构体范例举行重命名,从而简化结构体的利用
#define_CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

//方法一
typedef struct
{
        char name;
        float score;
        int age;
}Stu;//全局变量

int main()
{
        //方法二
       Stu s3, s4;//全局变量
        return 0;
} 注:s1,s2,s3,s4是结构体变量

4. 结构体范例的自引用(引用同范例的下一个节点)

我们先来看一下链表中的数据结构:
我们起首来定义一个整型数组:int arr[]={ 1, 2, 3, 4, 5 };

https://i-blog.csdnimg.cn/img_convert/e069fa6b5f3ee65c0c4ce250d2f0ba73.png
那么在结构体的自引用中我们用到的就是链表

https://i-blog.csdnimg.cn/img_convert/6dc7a5a732fdfcbdf28ec33118d5ba2b.png
我们通过结点在结构体中寻找下一个结点
struct Node
{
        int data;
        struct Node next;
}; 如果我们这样写,就有一个大题目,就是这个结构体的大小不确定,如果结点过多,大小会一直大下去

https://i-blog.csdnimg.cn/img_convert/74e69565e11f3091dd3380662ec1b20c.png
那么我们怎么来优化呢?
解答:我们可以利用指针,因为指针在编译器中的大小是固定的4/8字节
struct Node
{
        int data;
        struct Node* next;
};
5. 结构体范例变量的定义、初始化和访问

a. 结构体指针的定义

#define_CRT_SECURE_NO_WARNINGS 1

//方法一
struct Point
{
        int a;
        int b;
}p1;

//方法二
struct Point p2;

int main()
{
        //方法三
        struct Point p3;
        return 0;
}
b. 结构体指针的初始化

#define_CRT_SECURE_NO_WARNINGS 1

//方法一
struct Point
{
        int a;
        int b;
}p1 = {1,2};

//方法二
struct Point p2 = {3,4};

int main()
{
        //方法三
        struct Point p3 = {5,6};
        return 0;
} c. 结构体指针的访问

#define_CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

struct Point
{
        int x;
        int y;
};

struct Stu
{
        int num;
        char ch;
        struct Point p;
        float d;
};

int main()
{
        //顺序访问
        struct Stu s1 = { 100,'a',{2,5},3.3 };
        //乱序访问
        struct Stu s2 = { .d = 8.9,.num = 245,.ch = 'h',.p.x = 55,.p.y = 78 };
        return 0;
}
6. 结构体内存对齐

我们先来看一下这个小题
#define_CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

struct S1
{
        char c1;
        int a;
        char c2;
};

struct S2
{
        char c1;
        char c2;
        int a;
};

int main()
{
        printf("%d\n", sizeof(struct S1));
        printf("%d\n", sizeof(struct S2));

        return 0;
} 输出效果:

https://i-blog.csdnimg.cn/img_convert/91830405fd00adf6fa48f5372f63b10c.png
在我们的认识中,char为1个字节,int为4个字节,那么两个都应该是6才对,那为什么确实不为6且不相同的数呢?
解答:
a. 结构体内存对齐规则



[*]第一个成员的对齐:结构体的第一个成员通常从偏移量0的地址开始存储。
[*]后续成员的对齐:其他成员变量的存储地址需要是对齐数的整数倍。
对齐数是编译器默认的对齐数和该成员自身大小的较小值。


[*]结构体总大小的对齐:整个结构体的大小也会按照最大对齐数的整数倍举行对齐。这个最大对齐数是全部成员中自身对齐值最大的那个,以及指定对齐值中较小的一个。
[*]嵌套结构体的对齐:如果结构体内部有嵌套的结构体,那么嵌套的结构体也会按照上述规则举行对齐,终极结构体的整体大小将是全部最大对齐数(包罗嵌套结构体的对齐数)的整数倍。
[*]编译器默认对齐数:在不同的编译器和平台上,默认的对齐数可能不同。例如,在Linux体系中,默认对齐数可能是4字节,而在某些64位体系或Visual Studio编译器中,默认对齐数可能是8字节



https://i-blog.csdnimg.cn/img_convert/490a0beef739962949d8275219d8a500.png


[*]结构的总大小,必须是全部成员的对齐数的整数倍
在这里我们可以看见这里只占了9个内存,还不足以满足对齐数的全部条件

https://i-blog.csdnimg.cn/img_convert/8aff2ce53b0916aba0e0c19dfb5f5a81.png


https://i-blog.csdnimg.cn/img_convert/3a26e3d4389f501fc52219519143825f.png
b. why:为什么存在内存对齐数呢?

ⅰ. 提高CPU访问数据的效率

内存对齐可以提高CPU访问数据的效率。这是因为CPU在访问内存时通常是以固定大小的块(如4字节或8字节)举行读取的。如果数据按照特定的对齐规则举行排列,CPU可以在一次访问中读取到完备的数据块,从而淘汰访问次数,提高效率。例如,如果一个int范例的数据位于4字节的边界上,那么在32位体系中,CPU可以在一个读周期内读取到这个int数据,而不需要举行额外的操作来拼接数据2。
ⅱ. 兼容性和可移植性

不同的硬件平台可能有不同的内存访问限定。有些平台只能在特定地址上访问特定范例的数据,否则可能会引发硬件异常。内存对齐可以确保步调在不同硬件平台上的一致性和可移植性。例如,Windows操作体系默认的对齐数是8,而Linux默认的对齐数是4。
ⅲ. 淘汰内存碎片

内存对齐有助于淘汰内存碎片的产生。通过将数据按照对齐规则举行排列,可以制止数据分散在内存中的各个角落,形成难以利用的小块内存,从而提高内存的利用率3。
ⅳ. 结构体和联合体的对齐规则

在C和C++编程中,结构体和联合体的成员通常需要按照一定的对齐规则举行排列。这些规则包罗数据成员的起始位置、结构体成员的对齐方式以及结构体总大小的对齐方式。通过对齐,可以确保结构体和联合体在内存中的结构符合特定的硬件平台要求,从而提高步调的实行效率和可移植性

c. 修改默认对齐数

我们用到 #pragma
#define_CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
#pragma pack(1)//设置默认对齐数

struct S1
{
        char c1;
        int a;
        char c2;
};

#pragma pack()//恢复默认对齐数

struct S2
{
        char c1;
        char c2;
        int a;
};


int main()
{
        printf("%d\n", sizeof(struct S1));
        printf("%d\n", sizeof(struct S2));
        return 0;
} 输出效果:

https://i-blog.csdnimg.cn/img_convert/5c280b01debf91e3dc5af2778cde6d8e.png

7. 结构体传参

a. 结构体传参分为两个方式



[*]结构体传参
[*]结构体地址传参
#define_CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

struct S
{
        int a;
        int b;
};

struct S s= { 4,5 };

//结构体传参
void Print1(struct S s)
{
        printf("%d\n", s.a);
}
//结构体地址传参
void Print2(struct S* pc)
{
        printf("%d\n", pc->b);
}
int main()
{
        Print1(s);
        Print2(&s);
        return 0;
} 那么结构体传参和结构体地址传参我们保举哪一个呢?
答:保举结构体地址传参
b. 为什么保举结构体地址传参

在C语言中,结构体是一种复合数据范例,它可以包含多个不同范例的成员。当需要将结构体作为参数通报给函数时,有两种重要的方式:传值和传地址。下面我们将探讨为什么通常保举利用结构体地址传参。


[*]淘汰内存开销
当通过值通报结构体时,函数会收到结构体的一个完备副本。如果结构体非常大,这可能会导致大量的内存被不须要的复制,增加了内存开销。相比之下,通过地址通报结构体只需要通报一个指向结构体的指针,这通常只需要4字节(32位体系)或8字节(64位体系),大大淘汰了内存占用
[*]提高性能
由于通过值通报结构领会创建结构体的副本,这不但斲丧更多的内存,还会增加CPU的负担,因为它需要额外的时间来举行数据复制。而通过地址通报则制止了这种复制,提高了步调的实行效率
[*]改变原始数据
如果希望在函数内部对结构体的成员举行修改,而且这些修改应该反映在调用函数的上下文中,那么必须通过地址通报结构体。因为通过值通报只会通报结构体的副本,任何对副本的修改都不会影响原始结构体
[*]代码简洁性
通过地址通报结构体可以使代码更加简洁和易读。例如,可以利用指向结构体的指针来调用结构体的方法,这在面向对象编程风格的C代码中很常见

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 自定义范例之结构体