Powinienem używać char ** argv czy char * argv []?

125

Właśnie uczę się C i zastanawiam się, którego z nich powinienem użyć w mojej głównej metodzie. Czy jest jakaś różnica? Który jest bardziej powszechny?

Kredns
źródło
2
Wolę 'char ** argv', więc widzę to częściej, ale oba są poprawne i nie zmieniłbym deklaracji tylko dlatego, że została napisana 'char * argv []'.
Jonathan Leffler
+1, ponieważ głębsze kwestie dotyczące tablicy i wskaźnika są ważne, aby dobrze zrozumieć.
RBerteig
2
Naprawdę, naprawdę dobre pytanie. Dziękuję Ci.
Frank V
5
Przeczytaj sekcję 6 FAQ comp.lang.c ; jest to najlepsze wyjaśnienie związku między tablicami C i wskaźnikami, jakie widziałem. Dwa istotne punkty: 1. char **argvjest dokładnie równoważne char *argv[]jako deklaracja parametru (i tylko jako deklaracja parametru). 2. tablice nie są wskaźnikami.
Keith Thompson

Odpowiedzi:

160

Ponieważ dopiero uczysz się C, radzę ci najpierw naprawdę spróbować zrozumieć różnice między tablicami i wskaźnikami, zamiast zwykłych rzeczy.

W obszarze parametrów i tablic istnieje kilka zagmatwanych reguł, które powinny być jasne, zanim przejdziemy dalej. Po pierwsze, to , co deklarujesz w liście parametrów, jest traktowane jako specjalne. Są takie sytuacje, w których rzeczy nie mają sensu jako parametr funkcji w C. Są to

  • Działa jako parametry
  • Tablice jako parametry

Tablice jako parametry

Druga może nie jest od razu jasna. Ale staje się jasne, gdy weźmiesz pod uwagę, że rozmiar wymiaru tablicy jest częścią typu w C (a tablica, której rozmiar wymiaru nie jest podana, ma niepełny typ). Tak więc, gdybyś utworzył funkcję, która pobiera tablicę według wartości (otrzymuje kopię), mogłaby to zrobić tylko dla jednego rozmiaru! Ponadto tablice mogą stać się duże, a C stara się działać tak szybko, jak to możliwe.

Z tych powodów w języku C wartości-tablic nie istnieją. Jeśli chcesz uzyskać wartość tablicy, zamiast tego otrzymasz wskaźnik do pierwszego elementu tej tablicy. I tak właściwie już jest rozwiązanie. Zamiast rysować z góry nieprawidłowy parametr tablicy, kompilator C przekształci typ odpowiedniego parametru, aby był wskaźnikiem. Pamiętaj o tym, to jest bardzo ważne. Parametr nie będzie tablicą, ale zamiast tego będzie wskaźnikiem do odpowiedniego typu elementu.

Teraz, jeśli spróbujesz przekazać tablicę, zamiast tego przekazywany jest wskaźnik do pierwszego elementu tablicy.

Wycieczka: Funkcje jako parametry

Na zakończenie, a ponieważ myślę, że pomoże ci to lepiej zrozumieć sprawę, przyjrzyjmy się, jaki jest stan rzeczy, gdy spróbujesz przyjąć funkcję jako parametr. Rzeczywiście, najpierw nie będzie to miało żadnego sensu. Jak parametr może być funkcją? Hm, oczywiście chcemy mieć zmienną w tym miejscu! Zatem kompilator ponownie przekształca funkcję we wskaźnik funkcji . Próba przekazania funkcji spowoduje zamiast tego przekazanie wskaźnika do odpowiedniej funkcji. Tak więc następujące są takie same (analogiczne do przykładu tablicy):

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

Zwróć uwagę, że *gpotrzebne są nawiasy wokół . W przeciwnym razie określiłby funkcję powracającą void*zamiast wskaźnika do funkcji powracającej void.

Wróćmy do tablic

Teraz powiedziałem na początku, że tablice mogą mieć niekompletny typ - co dzieje się, jeśli nie podasz jeszcze rozmiaru. Ponieważ już ustaliliśmy, że parametr tablicy nie istnieje, ale zamiast tego każdy parametr tablicy jest wskaźnikiem, rozmiar tablicy nie ma znaczenia. Oznacza to, że kompilator przetłumaczy wszystkie poniższe elementy i wszystkie oznaczają to samo:

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]);

Oczywiście nie ma sensu umieszczać w nim dowolnego rozmiaru i jest po prostu wyrzucany. Z tego powodu C99 wymyślił nowe znaczenie tych liczb i pozwala na pojawienie się innych rzeczy w nawiasach:

// 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);

Ostatnie dwie linie mówią, że nie będziesz w stanie zmienić "argv" w funkcji - stał się on wskaźnikiem do stałej. Jednak tylko kilka kompilatorów C obsługuje te funkcje C99. Ale te cechy jasno pokazują, że „tablica” w rzeczywistości nie jest jedną. To wskaźnik.

Słowo ostrzeżenia

Zauważ, że wszystko, co powiedziałem powyżej, jest prawdą tylko wtedy, gdy masz tablicę jako parametr funkcji. Jeśli pracujesz z tablicami lokalnymi, tablica nie będzie wskaźnikiem. Będzie zachowywał się jak wskaźnik, ponieważ jak wyjaśniono wcześniej, tablica zostanie przekonwertowana na wskaźnik po odczytaniu jej wartości. Nie należy go jednak mylić ze wskazówkami.

