Niedawno zacząłem uczyć się C ++ i jak większość ludzi (zgodnie z tym, co czytałem) mam problemy ze wskaźnikami.
Nie w tradycyjnym tego słowa znaczeniu, rozumiem czym one są i dlaczego są używane oraz w jaki sposób mogą być przydatne, jednak nie rozumiem, w jaki sposób przydatne byłyby zwiększanie wskaźników, czy ktoś może wyjaśnić, w jaki sposób zwiększanie wskaźnika jest przydatna koncepcja i idiomatyczny C ++?
To pytanie pojawiło się po tym, jak zacząłem czytać książkę A Tour of C ++ autorstwa Bjarne Stroustrup, polecono mi tę książkę, ponieważ znam się na Javie, a chłopaki z Reddit powiedzieli mi, że będzie to dobra książka do przejścia .
Odpowiedzi:
Gdy masz tablicę, możesz ustawić wskaźnik wskazujący na element tablicy:
Tutaj
p
wskazuje na pierwszy elementa
, którym jesta[0]
. Teraz możesz zwiększyć wskaźnik, aby wskazywał następny element:Teraz
p
zwraca się do drugiego elementua[1]
. Możesz uzyskać dostęp do elementu tutaj za pomocą*p
. Różni się to od języka Java, w którym należy użyć zmiennej indeksu liczb całkowitych, aby uzyskać dostęp do elementów tablicy.Zwiększanie wskaźnika w C ++, gdy wskaźnik ten nie wskazuje na element tablicy, jest niezdefiniowanym zachowaniem .
źródło
Zwiększanie wskaźników to idiomatyczne C ++, ponieważ semantyka wskaźników odzwierciedla podstawowy aspekt filozofii projektowania stojącej za standardową biblioteką C ++ (opartą na STL Aleksandra Stepanowa )
Ważną koncepcją jest to, że STL jest zaprojektowany wokół kontenerów, algorytmów i iteratorów. Wskaźniki są po prostu iteratorami .
Oczywiście, zdolność do zwiększania (lub dodawania / odejmowania) wskaźników wraca do C. Wiele algorytmów manipulacji ciągiem C można zapisać po prostu za pomocą arytmetyki wskaźnika. Rozważ następujący kod:
Ten kod używa arytmetyki wskaźnika do kopiowania łańcucha C zakończonego znakiem null. Pętla automatycznie kończy się, gdy napotka zero.
W C ++ semantyka wskaźników jest uogólniona na pojęcie iteratorów . Większość standardowych kontenerów C ++ zapewnia iteratory, do których można uzyskać dostęp za pomocą funkcji
begin
iend
członków. Iteratory zachowują się jak wskaźniki, ponieważ mogą być zwiększane, usuwane, a czasem zmniejszane lub rozszerzane.Aby powtórzyć
std::string
, powiedzielibyśmy:Zwiększamy iterator tak samo, jak zwiększamy wskaźnik do zwykłego ciągu C. Powodem, dla którego ta koncepcja jest potężna, jest to, że można używać szablonów do pisania funkcji, które będą działać dla dowolnego typu iteratora, który spełnia niezbędne wymagania dotyczące koncepcji. A to jest siła STL:
Ten kod kopiuje ciąg do wektora. Ta
copy
funkcja jest szablonem, który będzie działał z dowolnym iteratorem obsługującym inkrementację (w tym zwykłe wskaźniki). Możemy użyć tej samejcopy
funkcji na zwykłym łańcuchu C:Mogliśmy korzystać
copy
na zasadziestd::map
albostd::set
czy jakiejkolwiek niestandardowego kontenera, który obsługuje iteratorów.Zauważ, że wskaźniki są specyficznym typem iteratora: iterator o dostępie swobodnym , co oznacza, że obsługują one zwiększanie, zmniejszanie i przyspieszanie z operatorem
+
i-
. Inne typy iteratorów obsługują tylko podzbiór semantyki wskaźnika: dwukierunkowy iterator obsługuje co najmniej zwiększanie i zmniejszanie; A forward iteracyjnej podpory przynajmniej zwiększany. (Wszystkie typy iteratorów obsługują dereferencje). Tacopy
funkcja wymaga iteratora, który przynajmniej obsługuje inkrementację.Możesz przeczytać o różnych koncepcjach iteratora tutaj .
Zatem zwiększanie wskaźników jest idiomatycznym sposobem C ++ do iteracji po tablicy C lub uzyskiwania dostępu do elementów / przesunięć w tablicy C.
źródło
Arytmetyka wskaźnika jest w C ++, ponieważ była w C. Arytmetyka wskaźnika jest w C, ponieważ jest to zwykły idiom w asemblerze .
Istnieje wiele systemów, w których „rejestr przyrostów” jest szybszy niż „ładowanie stałej wartości 1 i dodawanie do rejestru”. Co więcej, wiele systemów pozwala „załadować DWORD do A z adresu określonego w rejestrze B, a następnie dodać sizeof (DWORD) do B” w jednej instrukcji. W dzisiejszych czasach możesz spodziewać się optymalizującego kompilatora, który to rozwiązuje, ale tak naprawdę nie było takiej możliwości w 1973 roku.
Jest to w zasadzie ten sam powód, dla którego tablice C nie są sprawdzane pod kątem granic, a łańcuchy C nie mają w sobie osadzonego rozmiaru: język został opracowany w systemie, w którym liczy się każdy bajt i każda instrukcja.
źródło