目录
一、结构体语法基础
1.1结构体的创建
struct 结构体名
{
int a; //成员变量名
char b;
……
};
结尾的分号不能丢!!!
1.2结构体变量的定义
//法一:声明全局变量
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
//法二:声明全局变量
struct Point
{
int x;
int y;
}
int main()
{
struct Point p2; //定义结构体变量p2
}
1.3结构体变量的初始化
struct Stu //类型声明
{
char name[50]; //名字
int age; //年龄
};
struct Stu s1 = {"Lily", 18};//初始化
struct Stu s2 = {.age=18, .name="Lily"};//指定顺序初始化
1.4特殊的结构体声明
//匿名结构体
struct
{
int a;
char b;
double c;
}x;
缺陷:匿名的结构体,如果没有对结构体类型重命名的话,只能使⽤⼀次。
1.4结构体的自引用(链表的创建)
//法一
struct Node
{
int data;
struct Node next;
};
//法二
struct Node
{
int data;
struct Node* next;
}
法一错误,法二正确
1.5结构体成员的访问
- 直接访问:" . "操作符
#include <stdio.h>
struct example
{
int x;
int y;
}s = {1,2};
int main()
{
printf("x: %d y: %d\n", s.x, s.y);
return 0;
}
- 间接访问:"->"操作符
#include <stdio.h>
struct example
{
int x;
int y;
};
int main()
{
struct Point p = {1, 2};
struct Point *ptr = &p;
ptr->x = 10;
ptr->y = 20;
printf("x = %d y = %d\n", ptr->x, ptr->y);
return 0;
}
1.6结构体传参
struct S
{
int data[1000];
int num;
}s = {{1,2,3,4}, 1000};
//法一:结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//法二:结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
法二优于法一:只传一个指针的内存开销小于传结构体的内存开销
二、结构体的内存
2.1引入
#include<stdio.h>
struct s1
{
char c1; //1个字节
int i; //4个字节
char c2; //1个字节
};
int main()
{
struct s1 s = { 1,2,3 };
printf("%zd", sizeof(s));
return 0;
}
按照正常理解,占用内存应该为6个字节。但是结果却为12个字节。
2.2结构体内存对齐规则
- 结构体的第⼀个成员要对⻬到和结构体变量起始位置偏移量为0的地址处。
- 其他成员变量要对⻬到对⻬数的整数倍的地址处。
- 对⻬数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较⼩值。
- 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对齐数中最⼤的)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体成员对齐到⾃⼰的成员中最⼤对齐数的整数倍处,结构体的整体大小就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
- 注意:不同环境下不同编译器中默认的对齐数不同。VS编译器默认为8,Linux环境下gcc编译器无默认对齐数
- 如何修改默认对齐数?
#pragma pack(1) //设置默认对⻬数为1 #pragma pack() //取消设置的对⻬数,还原为默认对齐数
2.3为什么要存在内存对齐?
解释仅作参考:内存对齐是为了提高计算机系统的访问效率。当数据结构或变量按照规定的边界对齐存放在内存中时,CPU可以更高效地访问这些数据,避免因访问未对齐的数据而产生额外的开销。此外,一些体系结构要求数据的地址必须是某种特定大小的倍数,否则可能导致错误或者性能下降。因此,通过内存对齐可以确保数据存储和访问的正确性和效率。
2.4题目讲解
回到开始的题目
struct S1
{
char c1;
int i;
char c2;
};
三、位段相关知识
3.1位段定义
- 位段的成员必须是 int、unsigned/signed int 、char等类型 。C99中位段成员的类型也可以选择其他类型
- 位段的成员名后边有⼀个冒号和⼀个数字。
- 数字代表指定一个结构体成员占用的位数。
struct A { int _a:1; int _b:2; int _c:3; int _d:10; };
3.3位段应用
位段通常用于需要精细控制数据存储方式的场景,例如硬件寄存器的位域定义、节省内存空间等。在定义位段时,可以指定成员名字、位宽以及可选的符号性质(有符号或无符号)。
3.4注意事项
- 不同机器的最大位数不同,C语言标准未规定位段中的成员在内存中从左向右分配,还是从右向左分配。因此位段存在移植性低的问题,跨平台使用会出现问题
- 位段的⼏个成员共有同⼀个字节,意味着有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。所以不能对位段的成员使⽤&操作符!!
struct A
{
int _a:1;
int _b:2;
int _c:3;
int _d:10;
};
int main()
{
struct A s = {0};
//错误
scanf("%d", &s._b);
//正确
int b = 0;
scanf("%d", &b);
s._b = b;
return 0;
}