Oto jeden klasyczny przykład:

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;
Johannes Schaub - litb
źródło
2
Nie miałem pojęcia, że ​​ten istnieje: * argv [statyczny 1]
superlukas
12

Możesz użyć albo. Są całkowicie równoważne. Zobacz komentarze Lita i jego odpowiedź .

To naprawdę zależy od tego, jak chcesz go używać (i możesz użyć dowolnego w każdym przypadku):

// 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;
}

A co jest bardziej powszechne - to nie ma znaczenia. Każdy doświadczony programista C czytający twój kod będzie postrzegał oba jako zamienne (w odpowiednich warunkach). Tak jak doświadczony mówca po angielsku czyta „oni” i „oni są” równie łatwo.

Ważniejsze jest to, aby nauczyć się je czytać i rozpoznawać, jak bardzo są podobne. Będziesz czytać więcej kodu niż pisać i będziesz musiał czuć się równie dobrze z oboma.

rampion
źródło
7
char * argv [] jest w 100% odpowiednikiem char ** argv, gdy jest używany jako typ parametru funkcji. brak „const”, także niejawnie. Obydwa są wskaźnikami do znaków. Jest inaczej, jeśli chodzi o to, co deklarujesz. Ale kompilator dostosowuje typ parametru, aby był wskaźnikiem do wskaźnika, nawet jeśli powiedziałeś, że jest to tablica. Zatem wszystkie poniższe są takie same: void f (char * p [100]); void f (char * p []); nieważne f (char ** p);
Johannes Schaub - litb
4
W C89 (którego używa większość ludzi) nie ma również sposobu, aby skorzystać z faktu, że zadeklarowałeś ją jako tablicę (więc semantycznie nie ma znaczenia, czy zadeklarowałeś tam wskaźnik, czy tablicę - oba będą traktowane jako wskaźnik). Począwszy od C99, możesz zadeklarować go jako tablicę. Następujący tekst mówi: „p jest zawsze niezerowe i wskazuje na region zawierający co najmniej 100 bajtów”: void f (char p [static 100]); Zwróć jednak uwagę, że jeśli chodzi o typ, p jest nadal wskaźnikiem.
Johannes Schaub - litb
5
(w szczególności & p da ci char **, ale nie char ( ) [100], co miałoby miejsce, gdyby p * był tablicą). Dziwię się, że nikt jeszcze nie wymieniony w odpowiedzi. Uważam, że zrozumienie tego jest bardzo ważne.
Johannes Schaub - litb
Osobiście wolę, char**ponieważ przypomina mi, że nie należy go traktować jako prawdziwej tablicy, tak jak robi sizeof[arr] / sizeof[*arr].
raymai97
9

Nie robi to różnicy, ale używam, char *argv[]ponieważ pokazuje, że jest to tablica o stałym rozmiarze zawierająca ciągi o zmiennej długości (które zwykle są char *).

Zifre
źródło
4

Tak naprawdę to nie robi różnicy, ale to drugie jest bardziej czytelne. Otrzymujesz tablicę wskaźników znaków, jak mówi druga wersja. Można go jednak niejawnie przekonwertować na wskaźnik podwójnego znaku, jak w pierwszej wersji.

jalf
źródło
2

powinieneś zadeklarować go jako char *argv[], ze względu na wszystkie równoważne sposoby jego deklarowania, najbliższy jego intuicyjnemu znaczeniu: tablica ciągów.

Andras
źródło
1

char ** → wskaźnik do wskaźnika do znaku, a char * argv [] oznacza tablicę wskaźników do znaków. Ponieważ zamiast tablicy możemy użyć wskaźnika, można użyć obu.

Alphaneo
źródło
0

Nie widzę żadnych szczególnych zalet używania jednego z podejść zamiast drugiego - użyj konwencji, która jest najbardziej zgodna z resztą kodu.

Christoffer
źródło
-2

Jeśli potrzebujesz zmiennej lub dynamicznej liczby ciągów, łatwiej będzie pracować z char **. Jeśli jednak liczba łańcuchów jest ustalona, ​​preferowany byłby znak * var [].

samoz
źródło
-2

Wiem, że jest to przestarzałe, ale jeśli dopiero uczysz się języka programowania C i nie robisz z nim nic ważnego, nie używaj opcji wiersza poleceń.

Jeśli nie używasz argumentów wiersza poleceń, nie używaj ich. Po prostu zadeklaruj główną funkcję jako int main() Jeśli ty

  • Chcesz, aby użytkownik twojego programu mógł przeciągnąć plik do twojego programu, abyś mógł zmienić wynik programu za jego pomocą lub
  • Chcą obsługiwać opcje wiersza polecenia ( -help, /?lub jakąkolwiek inną rzecz, że idzie po program namew terminalu lub wiersza polecenia)

użyj tego, co jest dla Ciebie bardziej sensowne. W przeciwnym razie po prostu użyj opcji W int main() końcu, jeśli chcesz dodać opcje wiersza poleceń, możesz je łatwo edytować później.

Jesse
źródło
To nie odpowiada na pytanie.
Lundin