也许你没有听过柔性数组(flexible array)这个概念,但大家一定都听过数组吧!
C99中,结构中的最后一个元素允许是未知大小的数组,这个就叫柔性数组成员。
那么它在代码中是怎么体现的呢?让我们来看看
例:
#include<stdio.h>
struct S
{
int n;
int arr[]; //柔型数组是未知的大小 也可以写成arr[0].
};
int main()
{
return 0;
}
像图中这个结构体的第二个成员就是柔性数组,它的写法也有两种,可以是arr[ ],也可以是arr[0]。具体是什么形式得看编译器,编译器不同,所允许的形式也不一样,当然我们的vs是很宠我们的,哪个形式都可以跑的过去。
知道了它的概念,那么接下来我们来了解一下它的特点吧:
1.结构体中的柔性数组成员前面必须至少有一个其他成员。
2.sizeof返回的是这种结构体的大小不包括柔性数组的内存
3.包含柔性数组成员的结构用malloc函数进行动态的内存分配,并且分配的内存应大于结构体的大小,以便于适应柔性数组成员的预期大小。
接下来就让我来带你了解它->(柔性数组),
首先,前面至少有一个成员,这是它的特点,这个你就不要疑惑了,就像你一生下来就很有气质也是你的特点哈哈!
那么这个sizeof求结构体不包括柔性数组大小是什么意思呢?请看VCR
#include <stdio.h>
struct S
{
int n;
int arr[]; //柔型数组是未知的大小 也可以写成arr[0].
};
int main()
{
struct S s = { 0 };
printf("%d", sizeof(s));
return 0;
}
我们在main函数中用struct S结构体类型创建了一个s变量,然后通过sizeof对这个s变量求大小,也就等于对整个结构体求大小。
求出的结果是4,也就是结构体中整形变量n的大小(一个int类型占四个字节),而大小却不包括柔性数组,就是因为他是未知大小的,所以怎么可能求出来的嘛,就像你都不知道你女朋友的身高,怎么能求出她的衣服尺码。
那么我们接下来看看它的第三个特点(包含柔性数组的结构是怎么在内存中开辟空间的,以及为什么开辟的空间应该大于结构的大小)
假设我们预期的柔性数组大小为10个整形(int)字节
#include <stdio.h>
#include <stdlib.h>
struct S
{
int n;
int arr[];
};
int main()
{
struct S * ps=(struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
return 0;
}
这里先给大家说一下:使用对应的函数时要引用相对应的头文件喔
这里malloc函数对应的头文件就是<stdlib.h>
在main函数中我们用malloc函数给struct S结构体创建了一个比它本身大十个整形(int)字节的空间,然后用struct S这个结构体类型创建了一个结构体指针ps,用来指向这块被强制类型转化成struct S*(结构体指针类型)的空间,因为柔性数组是未知大小的可以任意变化,所以我们才用malloc函数来开辟内存,方便调整大小。
当然如果上面这段话看不懂,那我们来看图片(picture)
ps这个指针指向了malloc开辟的这块空间,先开辟的结构体本身的大小给了n,后面的10个int空间给了柔性数组arr。
柔性数组的使用
#include <stdio.h>
#include <stdlib.h>
struct S
{
int n;
int arr[];
};
int main()
{
struct S * ps=(struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
ps->n = 10;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
return 0;
}
这里说一下结构体操作符 (“ . ”),(“->”)
第一个使用形式:结构体.成员名 例 :S.n
第二个使用形式:结构体指针->成员名 例:ps->n
这里使用for循环将每次加1的i放进柔性数组中,也就是将0-9的数字放进去。
柔性数组在内存中显示如下:
这时柔性数组已经被放入了0-9的数字了,现在打印的话也是可以打印出来的
那么我们接下来就来看一下柔性数组是怎么扩大内存的趴!
柔性数组的扩容
#include <stdio.h>
#include <stdlib.h>
struct S
{
int n;
int arr[];
};
int main()
{
struct S * ps=(struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
if (ptr != NULL)
{
ps = ptr;
}
return 0;
}
它既然是用malloc函数开辟的内存,那当然是用它的好兄弟realloc函数来扩大啦,毕竟它俩关系好嘛!
realloc函数返回参数是void*,它有两个参数,第一个参数就是需要扩大内存的空间地址,第二个是重新分配的空间,分配成功,返回void*指针(指向重新开辟的空间地址),失败则返回NULL(空指针)值
这段代码又扩大了柔性数组的内存空间,然后用struct S这个结构体类型再次创建了一个结构体指针ptr,用来指向这块被强制类型转化成struct S*(结构体指针类型)的新空间,if判断ptr如果不是空指针的话,就让第一次的ps指针从ptr手里接管这块空间,也就是让ps指针再次指向这块新空间的地址
释放
那么为什么要释放指针所指向的空间以及把指针置为空指针呢?
当然是为了避免内存泄露和资源浪费,像realloc和malloc这种函数开辟的空间都在堆区上面,需要手动管理它的生命周期,以便告诉系统该内存不再使用,可以被回收和利用,而free函数的参数是指向那块空间的指针,这样系统就能知道要释放哪块内存了。释放后的指针一定要设置为空指针,因为free只会释放掉原地址内存,并不会改变指针指向,为了防止野指针,必须将指向已释放内存空间的指针置为空指针。
到这里就基本讲完了,下面总结柔性数组的一些好处:
方便内存释放(只用一次free),有利于访问速度,减少内存碎片
当然你以为到这儿就完了的话就想多咯,反正是知识嘛,你多吸收一点总归是没有坏处滴!
拓展:
接下来我们来看看柔性数组能不能用一个指针来代替趴
我们先来梳理一下思路,如果这个柔性数组是未知大小的话,那么我们用一个指针代替它放到结构中,然后也用malloc开辟空间,用realloc扩大空间,这样是不是也可以实现柔性数组的类似用法呢?
我们直接上代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
struct S
{
int n;
int* arr;
};
int main()
{
return 0;
}
如图,我们已经在结构中放了一个整形指针,接下来的操作就比较熟悉又陌生了
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S)); //先开辟一块结构体空间
if (ps == NULL)
{
return;
}
ps->arr = (struct S*)malloc(10 * sizeof(int)); //在给arr指针开辟空间
if (ps->arr == NULL)
{
return;
}
return 0;
}
熟悉是因为我们又进行了类似柔性数组开辟空间的操作,但是陌生的是这次我们先给结构体开辟一块空间,然后if判断是不是空指针,其次才是给我们的整形指针arr再次开辟一块空间,然后也判断一下是否为空指针。
那么如果文字看不懂,我这个灵魂画手就要上场啦:
首先开辟的结构体空间是这样的,里面包括n变量和arr指针,但此时arr指针是没有空间的兄弟们,这时要想做到柔性数组一样的效果,arr指针必须也指向一块在堆上开辟的空间,n有的我arr也必须有对吧,公平一点嘛!
所以接下来给arr用malloc函数也开辟一块在堆上的空间
接下来我们测试一下看能不能和柔性数组一样正常使用
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S)); //先开辟一块结构体空间
if (ps == NULL)
{
return;
}
ps->arr = (struct S*)malloc(10 * sizeof(int)); //在给arr指针开辟空间
if (ps->arr == NULL)
{
return;
}
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
printf("%d", ps->arr[i]);
}
我已经替大家测试过了,没问题的,这里在讲个小细节:
因为创建的内存是连续存放的,所以ps->arr指针也可以像数组一样去访问这块内存空间
p->arr[i] 就像这样就可以把空间当做数组来访问
我们既然是模仿就必须到位,给指针扩容用一下realloc当然也是没问题的
当然如果你能看到这儿的话,这段代码我想就不用给聪明的你解释啦!
释放
这里就要注意!注意!注意!啦
我们这次用指针代替柔性数组可是创建了两个指针两块空间喔,所以你识趣一点,怎么跟系统借的怎么还回去,“好借好还,再借不难”嘛!
这里还有一个点,我们要搞清楚指针释放的先后顺序,先释放ps->arr指针,在释放ps指针,然后分别置为空指针,搞不懂顺序没事哈,我给你画画画!
我们的两块空间如上,必须先释放第2块空间,最后释放ps指针指向的空间,因为先释放ps指针的话,如图我们的ps->arr指针就找不到它自己所指向的那块空间啦,所以得按顺序来。
接下来就看看用指针的弊端趴: