【问题标题】:Array of pointers in C with easy iterationC中的指针数组,易于迭代
【发布时间】:2021-05-22 12:04:09
【问题描述】:

最近我在思考这个问题:如何用一种更简单的方法来迭代 C 中的指针数组。

如果我在 C 中创建一个字符串数组,它应该看起来像这样吧?

int size = 5;
char ** strArr = (char **) malloc(sizeof(char *) * size);
if (strArr == NULL) return;

但问题是,当您出于某种原因(例如打印其中的所有值)要迭代此数组时,您必须跟踪其当前大小,并将其存储在另一个变量中。

这不是问题,但是如果您创建大量数组,则必须在代码中跟踪它们的每一个大小。如果将此数组传递给另一个函数,则也必须传递它的大小。

void PrintValues (char ** arr, int size) {
    for (int i = 0; i < size; i++)
        printf("%s\n", arr[i]);
}

但是当迭代一个字符串时,情况就不同了。您有 '\0' 字符,它指定字符串的结尾。所以,你可以像这样迭代一个字符串,而不需要保持它的大小值:

char * str = (char *) malloc(sizeof(char) * 4);
str[0] = 'a';
str[1] = 'b';
str[2] = 'c';
str[3] = '\0';

for (int i = 0; str[i] != '\0'; i++)
    printf("%c", str[i]);
printf("\n");

现在我的问题: 在指针数组中分配 +1 单元以保持其尾部为 NULL 是否可以或在道德上正确?

char ** strArr = (char **) malloc(sizeof(char *) * (5   +1);
if (strArr == NULL) return;
strArr[0] = PseudoFunc_NewString("Car");
strArr[1] = PseudoFunc_NewString("Car#1");
strArr[2] = PseudoFunc_NewString("Car#2");
strArr[3] = PseudoFunc_NewString("Tree");
strArr[4] = PseudoFunc_NewString("Tree#1");
strArr[5] = NULL; // Stop iteration here as next element is not allocated

然后我可以使用 NULL 指针来控制迭代器:

void PrintValues (char ** arr) {
    for (int i = 0; arr[i] != NULL; i++)
        printf("%s\n", arr[i]);
}

这将帮助我保持代码更简洁,尽管它会消耗更多内存,因为指针大小大于整数大小。

另外,当使用基于事件的库(如 Gtk)进行编程时,大小值会在某个时候从堆栈中释放,因此我必须创建一个指针来动态存储大小值。

在这种情况下,可以这样做吗?还是被认为是坏事?

这种技术是否只用于 char 指针,因为 char 类型的大小只有 1 个字节?

我怀念在 C 中使用 foreach 迭代器...

【问题讨论】:

  • 这对我来说似乎很好。旁注:malloc() 在 C 中的转换结果是 discouraged
  • NULL 终止数组很常见 - 这正是 argv 的工作原理。
  • 您使用malloc(sizeof(char *) * size) 分配的只是一些内存,一个连续的字节区域。编译器实际上并不知道您分配了多少内存,特别是如果size 的值是在运行时设置的。返回的指针是指向已分配内存的第一个字节的指针。这个指针实际上是编译器所拥有的。因此,您可以跟踪分配的内存大小,并将其传递给需要它的函数。如果您使用特殊的终止符或使用“元素数”值并不重要,只要您保持跟踪即可。
  • @Oka 除了在 argv 的情况下,您还有 argc 并且循环迭代 for(size_t i=0; i&lt;argc; i++) 可能比在 argv 中查找 NULL 更有效。
  • @CarlHR 并不是真的气馁,只是毫无意义,因为您使用的是千禧年制造的 C 编译器。这里有一些关于 SO 的风格之战,特别是关于是否演员。

标签: arrays c for-loop iteration dynamic-memory-allocation


【解决方案1】:

没关系,是常用的模式。

作为替代方案,您可以使用struct,在那里您可以创建一个size 变量,您可以在其中存储数组的当前大小,并将结构作为参数传递。优点是您无需遍历整个数组即可知道其大小。

例子:

Live demo

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

typedef struct
{
    char **strArr;
    int size;
} MyStruct;

void PrintValues(MyStruct arr) //pass the struct as an argument
{
    for (int i = 0; i < arr.size; i++) //use the size passed in the struct
        printf("%s\n", arr.strArr[i]);
}

int main()
{
    // using the variable to extract the size, to avoid silent errors 
    // also removed the cast for the same reason
    char **strArr = malloc(sizeof *strArr * 5); 

    if (strArr == NULL) return EXIT_FAILURE;

    strArr[0] = "Car";
    strArr[1] = "Car#1";
    strArr[2] = "Car#2";
    strArr[3] = "Tree";
    strArr[4] = "Tree#1";
    
    MyStruct strt = { strArr, 5 }; // initialize the struct
    PrintValues(strt); //voila
    free(strArr); // don't forget to free the allacated memory
    return EXIT_SUCCESS;
}  

这允许通过错误检查直接访问索引:

// here if the array index exists, it will be printed
// otherwise no, allows for O(1) access error free
if(arr.size > 6){
    printf("%s\n", arr.strArr[6]);
}

【讨论】:

    【解决方案2】:

    现在我的问题是:在指针数组中分配 +1 单元以将其尾部保持为 NULL 是否可以或在道德上正确?

    这没关系,最终的 NULL 称为 sentinel 值,使用一个是一种常见的做法。当您出于某种原因甚至不知道数据的大小时,最常使用此方法。

    但是,这不是最好的解决方案,因为您必须遍历所有数据才能找到大小。单独存储大小的解决方案快得多。例如,一个结构数组,在同一位置包含大小和数据。

    【讨论】:

    • 在某些编程语言中,数组分配的大小是您需要的两倍。这将显着提高速度,因为大量时间可以花在内存分配上。为了防止数组大小过度使用迭代,通常使用迭代器编程模式。
    • @PatrykGawroński 好吧... C 提供了完全不使用分配存储的选项,这甚至更快。
    【解决方案3】:

    现在我的问题是:在指针数组中分配 +1 单元以将其尾部保持为 NULL 是否可以或在道德上正确?

    在 C 中,这是一种很常见的模式,并且它有一个名称。你只是在使用sentinel value

    只要您的列表通常不能包含空指针,就可以了。一般来说,这有点容易出错,但是,再一次,这对你来说是 C。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-22
      • 2019-09-19
      • 1970-01-01
      • 2011-10-01
      • 2019-08-24
      • 1970-01-01
      • 1970-01-01
      • 2021-09-27
      相关资源
      最近更新 更多