目录
一、结构体类型的声明
struct stu
{
char arr[20];
int age;
int hight;
}s1;
其中struct——结构体关键字,stu——结构体标签,是根据自己的需求写,这里我举例为学生,大括号里面的是结构体成员变量,s1——结构体变量,这里注意,虽然在创建结构体的时候,加了大括号后末尾会自动分号“;”,但是还是不要忘记不能丢。
结构体还可以匿名声明,就是不写结构体标签:
struct
{
char arr[20];
int age;
int hight;
}s1;
这种结构体变量也能用,不过只能在大括号后面和分号中间创建变量,在其他地方都不能创建变量,
另外,如果再创建一个一样的结构体,成员变量也一模一样的结构体指针p,是不能取地址s1赋值给p的,因为编译器会认为它们两个是不同的结构体类型。
二、结构体的自引用
结构体是可以包含一个结构体的,但是这个被包含的结构体的大小必须是确定了的,否则这个大的结构体大小也无法确定。
struct Node
{
int data;
struct Node next;
};
像这样结构体中包含自己,那就是无法算出它的大小,如果想包含,就在成员变量里的结构体加一个“*”,表示是一个指针,大小就是4个字节:
struct Node
{
int data;
struct Node* next;
};
三、结构体变量的定义和初始化
1、结构体变量的定义
struct stu
{
char arr[20];
int age;
int hight;
}s1 = { "王五",19,183 };
struct stu s2 = { "李四",18,175 };
int main()
{
struct stu s3 = { .arr = "张三",.age = 18,.hight = 180 };
struct stu s2 = { .age = 19,.hight = 183,.arr = "王五" };
printf("姓名:%s 年龄:%d 身高:%d\n", s1.arr, s1.age, s1.hight);
printf("姓名:%s 年龄:%d 身高:%d\n", s2.arr, s2.age, s2.hight);
printf("姓名:%s 年龄:%d 身高:%d\n", s3.arr, s3.age, s3.hight);
return 0;
}
我们有三种定义方式:
1、在创建结构体的时候一起创建了结构体变量,例如s1,
2、在main函数外部创建结构体变量,例如s2,
3、在main内部创建的变量,例如s3,
其中s1和s2是全局变量,s3是局部变量。
2、结构体变量的初始化
可以在创建变量的同时一起初始化,也可以在函数内部进行初始化,在函数内部初始化可以打乱顺序,即在括号内指定成员变量进行初始化。
四、结构体内存对齐
我们知道char占1个字节,short占2个字节,int占4个字节,float占4个字节,long占8个字节,double占8个字节,那么一个结构体大小是几呢?
计算方法如下:
1、第一个成员在与结构体变量偏移量为0的地址处。
2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,在vs中默认对齐数是8。
3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐
数)的整数倍。
举个例子:
struct stu
{
char arr;
int age;
int hight;
}s1;
第一个成员变量是char类型,1个字节,比默认对其数8小,所以对齐数为1,从0开始,占1个字节空间。
第二个是int类型,4个字节,默认对齐数是8,而int是4,所以对齐数是4,前面只占了一个字节的空间,不够4的倍数,所以只能浪费空间,从4开始,占4个空间(4,5,6,7),
第三个也是int类型,此时,正好是从8开始分配空间(8,9,10,11)。
从0~11,总共是12个字节,是最大对其数4的倍数,所以该结构体的大小为12。
再来一道题:
struct stu
{
char arr[3];
int age;
int hight;
}s1;
struct A
{
int a;
struct stu s1;
}A1;
int main()
{
printf("%d\n", sizeof(A1));
//printf("%d\n", sizeof(s1));
return 0;
}
该输出结果为16,这里要注意两个点:
第一,s1中的char类型是个数组,是占3个字节,经过计算,该结构体的大小为12,
第二、A1中包含s1结构体,算大小的时候,先给a分配4个字节空间,在看s1中的最大对齐数,是4,所以不用浪费空间,直接从4开始分配,分配空间大小就是s1的大小,,所以是4 + 12 = 16个字节。
搞这么麻烦,为什么要这样设置内存对齐呢?
1、平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内
存访问仅需要一次访问。
那么如何有效的减小空间呢?
我们可以在创建结构体变量时,把结构体成员变量的类型大小从小到大排。
五、修改对齐数
这里我们用到一个预处理指令:#pragma
#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;
}
传值,相当于是临时拷贝一份,而传址,是把地址传过去,我们优先考虑传址,因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销就比较大,所以会导致性能的下降。