一.什么是结构体
在c语言中具有许多基本内置数据类型,例如int、char、short、float、double等等,但对于解决实际问题来说,这并不够,例如在描述一名学生时,有学号,性别,名字,年龄等信息,在这时单一的数据类型无法准确描述,于是便引入了一种自定义的数据类型——结构体(struct)。
二.结构体的声明
![](https://i-blog.csdnimg.cn/blog_migrate/4920cf2ae769ec475dc7a931dabd8400.png)
tag:结构体名称
member-list:成员变量列表
variable-list:变量列表(相当于直接创建变量)
以一个学生类型的结构体来举例子:
![](https://i-blog.csdnimg.cn/blog_migrate/1d004823f39b93bb481c9830c34446a5.png)
在这个结构体中,student为结构体名称,int age、char name[20]为成员变量,a1、a2为创建的struct student类型的全局变量。
(注意:末尾的分号不可省略)
在定义一个结构体时,可用typedef重命名
![](https://i-blog.csdnimg.cn/blog_migrate/b217113ead3e8d4d1d2bffe3d3810726.png)
此时在末尾的stu不是全局变量,而是对于struct student类型的重命名
(注意:使用typedef时无法在结构体末尾定义全局变量)
匿名结构体
![](https://i-blog.csdnimg.cn/blog_migrate/0e4573df7bd6d11c660eae79b52ab213.png)
在无名称的情况下,结构体变量只能在末尾定义,无法在之后定义,
但是在匿名情况下,也可使用typedef重命名,重命名之后便可正常使用。
三.结构体变量的定义与初始化
定义结构体变量
struct student
{
int age;
char name[20];
}a1,a2;
struct student a3;//这里定义的a1、a2、a3均为struct student类型的全局变量
int main()
{
struct student a4;//定义struct student类型的局部变量a4
}
由此可见,结构体变量的定义有两种方法
在结构体声明的末尾定义变量
直接以结构体名称加变量名定义
结构体变量的初始化
typedef struct S
{
int x;
char str;
}S;
int main()
{
stu a = { 18, 'l' };//方法一
stu b = { b.x = 18, b.str = 'l' };//方法二
return 0;
}
结构体变量的初始化同样有两种方法
按顺序初始化
用名称逐个对应初始化
四.结构体变量的使用
访问结构体变量需使用的操作符
.(点号) 与 ->(箭头)
点号的使用方式为:结构体变量名.结构体成员名
箭头的使用方式为:指向结构体变量的指针->结构体成员名
实例:
#include<stdio.h>
typedef struct student
{
int age;
char name[20];
}stu;
int main()
{
stu a = { 18, "lihua" };
stu* pa = &a;//定义结构体指针并初始化
printf("%d,%s\n", a.age, a.name);//以.操作符访问
printf("%d,%s\n", pa->age, pa->name);//以->操作符访问
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/a6bdd6c3ea4d8d497b26130621d1ad39.png)
结构体变量的函数传参
结构体变量的传参与基本内置数据类型一致,分为两种:传值调用、传址调用
#include<stdio.h>
typedef struct student
{
int age;
char name[20];
}stu;
void test1(stu x)
{
printf("%d,%s\n", x.age, x.name);
}
void test2(stu* px)
{
printf("%d,%s\n", px->age, px->name);
}
int main()
{
stu a = { 18,"wangwu" };
test1(a);//传值调用
test2(&a);//传址调用
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/da7139fad5dd2c6eb286e0810c3a50b4.png)
上述两种方式皆可实现,但是对于结构体变量的传参,更建议使用传址调用,因为结构体相对较大,在使用传值调用时,函数栈帧创建过程中压栈时会消耗相对较多的空间与时间
链表的简要介绍
链表是一种由结构体实现的数据结构(描述数据在内存中的存储结构)
例子:
struct Node
{
int data;
struct Node* next;
};
这是一种最简单的链表,通过前一个节点找到下一个节点,在最后一个节点可以通过置空指针(NULL)结束。(注意:不能在内部直接创建一个struct Node next,这样会陷入死循环)
五. 结构体内存对齐
规则:
结构体的第一个成员永远都放在0偏移处。
从第二个成员开始,以后的每个成员都要对齐到某个对齐数的整数倍处。对齐数:成员自身大小和默认对齐数的较小值(注意:在vs环境下,默认对齐数是8,在gcc环境下,没有默认对齐数;但可以使用#pragma pack(num)的方法改变默认对齐数)
当成员全部存放进去后,结构体的总大小必须是:所有成员对齐数中最大对齐数的整数倍,如果不够则浪费空间对齐。
如果嵌套了结构体,嵌套的结构体成员要对齐到自己成员最大对齐数的整数倍处。(此时整个结构体总大小必须是包含嵌套的结构体的成员在内的最大对齐数的整数倍)
例1:
struct S
{
char a;
int b;
};
int main()
{
printf("%d", sizeof(struct S));
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/7e38f6dfeae708726314ad95648f61d6.png)
对于上述结构体而言在内存中的存储如下:
![](https://i-blog.csdnimg.cn/blog_migrate/ccca1ac5c3fc0e8d63683a25182ebb43.png)
解释:
char a为结构体第一个成员,根据第一条规则,因此放在0偏移处。
而对于第二个成员int b,根据第二条规则,在vs下默认对齐数是8,而int的大小为4个字节,因而其对齐数为4,又因为需对齐到对齐数的整数倍处,因此从标号为4处开始存放,存放4个字节。
在这个结构体中最大对齐数为4,因此,结构体大小必须是4的倍数,因为大小为8恰好为4的倍数,因此不需要多浪费空间对齐,因此这个结构体大小为8
![](https://i-blog.csdnimg.cn/blog_migrate/b0189e25995b55582aee098cbf2f70d2.png)
例2:
#include<stdio.h>
struct S
{
char a;
int b;
};
struct SS
{
char e;
struct S s;
double f;
};
int main()
{
printf("%d", sizeof(struct SS));
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/85eacc8f61d61185aaae9592f49d3e28.png)
对于上述结构体而言在内存中的存储如下:
![](https://i-blog.csdnimg.cn/blog_migrate/118ee1d35d1a7c4b48a0cca9e8f7eccb.png)
解释:
char e为第一个成员因此放在0偏移位处。
第二个struct S为结构体成员,因此根据第4条规则,其应对齐到struct S的最大对齐数4的倍数上,则将struct S的第一个成员char a放在标号为4处,而int b对齐数为4,则对齐到4的整数倍处,从标号为8处往下存放四个字节。
结构体struct S的最大对齐数为4,因此其大小为4的倍数,此时其大小为8,恰好为4的倍数,因此无需浪费空间对齐。
最后一个成员double f对齐数为8,则对齐到8的整数倍处,从标号16处开始存放8个字节。
结构体struct SS的最大对齐数为8,因此整个结构体大小需为8的倍数,因为其大小为24恰好为最大对齐数的倍数,因此无需浪费空间对齐,结构体大小为24。
![](https://i-blog.csdnimg.cn/blog_migrate/4ad00afabf49111feeac8cd13a34e00f.png)
相信从上述例子中,我们已经理解了结构体内存对齐,此时难免会有所疑问,为什么要浪费空间去对齐呢?
平台原因(移植原因):
不是所有的硬件平台都能任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:
数据结构(尤其是栈)应该尽可能在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存仅需要一次访问。
例如:
![](https://i-blog.csdnimg.cn/blog_migrate/7e38f6dfeae708726314ad95648f61d6.png)
对于上述结构体,在32位环境下,如果不进行对齐,要读取int b需要两次访问
![](https://i-blog.csdnimg.cn/blog_migrate/2a15168f6a5c84f006e00eeaf12753d5.png)
但在对齐的情况下只需要一次访问即可
![](https://i-blog.csdnimg.cn/blog_migrate/ad59954fe9801c7f9045d4281e7338a2.png)
总体来说结构体内存对齐就是拿空间换时间的做法。
六.位段
位段的使用
位段是依赖于结构体来实现的,其用法是:在定义结构体成员时在末尾加:加所占用的比特位(注意:位段不可用于浮点类型)。
例如:
struct A
{
int a : 4;
int b : 3;
int c : 5;
int d : 30;
};
int main()
{
printf("%d", sizeof(struct A));
return 0;
}
上述意思为,先开辟一个int类型大小的空间,然后赋予a 4个比特位的空间,赋予b 3个比特位的空间,赋予c 5个比特位的空间,因为剩下的空间不够分配,则再开辟一个字节的空间,赋予d 30个比特位的空间,因此上述结构体大小为8个字节。
![](https://i-blog.csdnimg.cn/blog_migrate/b296296abd1d566e0091aa2f6342e007.png)
由此可得出:
对于连续的相同类型的变量,在足够存放下的情况,它们会对这块内存进行分配,如果不足则再开辟一块空间。
位段的跨平台问题
int位段被当成有符号数或是无符号数是不确定的。
位段中最大位的数目是不能确定(16位机器最大为16,32为机器最大为32,写成大于16的数时,在16位机器上会出问题)。
位段中的成员从左向右分配还是从右向左分配,标准尚未规定。
当一个结构包含两个位段,第二个位段成员过大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
(在vs环境下,int位段被当成无符号数,位段中的成员是从右向左分配的,并且当无法容纳时,舍弃剩余的位)
总结:与结构体相比,位段可以达到相同的效果,并且很好的节省空间,但是有着跨平台问题存在。
如有问题欢迎指出
感谢阅读~