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[static5]);// says: argv is a constant pointer pointing to a char*int main(int c,char*argv[const]);// says the same as the previous oneint 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.typedefchararray[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;
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.
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 *).
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.
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.
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.
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 [].
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.
char **argv
jest dokładnie równoważnechar *argv[]
jako deklaracja parametru (i tylko jako deklaracja parametru). 2. tablice nie są wskaźnikami.Odpowiedzi:
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
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):
Zwróć uwagę, że
*g
potrzebne są nawiasy wokół . W przeciwnym razie określiłby funkcję powracającąvoid*
zamiast wskaźnika do funkcji powracającejvoid
.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:
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:
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:
źródło
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):
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.
źródło
char**
ponieważ przypomina mi, że nie należy go traktować jako prawdziwej tablicy, tak jak robisizeof[arr] / sizeof[*arr]
.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 *
).źródło
Możesz użyć jednej z dwóch form, tak jak w C tablice i wskaźniki są wymienne na listach parametrów funkcji. Zobacz http://en.wikipedia.org/wiki/C_(programming_language)#Array-pointer_interchangeability .
źródło
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.
źródło
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.źródło
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.
źródło
Nie widzę żadnych szczególnych zalet używania jednego z podejść zamiast drugiego - użyj konwencji, która jest najbardziej zgodna z resztą kodu.
źródło
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 [].
źródło
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-help
,/?
lub jakąkolwiek inną rzecz, że idzie poprogram name
w 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.źródło