25-伸缩型数组成员(C99)
14.7.9 伸缩型数组成员(C99)
C99新增了一个特性:伸缩型数组成员(flexible array member),利用这项特性声明的结构,其最后一个数组成员具有一些特性。第1个特性是,该数组不会立即存在。第2个特性是,使用这个伸缩型数组成员可以编写合适的代码,就好像它确实存在并具有所需数目的元素一样。这可能听起来很奇怪,所以我们来一步步地创建和使用一个带伸缩型数组成员的结构。
首先,声明一个伸缩型数组成员有如下规则:
- 伸缩型数组成员必须是结构的最后一个成员;
- 结构中必须至少有一个成员;
- 伸缩数组的声明类似于普通数组,只是它的方括号中是空的。
下面用一个示例来解释以上几点:
struct flex
{
int count;
double average;
double scores[]; // 伸缩型数组成员
};
声明一个 struct flex
类型的结构变量时,不能用 scores
做任何事,因为没有给这个数组预留存储空间。实际上,C99的意图并不是让你声明 struct flex
类型的变量,而是希望你声明一个指向 struct flex
类型的指针,然后用 malloc()
来分配足够的空间,以存储 struct flex
类型结构的常规内容和伸缩型数组成员所需的额外空间。例如,假设用 scores
表示一个内含5个 double
类型值的数组,可以这样做:
struct flex * pf; // 声明一个指针
// 请求为一个结构和一个数组分配存储空间
pf = malloc(sizeof(struct flex) + 5 * sizeof(double));
现在有足够的存储空间存储 count
、 average
和一个内含5个 double
类型值的数组。可以用指针 pf
访问这些成员:
pf->count = 5; // 设置 count 成员
pf->scores[2] = 18.5; // 访问数组成员的一个元素
程序清单14.12进一步扩展了这个例子,让伸缩型数组成员在第1种情况下表示5个值,在第2种情况下代表9个值。该程序也演示了如何编写一个函数处理带伸缩型数组元素的结构。
程序清单14.12 flexmemb.c
程序
// flexmemb.c -- 伸缩型数组成员(C99新增特性)
#include <stdio.h>
#include <stdlib.h>
struct flex
{
size_t count;
double average;
double scores []; // 伸缩型数组成员
};
void showFlex(const struct flex * p);
int main(void)
{
struct flex * pf1, *pf2;
int n = 5;
int i;
int tot = 0;
// 为结构和数组分配存储空间
pf1 = malloc(sizeof(struct flex) + n * sizeof(double));
pf1->count = n;
for (i = 0; i < n; i++)
{
pf1->scores[i] = 20.0 - i;
tot += pf1->scores[i];
}
pf1->average = tot / n;
showFlex(pf1);
n = 9;
tot = 0;
pf2 = malloc(sizeof(struct flex) + n * sizeof(double));
pf2->count = n;
for (i = 0; i < n; i++)
{
pf2->scores[i] = 20.0 - i / 2.0;
tot += pf2->scores[i];
}
pf2->average = tot / n;
showFlex(pf2);
free(pf1);
free(pf2);
return 0;
}
void showFlex(const struct flex * p)
{
int i;
printf("Scores : ");
for (i = 0; i < p->count; i++)
printf("%g ", p->scores[i]);
printf("\nAverage: %g\n", p->average);
}
下面是该程序的输出:
Scores : 20 19 18 17 16
Average: 18
Scores : 20 19.5 19 18.5 18 17.5 17 16.5 16
Average: 17
带伸缩型数组成员的结构确实有一些特殊的处理要求。
第一,不能用结构进行赋值或拷贝:
struct flex * pf1, *pf2; // *pf1 和*pf2 都是结构
...
*pf2 = *pf1; // 不要这样做
这样做只能拷贝除伸缩型数组成员以外的其他成员。确实要进行拷贝,应使用 memcpy()
函数(第16章中介绍)。
第二,不要以按值方式把这种结构传递给结构。原因相同,按值传递一个参数与赋值类似。要把结构的地址传递给函数。
第三,不要使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员。
这种类似于在结构中最后一个成员是伸缩型数组的情况,称为struct hack。除了伸缩型数组成员在声明时用空的方括号外,struct hack特指大小为 0
的数组。然而,struct hack是针对特殊编译器(GCC)的,不属于C标准。这种伸缩型数组成员方法是标准认可的编程技巧。