一、结构体类型的声明
需要注意到的是,结构体类型的书写是关键字+标签:如 struct Stu
struct Stu a[ 20 ] ; //定义一个结构体数组
struct Stu * p ; //定义一个结构体指针
struct Stu
{
int age;
char name[10];
char tele[12];
};
在之前我们已经学习了基础知识,现在学习一些特殊的形式。
typedef重命名
typedef struct Stu
{
int age;
char name[10];
}Stu;
struct Stu s1;
Stu s2; //typedef的效果
匿名结构体类型
即在声明的时候省略掉结构体标签。
struct
{
int a;
char b;
doubel c;
}x;
struct
{
int a;
char b;
doubel c;
}a[20],*p;
p = &x; //warning
注意,当对一个匿名结构体类型地址赋值是会有警告,因为编辑器会将两个匿名声明当作两个不同的类型。
二、结构的自引用
结构体自引用即在结构中包含一个结构自己这样的一个成员。
自引用体现出链表的数据结构,即找到第一个数据后,同时会指向下一个数据,如此使得需要的数据可以被连续起来。
struct Node
{
int data;
struct Node next;
};
如此自引用是错误的,因为每一次引用结构体其内都会含有一个结构体,如此无休止所以非法。
struct Node
{
int data;
struct Node* next;
};
正确的自引用步骤应该是以一个指向结构体的指针为结构体成员,如此在定义结构体变量时在不需要引用时,赋值一个空指针即可。
typedef struct Node
{
int data;
Node* next;
}Node;
//error
如此书写是错误的,因为无法确定时先重命名为Node还是先在结构体内定义指针。
三、结构体变量的定义和初始化
struct S
{
double weight;
char c;
};
struct Stu
{
int age;
char name[10];
struct S s;
char tele[12];
}s1; //全局变量
struct Stu s2; //全局变量
int main()
{
struct Stu s3 = {20;"张三";{56.3,'w'};"12345678900"}; //局部变量
return 0;
}
四、结构体内存对齐
小试牛刀:计算结构体的大小
struct S1
{
char c1;
int a;
char c2;
};
struct S2
{
char c1;
char c2;
int a;
};
struct S3
{
char c1;
struct S1 s1;
double d;
}
int main()
{
struct S1 s1 = {0};
printf("%d\n",sizeof(s1)); // 12
struct S2 s2 = {0};
printf("%d\n",sizeof(s2)); // 8
struct S3 s3 = {0};
printf("%d\n",sizeof(s3)); // 24
return 0;
}
引出结构体储存方式,需要了解结构体对齐规则。
1)结构体第一个成员在结构体变量偏移量为0的地址处。
2)其余成员的偏移量和对齐数的整数倍对齐,跳过的空间被浪费。
对齐数 = min(编译器默认对齐数,成员大小)。
vs默认对齐数为8,那么对于一个int成员,对齐数就是min(8,4)= 4 。
3)结构体总大小为成员中最大对齐数的整数倍(不足则扩大至满足条件)。
4)如果嵌套结构体,那么嵌套的结构体对齐到其成员最大对齐数的整数倍的偏移量地址,总结构体在计算总大小比较对齐数时,将嵌套的结构体内成员的对齐数也要考虑在内。
分析举例:
对于上述代码struct S1,char c1处于偏移量为0处,占一个字节;int对齐数为4,应和偏移量4的倍数对齐,即为4处,与char c1间的三个字节被浪费,int占4个字节;char c2对齐数为1,则可以储存到8处。结构体总大小应该为对齐数(1,4,1)最大者的整数倍,即4的整数倍,现在为9个字节,即浪费三个字节到11为止,总计为12个字节。
对于上述代码struct S2,char c1处于偏移量为0处,占一个字节;char c2对齐数为1,则可以储存到1处;int对齐数为4,应和偏移量4的倍数对齐,即为4处,与char c2间的两个字节被浪费,int占4个字节。那么结构体总大小应该为对齐数(1,1,4)最大者的整数倍,即4的整数倍,现在共为8个字节,所以最后储存空间为8个字节。
对于上述代码struct S3,char c1处于偏移量为0处,占一个字节;struct s1的最大对齐数为4,则找到偏移量4的为止储存该结构体,与char c1之间的3个字节浪费,前面计算占12个字节;double d对齐数为8,则此时偏移数为16,所以不空字节,直接储存。那么结构体总大小应该为对齐数(1,(1,1,4),8)最大者的整数倍,即8的整数倍,现在共为24个字节,所以最后储存空间为24个字节。
之所以采取这种内存对齐的方式,是出于移植原因和性能原因考虑,因为一些硬件平台只能在某些特定的位置访问数据,所以需要这样对齐存放,并且对其存放便于处理器访问,以空间换时间。
为了尽量节省空间我们可以让占用空间小的成员尽量集中在一起。如上述S1和S2大小就有所不同。
修改默认对齐数
利用#pragma这个预处理指令可以改变默认对齐数。
//设置默认对齐数为1
#pragma pack(1)
struct S1
{
char c1;
int a;
char c2;
};
#pragma pack()
//取消设置的默认对齐数
int main()
{
struct S1 s1 = {0};
printf("%d\n",sizeof(s1)); // 6
return 0;
}
offsetof计算偏移量
可以通过offsetof宏来实现对成员偏移量的计算。
struct S1
{
char c1;
int a;
char c2;
};
int main()
{
printf("%d\n",offsetof(struct S1,c1)); // 0
printf("%d\n",offsetof(struct S1,a)); // 4
printf("%d\n",offsetof(struct S1,c2)); // 8
return 0;
}
五、结构体传参
之前强调了结构体的数据类型为 struct+标签,传参时也要注意。
注意传址调用和传值调用的区别与应用。
struct S
{
int a;
char b;
double c;
};
void Init(struct S* ps)
{
ps->a = 100;
ps->b = 'w';
ps->c = 3.14;
}
void Print(struct S s)
{
printf("%d %c %lf",s.a,s.b,s.c);
}
int main()
{
struct S s = {0};
Init(&s); //传址
Print(s); //传值
return 0;
}