自学内容网 自学内容网

【C语言】你不知道的知识小盲区——柔性数组

在这里插入图片描述

一、什么是柔性数组

   也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。在C99标准中,如果结构体的最后一个成员是数组,那么这个数组可以不指定大小,它的大小是未知的,被称为柔性数组
   例如:

struct Stu
{
int i;
int arr[0];
};

   这种写法有些编译器可能会报错,可以使用这种形式:

struct Stu
{
int i;
int arr[];
};

   这两种写法都可以创建柔性数组,具体看编译器的选择

二、柔性数组的特点

  1. 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员,比如我们上面的例子中,在柔性数组前都建立了另一个整型成员在前面
  2. sizeof返回的这种结构大小不包括柔性数组的内存,只包含其它成员的大小,我们可以来试一下计算上面的结构体Stu:
    在这里插入图片描述
       可以看到,sizeof(struct Stu)只包含了结构体中的整型成员i的大小,并没有包含柔性数组进去
  3. 包含柔性数组成员的结构体使用动态内存开辟函数进行内存的动态分配,并且分配的内存应该⼤于结构的大小,以适应柔性数组的预期大小(如果还没有学习过动态内存分配可以参考文章:【C语言】动态内存管理及相关笔试题

   我们在给柔性数组申请空间时,一般会结合柔性数组第2和第3条特点来进行,第2点说使用sizeof计算结构体大小时不会计算柔性数组的大小,而第3点则说开辟空间时,需要大于计算出来的结构体的大小,所以我们开辟空间时的大小可以写成:sizeof(struct Stu) + 柔性数组的空间,如下:

//给结构体改名:
typedef struct Stu Stu
//为带有柔性数组的结构体Stu开辟空间
Stu* p = (Stu*)malloc(sizeof(Stu) + 10 * sizeof(int));

   以上操作就是为带有柔性数组的结构体Stu开辟空间,给除了柔性数组外的其它成员开辟空间我们采用的就是sizeof(Stu),然后再加上柔性数组的大小,上述代码中就为柔性数组开辟了10个整型的空间

三、柔性数组的使用

   刚刚我们学会了给包含柔性数组的结构体初始化,接着我们就举一个例子,就是给柔性数组开辟指定的空间,然后使用它,如下结构体:

typedef struct Stu
{
    int i;
    int arr[]; 
}Stu;

   上面的代码中,我们创建了一个带有柔性数组的结构体,将其重命名为了Stu,由于我们要指定空间内容,所以我们可以把其中的i用来表示柔性数组arr的元素个数
   但是我们要注意一点,不能直接来创建空间,因为我们的成员i需要用户输入,但是最开始的时候结构体的空间都还没有申请,成员i自然也就没有自己的空间,也就不能给i输入值
   本质上就是i不能输入,不知道柔性数组arr的元素个数,我们给结构体申请空间时就不能正常给柔性数组开辟空间,那么该怎么办呢?
   第一步就是给结构体申请不包含柔性数组的空间,因为柔性数组的元素个数还不确定,这时我们就给i申请到空间了,这时就可以使用scanf为i输入值了,如下:

typedef struct Stu
{
    int i;
    int arr[]; 
}Stu;

int main()
{
Stu* p = (Stu*)malloc(sizeof(Stu));
scanf("%d", &p->i);
return 0;
}

   这时我们就知道了用户输入的i了,也就是知道数组元素的个数,那么我们就可以为柔性数组arr开辟空间了,由于结构体Stu的空间已经被开辟了,只是没有开辟柔性数组的空间
   所以我们就可以使用realloc对原空间进行增容,再次提醒一下,不能直接用原来的指针p来接收,因为一旦realloc开辟空间失败返回了空指针,那么我们就找不到原数据了
   所以我们这里重新创建一个变量tmp来接收,然后判断它是否为空,不为空再传给我们的p,如果为空那么就直接打印错误信息,然后返回,如下:

#include <stdio.h>
#include <stdlib.h>

typedef struct Stu
{
int i;
int arr[];
}Stu;

int main()
{
Stu* p = (Stu*)malloc(sizeof(Stu));
scanf("%d", &p->i);
Stu* tmp = (Stu*)realloc(p, sizeof(Stu) + p->i * sizeof(int));
if (tmp == NULL)
{
perror("realloc");
return 1;
}
p = tmp;
return 0;
}

   现在我们已经为柔性数组开辟了空间,现在就该使用这段空间了,我们遍历这个柔性数组,然后给它填充值,当然,使用完后也一定要记得将空间手动释放,然后将其置为空指针,以免造成内存泄漏和出现野指针,如下:

#include <stdio.h>
#include <stdlib.h>

typedef struct Stu
{
int i;
int arr[];
}Stu;

int main()
{
Stu* p = (Stu*)malloc(sizeof(Stu));
scanf("%d", &p->i);
Stu* tmp = (Stu*)realloc(p, sizeof(Stu) + p->i * sizeof(int));
if (tmp == NULL)
{
perror("realloc");
return 1;
}
p = tmp;
for (int j = 0; j < p->i; j++)
{
p->arr[j] = j;
printf("%d ", p->arr[j]);
}
free(p);
p = NULL;
return 0;
}

   我们来看看运行结果:
在这里插入图片描述
   可以看到我们已经实现了我们的需求,根据输入的值来确定柔性数组元素个数,从而创建了一个柔性数组,随后进行使用,这就是我们柔性数组的基本用法
   我们扩散一下思维,这里的柔性数组是不是很像我们学过的一个东西,每错,就是变长数组,根据输入的值来确定数组的元素个数,这不就是变长数组吗?我们也说过VS默认不支持变成数组,那么我们就可以使用柔性数组,虽然麻烦一点,但是至少可以用了
   当然还有另一个办法,就是给VS加上clang组件,然后在项目菜单,选择属性,在常规中将平台工作集改成clang,流程图如下:
在这里插入图片描述

四、柔性数组的优势

   实际上上面的那些关于柔性数组的操作,也可以不使用柔性数组完成,那么我们为什么还要引入柔性数组呢?我们接下来不使用柔性数组来完成以上操作来和柔性数组做一个对比就知道了
   具体是什么方法呢?实际上也不陌生,就是我们上一篇在动态内存管理中讲过的malloc模拟实现数组的功能的办法,使用一个整型指针来当作一个数组的首元素,然后给它开辟空间,把这段连续空间当作数组使用,首先创建如下结构体:

typedef struct Stu
{
int i;
int* arr;
}Stu;

   接着我们就使用动态内存管理的malloc来为结构体指针Stu开辟空间,接着还是使用scanf让用户输入一个i,代表数组元素个数,然后根据i的大小来为这个数组开辟相应的空间,如下:

Stu* p = (Stu*)malloc(sizeof(Stu));
scanf("%d", &p->i);
int* tmp = (int*)malloc(p->i * sizeof(int));
if (tmp == NULL)
{
perror("malloc");
return 1;
}
p->arr = tmp;

   现在我们给这个数组申请了空间,就可以使用了,然后对它进行释放,如下:

#include <stdio.h>
#include <stdlib.h>

typedef struct Stu
{
int i;
int* arr;
}Stu;

int main()
{
Stu* p = (Stu*)malloc(sizeof(Stu));
scanf("%d", &p->i);
int* tmp = (int*)malloc(p->i * sizeof(int));
if (tmp == NULL)
{
perror("malloc");
return 1;
}
p->arr = tmp;
for (int j = 0; j < p->i; j++)
{
p->arr[j] = j;
printf("%d ", p->arr[j]);
}

free(p);

p = NULL;
return 0;
}

   那么这段代码有没有问题?其实是有问题的,因为我们这里使用了两个malloc,一个给了结构体开辟空间,另一个为结构体中的arr开辟了空间,而两次malloc开辟的空间不一定是连续的,所以我们释放时只释放了结构体的空间就不够
   我们还需要先释放开辟的数组的空间,把它置为空指针,再释放掉p,将p置为空指针,所以 最后真正完整的代码如下:

#include <stdio.h>
#include <stdlib.h>

typedef struct Stu
{
int i;
int* arr;
}Stu;

int main()
{
Stu* p = (Stu*)malloc(sizeof(Stu));
scanf("%d", &p->i);
int* tmp = (int*)malloc(p->i * sizeof(int));
if (tmp == NULL)
{
perror("malloc");
return 1;
}
p->arr = tmp;
for (int j = 0; j < p->i; j++)
{
p->arr[j] = j;
printf("%d ", p->arr[j]);
}
free(p->arr);
p->arr = NULL;
free(p);
p = NULL;
return 0;
}

我们来看看运行结果:
在这里插入图片描述

   可以我们就使用这种方式就模拟了柔性数组的用法,随后我们可以对比一下它们,其中柔性数组有以下两个优势:

  1. 第⼀个好处是:使用柔性数组方便内存释放
       如果我们的代码是在⼀个给别人用的函数中,你里面做了⼆次内存分配,并把整个结构体返回给用户,用户调⽤free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事
       所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给用户⼀个结构体指针,用户做⼀次free就可以把所有的内存也给释放掉,否则用户可以忘记释放数组的空间,造成内存泄漏
  2. 第⼆个好处是:使用柔性数组有利于访问速度
       柔性数组的空间是连续的,而我们上面的那种方式空间就不是连续的,而连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚(其实,我个⼈觉得也没多⾼了,反正你跑不了要⽤做偏移量的加法来寻址,但是至少也算是好处之一)

   今天分享的柔性数组就到这里了,友友们之前有没有听说过柔性数组这个知识点呢?欢迎在评论区说出你们的想法
   那么我们下次再见,bye~


原文地址:https://blog.csdn.net/hdxbufcbjuhg/article/details/142904470

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!