[Powered by Google Translate] [Wskaźniki] [Rob Bowden] [Harvard University] [To jest CS50] [CS50.TV] Porozmawiajmy o wskaźniki. Aż do teraz, zawsze tylko określone rzeczy w pamięci wyraźnie przez nazwę. Powiedzieliśmy int n = 42, a następnie, gdy chcemy użyć zmiennej n, właśnie nazwać nazwą dajemy go napisanie N * 2. Ale zmienna musi gdzieś mieszkać w pamięci. Gdy chcesz użyć wartości, która jest obecnie przechowywany wewnątrz N, lub zaktualizować wartość n jest gospodarstwo, Twój program musi wiedzieć, gdzie w pamięci szukać n. Jeżeli w pamięci zmienną mieszka nazywa jego adres. To jak adres domowy. Mogę znaleźć kogoś w domu tak długo, jak wiem, ich adres domowy, oraz program komputerowy może znaleźć zmiennej dopóki zna jej adres pamięci. Jakie wskaźniki zapewniają to sposób bezpośrednio do czynienia z tych adresów pamięci. Dużo mocy w C pochodzi od bycia w stanie manipulować pamięci tak. Ale z wielką mocą przychodzi wielka odpowiedzialność. Wskaźniki mogą być stosowane na tyle niebezpiecznie, że wiele języków programowania ukryj wskaźniki całkowicie. Więc, dlaczego nie daje nam wskazówek C potem? Jak wiecie, argumenty funkcji w C zawsze kopiowane do parametrów funkcji. Więc, nazywając coś jak zamiana niektórych zmiennych x i y nie można zamieniać wartości X i Y w funkcji wywołującej, mimo, że może być przydatny. Jak zobaczymy później, przepisywanie Swap podjąć wskaźniki do miejsc, które muszą zostać zamienione Pozwala to mieć wpływ na jego rozmówcy zmienne. Chodźmy w stosunkowo prosty przykład tego, co może zrobić wskaźniki. Powiedzmy, że mamy int n = 4, a int * pointer_to_n = & n. Whoa! Trochę nowej składni do deski. Najpierw, zinterpretować i n. Pamiętaj, że wszystko, co w pamięci ma jakiś adres. Ampersand nazywany jest "adres" operatora. Tak, i n oznacza adres w pamięci, w którym przechowywane jest N. Teraz jesteśmy przechowywania tego adresu w nowej zmiennej, pointer_to_n. Co to jest ten nowy typ zmiennej? Gwiazdka jest częścią typu zmiennej, i będziemy czytać ten typ jako int *. Oznacza to, że * Int. pointer_to_n jest zmienną, która przechowuje adres całkowitej. Wiemy, że & n jest int * od n jest liczbą całkowitą, a my bierzemy adres n. Int. * jest przykład typ wskaźnika. Jak tylko zaczniesz widzieć gwiazdkami w rodzaju, wiesz, że masz do czynienia z wskaźników. Tak jak możemy zadeklarować zmienną jako int x, y, char możemy powiedzieć, int * Z i char * sz. * Int i char *, to tylko nowe typy dla nas do wykorzystania. Lokalizacja * można przejść w dowolnym miejscu przed nazwą zmiennej. Tak więc zarówno int * pointer_to_n - z * obok int, jak my tu - i int pointer_to_n * z * obok pointer_to_n są ważne. Ale tutaj, będę umieszczać * obok int. To nie ma znaczenia, który wolisz, po prostu być spójne. Miejmy narysować schemat do tego. Musimy najpierw zmiennej n, które będziemy czerpać jak małe pole pamięci. Dla tego przykładu, załóżmy, że pole to znajduje się pod adresem 100. Wewnątrz tego pola, my przechowywania wartości 4. Teraz mamy nową zmienną, pointer_to_n. Ma własne pola w pamięci, której mówimy, jest pod adresem 200. Wewnątrz tego pola, więc przechowywanie adresu n które wcześniej powiedział było 100. Najczęściej w diagramach, zobaczysz to pokazane jako dosłowne strzałką pozostawiając pole pointer_to_n wskazując na pole, które przechowuje n. Teraz, co naprawdę możemy zrobić z pointer_to_n? Cóż, jeśli powiedzieć coś w stylu * pointer_to_n = 8, to inne zastosowanie gwiazdka który jest całkowicie niezależny od stosowania gwiazdką w deklarowanie zmiennej typu wskaźnika. Tu gwiazdka nazywa operator dereference. W naszym schemacie, co * = 8 oznacza pointer_to_n jest Przejdź do okna pointer_to_n zawierającego następujące strzałkę, i przypisanie pola na końcu wartość 8 strzałki. Oznacza to, że po tej linii, jeśli próbuje użyć n będzie miał wartość 8. Termin "wskaźnik" jest stosowany w wielu różnych sytuacjach. Tutaj postaramy się być spójne. Bībenkowe jest coś jak int *. W tym filmie, wskaźnik będzie wykorzystywany jedynie oznacza wartość z typem wskaźnika, jak pointer_to_n który posiada * typu int. Gdziekolwiek byliśmy tylko powiedzieć n, możemy zamiast powiedzieć pointer_to_n *, i wszystko będzie działać tak samo dobrze. Przejdźmy przez inny prosty przykład. Powiedzmy, że mamy int n = 14; int * pointer = &N; n + +, oraz (* wskaźnik) + +. Pierwsza linia tworzy nowe pole w pamięci oznaczony n. Tym razem nie będzie oznaczyć pole z wyraźnym adresem, ale nadal ma. Wewnątrz pudełka, mamy przechowywania liczby 14. Następna linia tworzy sekund pole wyboru wskaźnika. A w środku tego pola, będziemy przechowywać wskaźnik do pola oznaczonego n. Więc, narysujmy strzałkę ze wskaźnika na n. Teraz, n + + zwiększa wartość w polu o nazwie n, więc przejść od 14 do 15 lat. Wreszcie (* wskaźnik) + + idzie na pole wyboru wskaźnika, dereferences wartość w polu, co oznacza, śledzić strzałkę, gdzie wskazuje, i zwiększa wartość przechowywana tam, więc przechodzimy od 15 do 16 lat. I to jest to. N teraz zapisuje numer 16 po ustaniu zwiększany dwukrotnie - raz bezpośrednio za pomocą zmiennej n nazwy, a inne przez pointer_to_n. Szybki quiz. Jak myślisz, co to znaczy, jeśli staram się powiedzieć coś && n? Cóż, przepisać to jako & (& n), który realizuje to samo. (& N) zwraca adres zmiennej n w pamięci. Ale potem następnie zewnętrzna ampersand próbuje zwrócić adres adres. To jak próba zrobienia i 2. To nie ma sensu, aby uzyskać adres tylko pewnej liczby ponieważ nie jest przechowywane w pamięci. Używanie dwóch znaków handlowego w rzędzie nigdy prawo pomysł. Ale teraz, co to znaczy, gdy próbuję powiedzieć, int ** double_pointer = & wskaźnik? Teraz tworzę nowe pole wyboru double_pointer, i wewnątrz tym polu jestem przechowywania adres wskaźnika, co oznacza, że ​​narysować strzałkę z pola double_pointer do skrzynki wskaźnika. Zwróć uwagę na rodzaj double_pointer, an ** int. N jest liczbą całkowitą, wskaźnik przechowywany adres n, a więc ma * typu int. Teraz double_pointer przechowuje adres wskaźnika, więc ma typ int **. Więc, co myślimy, to znaczy - ** Double_pointer = 23? Zauważ, że teraz jestem dereferencji dwukrotnie. Wystarczy postępować zgodnie z box-a strzałki diagramu mamy już utworzony. Najpierw idziemy na polu oznaczonym double_pointer. Pierwszy * oznacza śledzić strzałkę raz. Teraz jesteśmy na pole wyboru wskaźnika. Druga gwiazda mówi śledzić strzałkę ponownie, a teraz jesteśmy na polu oznaczonym n, i ustawić wartość tego pola na 23. Zauważ, że "adres" dereference i operatorów to odwraca od siebie. To pozwala mi zrobić coś jak * & * & n = 42. Mimo to działa, nigdy nie powinno się zrobić coś takiego w praktyce. Co my właściwie tu robisz? Pierwszy ampersand łapie adres zmiennej n. Następnie, mamy operatora dereference, co oznacza, że ​​będą do tego adresu w pamięci, więc jesteśmy z powrotem w n. Teraz łapiemy adres n ponownie i natychmiast dereference, tak jesteśmy z powrotem przy n i przechowywać 42. Tak więc, każda para * i po prostu anuluje. Istnieje specjalny wskaźnik zwany wskaźnikiem NULL. Jest to wskaźnik, który nigdy nie powinien dereference. Taki wskaźnik jest ważny, ponieważ daje nam sposób rozróżniania wskaźnik, który powinien i nie powinny być przekształcone. Jeśli próbujesz nieprawidłowego wskaźnika pustego, Zazwyczaj program będzie awarię z winy segmentacji, których można nie widział. Więc powiedzmy, że masz kodu int * x = null; * x = 4. W tym przykładzie, to może wydawać się oczywiste, że robimy coś złego, ale pamiętaj, że zerowy może być naprawdę wartość zwracana z wywołania funkcji jak malloc, jeśli malloc nie może przydzielić pamięci żądanej przez użytkownika. Z tego powodu, jeśli zamiast możemy ustawić wartość x z wywołania malloc, jak int * x = malloc (sizeof (int)), to powinniśmy zawsze jawnie sprawdzić aby sprawdzić, czy wartość null został zwrócony. If (x == null) / / uhoh! return; jeszcze możemy dalej i powiedzieć * x = 4. Tak więc raz jeszcze, dlaczego powinniśmy nigdy używać wskaźników? Spójrzmy na przykład programu, gdzie musimy użyć wskaźników - prosta funkcja swap. Powiedzmy, że mam dwie liczby całkowite, int x = 4, a int y = 15; i chcę, aby napisać funkcję o nazwie Swap, że mogę używać tak: wymiany (x, y). Po tej linii, wewnątrz wartości zmiennej x wynosi 15, a wartość wewnątrz zmiennej y powinny być 4. Wewnątrz wartości x i y są zamienione. Bez wskazówek, możemy spróbować czegoś takiego swapu void (int a, int b); int tmp = b b = a; = tmp. Ale czy można zauważyć problem z tym? Pamiętaj, że wartość przechowywana w zmiennej jest po prostu kopią wartości x, i wartość b jest kopiowany z y. Wszelkie zmiany wprowadzane do A i B nie będą odzwierciedlone w X i Y. Tak więc, podczas gdy wartości A i B są poprawnie zamienione, X i Y nie zmieniła. Teraz zmieńmy funkcję wymiany tak, że jej argumenty są wskaźnikami do zmiennych powinno to swap, tak: Swap void (int * a, int * b); int tmp = * b, * b = *, * = tmp. Pamiętaj, że swapy argumenty są teraz wskaźniki, i tak trzeba przekazać adres xiy w zaproszeniu do wymiany, tak: swap (& x, & y). To teraz poprawnie zamienia wartości X i Y. Narysujmy pole i strzałki diagramu aby zobaczyć, dlaczego to działa. Zaczynamy z naszych dwóch pudełek w pamięci, X i Y. Wewnątrz pudełka dla x to numer 4, a wewnątrz z pudełka dla Y mamy 15. Teraz wewnątrz wywołania funkcji swap, mamy jeszcze dwa pola argumentów dla A i B; a wskazuje na polu dla x oraz b wskazuje na polu dla Y. Nowe okno jest tworzone dla zmiennej tmp i wewnątrz niej możemy przechowywać wynik dereferencji b, co oznacza "śledzić strzałkę z pola oznaczonego b". Tak więc, możemy przechowywać 15 wnętrze tmp. Następnie kierujemy strzałkę w B i przechowywać tutaj wynik dereferencji, które jest 4 wartość. Wreszcie kierujemy strzałkę w sklepie i co jest obecnie wewnątrz tmp, który jest 15. Należy zauważyć, że X i polach oznaczonych prawidłowo zamienione Y wartości. Po dowiedzieć się więcej o malloc i dynamicznego zarządzania pamięcią, zobaczymy, że nie mamy wyboru, ale na wskaźniki. Spacery po box-and-strzałki diagramu dla każdego programu może pomóc Ci dowiedzieć się, co program jest naprawdę robi. Nazywam się Rob Bowden, a to CS50. [CS50.TV] To jest inne zastosowanie gwiazdką - bleah, nienawidzę tego słowa. Gdziekolwiek byliśmy tylko powiedzieć n, możemy teraz powiedzieć pointer_to_n - nie można nie mogę się - pointer_to_n *.