当前位置: 首页> 娱乐> 八卦 > 小众写作网站_无锡网站制作推荐_域名ip查询_培训课程安排

小众写作网站_无锡网站制作推荐_域名ip查询_培训课程安排

时间:2025/7/10 13:33:00来源:https://blog.csdn.net/cui211472325/article/details/147000800 浏览次数:1次
小众写作网站_无锡网站制作推荐_域名ip查询_培训课程安排

文章目录

    • 前言
    • 一、结构体类型的声明
      • 1.1 什么是结构体?
      • 1.2 结构体的声明
      • 1.3 结构体变量的创建和初始化
      • 1.4 使用 typedef 简化结构体名称
      • 1.5 结构体的自引用
    • 二、结构成员的访问操作符
      • 2.1 使用点号 `.` 直接访问
      • 2.2 使用箭头 `->` 通过指针访问
    • 三、结构体内存对齐
      • 3.1 为什么需要内存对齐?
      • 3.2 内存对齐的规则
      • 3.3 修改默认对齐数
    • 四、结构体传参
      • 4.1 值传递
      • 4.2 指针传递
    • 五、结构体实现位段
      • 5.1 什么是位段?
      • 5.2 位段的声明方法
      • 5.3 位段的内存分配
      • 5.4 位段的跨平台问题
      • 5.5 位段的使用注意事项
    • 总结

前言

在 C 语言中,我们常用到一种叫做结构体(struct)的数据类型。
结构体就像一个小盒子,你可以把不同类型的数据(数字、字符串等)放进这个盒子中,这样便于管理和传递数据。
例如,一个学生的信息可以包含学号、姓名和成绩,把这些放到一个结构体中,就很方便了。

本文将一步步介绍:

  • 结构体的声明和使用
  • 如何创建和初始化结构体变量
  • 如何访问结构体中的数据
  • 内存对齐的概念和原因
  • 结构体传参的方法
  • 位段的作用及使用注意事项

即使你没有编程基础,也能看懂每个步骤。
在这里插入图片描述


一、结构体类型的声明

1.1 什么是结构体?

结构体就是一个自定义的数据类型,用来将不同类型的数据组合在一起。
举个例子:我们想表示一个学生的信息,可能包括学号、姓名和成绩。

1.2 结构体的声明

在 C 语言中,声明结构体的格式如下:

struct 结构体名 {成员类型 成员名;// 可以有多个成员,每个成员之间用分号隔开
};

示例:定义一个表示学生的结构体

// 定义一个结构体类型,名字叫 Student
struct Student {int id;             // 学号,整型数字char name[20];      // 姓名,字符数组(字符串)float score;        // 成绩,浮点数(可以有小数)
};

这段代码定义了一个结构体类型 Student,它有三个成员:

  • id:用来存学号
  • name:用来存姓名(最多20个字符)
  • score:用来存成绩

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

定义好结构体类型后,我们可以创建这种类型的变量。
创建变量的方法和普通变量类似,例如:

// 创建一个 Student 类型的结构体变量 stu1
struct Student stu1;

如果想在创建的同时给成员赋初值,可以使用初始化列表:

// 按照成员顺序初始化,依次赋值给 id、name、score
struct Student stu2 = { 1001, "Alice", 88.5 };// 或者使用“指定成员”的方式初始化(顺序可以不同)
struct Student stu3 = { .name = "Bob", .score = 92.0, .id = 1002 };

这样,stu2 中的 id 就是 1001,name 是 “Alice”,score 是 88.5。

1.4 使用 typedef 简化结构体名称

为了让结构体的使用更加方便,我们常常使用 typedef 给结构体取个别名。
例如:

// 定义结构体并取别名 Person
typedef struct {char name[20];int age;
} Person;// 现在可以直接用 Person 来声明变量
Person p1 = { "Charlie", 25 };

这样以后声明变量时就不用写 struct Person 了,直接用 Person 即可。

1.5 结构体的自引用

有时我们需要构造“链表”等数据结构,此时需要结构体中包含指向自身类型的指针。
注意:不能直接在结构体中嵌入结构体本身,否则会无限递归,导致大小无法确定。
正确的做法是使用指针,例如:

// 定义链表节点的结构体
typedef struct Node {int data;           // 数据struct Node *next;  // 指向下一个节点的指针
} Node;

在这个例子中,每个 Node 包含一个整型数据和一个指向下一个 Node 的指针,从而可以构成链表。


二、结构成员的访问操作符

当我们定义好结构体变量后,需要访问其中的成员。有两种常用的方式:

2.1 使用点号 . 直接访问

对于结构体变量,使用点号 . 来访问其中的成员。例如:

// 假设我们有一个 Student 类型的变量 stu1
stu1.id = 1001;             // 给学号赋值
printf("Name: %s\n", stu1.name);  // 打印姓名

2.2 使用箭头 -> 通过指针访问

如果我们有一个指向结构体的指针,就用箭头 -> 来访问成员。例如:

// 定义一个指向 Student 结构体的指针
struct Student *ptr = &stu1;// 通过指针访问成员
ptr->score = 95.5;           // 修改成绩
printf("ID: %d\n", ptr->id); // 打印学号

箭头操作符是 (*ptr).成员 的简写,写起来更方便。


三、结构体内存对齐

当 CPU 访问数据时,为了提高效率,通常要求数据按照特定的边界存储,这就是内存对齐
简单来说,内存对齐就是让数据存储在“整齐”的地址上。

3.1 为什么需要内存对齐?

  • 硬件要求:有的计算机硬件要求数据必须存放在某个地址边界上,否则会产生错误。
  • 提高效率:对齐后的数据可以让 CPU 一次性读取完整数据,速度更快。如果数据没有对齐,可能需要多次读取。

