结构体是一种用户自定义的数据类型,可以用来存储不同类型的数据项。
结构体的声明和定义
将一些不同类型组合起来描述自定义的类型,例如:
struct stu
{
char name[20];
int age;
char id[20]; // 这三者均为结构体的成员变量
}s1; // s1指结构体变量(第一种定义方式)
struct stu s2; // s2指结构体变量 (第二种定义方式)
初始化 -- 三种方式
struct stu
{
char name[20];
int age;
char id[20];
}s1,s2,s3;
struct stu s1 = { "张三",20,2024001 }; // 第一种,按顺序声明
struct stu s2 = { .age = 21,.id = 2024002,.name = "李四" }; // 第二种,指定顺序初始化
// 定义内部结构体
struct Date {
int day;
int month;
int year;
};
// 定义外部结构体,内部包含一个 Date 结构体作为成员
struct Employee {
char name[20];
int age;
struct Date TheDate;
};
// 初始化外部结构体变量
struct Employee emp1 = { "ice", 30, {1, 1, 2024} }; // 第三种,结构体嵌套初始化
访问--两种方式
.
运算符用于访问结构体变量的成员,该结构体变量通过结构体名直接访问。而 ->
运算符用于访问结构体指针所指向的结构体变量的成员。 例子:
#include <stdio.h>
// 定义结构体
struct Point {
int x;
int y;
};
int main() {
// 使用 . 访问结构体成员
struct Point p1;
p1.x = 10;
p1.y = 20;
printf("Using . : x = %d, y = %d\n", p1.x, p1.y);
// 使用指针和 -> 访问结构体成员
struct Point p2;
struct Point *ptr = &p2; // 指针ptr指向结构体变量p2,可以通过指针ptr来访问和操作结构体变
// 量p2 的成员
ptr->x = 30;
ptr->y = 40;
printf("Using ->: x = %d, y = %d\n", ptr->x, ptr->y);
return 0;
}
自引用和匿名结构体
匿名结构体是在定义结构体变量的同时省略结构体名,只使用结构体内部的成员来创建结构体变量的一种方式。匿名结构体通常用于临时场景或者简单的数据结构中。
struct Node
{
int data;
struct Node *next; // 不可以struct Node next ,如果这样结构体变量的⼤
// ⼩就会⽆穷的⼤,是不合理的。
};
结构体传参
原因:
结构体内存对齐
规则:
相关图示
那为什么有内存对齐?
为了保证数据在内存中以高效、可预测的方式存储和访问,从而提高程序的性能和可靠性,拿空间来换取时间的做法
#pragma pack() //取消设置的对齐数,还原为默认
结构体位段
位段的声明和结构是类似的,有两个不同:
位段内存分配
位段跨平台问题(了解):
位段应用(了解):
嵌入式系统中的设备寄存器映射:在嵌入式系统开发中,通常需要与硬件设备进行交互,如读取或写入设备的控制寄存器。这些设备寄存器中的各个位通常用于表示不同的控制或状态信息,使用位段可以方便地对这些位进行操作,提高程序的可读性和可维护性。
网络协议编程:在网络编程中,常常需要对数据包的各个位进行解析或者构造。使用位段可以方便地定义数据包的结构,提取或设置其中的各个字段,以便进行网络通信操作。
节省内存空间:在内存有限的嵌入式系统或者对内存占用有严格要求的系统中,使用位段可以有效地节省内存空间。例如,一个只需要 3 位来表示的状态标志,使用传统的整型数据类型可能浪费了较多的位数,而使用位段则可以节省内存空间。
注意事项:
位段的⼏个成员共有同⼀个字节,内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的,所以不能对位段的成员使⽤&操作符。
正确做法
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//这是错误的
//正确的
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}