目录
一、结构体的声明
1.1结构体的定义
结构(体)是一些值的集合,这些值被称为成员变量。结构的每个成员变量可以具有不同类型。
结构的成员可以是标量、数组、指针,甚至是其他结构体。
1.2结构体的声明
struct tag
{
a;
b;
}c;
以上即为结构体的声明格式
struct声明此处声明结构体
tag为结构体标签(即我们创建的结构体类型,类似于int x;里的int)
a、b为结构体成员,可以为任何类型,此处成员数量可以为任意数。
c为我们在声明结构体时,一同创建的结构体变量,c为结构体变量的名称(根据创建位置分为全局变量和局部变量)
注:创建结构体时,最后的分号不要丢。
1.3特殊的声明
结构体在声明时可以省略结构体标签(即上文提到的tag)
struct
{
a;
b;
}c;
struct tag
{
a;
b;
}d;
此时称该声明为不完全声明,以后不可以再根据该次声明创建变量,只能在声明后创建一次变量(c和d)
注:虽然变量c和d的结构体内容完全相同,但是由于是不完全声明,系统会认为c和d为不同类型的变量。
二、成员的访问
2.1直接访问
结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数。
格式为 结构体变量名.结构体成员名
例如:
struct tag
{
int a;
char arr1[20];
}c;
strcpy(c.arr1, "abcd");
c.a=20;
printf("%d",c.a);
2.2通过指针进行访问
有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针。那该如何访问结构体成员呢?
当然我们可以先对指针进行解引用,再用(.)进行访问
struct tag
{
int a;
char arr1[20];
}c;
struct tag* p=&c;
(*p).a=20;
但是这样略显繁琐,有没有更简单的方式呢?
我们也可以通过->直接用指针访问成员
格式为 结构体指针->结构体成员名 例如
struct tag
{
int a;
char arr1[20];
}c;
struct tag* p=&c;
p->a=20;
三、自引用
在结构中包含一个类型为该结构本身的成员是否可以呢?(类似于函数的递归)
struct tag
{
int a;
struct tag b;
};
但是,我们会发现一个问题,这里的结构体是无线包含的,那么sizeof(struct tag)将无法计算,在运行代码时也无法为其分配内存,所以该方法显然是不行的。
那该怎样正确的自引用呢?
struct tag
{
int a;
struct tag* b;
};
将结构体内部的结构体改为结构体指针就可以很好的避免上述问题,完成自引用。
三、初始化与定义
声明了结构体后,对变量的定义就很简单了。
3.1创建结构体变量的两种方法
struct tag
{
int a;
char arr1[20];
}c;//方法一:直接跟在定义后面
//缺点:变量创建位置受限,无法自由创建全局变量/局部变量
//优点:省字数,可以快捷创建变量
struct tag d;//方法二 不受声明位置的限制,比较灵活
3.2结构体的初始化和嵌套初始化
struct tag1 //类型声明
{
char arr1[15];
int a;
};
struct Stu s = {"abedef", 20};//初始化
struct tag2
{
int b;
struct tag1 p;
struct tag2* next;
}n1 = {10, {"abedef",5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {"abedef", 6}, NULL};//结构体嵌套初始化
四、结构体内存对齐
声明了一个结构体之后,我们该如何定义结构体大小呢?
显然,直接将内部成员的所占的内存大小相加的计算方法是错误的
4.1内存对齐规则
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 (根据不同编译器决定)与 该成员大小的较小值。
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
注:对于数组而言,它可以理解为一堆数组元素,所以它的对齐数为其中元素的大小与默认对齐数的较小值
4.2为什么要内存对齐
(1)平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常
(2)性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总的来说,结构体的内存对齐是拿空间来换取时间的做法。
4.3成员顺序最优解
在设计结构体的时候,我们既要满足对齐,又要节省空间,就要让占用内存小的成员在一起,(尽量让占用内存大小相同的成员在一起)
例如
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
s1占用12个字节,而s2占用8个字节,显然s2相较于s1更好。
4.4修改默认对齐数
#pragma 这个预处理指令可以改变我们的默认对齐数
#pragma pack(8)//设置默认对齐数为8
#pragma pack()//取消设置的默认对齐数,还原为默认
五、结构体传参
有两种传参方法:一种是传实参,另一种是传结构体变量的地址
请看代码
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;
}
如果要在二者中选一个的话,显然是print2更好的
因为如果直接传结构体的话,在函数内需要再次创建一个形参,而形参也会占用内存,如此就会对系统压栈造成压力,使系统性能下降。
所以,结构体传参要穿结构体的地址