Чи варто використовувати char ** argv або char * argv []?


125

Я просто вивчав С і цікавився, який із них я повинен використовувати в своєму головному методі. Чи є різниця? Який із них зустрічається частіше?


2
Я віддаю перевагу "char ** argv", і тому я це частіше бачу, але обидва є правильними, і я б не міняв декларацію просто тому, що вона була написана "char * argv []".
Джонатан Леффлер

+1, тому що для розуміння важливо глибші питання масиву проти вказівника.
RBerteig

2
Дійсно, справді гарне запитання. Дякую.
Франк V

5
Прочитайте розділ 6 поширених запитань на 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" в межах функції - це стало покажчиком 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
Поняття не мав, що це існує: * argv [статичний 1]
superlukas

12

Ви можете використовувати будь-який. Вони абсолютно рівноцінні. Дивіться коментарі лампа та його відповідь .

Це дійсно залежить від того, як ви хочете ним користуватися (і ви можете використовувати їх у будь-якому випадку):

// 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 [статичний 100]); Зауважте, що, відповідно до типу, p все ще є покажчиком.
Йоханнес Шауб - ліб

5
(зокрема, & p надасть вам char **, але не char ( ) [100], що було б у випадку, якщо p * буде масивом). Я здивований, поки ніхто не згадував у відповіді. Я вважаю це дуже важливим для розуміння.
Йоханнес Шауб - ліб

Особисто я віддаю перевагу, char**тому що це мені нагадує, що не слід розглядати як справжній масив, як робити sizeof[arr] / sizeof[*arr].
raymai97

9

Це не має значення, але я використовую, char *argv[]оскільки це показує, що це масив фіксованого розміру рядків змінної довжини (які зазвичай char *).



4

Це насправді не має значення, але остання читабельніше. Що вам надається - це масив покажчиків символів, як це говорить друга версія. Однак це можна неявно перетворити на подвійний покажчик символів, як і у першій версії.


2

ви повинні оголосити це як char *argv[]через всі безліч рівнозначних способів його оголошення, що найбільш близьке до його інтуїтивного значення: масив рядків.


1

char ** → покажчик на покажчик символів та char * argv [] означає масив символьних вказівників. Оскільки ми можемо використовувати вказівник замість масиву, обидва можуть бути використані.


0

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


-2

Якщо вам потрібна змінна або динамічна кількість струн, char ** може бути простіше працювати. Якщо число рядків зафіксовано, кращим буде * char * var [].


-2

Я знаю, що це застаріло, але якщо ви просто вивчаєте мову програмування на С і не робите з цим нічого важливого, не використовуйте параметри командного рядка.

Якщо ви не використовуєте аргументи командного рядка, не використовуйте жодного з них. Просто оголосьте основну функцію так, як int main() якщо б ви

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

користуйтеся тим, що має для вас більше сенсу. В іншому випадку просто використовуйте int main() Зрештою, якщо ви хочете додати параметри командного рядка, ви можете їх легко відредагувати пізніше.


Це не відповідає на запитання.
Лундін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.