Что мне следует использовать - char ** argv или char * argv []?

125

Я только изучаю C, и мне было интересно, какой из них мне следует использовать в своем основном методе. Есть ли разница? Какой из них чаще встречается?

Kredns
источник
2
Я предпочитаю 'char ** argv' и поэтому вижу его чаще, но оба верны, и я бы не стал менять объявление просто потому, что оно написано 'char * argv []'.
Джонатан Леффлер,
+1, потому что более глубокие вопросы, связанные с массивом и указателем, важно хорошо понимать.
RBerteig
2
Действительно хороший вопрос. Спасибо.
Frank V
5
Прочтите раздел 6 FAQ по comp.lang.c ; это лучшее объяснение взаимосвязи между массивами C и указателями, которые я видел. Два важных момента: 1. char **argvТочно эквивалентно char *argv[]объявлению параметра (и только объявлению параметра). 2. массивы не являются указателями.
Кейт Томпсон,

Ответы:

160

Поскольку вы только изучаете C, я рекомендую вам сначала действительно попытаться понять различия между массивами и указателями, а не обычными вещами.

В области параметров и массивов есть несколько сбивающих с толку правил, которые следует прояснить перед тем, как продолжить. Во-первых, то , что вы объявляете в списке параметров, считается особенным. Бывают ситуации, когда что-то не имеет смысла в качестве параметра функции в C. Это

  • Функции как параметры
  • Массивы как параметры

Массивы как параметры

Второе, может быть, не сразу понятно. Но это становится ясно, если учесть, что размер измерения массива является частью типа в C (а массив, размер которого не указан, имеет неполный тип). Итак, если вы создадите функцию, которая принимает массив по значению (получает копию), то она может делать это только для одного размера! Кроме того, массивы могут становиться большими, и C старается работать как можно быстрее.

В C по этим причинам значения массива не существуют. Если вы хотите получить значение массива, вместо этого вы получите указатель на первый элемент этого массива. И вот собственно уже решение. Вместо того, чтобы заранее рисовать недопустимый параметр массива, компилятор C преобразует тип соответствующего параметра в указатель. Помните об этом, это очень важно. Параметр не будет массивом, а будет указателем на соответствующий тип элемента.

Теперь, если вы попытаетесь передать массив, вместо этого будет передан указатель на первый элемент массива.

Экскурсия: функции как параметры

В заключение, и поскольку я думаю, что это поможет вам лучше разобраться в этом вопросе, давайте посмотрим, как обстоят дела, когда вы пытаетесь использовать функцию в качестве параметра. Действительно, сначала в этом нет никакого смысла. Как параметр может быть функцией? Ха, нам, конечно же, нужна переменная в этом месте! Поэтому компилятор снова преобразует функцию в указатель на функцию . При попытке передать функцию вместо этого будет передан указатель на соответствующую функцию. То же самое (аналогично примеру с массивом):

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

Обратите внимание, что скобки вокруг *gнеобходимы. В противном случае он будет указывать возвращающуюся функцию void*вместо указателя на возвращающуюся функцию 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" внутри функции - он стал константным указателем. Однако только несколько компиляторов 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;
Йоханнес Шауб - litb
источник
2
Не знал, что он существует: * argv [static 1]
superlukas 01
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 [] на 100% эквивалентен char ** argv при использовании в качестве типа параметра функции. никакая "const" не задействована, также не косвенно. Оба являются указателями на указатели на символы. Другое дело, что вы заявляете. Но компилятор настраивает тип параметра, чтобы он был указателем на указатель, даже если вы сказали, что это массив. Таким образом, следующие элементы одинаковы: void f (char * p [100]); void f (char * p []); void f (char ** p);
Йоханнес Шауб - литб,
4
В C89 (который использует большинство людей) также нет возможности воспользоваться тем фактом, что вы объявили его как массив (поэтому семантически не имеет значения, объявили ли вы там указатель или массив - оба будут приняты как указатель). Начиная с C99, вы можете объявить его как массив. Следующее говорит: «p всегда не равно нулю и указывает на область размером не менее 100 байт»: void f (char p [static 100]); Обратите внимание, что по типу p по- прежнему является указателем.
Йоханнес Шауб - литб,
5
(в частности, & p выдаст вам char **, но не char ( ) [100], что было бы в случае, если бы p * был бы массивом). Я удивлен, что никто еще не упомянул в ответе. Я считаю это очень важным понять.
Йоханнес Шауб - литб,
Лично я предпочитаю, char**потому что это напоминает мне, что его не следует рассматривать как настоящий массив, например, делать sizeof[arr] / sizeof[*arr].
raymai97
9

Это не имеет значения, но я использую, char *argv[]потому что он показывает, что это массив строк переменной длины фиксированного размера (которые обычно есть char *).

Zifre
источник
4

На самом деле это не имеет значения, но последнее более читабельно. Вам предоставляется массив указателей на символы, как сказано во второй версии. Однако его можно неявно преобразовать в указатель на двойной символ, как и в первой версии.

jalf
источник
2

вы должны объявить его как char *argv[], из-за множества эквивалентных способов объявления, который ближе всего к его интуитивному значению: массив строк.

Андраш
источник
1

char ** → указатель на символьный указатель, а char * argv [] означает массив символьных указателей. Поскольку мы можем использовать указатель вместо массива, можно использовать оба.

Alphaneo
источник
0

Я не вижу особых достоинств использования одного из подходов вместо другого - используйте соглашение, которое больше всего соответствует остальной части вашего кода.

Кристоффер
источник
-2

Если вам нужно переменное или динамическое количество строк, с char ** может быть проще работать. Если количество строк фиксировано, предпочтительнее использовать char * var [].

samoz
источник
-2

Я знаю, что это устарело, но если вы просто изучаете язык программирования C и не делаете с ним ничего серьезного, не используйте параметры командной строки.

Если вы не используете аргументы командной строки, не используйте их. Просто объявите основную функцию, как int main() если бы вы

  • Хотите, чтобы пользователь вашей программы мог перетащить файл в вашу программу, чтобы вы могли с его помощью изменить результат вашей программы, или
  • Хотите обрабатывать параметры командной строки ( -help, /?или любые другие вещи, которые идут после program nameв терминале или командной строке)

используйте то, что вам больше подходит. В противном случае просто используйте В int main() конце концов, если вы в конечном итоге захотите добавить параметры командной строки, вы можете легко отредактировать их позже.

Джесси
источник
Это не отвечает на вопрос.
Lundin