2

The other day, I was reading a book where I found this code:

char *words[][40] = {"Goat", "A herbivore",
                     "Dog", "An omnivore",
                     "Fox", "A carnivore",
                     "Bear", "An omnivore"
                     "", ""};

And the book said, "notice, the list must be terminated by two nulls". But when I compiled this code without the nulls, it compiled and worked just as desired. Please explain if the nulls are of any use. I am quite new to C(++), so please elaborate.

4

3 回答 3

7

"", ""用作标记值,以指示数组的结尾,因为未指定数组的维数,而是从初始化程序推导出来的。注意那些不是NULLs,而是空字符串。遍历此数组的循环将用于strlen(words[i])知道它正在访问数组中的最后一个元素并防止访问超出数组的范围。

例如:

for (int i = 0; strlen(words[i]); i++)
{
}

可以使用而不是使用空字符串NULL来避免strlen()调用来检测数组中的最后一个元素:

const char *words[] = {"Goat", "A herbivore",
                       "Dog", "An omnivore",
                       "Fox", "A carnivore",
                       "Bear", "An omnivore",
                       NULL};


for (int i = 0; words[i]; i++)
{
}

可以确定 中的元素数量words,即使维度没有明确说明使用sizeof(words)/sizeof(words[0]),但如果数组被传递给函数,它会衰减到指向数组第一个元素的指针,并且计算不再正确,并且要么数组中必须存在标记值,或者必须将数组中的元素数量提供给函数。

于 2013-04-24T11:39:12.433 回答
2

出于技术原因,该列表不必以两个空值终止。

但是如果你的代码不知道列表的长度,你可以遍历它直到它被空元素终止。

于 2013-04-24T11:40:00.153 回答
0

您发布的代码不会编译。你确定你是从书里抄来的吗?

如果我们添加明显缺失的逗号,我们最终会得到等价的

char* words[1][40] = 
{
    "Goat"
    "A herbivore",
    "Dog"
    "An omnivore",
    "Fox"
    "A carnivore",
    "Bear"
    "An omnivore",
    "",
    "",
    NULL,
    //  this last line repeated 30 times...
};

我怀疑这也是我们的意图。(它会生成一个以 30 个空值终止的列表,而不仅仅是 2 个。)

我怀疑这意味着什么也没有*

char words[][2][40] =
{
    "Goat", "A herbivore",
    "Dog", "An omnivore",
    "Fox", "A carnivore",
    "Bear", "An omnivore",
    "", ""
};

这导致数组 [10] 的数组 [40] 为char。当然,最好清楚地表明这一点:

char words[][2][40] =
{
    { "Goat", "A herbivore" },
    { "Dog" , "An omnivore" },
    { "Fox" , "A carnivore" },
    { "Bear", "An omnivore" },
    { ""    , ""            },
};

关于“null”的含义仍然存在问题。如果它是一个空指针,那么在这个版本中没有(也不可能有)任何指针,因为没有指针。如果是空字符 ( '\0'),则有 10 个,每个字符串一个。如果它是一个空字符串,那么作者使用了一个最不寻常和最令人困惑的术语;如果他的意思是空字符串,他应该说空字符串。

作为两个需要的东西:我想目的是使用哨兵。写这个(在 C 或 C++ 中)的常用方法 使用指针,并编写如下内容:

char const* const words[][2] =
{
    { "Goat", "A herbivore" },
    { "Dog" , "An omnivore" },
    { "Fox" , "A carnivore" },
    { "Bear", "An omnivore" },
    { NULL  , NULL          }
};

在这种情况下,我可能会使用两个空值,但初始化数组的最后一行可以是{}{ NULL }{ NULL, NULL }; 它们都会对编译器产生相同的结果。

即使是非指针版本,一个空字符串就足够了。编译器也会用空字符串填充所有附加值。

最后,关于为什么需要空指针或空字符串:在 C 中,它们经常被用作哨兵:你会循环直到遇到一个:

for ( char const* const (*p)[2] = words; (*p)[0] != NULL; ++ p ) {
    std::cout << (*p)[0] << " - " << (*p)[1] << std::endl;
}

在 C++ 中,您几乎从不使用哨兵,因为您可以编写如下内容:

for ( char const* const (*p)[2] = begin( words );
        p != end( words );
        ++ p ) {
    std::cout << (*p)[0] << " - " << (*p)[1] << std::endl;
}

在 C++11 中,这将是std::beginand std::end,但当然,在 C++11 之前,每个人在他们的个人工具包中都有它们的实现。在 C++11 中,您还可以使用auto

for ( auto p = begin( words ); p != end( words ); ++ p ) {
    std::cout << (*p)[0] << " - " << (*p)[1] << std::endl;
}

但是,我不确定这是否能给我们带来很多好处。使用的时候还是要注意复杂的类型。(auto当您拥有基本上易于使用但难以拼写的类型时,确实会购买很多,例如迭代器。)

最后,当然,无论是在 C 中还是在 C++ 中,都不会有人为此使用二维数组。我们将定义一个结构:

struct Mapping
{
    char const* animalName;
    char const* eatingType;
};

Mapping const map[] = 
{
    { "Goat", "A herbivore" },
    { "Dog" , "An omnivore" },
    { "Fox" , "A carnivore" },
    { "Bear", "An omnivore" },
    { NULL  , NULL          }
};

for ( Mapping const* p = map; p->animalName != NULL; ++ p ) {
    //  ...
}

(当然,在 C++ 中使用beginandend而不是哨兵。)

顺便说一句:除非你严重错误地引用了这本书,否则就把它扔掉。作者显然既不懂 C 也不懂 C++。

于 2013-04-24T12:40:29.457 回答