自定义范例之结构体
https://i-blog.csdnimg.cn/direct/7beea27c86fe40c18a4e356d9dfe8bdb.jpeg1.结构体范例概述
结构体范例是一种用户自定义的数据范例,用于将不同范例的数据组合成一个整体。在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]