8

我对以下段落如何与后面的代码匹配感到困惑:

由于 argv 是指向指针数组的指针,我们可以操作指针而不是索引数组。下一个变体基于递增 argv,它是指向 char 的指针,而 argc 是倒计时的:

#include <stdio.h>
/* echo command-line arguments; 2nd version */
main(int argc, char *argv[])
{
    while (--argc > 0)
        printf("%s%s", *++argv, (argc > 1) ? " " : "");
    printf("\n");
    return 0;
}

char *argv[]只是一个指针数组吗?指向指针数组的指针不会写成char *(*argv[])或类似的东西吗?

作为旁注,通常我发现混合数组和指针的声明相当混乱,这是否正常?

4

6 回答 6

12

诸如“指向数组的指针”或“指向数组的指针”之类的术语在 C 术语中经常被相当松散地处理。它们可能意味着至少两种不同的东西。

在术语的最严格和迂腐的意义上,“指向数组的指针”必须用“指向数组的指针”类型声明,如

int a[10];
int (*p)[10] = &a;

在上面的示例中,它p被声明为指向 10 ints 数组的指针,并且它实际上被初始化为指向这样的数组。

然而,该术语也经常被使用是其不太正式的含义。在这个例子中

int a[10];
int *p = &a;

p被声明为仅指向int. 它被初始化为指向数组的第一个元素a。您经常可以听到和看到人们说p在这种情况下也“指向一个数组” ints,尽管这种情况在语义上与以前的情况不同。在这种情况下,“指向数组”的意思是“通过指针算法提供对数组元素的访问”,如p[5]or *(p + 3)

这正是argv您引用的短语“......是指向指针数组的指针......”的意思。argv的参数列表中的声明main等价于char **argv,这意味着它argv实际上是一个指向指针的char *指针。但由于它在物理上指向某个指针数组的第一个元素char *(由调用代码维护),因此半非正式地说argv指向指针数组是正确的。

这正是您引用的文字的意思。

于 2013-06-22T20:30:08.557 回答
5

在 C 函数声称接受数组的地方,严格来说它们接受指针。该语言不区分void fn(int *foo) {}void fn(int foo[])。它甚至不关心你是否有void fn(int foo[100])然后传递一个int [10].

int main(int argc, char *argv[])

是相同的

int main(int argc, char **argv)

因此,argv指向指针数组的第一个元素char,但它本身不是数组类型,并且它不(正式)指向整个数组。但是我们知道数组在那里,我们可以索引它来获取其他元素。

在更复杂的情况下,比如接受多维数组,它只是第一个[]返回指针(并且可以不设置大小)。其他的仍然是被指向的类型的一部分,它们对指针算法有影响。

于 2013-06-22T20:48:55.610 回答
2

数组指针等价仅适用函数arguments,因此虽然void fn(const char* argv[])void fn(const char** argv)是等价的,但当涉及到您可能想要传递给函数的变量时,它并不成立。

考虑

void fn(const char** argv)
{
    ...
}

int main(int argc, const char* argv[])
{
    fn(argv); // acceptable.

    const char* meats[] = { "Chicken", "Cow", "Pizza" };

    // "meats" is an array of const char* pointers, just like argv, so
    fn(meats); // acceptable.

    const char** meatPtr = meats;
    fn(meatPtr); // because the previous call actually cast to this,.

    // an array of character arrays.
    const char vegetables[][10] = { "Avocado", "Pork", "Pepperoni" };
    fn(vegetables); // does not compile.

    return 0;
}

"vegetables" 不是指向指针的指针,它直接指向 3*10 连续字符序列中的第一个字符。替换上面的 fn(vegetables) 得到

int main(int argc, const char* argv[])
{
    // an array of character arrays.
    const char vegetables[][10] = { "Avocado", "Pork", "Pepperoni" };
    printf("*vegetables = %c\n", *(const char*)vegetables);

    return 0;
}

并且输出是“A”:蔬菜本身直接——没有间接地——指向字符,而不是中间指针。

蔬菜分配基本上是一个捷径:

const char* __vegetablesPtr = "Avocado\0\0\0Pork\0\0\0\0\0\0Pepperoni\0";
vegetables = __vegetablesPtr;

const char* roni = vegetables[2];

翻译成

const char* roni  = (&vegetables[0]) + (sizeof(*vegetables[0]) * /*dimension=*/10 * /*index=*/2);
于 2013-06-22T22:03:55.370 回答
0

“指向数组第一个元素的指针”是一种常见的构造。每个字符串函数都使用它,包括输入和输出字符串的 stdio 函数。main 将其用于 argv。

“指向数组的指针”是一种罕见的构造。我在 C 标准库或 POSIX 中找不到它的任何用途。grepping我在本地安装的所有头文件(对于'([^)]*\*[^)]) *\[')我找到了2个指向数组的合法实例,一个在libjpeg中,一个在gtk中。(两者都是结构成员,而不是函数参数,但这不是重点。)

因此,如果我们坚持使用官方语言,我们会发现一个少见的短名称和一个类似但更常见的长名称。这与人类语言自然想要工作的方式相反,因此存在紧张,除了最正式的情况外,通过使用“不正确”的简称来解决。

我们不只是说“指向指针的指针”的原因是指针作为函数参数的另一种常见用法,其中参数指向不是数组成员的单个对象。例如,在

long strtol(const char *nptr, char **endptr, int base);

endptrargv与in 中的类型完全相同main,都是指针到指针,但它们的使用方式不同。argv指向schar *数组中的第一个char *;在 main 中,您应该将它与 , 等索引一起使用argv[0]argv[optind]或者通过使用 . 递增它来逐步遍历数组++argv

endptr指向单个char *. 在内部strtol,增加endptr或引用除零以外endptr[n]的任何值都没有用。n

这种语义差异通过“argv 是指向数组的指针”的非正式用法来表达。忽略与正式语言中“指向数组的指针”含义的可能混淆,因为使用简洁语言的自然本能比坚持正式定义的愿望更强,该正式定义告诉您不要使用最明显的简单短语,因为它是保留的对于几乎永远不会发生的情况。

于 2013-06-22T21:04:43.720 回答
0

Since argv is a pointer to an array of pointers.

这是错误的。argv是一个指针数组。

于 2013-06-22T20:11:40.173 回答
0

由于 argv 是指向指针数组的指针,

不,甚至没有接近。

char *argv[]只是一个指针数组吗?

不,它是指向指针的指针。

于 2013-06-22T20:12:16.397 回答