3.2 内存对齐的规则

假设在 32 位系统中:

  • char 类型占 1 个字节
  • int 类型占 4 个字节

下面举个例子说明对齐的概念:

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

可能的内存布局如下:

  • c1:占 1 个字节,从地址 0 开始。
  • 填充:为了让后面的 i 对齐到 4 的倍数,编译器在 c1 后面自动添加 3 个填充字节。
  • i:占 4 个字节,从地址 4 开始。
  • c2:占 1 个字节,紧接在 i 后面存放,地址 8。
  • 尾部填充:为了让整个结构体的大小是最大的对齐数(这里是 4)的整数倍,后面可能填充 3 个字节。

这样整个结构体的大小就是 12 个字节,而不是简单的 1+4+1=6 个字节。

3.3 修改默认对齐数

如果你想让结构体更紧凑,可以使用 #pragma pack(n) 指令来改变默认对齐数。例如,将对齐设置为 1 字节:

#pragma pack(1)  // 设置对齐数为 1 字节
struct Test {char a;int b;char c;
};
#pragma pack()   // 恢复默认对齐// 现在 Test 结构体的大小为 1 + 4 + 1 = 6 字节(没有填充)

注意:降低对齐数可能会影响程序运行速度,因为数据访问可能不再高效。


四、结构体传参

在函数调用时,我们可以将结构体作为参数传递。主要有两种方式:

4.1 值传递

直接传递结构体变量的值。
这种方式会把整个结构体的内容复制一份传递给函数,如果结构体很大,复制的过程会占用更多时间和内存。

void printStudent(struct Student stu) {printf("ID: %d, Name: %s\n", stu.id, stu.name);
}int main() {struct Student stu = { 1001, "Alice", 88.5 };printStudent(stu);  // 将 stu 的一份副本传入函数return 0;
}

4.2 指针传递

传递结构体的地址,只复制一个指针。
这种方式效率更高,并且在函数中修改结构体内容会影响到原变量。

void modifyStudent(struct Student *stu) {// 使用箭头操作符访问成员stu->score = 100;
}int main() {struct Student stu = { 1002, "Bob", 92.0 };modifyStudent(&stu);  // 传递 stu 的地址printf("New score: %.1f\n", stu.score);  // 输出 100.0return 0;
}

推荐在传递大结构体时使用指针传递。


五、结构体实现位段

5.1 什么是位段?

有时我们只需要一个数据的几个二进制位,而不是整个字节。
位段(bit-field)就是用来指定一个结构体成员占用多少个二进制位。
位段常用于硬件编程、网络协议等对内存要求非常严格的场合。

5.2 位段的声明方法

位段的声明语法与结构体类似,只不过在成员名后面加上冒号和位数:

struct Flags {unsigned int flag1 : 1;  // 只占 1 位(0 或 1)unsigned int flag2 : 3;  // 占 3 位(可表示 0~7)unsigned int flag3 : 4;  // 占 4 位(可表示 0~15)
};

这里,flag1 只需要 1 个比特位,flag2 用 3 个比特位,flag3 用 4 个比特位。
总共占用的位数为 1 + 3 + 4 = 8 位,正好 1 个字节。

5.3 位段的内存分配

  • 同一存储单元:相邻的位段成员如果属于相同的类型且剩余位足够,会被存放在同一个存储单元中(通常是 1 个、2 个或 4 个字节)。
  • 不足则分新单元:如果剩余空间不足以存放下一个位段,则会从下一个存储单元开始存放。

例如:

struct Bits {char a : 3;char b : 4;char c : 5;char d : 4;
};

在某些编译器下,ab 可能会存放在同一字节中;如果一字节不够存放 c 的 5 位,则 c 可能从下一个字节开始,d 同理。
注意:不同编译器可能在位段存储细节上略有差异。

5.4 位段的跨平台问题

由于位段的存储和对齐规则依赖于编译器和平台,可能会出现以下问题:

  • 存储顺序:某些编译器可能将位段从左到右存储,有的则相反;
  • 符号问题:位段默认是有符号还是无符号可能不同;
  • 填充方式:当位段不足时,编译器如何填充可能不同。

因此,使用位段时要特别注意代码的可移植性。

5.5 位段的使用注意事项

  • 不能取地址:由于位段可能并不占满一个完整的字节,不能对位段成员使用取地址符 &。这意味着你不能获取位段成员的指针。
  • 赋值范围:给位段赋值时,要确保数值不超过其位数能表示的范围,否则可能出现错误或数据截断。
  • 跨平台谨慎使用:如果你的代码需要在多个平台上运行,请慎重使用位段,因为它们的具体存储方式可能会有所不同。

总结

本文详细介绍了 C 语言中结构体的知识点,内容包括:

  1. 结构体的声明与使用:如何定义结构体、创建结构体变量、使用 typedef 简化类型以及如何实现结构体的自引用。
  2. 结构体成员的访问:介绍了使用点号 . 和箭头 -> 两种方法访问结构体成员。
  3. 内存对齐:解释了为什么需要内存对齐、内存对齐的规则以及如何使用 #pragma pack 调整对齐方式,让数据在内存中存放得更加“整齐”。
  4. 结构体传参:讲解了值传递和指针传递两种方式,并指出大结构体使用指针传递可以提高效率。
  5. 位段:介绍了位段的基本概念、声明方法、内存分配方式及使用中的注意事项。

希望这篇博客能帮助你对 C 语言中的结构体及相关概念有一个全面而清晰的认识。如果有任何问题或疑问,欢迎在评论区留言讨论,一起进步!

关键字:小众写作网站_无锡网站制作推荐_域名ip查询_培训课程安排

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: