前几天,我正在读一本书,在那里我找到了这段代码:
char *words[][40] = {"Goat", "A herbivore",
"Dog", "An omnivore",
"Fox", "A carnivore",
"Bear", "An omnivore"
"", ""};
这本书说,"注意,列表必须以两个空值终止"。但是当我编译没有null的代码时,它根据需要编译和工作。请解释空值是否有用。我是C(++)的新手,所以请详细说明。
答案 0 :(得分:7)
"", ""
用作标记值,用于指示数组的结尾,因为未指定数组的维度,并且是从初始值设定项推导出的。请注意,这些不是NULL
,而是空字符串。迭代此数组的循环将使用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])
明确说明,但如果数组传递给函数,它会衰减到指向第一个的指针数组的元素和计算不再正确,并且数组中必须存在sentinel值,或者必须将数组中的元素数提供给函数。
答案 1 :(得分:2)
由于技术原因,列表不必由两个空值终止。
但是如果你的代码不知道列表的长度,你可以迭代它直到它被null元素终止。
答案 2 :(得分: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",
"", ""
};
这导致char
的数组[40]的数组[10]。当然,
明确指出这一点会好得多:
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 ++中,你几乎从未使用过sentinal,因为你可以 写点如下:
for ( char const* const (*p)[2] = begin( words );
p != end( words );
++ p ) {
std::cout << (*p)[0] << " - " << (*p)[1] << std::endl;
}
在C ++ 11中,那将是std::begin
和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 ++中使用begin
和end
,而不是
前哨。)
顺便说一句:除非你把这本书误称为错误,否则扔掉 它离开了。作者显然不知道C和C ++。