让我先说一些题外话:
- 我不认为这是一本很好的书。我认为它混淆了一些主题,使它们看起来比实际上更难。如果想要一本更好的高级 C 书,我会推荐Peter van der Linden 的《深 C 秘密》 https://rads.stackoverflow.com/amzn/click/com/0131774298,对于初学者的书,我推荐原版凯瑞 https://rads.stackoverflow.com/amzn/click/com/0131103628
不管怎样,看起来你正在看来自的额外学分练习本章 http://c.learncodethehardway.org/book/ex15.html.
- 另外,我不认为这对于学习来说是一个特别明智的练习(另一个答案指出这个问题的形成没有意义),所以这个讨论会变得有点复杂。相反,我会推荐第 5 章中的练习。凯瑞 https://rads.stackoverflow.com/amzn/click/com/0131103628.
首先我们需要明白指针与数组不同 http://c-faq.com/aryptr/aryptr2.html。我在另一个答案中对此进行了扩展here https://stackoverflow.com/a/9628309/790070,我将从中借用相同的图表C FAQ http://c-faq.com/aryptr/aryptr2.html。以下是当我们声明数组或指针时内存中发生的情况:
char a[] = "hello"; // array
+---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
+---+---+---+---+---+---+
char *p = "world"; // pointer
+-----+ +---+---+---+---+---+---+
p: | *======> | w | o | r | l | d |\0 |
+-----+ +---+---+---+---+---+---+
因此,在书中的代码中,当我们说:
int ages[] = {23, 43, 12, 89, 2};
We get:
+----+----+----+----+---+
ages: | 23 | 43 | 12 | 89 | 2 |
+----+----+----+----+---+
我将使用非法声明来进行解释 - 如果我们could讲过了:
int *ages = {23, 43, 12, 89, 2}; // The C grammar prohibits initialised array
// declarations being assigned to pointers,
// but I'll get to that
这会导致:
+---+ +----+----+----+----+---+
ages: | *=====> | 23 | 43 | 12 | 89 | 2 |
+---+ +----+----+----+----+---+
稍后可以以相同的方式访问这两个元素 - 第一个元素“23”可以通过以下方式访问ages[0]
,无论它是数组还是指针。到目前为止,一切都很好。
然而,当我们想要获得计数时,我们遇到了问题。 C 不知道数组有多大——它只知道它所知道的变量有多大(以字节为单位)。这意味着,对于数组,您可以通过以下方式计算出大小:
int count = sizeof(ages) / sizeof(int);
或者,更安全的是:
int count = sizeof(ages) / sizeof(ages[0]);
在数组的情况下,这表示:
int count = the number of bytes in (an array of 6 integers) /
the number of bytes in (an integer)
它正确给出了数组的长度。然而,对于指针的情况,它将显示为:
int count = the number of bytes in (**a pointer**) /
the number of bytes in (an integer)
这几乎肯定与数组的长度不同。当使用指向数组的指针时,我们需要使用另一种方法来计算数组的长度。在 C 语言中,以下任一情况都是正常的:
-
记住有多少个元素:
int *ages = {23, 43, 12, 89, 2}; // Remember you can't actually
// assign like this, see below
int ages_length = 5;
for (i = 0 ; i < ages_length; i++) {
-
或者,保留一个标记值(该值永远不会作为数组中的实际值出现)来指示数组的结尾:
int *ages = {23, 43, 12, 89, 2, -1}; // Remember you can't actually
// assign like this, see below
for (i = 0; ages[i] != -1; i++) {
(这就是字符串的工作方式,使用特殊的 NULL 值 '\0' 来指示字符串的结尾)
现在,请记住我说过你实际上不能写:
int *ages = {23, 43, 12, 89, 2, -1}; // Illegal
这是因为编译器不允许您将隐式数组分配给指针。如果你真的想,你可以写:
int *ages = (int *) (int []) {23, 43, 12, 89, 2, -1}; // Horrible style
但不要这样做,因为读起来非常不愉快。出于本练习的目的,我可能会写:
int ages_array[] = {23, 43, 12, 89, 2, -1};
int *ages_pointer = ages_array;
请注意,编译器正在将数组名称“衰减”为指向其第一个元素的指针 - 就好像您编写了:
int ages_array[] = {23, 43, 12, 89, 2, -1};
int *ages_pointer = &(ages_array[0]);
但是 - 您也可以动态分配数组。对于这个示例代码,它会变得相当冗长,但我们可以将其作为学习练习。而不是写:
int ages[] = {23, 43, 12, 89, 2};
我们可以使用 malloc 分配内存:
int *ages = malloc(sizeof(int) * 5); // create enough space for 5 integers
if (ages == NULL) {
/* we're out of memory, print an error and exit */
}
ages[0] = 23;
ages[1] = 43;
ages[2] = 12;
ages[3] = 89;
ages[4] = 2;
请注意,我们然后需要释放ages
当我们完成记忆后:
free(ages);
另请注意,有几种编写 malloc 调用的方法:
int *ages = malloc(sizeof(int) * 5);
这对于初学者来说读起来更清晰,但通常被认为是不好的风格,因为如果更改类型,则需要更改两个地方ages
。相反,您可以编写以下任一内容:
int *ages = malloc(sizeof(ages[0]) * 5);
int *ages = malloc(sizeof(*ages) * 5);
这些陈述是等效的 - 您选择哪一个取决于个人风格。我更喜欢第一个。
最后一件事 - 如果我们将代码更改为使用数组,您可能会考虑更改此内容:
int main(int argc, char *argv[]) {
但是,你不需要。原因有点微妙。首先,声明如下:
char *argv[]
说“有一个指向 char 的指针数组,称为 argv”。但是,编译器将函数参数中的数组视为指向数组第一个元素的指针,因此如果您编写:
int main(int argc, char *argv[]) {
编译器实际上会看到:
int main(int argc, char **argv)
这也是您可以省略用作函数参数的多维数组的第一维长度的原因 - 编译器不会看到它。