之前我们学过数组,数组是什么呢?数组是一些同类型的数据的集合,而结构体是不同类型数据的集合
1.1结构体的声明
结构体的声明分完全声明和匿名声明
完全声明
struct tag
{
member-list;
}variable-list;
struct tag
类型名member - list
是成员变量。variable - list
声明结构体后创建的变量(属于全局变量),可以创建多组使用,
号隔开。
例如,数据库中学生表学生的结构,其中包含名字、学号、成绩。
struct student
{
char name[20];
char students[15];
double scores;
}stu1;
匿名结构
只能使用一次的结构体声明也只能在声明结构后创建变量。
struct
{
char name[20];
char students[15];
double scores;
}stu1,stu2;
stu1
和stu2
两个变量的类型是相同的,但是在 该程序中无法再创建一个同类型的变量.
即使有两个相同成员变量的匿名结构体,他们也是不相同的,如图可以看到运行时编译器发生了类型不匹配的警告
1.2结构体的自引用
结构体的自引用是怎样创建的呢?需要注意的是结构体只能使用结构体指针来完成结构体自引用操作
struct Node
{
int data;
struct Node * next;
}
1.3. 结构体变量的定义和初始化
结构体变量的定义十分简单,可以声明结构体的同时创建变量如:
struct student
{
char name[20];
char students[15];
double scores;
}stu1;
声明了student类型的同时创建了变量stu1
;
也可自行创建变量
struct student s1;
结构体量的初始化
结构体的初始化可以如数组类似在对大括号里按照结构体成员变量的顺序依次赋值即可。
如:
struct student s1 = {"zhangsan","zh2222005163951",56.5};
也可以指定成员变量初始化
struct student stu1 = {.scores = 75.5,.name="hello"};
也和数组类似可以使用不完全初始化。
s1 = {0}
嵌套结构体则是在大括号里放一个括号
struct Point
{
int x;
int y;
};
struct num
{
int num;
struct Point;
}
struct num n = {5,{2,3}};
1.4 结构体对齐
了解了结构体,那么我们知道结构体大小吗?
结构体1 占用大小为什么是 8 字节呢?
看看代码2
代码2 就是 代码1成员变量位置修改了一下,为什么字节又占用飙升到 12 呢?
想要深入探究这个问题,我们需要先知道 结构体内存对齐规则
1.4.1结构体内存对齐
结构体成员不是按照顺序在内存中连续存放的,而是有一定对齐规则。
规则:
- 结构体的第一个成员永远放在相较于结构体变量起始位置偏移量为0的位置。
- 从第二个成员开始,往后的每个成员都要对齐到某个对齐数的整数倍处。
对齐数:结构体成员自身的大小和默认对齐数的较小值,
vs上默认对齐数:8,gcc:没有默认对齐数,对齐数就是结构体成员的自身大小 - 结构体的总大小,必须是最大对齐数的整数倍
最大对齐数是:所有成员的对齐数中最大的值 - 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐倍数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
以代码1 为例:
num
成员是结构体第一个成员变量,所以在结构体开辟的内存中从0处开始漂移,又因它属于int
类型占用了四个字节,漂移位置从0漂移到4.
ch0
的对齐数就是它的字节数,和默认对齐数对比它的对齐数最小,此时漂移位置为4,属于ch0
的对齐数的倍数,占用了4,ch1
同理.
最终占用字节为6,而这时结构体成员变量中最大的对齐数为4,而现占的字节内存不属于最大的对齐数的倍数,下漂移两个字节漂移到7位置,现所占字节 8 字节属于最大对齐数的倍数.
最后该结构体占用字节:8字节
代码2 为例:
ch0
是第一个成员变量,所以占用结构体开辟的内存中起始位置.此时漂移到1
num
经过对比,最终对齐数是4,此时漂移数1不是4的倍数,漂移到4开始占用内存
ch1
则是占用8这个位置,此时所占字节为 9 字节,不是结构体成员最大对齐数,逻辑下移到直到 是 最大倍齐数的整数倍,所以最终对齐数为 12字节.
嵌套结构体大小:
#include <stdio.h>
struct student
{
char ch0;
int num;
char ch1;
};
struct Node
{
short sh[3];
struct student st;
};
int main() {
printf("%d", sizeof(struct Node));
return 0;
}
注意:结构体中存在数组时,该数组变量对齐数为数组成员对齐数,当存在指针时,则根据运行环境判断对齐数.
sh[]
是第一个成员变量,但又是一个数组,数组的对齐数则是数组成员对齐数,所以从0开始占用6个字节
st
是一个结构体变量,内嵌的结构体变量的对齐数则是该结构体变量中最大成员变量对齐数,6位置,不属于该变量的整数倍,逻辑下移到8处,从该字节开始开辟st
数据
最后该字节所占用字节20个字节,20个字节又是成员变量最大对齐数4的整数倍,所以该结构体占用20个字节.
1.4.2为什么会存在结构体对齐呢?
这是因为结构体的内存对齐是拿空间来换取时间的做法。
在设计结构体的时候,想要对齐和所占空间小
尽量使占用空间相同的变量成员集中在一起.
需要修改默认对齐数可以使用
#pragma pack(8)修改默认对齐数.
#pragma pack()恢复默认对齐数为默认.
1.5 结构体传参
#include <stdio.h>
struct student
{
char ch0;
int num;
char ch1;
}s1 = {'x',5,'c'};
//传值
void foc_1(struct student stTmp) {
printf("%c", stTmp.ch0);
}
//传址
void foc_2(struct student * stTmp){
printf("%c", stTmp->ch0);
}
int main() {
foc_1(s1);
foc_2(&s1);
return 0;
}
在使用结构体传参时我们应该注意:
- 传值既在开辟的函数空间中重新创建一份同类型结构体拷贝,空间开销大
- 传址调用,既传递内存地址给函数,空间开销小