1、结构体的引入
我们都知道,C语言本身给我们提供了许多数据类型:整形(int、long....)、浮点型(float、double)、字符型还有数组、字符串。但在实际应用中,只有这些数据类型是不够的,有时候我们需要几种数据类型来修饰一个变量,例如一个学生的基本信息就需要:姓名(字符串),年龄(整形),体重(浮点型)等,这些数据类型各不相同,但是他们又要表示一个整体,所以我们在这时候就需要一个新的数据类型:结构体。
2、结构体的定义
结构体就是一些变量的集合,每个成员变量的类型可以不相同;
3、结构体的声明
3.1结构体的声明
例如描述一个学生
struct Stu
{
char name[20];//一个字符串用来表示姓名
int age;//一个整形用来表示年龄
char id[10];//一个字符串用来表示学号
};//分号不能丢
3.2结构体变量的创建和初始化和访问
#include<stdio.h>
struct Stu
{
char name[20];
int age;
char id[13];
};
int main()
{
struct Stu s2 = { "张三",18,"2419562412"};//s2是局部变量
printf("%s\n", s2.name);//结构体访问
printf("%d\n", s2.age);
printf("%s\n", s2.id);
return 0;
}
运行我们便得到
也可以使用指针来访问结构体
#include<stdio.h>
struct Stu
{
char name[20];
int age;
char id[13];
};
int main()
{
struct Stu s2 = { "张三",18,"2419562412"};
struct Stu* ps = &s2;//定义指向s2的指针
printf("%s\n", ps->name);//结构体访问
printf("%d\n", ps->age);
printf("%s\n", ps->id);
return 0;
}
4、结构体的内存对齐
我们已经掌握了结构体的基本使用了。
现在我们深入讨论一个问题:计算结构体的大小。
#include<stdio.h>
struct s
{
char a;
int b;
char c;
}s1;
int main()
{
printf("%zd",sizeof(s1));
return 0;
}
如上代码执行结果是什么呢?按照我们的惯性思维char类型是1字节,int占4字节,很明显输出结果应该是6;但实际上的结果却是12,为什么呢? 原因是在结构体中的元素在内存中存储的时候不是一个挨着一个存储的,而是按照内存对齐的规则存储的,内存对齐的规则如下
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
因此 struct s s1 在内存中其实是这样存储的 “X”表示浪费的内存空间,故s1占用内存为12个字节
那有怎样缩小s1的空间呢?很简单
struct s
{
char a;
char c;
int b;
}s1;
此时s1在内存中的存储结构就变成了下图所示,再用sizeof计算内存大小就变成了8字节
5、结构体传参
了解以上知识后,我们继续考虑结构体传参
请看以下代码
#include<stdio.h>
struct A
{
int arr1[1000];
char a;
float b;
};
struct A s = { {1,2,3,4}, 'a',3.14};
void print1(struct A a)
{
printf("%f\n", a.b);
}
void print2(struct A* ps)
{
printf("%f\n", ps->b);
}
int main()
{
print1(s);
print2(&s);
return 0;
}
对比print1和print2的两种传参方式,print1中以结构体接受参数虽然没有问题,但是使用print1时,会将实参s里的数据拷贝给形参a,这就比较消耗内存了,反观print2传地址的方式就很节省空间,所以我们使用结构体传参时优先考虑穿结构体的地址。
6、结构体实现位段
6.1 什么是位段
位段和结构体声明有些类似 ,有两点不同
(1).位段的成员必须是 int 、signed int、unsigend int或者是char类型的变量
(2). 位段的成员名后边有⼀个冒号和⼀个数字。
比如:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
A便是一个位段;
6.2位段的内存分配
举个例子
struct A
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
}s;
int main()
{
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
a,b,c,d在内存中的存储方式为
我们可以调试以上程序观察内存验证结果如下
6.3位段使用的注意事项
struct A
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct A sa = { 0 };
scanf("%d", &sa.b);//这是错误的
//正确的⽰范
int b = 0;
scanf("%d", &b);
sa.b = b;
return 0;
}
位段使⽤的注意事项 位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。 所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊ 放在⼀个变量中,然后赋值给位段的成员。
完。