我应该使用char ** argv还是char * argv []吗?


125

我只是在学习C,并且想知道在我的主要方法中应该使用哪一个。有什么区别吗?哪个更常见?


2
我更喜欢'char ** argv',所以我经常看到它,但是两者都是正确的,我不会仅仅因为它被写为'char * argv []'而更改声明。
乔纳森·莱夫勒

+1是因为数组与指针的更深层次的问题对于理解非常重要。
RBerteig

2
真的,真的是个好问题。谢谢。
Frank V

5
阅读comp.lang.c FAQ的第6节;这是我见过的C数组和指针之间关系的最好解释。有两个相关的要点:1. char **argv完全等同于char *argv[]作为参数声明(并且作为参数声明)。2.数组不是指针。
基思·汤普森

Answers:


160

当您刚刚学习C时,我建议您真正地尝试首先理解数组和指针之间的区别,而不是普通的东西。

在参数和数组方面,有一些令人困惑的规则应该在继续之前弄清楚。首先,您在参数列表中声明的内容是特殊的。在某些情况下,将事情作为C中的函数参数没有意义。

  • 用作参数
  • 数组作为参数

数组作为参数

第二个也许还不清楚。但是,当您考虑到数组维的大小是C语言中的类型的一部分时(而未给出维大小的数组具有不完整的类型),这一点就变得很清楚。因此,如果您要创建一个函数,该函数需要一个按值作为数组的值(接收一个副本),那么它只能对一个大小执行此操作!另外,数组可能会变大,并且C会尝试尽可能快。

由于这些原因,在C中,不存在数组值。如果要获取数组的值,则获取的是指向该数组第一个元素的指针。解决方案实际上已经在这里。C编译器不会预先绘制无效的数组参数,而是将各个参数的类型转换为指针。记住这一点,这非常重要。该参数不是数组,而是一个指向各个元素类型的指针。

现在,如果您尝试传递数组,则传递的是指向数组第一个元素的指针。

游览:用作参数

为了完整起见,并且因为我认为这将帮助您更好地理解问题,所以让我们看看尝试将函数用作参数时的事务状态。确实,首先这没有任何意义。参数如何成为函数?嗯,我们当然想要那个地方的变量!那么,当这种情况发生的编译器所做的是,再次,要转变功能为函数指针。尝试传递一个函数将改为传递一个指向该函数的指针。因此,以下内容相同(类似于数组示例):

void f(void g(void));
void f(void (*g)(void));

注意,*g需要用括号括起来。否则,它将指定一个returning函数void*,而不是一个指向returning函数的指针void

返回数组

现在,我在开始时说过数组可以具有不完整的类型-如果您还不提供大小,则会发生这种情况。由于我们已经发现数组参数不存在,而是任何数组参数都是指针,因此数组的大小无关紧要。这意味着,编译器将转换以下所有内容,并且都是同一件事:

int main(int c, char **argv);
int main(int c, char *argv[]);
int main(int c, char *argv[1]);
int main(int c, char *argv[42]);

当然,可以在其中放置任何大小都没有多大意义,而只是被扔掉了。因此,C99为这些数字提出了新的含义,并允许在括号之间显示其他内容:

// says: argv is a non-null pointer pointing to at least 5 char*'s
// allows CPU to pre-load some memory. 
int main(int c, char *argv[static 5]);

// says: argv is a constant pointer pointing to a char*
int main(int c, char *argv[const]);

// says the same as the previous one
int main(int c, char ** const argv);

最后两行说您将无法在函数中更改“ argv”-它已成为const指针。尽管只有少数C编译器支持这些C99功能。但是这些功能清楚地表明“数组”实际上不是一个。这是一个指针。

一句警告

请注意,仅当您将数组作为函数的参数时,以上所述才是正确的。如果您使用本地数组,则数组将不是指针。这将表现为指针,因为当读出其值如前面所述阵列将被转换为一个指针。但这不应与指针混淆。

以下是一个经典的示例:

char c[10]; 
char **c = &c; // does not work.

typedef char array[10];
array *pc = &c; // *does* work.

// same without typedef. Parens needed, because [...] has 
// higher precedence than '*'. Analogous to the function example above.
char (*array)[10] = &c;

2
不知道这个存在

12

您可以使用任何一个。它们完全等效。请参阅litb的评论和他的答案

这实际上取决于您要使用它的方式(在任何情况下都可以使用):

// echo-with-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char **argv)
{
  while (--argc > 0)
  {
    printf("%s ", *++argv);
  }
  printf("\n");
  return 0;
}

// echo-without-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char *argv[])
{
  int i;
  for (i=1; i<argc; i++)
  {
    printf("%s ", argv[i]);
  }
  printf("\n");
  return 0;
}

至于哪个更常见-没关系。任何有经验的C语言程序员(在适当的条件下)都可以互换代码。就像一位经验丰富的英语讲者一样轻松地读“他们是”和“他们是”。

更重要的是,您学会阅读它们并认识它们的相似之处。您阅读的代码将比编写的更多,并且您需要对两者都同样满意。


7
当作为函数的参数类型使用时,char * argv []等同于char ** argv 100%。不涉及“ const”,也不隐含。两者都是指向字符的指针。您声明的内容有所不同。但是,即使您说它是数组,编译器也会将参数的类型调整为指向指针的指针。因此,以下内容都是相同的:void f(char * p [100]); 无效f(char * p []); 无效f(char ** p);
Johannes Schaub-litb

4
在C89(大多数人使用)中,也没有办法利用您声明为数组这一事实(因此从语义上来讲,无论您在其中声明了指针还是数组都没有关系-两者都将被视为指针)。从C99开始,您可以受益于将其声明为数组。如下所示:“ p始终为非null,并指向至少具有100bytes的区域”:void f(char p [static 100]); 请注意,按类型划分,p 仍然是一个指针。
Johannes Schaub-litb

5
(特别是&p将给您一个char **,但不会给您char()[100],如果p *将是一个数组,情况将会是这样)。我很惊讶在答案中没有人提到。我认为理解非常重要。
Johannes Schaub-litb

我个人比较喜欢,char**因为它提醒我不应将其视为真实数组,例如doing sizeof[arr] / sizeof[*arr]
raymai97 '17

9

它没有什么区别,但我使用char *argv[]它是因为它显示的是固定长度的可变长度字符串数组(通常是char *)。



4

它并没有真正的改变,但后者更具可读性。像第二个版本一样,您将得到一个char指针数组。可以像第一个版本一样将其隐式转换为double char指针。


2

char *argv[]由于所有许多等效的声明方式,您都应将其声明为,最接近其直观含义:字符串数组。


1

char **→指向字符指针的指针,而char * argv []表示字符指针的数组。因为我们可以使用指针而不是数组,所以两者都可以使用。


0

我看不到使用任何一种方法代替另一种方法的特殊优点-使用与您的代码其余部分最一致的约定。


-2

如果您需要可变数量或动态数量的字符串,char **可能更易于使用。如果您的字符串数量是固定的,那么char * var []将是首选。


-2

我知道这已经过时了,但是如果您只是在学习C编程语言并且不做任何重要的事情,请不要使用命令行选项。

如果您没有使用命令行参数,则不要使用任何一个。只需将main函数声明为int main() If

  • 希望程序的用户能够将文件拖到您的程序上,以便您可以使用它来更改程序的结果,或者
  • 想要处理命令行选项(-help/?program name终端或命令提示符中出现的任何其他内容)

使用对您更有意义的方法。否则,只需使用int main() 毕竟,如果您最终想要添加命令行选项,则可以在以后轻松地对其进行编辑。


这不能回答问题。
伦丁
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.