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

Posted

技术标签:

【中文标题】在 C 中使用第一个数组元素作为数组长度是一种好的编程习惯吗?【英文标题】:Is it good programming practice in C to use first array element as array length? 【发布时间】:2021-05-02 08:40:57 【问题描述】:

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

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

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

int printarr(int *ARR) 
    for (int i=1; i<ARR[0]; i++) 
        printf("%d ", ARR[i]);
    
 

我认为这没有问题,但更愿意先咨询有经验的 C 程序员。我将是唯一使用该代码的人。

【问题讨论】:

TLDR:不。如果数组的类型不是数字怎么办? 这真的取决于情况。很难做出笼统的陈述。如果您编写这样的代码,至少应该在 cmets 中正确记录。 如果数组是一个固定大小的数组,就像你的例子一样,你可以简单地这样做:#define MYSIZE 9 然后int arr[MYSIZE]=0,1,2,3,4,5,6,7;。现在您知道大小是MYSIZE。否则,固定大小数组的大小始终为sizeof(array) / sizeof(array[0])。有些实现有_countofmacro,如果没有,你可以根据上面的方法自己做。 我喜欢这个问题。这是一个坏主意,但却是个好问题。 非常感谢所有优秀的 cmets 和建议。我现在将使用数组、长度和附加描述所推荐的结构。我想我倾向于通过在数组中包含元数据来使用 C 的俚语版本。 【参考方案1】:

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

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

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

【讨论】:

【参考方案2】:

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

同样值得注意的是,使用这种方法,如果数组大于元素可以容纳的最大值,您将遇到问题,这对于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 中所写的那样

【讨论】:

我们不应该总是使用sizeof和括号sizeof(XXX)来保存吗? @Cosinus 这是一个品味问题。我还没有真正下定决心。使用括号使它看起来像一个函数,但事实并非如此。它是一个运算符。 我最近有一些奇怪的行为。当您使用多个标记定义类型时,它可能不起作用,例如 unsigned int @Cosinus“只是为了安全”做过多的事情通常表明不了解具体细节,即没有完全理解syntax。类型需要括号来表示,表达式不需要。表达式遵循operator precedence。没有“可能行不通”的情况。 @Cosinus 将其解释为sizeof(a / sizeof(b)) 的编译器不符合标准。【参考方案3】:

在 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&lt;int&gt;)。

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

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

【讨论】:

如果你提到一个结构可以有最多 1 个灵活的数组成员并且它必须在结构中的最后一个,那就太好了。 @klutt 谢谢我添加了评论 This is also a common idiom for Windows (有一些兼容的警告,afaik 0 长度数组在 c99 中也是不合法的,所以要小心——但一般观点是成立的)。除此之外,我习惯使用offsetof(),sizeof 是否可以保证处理填充? @Voo sizeof “处理填充” - 相反 - 它通常太多 也许只使用sizeof *arr + sizeof arr-&gt;data[0] * count :D【参考方案4】:

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

它的一个问题是,如果它没有被普遍应用,那么您将混合使用约定和不使用约定的数组 - 您无法判断数组是否使用约定。例如,对于用于携带字符串的数组,您必须在调用标准字符串库时不断传递&amp;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 成员的类型可以是任何类型。

【讨论】:

请注意,对于托管系统,灵活的数组成员版本可能是一个更优雅的解决方案,而在这个答案中,这是没有堆的嵌入式系统的首选。【参考方案5】:

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

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

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

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

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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

#include <stdio.h>

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

【讨论】:

嗯,这是做坏事的更好方法:)

以上是关于在 C 中使用第一个数组元素作为数组长度是一种好的编程习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章

Javascript算法在数组中查找不在另一个数组中的元素

C语言如何检查一个数组中元素的个数

C 数组使用时注意点

在 React 类组件中同时使用继承和组合是一种好的做法吗?

Dart数据类型--List (数组)及其相关操作

计数排序(Counting Sort)