结构体的声明和创建
一、概念
结构体是用来描述复杂对象的一种变量。结构是一些值的集合,这些值被称为成员变量,他们可以是不同类型的变量(变量,数组,指针等)。相比之下,数组是一组相同类型变量的集合。
二、结构体的声明与创建
声明格式:
struct tag {
member-list;}
variable-list;
其中tag是自定义的标签(类似于数组名);{}内是成员列表,内容为结构体包含的变量及类型;{}后面紧随的是变量列表,创建的是对应结构体的结构体变量,不过这里的变量并不是必须的,可以在之后的程序中进行创建等操作。创建的格式为:
struct tag variable={... ,... ,... }; variable为对应的变量名。紧随的“={}”是对结构体变量的初始化,初始化时,只需要在对应的位置输入内容,不同变量之间用“,”隔开就可以了。
以下为例子:
struct stu{ //结构体stu
char name[20];//定义姓名变量
int age; //年龄变量
float score; //成绩变量
}
s1={"zhangsan",20,95.0f},//创建的结构体变量,且在这里创建的属于全局变量
s2;//s2是未初始化的结构体变量
int main()
{
struct stu s3={"wangwu",18,100.0f};
//在主程序中创建结构体变量
return 0;
}
同时,结构体也可以进行嵌套即struct{... ;... ;struct{...}};在初始化时则需要相应的写成s={...,...,{...}}
三、结构体变量的使用
这里就必须提到结构成员访问操作符:. 和 -> 。
使用时的格式为:结构体变量.成员名 或者 结构体变量->成员名 。都表示该结构体变量对应成员中储存的数据。比如要将上述代码s1中的数据都打印出来,可以写:
printf("%s %d %f\n",s1.name,s1.age,s1.score};
四、结构体的特殊声明
在声明结构体时可以省略结构体标签(tag),这就是一个匿名的结构体类型。必须注意这种结构体类型只能使用一次。如下代码就存在问题:
//以下是两个成员一样的匿名结构体
struct{
int a;
char b;
} x; //变量
struct{
int a;
char b;
} *p;//指针
//由于成员一样但是匿名,在之后的程序中使用p=&x;将出现问题
五、结构体的自引用
结构体的自引用就是结构体内部包含一个同类型的指针,而不是包含一个同类型的结构体变量。
struct Node
{
int data;//存放数据
struct Node* next;//存放下一个节点的地址
};
如果要用typedef对匿名结构体类型进行重命名来完成结构体自引用,容易产生问题,这时应该在定义结构体时不使用匿名结构体 。
typedef struct Node
{
int data;//存放数据
struct Node* next;//存放下一个节点的地址
}Node;
结构体内存对齐
如果要计算结构体的大小,最简单的思路可能是将其中每一个成员变量的大小累加起来作为整体在内存中所占的大小,但事实上这是会出错的,这里涉及到结构体在储存时的内存对齐。
一、规则
①结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址(也就是在结构体地址的最开始存放);
②其他成员变量要对齐到对齐数的整数倍的地址处,对齐数=编译器默认的对齐数和该成员变量大小的较小值(VS中默认为8,Linux中gcc没有默认对齐数,就是成员变量自身的大小);
③结构体总大小是最大对齐数的整数倍;
④如果嵌套了结构体,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体整体大小是所有最大对齐数(包括嵌套结构体中的成员变量的对齐数)的整数倍。
例如:struct s1{double a;char b;int c;};
存入时double类型的a有8个字节,则从位置‘0’开始,到位置‘7’存入8个字节,接下来的char类型b一个字节,对齐数就是1,任意数为1的整数倍,故char类型没有偏移量,直接存在位置‘8’,然后的int类型c四个字节,要对齐到最近的4的整数倍地址,即位置‘12’,从位置‘12’开始存放,到位置‘15’共4个字节。最后该结构体中最大对齐数为8,总大小须为8的整数倍,则再到最近的位置‘16’,即结构体大小为16字节。(可以看到,这里浪费了一些空间)
struct s2{char a;struct s1 s;double b;};
首先一个char类型变量一个字节,存在位置‘0’,s1类型结构体变量s,需要对齐到8(最大对齐数)的整数倍,直接偏移到位置‘8’,大小为16个字节,一直存入到位置‘23’,接下来double类型8个字节,可以直接从紧跟着的位置‘24’开始存入,直到位置‘31’,最后是结构体大小因为8的整数倍,即最近的‘32’,为32个字节。
二、原因
这样的内存对齐存入方式(有时浪费了空间),是有其原因的。
①平台:并不是所有平台都能访问任意地址上的任意数据。
②性能:数据结构应该尽可能地在自然边界上对齐。具体来说,这样内存对齐使得每个数据都存入在分开的独立空间内,不会存在某一数据存入跨了空间,访问时也就只需要进行一次检测。总结一下,就是拿空间换时间。
三、修改默认对齐数
#pragma pack(1) 就是将默认对齐数设置为1。
#pragma pack() 则是重置,清除设置的对齐数。
结构体传参
结构体传参时需要传地址。以下代码有较完整的示例。
struct S{int arr[5];int n;};
void print(struct S *ps)
{
printf("%d %d\n",ps->arr[0],ps->n);
}
int main()
{
struct S s={{1,2,3,4,5},10};
print(&s);
return 0;
}