Niedawno miałem przyjemność wyjaśnić wskazówki początkującym programistom w C i napotkałem następującą trudność. Może się to w ogóle nie wydawać problemem, jeśli już wiesz, jak używać wskaźników, ale spróbuj spojrzeć na poniższy przykład z czystym umysłem:
int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);
Dla absolutnie początkującego rezultat może być zaskakujący. W linii 2 właśnie zadeklarował * bar jako & foo, ale w linii 4 okazuje się, że * bar to w rzeczywistości foo zamiast & foo!
Można powiedzieć, że zamieszanie wynika z niejednoznaczności symbolu *: w linii 2 jest używany do zadeklarowania wskaźnika. W linii 4 jest używany jako operator jednoargumentowy, który pobiera wartość wskazywaną przez wskaźnik. Dwie różne rzeczy, prawda?
Jednak to „wyjaśnienie” wcale nie pomaga początkującym. Wprowadza nową koncepcję, wskazując na subtelną rozbieżność. To nie może być właściwy sposób nauczania.
Jak więc Kernighan i Ritchie to wyjaśnili?
Operator jednoargumentowy * jest operatorem pośrednim lub wyłuskiwaniem; po zastosowaniu do wskaźnika uzyskuje dostęp do obiektu wskazywanego przez wskaźnik. […]
Deklaracja wskaźnika ip
int *ip
jest traktowana jako mnemonik; mówi, że wyrażenie*ip
jest int. Składnia deklaracji zmiennej naśladuje składnię wyrażeń, w których zmienna może się pojawić .
int *ip
powinno być odczytywane jako „ *ip
zwróci int
”? Ale dlaczego w takim razie przypisanie po deklaracji nie jest zgodne z tym wzorcem? Co jeśli początkujący chce zainicjować zmienną? int *ip = 1
(czytaj: *ip
zwróci wartość int
i int
jest 1
) nie będzie działać zgodnie z oczekiwaniami. Model konceptualny po prostu nie wydaje się spójny. Czy coś mi umyka?
Edycja: próbowano podsumować odpowiedzi tutaj .
*
w deklaracji jest to token oznaczający „deklaruj wskaźnik”, w wyrażeniach jest to operator wyłuskiwania i te dwa reprezentują różne rzeczy, które mają ten sam symbol (to samo co operator mnożenia - ten sam symbol, inne znaczenie). To zagmatwane, ale wszystko inne niż rzeczywisty stan rzeczy będzie jeszcze gorsze.
int* bar
było bardziej oczywiste, że gwiazda jest w rzeczywistości częścią typu, a nie częścią identyfikatora. Oczywiście powoduje to różne problemy z nieintuicyjnymi rzeczami, takimi jak int* a, b
.
*
może mieć dwa różne znaczenia w zależności od kontekstu. Tak jak ta sama litera może być wymawiana różnie w zależności od słowa, w którym jest, przez co trudno jest nauczyć się mówić wieloma językami. Gdyby każda koncepcja / operacja miała swój własny symbol, potrzebowalibyśmy znacznie większych klawiatur, więc symbole są przetwarzane, gdy ma to sens.
int* p
), ostrzegając ucznia przed używaniem wielu deklaracji w tym samym wierszu, gdy w grę wchodzą wskaźniki. Kiedy uczeń w pełni zrozumie pojęcie wskaźników, wyjaśnij mu, że int *p
składnia is jest równoważna, a następnie wyjaśnij problem z wieloma deklaracjami.