一、结构体的声明
1.结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
2.结构的声明
struct tag//(tag是一个结构体的标签)
{
member-list;//结构体的成员变量
}variable-list;//结构体变量
例:如果我们简述一个学生的信息
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //重点:::分号不能丢
注意:::** 这里我们发现,我的这个例子似乎和上面我们给的声明不一样,那难道是我写错啦???**
其实是因为我们如果按照第一种方式创建的结构体变量是一个全局变量,第二种如果我们要去使用这种结构体,我们需要去在main函数中创建,在main函数中创建的变量我们称为临时变量,它只要出了main函数的大括号就销毁了。
3.特殊的声明
注:在声明结构的时候,可以不完全的声明。
例如:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
int main()
{
p = &x;
}
//我们发现这两个结构体都缺了一个东西就是我们结构体的标签
我们将这样的声明叫做匿名结构体声明。
这个时候如果给你这样的式子,它合法吗???
如果我们将它放在编译器里面编译
我们发现编译器竟然报了一个警告
它说这两个类型不兼容,这就是编译器会将上面的两个声明当成完全不同的两个类型,所以是非法的。
二、结构体的自引用
在结构中包含一个类型为该结构本身的成员是否可以呢?
struct Node
{
int data;
struct Node next;
};
//可行否?
//如果可以,那sizeof(struct Node)是多少?
答案是不可行的,因为你想一下如果sizeof想要计算它的大小,将会像循环一样一直进入struct Node。
正确的写法应该是这样
struct Node
{
int data;
struct Node* next;
};
让它去寻找到下一个结构体的地址,然后可以将它们像链条一样串联起来
当你在函数内部想定义一个结构体变量的时候,我们需要这样去写
struct Node
{
int data;
struct Node *next;
};
int main()
{
struct Node s;
return 0;
}
所以在这个时候,我们就想起来了自定义类型,减少定义结构体变量时我们写的长度。
typedef struct
{
int data;
Node* next;
}Node;//所以我们这样写
这样写是不行的,因为在这里我们还在使用typedef定义结构体的名字。
//正确的方法是
typedef struct Node
{
int data;
struct Node* next;
}Node;
注:当然我们一般不使用自定义类型,因为可能会将结构体搞混,看到名字不知道是哪个结构体类型
三、 结构体变量的定义和初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
int main()
{
struct Point p2; //定义结构体变量p2
struct Point p3 = {x, y};//初始化:定义变量的同时赋初值。
}
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
int main()
{
struct Stu s = {"zhangsan", 20};//初始化
}
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
int main()
{
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
}
四、结构体内存对齐
结构体的对齐规则
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注: 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
VS中默认的值为8- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//练习1
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
}
由对齐数的规则,char类型的对齐数是1,int类型的对齐数是4
偏移量(从0开始)要是对齐数的整数倍
结构体总大小为最大对齐数的整数倍(最大对齐数为4)
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S2));
}
由对齐数的规则,char类型的对齐数是1,int类型的对齐数是4
偏移量(从0开始)要是对齐数的整数倍
结构体总大小为最大对齐数的整数倍(最大对齐数为4)
重点:这里我们发现,这两道题的所拥有的数据都一样
但是,我们发现这里的结果是8,这是因为我们的储存顺序不一样。
注:这里我们可以发现变量的创建顺序也会影响它的内存存储,所以我们可以合适的创建变量,然后节省空间。
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S3));
}
由对齐数的规则,char类型的对齐数是1,int类型的对齐数是4,double类型的对齐数是8。
偏移量(从0开始)要是对齐数的整数倍
结构体总大小为最大对齐数的整数倍(最大对齐数为8)
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
}
由对齐数的规则,char类型的对齐数是1,double类型的对齐数是8。
偏移量(从0开始)要是对齐数的整数倍
结构体总大小为最大对齐数的整数倍(最大对齐数为8)
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数(对齐数是8)的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
五、修改默认对齐数
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
六、结构体传参
struct S
{
int data[1000];
int num;
};
struct S 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);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
这里我们看到的结果是相同的,但是其实在内存上第一种方式是不好的,之前我们说过形参是实参的一份临时拷贝,既然是拷贝那就一定会占用内存空间。
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
结论:
结构体传参的时候,要传结构体的地址。