C中的指针数组,易于迭代

Array of pointers in C with easy iteration

最近我在思考这个问题:如何在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] = '[=12=]';

for (int i = 0; str[i] != '[=12=]'; 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 迭代器...

Now my question: Is it ok or morally right to allocate +1 unit in an array of pointers to maintain its tail as NULL?

在 C 语言中,这是一个很常见的模式,而且它有一个名字。您只是在使用 sentinel value.

只要您的列表通常不能包含空指针就可以了。总的来说,它有点容易出错,不过,话又说回来,这对你来说是 C。

Now my question: Is it ok or morally right to allocate +1 unit in an array of pointers to maintain its tail as NULL?

没关系,最后的 NULL 被称为 标记值 并且使用一个是比较常见的做法。当您出于某种原因甚至不知道数据的大小时,最常使用这种方法。

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

还可以,而且是常用的模式

作为替代方案,您可以使用 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]);
}