C语言-结构体

结构体类型的声明

结构体通过 struct 关键字声明。例如:

struct Person {
    char name[50];
    int age;
    float height;
};

结构体变量的创建和初始化

struct Person person1 = {"Alice", 30, 5.5};  // 直接初始化
struct Person person2;
person2.age = 25;                           // 逐个成员赋值

结构成员访问操作符

使用点(.)操作符来访问结构体的成员。如果通过指针访问结构体成员,使用箭头(->)操作符。例如:

printf("%s", person1.name);    // 直接访问
struct Person *ptr = &person2;
printf("%d", ptr->age);        // 指针访问

在声明结构的时候,可以不完全的声明

在C语言中,结构体可以有一个“不完全声明”或“前向声明”。这种声明方式在定义结构体时不完全指定其所有成员,通常用于处理交叉引用或循环引用的情况,以及在定义涉及相互引用的结构体时。

特点和用途

  1. 交叉引用:当两个或多个结构体彼此引用对方的成员时,不可能在一个声明中完全定义它们。不完全声明允许在结构体定义之前声明其存在,从而使结构体可以互相引用。

  2. 循环引用:在数据结构如链表、树、图等中,元素需要指向相同类型的其他元素。在这种情况下,不完全的结构体声明是必要的。

  3. 头文件和模块化:在模块化编程和使用头文件时,不完全声明允许将结构体的定义从其使用中分离。

示例

不完全声明的一个简单例子可能如下所示:

//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}a[20], *p;

在上⾯代码的基础上,下⾯的代码合法吗?

 p = &x;

p 是指向一个由匿名结构体构成的数组的指针,而 x 是一个单独的匿名结构体变量。由于 xa[20] 使用的是不同的匿名结构体类型(即使它们的字段相同),它们在C语言中被视为不同的类型。

C语言的类型系统是基于严格的类型兼容性的。即使两个匿名结构体具有完全相同的字段和布局,如果它们是分别声明的,它们仍然被视为不同的类型。因此,不能将一个结构体类型的地址赋给另一个不同结构体类型的指针,即使这两个结构体在实际布局上是相同的。

所以,p = &x; 这行代码在C语言中是不合法的,因为 p 是指向一个匿名结构体数组的指针,而 x 是一个不同的匿名结构体实例。如果要使这行代码合法,px 必须是相同的结构体类型。例如,可以这样声明:

struct {
 int a;
 char b;
 float c;
} x, *p;

匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。

结构的自引用

在C语言中,结构体的自引用是指结构体中包含指向相同类型的结构体的指针。这是一种常见的做法,特别是在创建链表、树、图等数据结构时。自引用结构体不能直接包含其自身的实例,因为这会导致无限大小的结构体,但它们可以包含指向相同类型的结构体的指针。

示例

以下是一个自引用结构体的例子,用于表示链表的节点:

struct Node {
    int data;
    struct Node *next; // 自引用:指向下一个节点的指针
};

在这个例子中,每个 Node 结构体包含两个成员:一个 int 类型的数据和一个指向下一个 Node 的指针。这样的结构体布局允许创建一个节点序列,每个节点都指向列表中的下一个节点。

自引用结构体的用途

  1. 链表:链表中的每个节点包含数据和一个指向列表中下一个节点的指针。
  2. :在树结构中,每个节点可能包含指向其子节点的指针。
  3. :图的节点(或顶点)可以包含指向其他节点的指针,表示边或链接。

注意事项

  • 在声明自引用结构体时,必须使用结构体的前向声明(如果结构体是匿名的,则无法实现自引用)。
  • 自引用通常涉及动态内存分配,因为结构体的完整大小在编译时未知。

这种自引用机制使得结构体在表示复杂数据结构时非常灵活和强大。

结构体内存对齐:

结构体的内存对齐是指在结构体中,各成员可能会有内存填充(padding)来确保特定的对齐方式。这是由于硬件和性能优化的原因。编译器通常根据成员类型的自然对齐边界进行对齐。

首先得掌握结构体的对齐规则:
1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的一个对齐数与该成员变量大小的较小值。

3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的
整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

为什么存在内存对齐?

大部分的参考资料都是这样说的:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定
类型的数据,否则抛出硬件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的doubl类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

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

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起

修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对齐数。

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
 //输出的结果是什么?
 printf("%d\n", sizeof(struct S));
 return 0;
}

结构体传参

结构体可以作为整体传递给函数,或者通过指针传递。传递整个结构体会复制整个结构体数据,而传递指针则更为高效。例如:

void displayPerson(struct Person p) { ... }  // 通过值传递
void modifyPerson(struct Person *p) { ... } // 通过指针传递

原因: 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。

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

结构体实现位段(Bit-fields): 

在C语言中,位段(Bit-fields)是一种用于结构体中的特殊语法,允许程序员更精确地控制数据结构中各成员的位级表示。这在需要精确大小控制或与特定硬件接口对接时特别有用,例如在硬件编程或网络协议中。

位段的声明和使用

  1. 声明:位段在结构体内部声明,通过在成员后面指定位数来定义。例如,声明一个可以使用3位存储的无符号整数:

    struct {
        unsigned int field : 3;
    };
    
  2. 大小和类型:位段成员的类型通常是整数类型(如 intunsigned intsigned int)。大小是通过冒号后面的数字指定的,表示该成员占用多少位。

  3. 内存布局:位段可以帮助减少数据结构的大小,因为它允许多个成员共享同一个字节,只要它们加起来的位数不超过该字节的大小。

  4. 访问:位段成员的访问方式与普通结构体成员相同。但是,位段的读取和写入可能比普通成员更慢,因为编译器可能需要生成额外的代码来处理位的掩码和位移操作。

示例

以下是位段在结构体中的一个示例:

struct BitField {
    unsigned int is_enabled : 1;  // 只占用1位
    unsigned int is_visible : 1;  // 只占用1位
    unsigned int mode       : 3;  // 占用3位
    unsigned int reserved   : 3;  // 占用3位,用作填充或未来使用
};

在这个例子中,BitField 结构体总共占用8位(1个字节),其中每个成员都分配了特定数量的位。

注意事项

  • 位段的实际布局(例如跨越字节边界的行为)可能取决于特定的编译器和平台。
  • 对位段的操作可能不如对常规整数成员的操作高效,因为可能涉及位掩码和位移。
  • 位段不应该用于跨平台的数据交换,因为不同的编译器可能会以不同的方式布局同一个位段。

通过位段,可以在保持数据结构清晰的同时节省空间,特别适合于资源受限的环境,如嵌入式系统。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值