柔性数组的归纳

也许你没有听过柔性数组(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指针就找不到它自己所指向的那块空间啦,所以得按顺序来。

接下来就看看用指针的弊端趴:

    用指针的弊端:效率低下,不方便释放,用两次malloc可能会开辟两块不连续的内存,
 会增加内存碎片,还会影响局部性原理。
局部性原理分两种: 空间局部性和时间局部性,当然我们要说的就是空间局部性啦

空间局部性:开辟了一块内存以后,接下来大概率会使用周边的内存数据,
而如果用指针的话,用两次malloc,开辟的可能不是连续的内存空间,所以用不到周边的
可能会产生内存碎片,影响效率,降低访问速度
由此可见,我们柔性数组还是有很多好处的对吧!

综上所述,就是我个人对柔性数组的见解

  • 43
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值