在C语言中,将数组及其长度变量封装在一个结构体中是否是最佳实践?

7

我很快就要开始使用C语言操作系统课程,我正在阅读有关使用C的最佳实践,以便以后减少头疼。

这一直是我对数组的第一个问题,因为它们很容易搞砸。

在结构体中捆绑数组及其相关变量(包括长度),这是常见做法吗?

我从来没有在书籍中看到过这种方法,通常它们总是保持两个分离或使用类似于sizeof(array []/array [1])的处理方式。

但是,通过将两者封装到结构体中,您将能够按值和按引用传递结构体,这是无法使用数组实现的,除非使用指针,在这种情况下,您必须再次跟踪数组长度。

我刚开始使用C语言,上述内容可能完全错误,我还是一名学生。

干杯, Kai。


谢谢大家的回复!注意:我的sizeof使用是错误的,感谢unwind指出,并且感谢glib中GArray的实际示例。 - Kai
11个回答

6

在C语言中,将相关的值封装到一个包含结构体中是一个很好的实践。这完全符合逻辑。

我会更进一步。最好不要直接修改这些值,而是编写函数,以结构体内部的一对值作为参数来改变长度和修改数据。这样可以添加不变式检查,并且使测试变得非常容易。


5
当然,你可以这样做。不确定是否应将其称为最佳实践,但将C语言相对简单的数组变得更加易于管理肯定是一个好主意。如果需要动态数组,则几乎需要将执行簿记所需的各个字段分组在一起。
有时您会有两个大小:一个当前大小和一个已分配大小。这是一种权衡,您可以用较少的分配来换取一些速度,以一些内存开销为代价。
许多时候,数组仅在本地使用,并且具有静态大小,这就是为什么sizeof运算符非常方便地确定元素数量的原因。顺便说一下,您的语法略有错误,通常看起来是这样的:
int array[4711];
int i;

for(i = 0; i < sizeof array / sizeof *array; i++)
{
  /* Do stuff with each element. */
}

请记住,sizeof不是一个函数,括号并不总是必需的。 编辑:一个真实世界的例子,正如你所描述的那样包装的是由glib提供的GArray类型。可见用户声明正是您所描述的。
typedef struct {
  gchar *data;
  guint len;
} GArray;

预期程序尽可能使用提供的API来访问数组,而不是直接操作这些字段。


4
有三种方法。
  1. 对于静态数组(没有动态分配,也没有作为指针传递的数组)大小在编译时已知,因此可以使用 sizeof 运算符,就像这样:sizeof(array)/sizeof(array[0])
  2. 使用终止符(用作最后一个数组元素的特殊值,不能用作常规数组值),就像空终止字符串。
  3. 使用单独的值,作为结构成员或独立变量。实际上并不重要,因为所有用于操作数组的标准函数都需要单独的大小变量,但将数组指针和大小合并为一个结构体将增加代码可读性。我建议使用更清晰的界面来为自己的函数提供服务。请注意,如果通过值传递结构体,则被调用的函数将能够更改数组,但无法更改大小变量,因此传递结构体指针将是更好的选择。

在最后一个关于按值传递时更改数组的观点上,您是指可以更改数组本身还是指向的项?(如果它是指针数组。)否则,我不明白您如何只更改其中一个而不更改另一个。 - mmccoo
Id结构包含指向数组和数组大小的指针(如我所提到的),更改数组不会成为问题,因为数组指针将通过值传递,而不是数组本身。 - qrdl

3

我认为这是一个好的实践。事实上,在C++中,他们已经把它放入标准库并称之为vector。每当你在C++论坛中谈论数组时,你会收到大量的回复,建议使用vector代替。


2

我认为这样做没有什么问题,但通常不这样做的原因是由于这种结构所产生的开销。大多数C语言都是为了性能而裸机编码,因此通常避免使用抽象。


嗯...这不是有点泛化了吗? - Steve Melnikoff

2

我也很少在书中看到这样的做法,但我已经这样做了一段时间了。把它们“打包”在一起似乎很有道理。如果需要从方法返回已分配的数组,我发现这样做特别有用。


2

对于公共API,我建议使用数组和大小值分开。这是我所知道的大多数(如果不是全部)C库处理方式。你可以完全自主决定如何在内部处理它。因此,使用结构体加上一些帮助函数/宏来完成繁琐的部分是一个好主意。重新思考如何插入或删除项总是让我头疼,所以这是一个很好的问题来源。一次通用地解决它有助于从一开始就减少错误。一个不错的动态和通用数组实现是kvec


我不会将 struct {void *ptr; size_t len;}; 作为任何函数 API 的参数使用,因为这样会失去一些能力,例如通过传递一个不同于第一个元素的地址或传递一个不同于数组完整长度的长度来仅对列表的一部分进行排序。 - potrzebie
如果您需要抽象化,但仍需要访问数组的部分,则应使用arraySlice结构体。您可以拥有一个arraySlice变量,该变量指向并跨越整个数组,并且其他arraySlice变量/值指向并跨越您正在处理的部分。 - potrzebie

1

如果您使用静态数组,可以使用sizeof运算符访问数组的大小。如果将其放入结构中,则可以通过值、引用和指针将其传递给函数。在汇编级别上,按引用和按指针传递参数是相同的(我几乎确定)。

但是,如果您使用动态数组,则无法在编译时知道数组的大小。因此,您可以将此值存储在结构中,但您还将仅在结构中存储对数组的指针:

struct Foo {
  int *myarray;
  int size;
};

所以你可以通过值传递这个结构,但实际上你传递的是指向int(数组指针)和int(数组大小)的指针。

在我看来,这并没有什么帮助。唯一的好处是,你可以将大小和数组存储在一个地方,并且很容易获取数组的大小。如果你将使用大量动态数组,可以用这种方式。但如果你只使用几个数组,不使用结构会更容易。


ANSI C 不支持通过引用传递变量。也许你在想 C++? - Judge Maygarden
我曾经考虑过C语言,但是我已经很久没有使用C语言编程了,所以这是我的错误。 - klew
“唯一的好处是你可以将大小和数组存储在一个地方...”这样做可以节省一个变量名和一个函数参数,但不会节省内存或速度。 - potrzebie

1

我从未见过以这种方式完成,但我已经十年没有进行操作系统级别的工作了... :-) 乍一看似乎是一个合理的方法。唯一的担忧是要确保大小在某种程度上保持准确...根据需要计算就没有这个问题。


0

我认为你问题的这一部分是反过来的:

“但是通过将两个数组包装到一个结构体中,您可以通过值和引用传递结构体,而使用指针无法实现这一点,在这种情况下,您必须再次跟踪数组长度。”

在传递数组时使用指针是默认行为;这并不能让你以按值传递整个数组。如果您想复制整个数组而不是使其衰减为指针,则需要将数组包装在结构体中。有关更多信息,请参见此答案:

是否可能将数组按值传递给递归函数?

这里还有更多关于数组特殊行为的信息:

http://denniskubes.com/2012/08/20/is-c-pass-by-value-or-reference/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接