在 C 中使用第一个数组元素作为数组长度是一种很好的编程习惯吗?

问题描述

因为在 C 中定义数组时必须声明数组长度,所以使用第一个元素作为长度是否可以接受,例如

<VirtualHost *:80>
DocumentRoot /path/to/an/example/documentroot
#make sure mod_rewrite is enabled
RewriteEngine On
RewriteCond %{HTTP_HOST} !^example\.org$ [NC]
RewriteRule ^ https://example.org%{REQUEST_URI} [L,R]
</VirtualHost>

然后使用这样的函数来处理数组:

int arr[9]={9,1,2,3,4,5,6,7};

我看不出这有什么问题,但更愿意先咨询有经验的 C 程序员。我将是唯一一个使用代码的人。

解决方法

好吧,从某种意义上说,您有一个数组,其中元素的含义不同,这很糟糕。将元数据与数据一起存储并不是一件好事。只是为了稍微推断一下你的想法。我们可以使用第一个元素表示元素大小,然后使用第二个元素表示长度。尝试使用两者编写一个函数;)

还值得注意的是,使用这种方法,如果数组大于元素可以容纳的最大值,则会出现问题,这对于 char 数组是一个非常重要的限制。当然,您可以使用前两个元素来解决它。如果您有浮点数组,也可以使用强制转换。但是我可以向您保证,您会因此遇到难以追踪的错误。除其他外,字节序可能会导致很多问题。

它肯定会让几乎每个经验丰富的 C 程序员感到困惑。这并不是反对这种想法的真正合乎逻辑的论据,而是一种务实的论据。即使这是一个好主意(事实并非如此),您也必须与每个与您的代码有任何关系的程序员进行长时间的交谈。

实现相同目的的合理方法是使用结构体。

struct container {
    int *arr;
    size_t size;
};

int arr[10];

struct container c = { .arr = arr,.size = sizeof arr/sizeof *arr };

但是在任何我会使用类似上面的东西的情况下,我可能不会使用数组。我会改用动态分配:

const size_t size = 10;
int *arr = malloc(sizeof *arr * size);
if(!arr) { /* Error handling */ }

struct container c = { .arr = arr,.size = size };

但是,请注意,如果您使用指针而不是数组以这种方式初始化它,您将获得“有趣”的结果。

您也可以使用灵活的数组,正如 Andreas 在his answer

,

在 C 中,您可以使用灵活的数组成员。那就是你可以写

struct intarray {
   size_t count;
   int data[];  // flexible array member needs to be last
};

你分配

size_t count = 100;
struct intarray *arr = malloc( sizeof(struct intarray) + sizeof(int)*count );
arr->count = count;

这适用于所有类型的数据。

它使 C 数组的使用更加安全(不如 C++ 容器安全,但比普通 C 数组更安全)。

不幸的是,C++ 在标准中不支持这个习语。 尽管许多 C++ 编译器将其作为扩展提供,但并不保证。

另一方面,这个 C FLA 习惯用法可能比 C++ 容器更明确,也可能更高效,因为它不使用额外的间接寻址和/或需要两次分配(想想 new vector<int>)。

如果您坚持使用 C,我认为这是处理具有集成大小的可变长度数组的一种非常明确且易读的方式。

唯一的缺点是 C++ 人不喜欢它并且更喜欢 C++ 容器。

,

当数组的元素是整数时,这还不错(我的意思是它不会调用未定义的行为或导致其他可移植性问题),但不是直接写幻数 9,而是应该让它计算长度数组以避免拼写错误。

#include <stdio.h>

int main(void) {
    int arr[9]={sizeof(arr)/sizeof(*arr),1,2,3,4,5,6,7};
    
    for (int i=1; i<arr[0]; i++) {
        printf("%d ",arr[i]);
    }
    return 0;
}
,

只有少数数据类型适合这种黑客攻击。因此,我不建议这样做,因为这会导致不同类型数组的实现风格不一致。

,

类似的方法经常用于字符缓冲区,其中在缓冲区的开头存储其实际长度。

C 中的动态内存分配也使用这种方法,即分配的内存以一个整数作为前缀,以保持分配的内存大小。

但是一般来说,这种方法不适用于数组。例如,字符数组可能比 char 类型的对象中可以存储的最大正值 (127) 大得多。此外,很难将这种数组的子数组传递给函数。大多数设计用于处理数组的函数在这种情况下不起作用。

声明处理数组的函数的一般方法是声明两个参数。第一个指针类型指定数组或子数组的初始元素,第二个指定数组或子数组中元素的数量。

当可以在运行时指定大小时,C 还允许声明接受可变长度数组的函数。

,

它适用于相当有限的情况。它解决的问题有更好的解决方案。

它的一个问题是,如果它没有被普遍应用,那么您将混合使用使用约定的数组和不使用约定的数组 - 您无法判断数组是否使用约定。例如,对于用于携带字符串的数组,您必须在对标准字符串库的调用中不断传递 &arr[1],或者定义一个使用“Pascal 字符串”而不是“ASCIZ 字符串”约定的新字符串库(这样的库将提高效率),

在真正的数组而不是简单的内存指针的情况下,sizeof(arr) / sizeof(*arr) 将产生元素的数量,而无需在任何情况下将其存储在数组中。

它仅适用于整数类型数组,而对于 char 数组会将长度限制为相当短。对于其他对象类型或数据结构的数组,这是不切实际的。

更好的解决方案是使用结构:

typedef struct
{
    size_t length ;
    int* data ;
} intarray_t ;

那么:

int data[9] ;
intarray_t array{ sizeof(data) / sizeof(*data),data } ;

现在您有一个数组对象,它可以传递给函数并保留大小信息,并且可以直接访问数据成员以在不接受 intarray_t 的第三方或标准库接口中使用。此外,data 成员的类型可以是任何类型。

,

显然不是。 所有编程语言都具有与变量类型一起存储的预定义函数。为什么不使用它们? 在你的情况下更适合访问 count /length 方法而不是测试第一个值。

if 子句有时比预定义函数花费更多时间。

乍一看似乎可以存储计数器,但想象一下您将不得不更新数组。您将必须执行 2 个操作,一个是插入,另一个是更新计数器。所以 2 个操作意味着要更改 2 个变量。 对于静态数组,让它们计数器然后列表可能没问题,但对于动态数组,NO NO NO。 另一方面,请阅读编程基本概念,你会发现你的想法很糟糕,不符合编程原则。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...