Witam i witam ponownie.
W tym wykładzie chcę omówić przypadek użycia wskaźników,
kiedy będziemy ich używać, w jaki sposób będziemy ich używać. Wiemy, jak je tworzyć.
zadeklaruj je i zdefiniuj.
Wiemy, jak przypisać im adres istniejącej zmiennej za pomocą adresu operatora.
Wiemy, jak je usunąć, jak
uzyskać wartość, na którą wskazuje wskaźnik.
Jak właściwie zacząć ich używać.
Więc c oferuje kilka podstawowych operacji, które można wykonać na wskaźnikach.
Możesz przypisać adres do wskaźnika, o
którym już się dowiedzieliśmy.
To jest istniejąca zmienna, w której zmienna ma adres.
Wystarczy spojrzeć na adres tej zmiennej, używając adresu operatora, i
mamy w tym rację. Moglibyśmy również przypisać istniejący wskaźnik do innego wskaźnika.
To dwa sposoby na przypisanie adresu do wskaźnika.
Więc może to być sama nazwa tablicy.
Nie rozmawialiśmy jeszcze o tablicach, ale kiedy przypisujesz podbicie do wskaźnika,
wszystko, co musisz zrobić, to określić nazwę. Nie musisz nawet używać adresu operatora.
Do tej pory skupialiśmy się tylko na wykorzystaniu istniejącej
zmiennej i przekazaniu adresu samej zmiennej.
Ale trzy sposoby, w jakie możesz to zrobić, są tam wymienione.
Możesz także wyłuskać wskaźnik. Właśnie dowiedzieliśmy się, że właśnie tam używasz,
gwiazdka jest operatorem pośrednim.
To da ci wartość, która jest przechowywana we wskaźniku do. Wskaźnik jest wartością wskaźnika,
która jest adresem, na który wskazuje punkt, po
prostu użyj tego. Możesz także wziąć adres wskaźnika.
Jest to rzeczywisty adres wskaźnika podczas tworzenia wskaźnika, w
którym jest przechowywany sam wskaźnik.
Możesz również użyć operatora adresu, który mam na tym wskaźniku.
Możesz także wykonywać arytmetykę wskaźnika. Będziemy mieć osobny wykład na ten temat.
W tym miejscu możesz użyć operatorów plus
i minus, aby dodać liczby całkowite do wskaźnika lub odjąć liczby całkowite od wskaźnika.
A to pozwala ci mieć określoną notację i
pozwala na szybsze przeskakiwanie w pamięci, zwłaszcza jeśli używasz tablic.
Więc to, co zwykle robisz, to kiedy dodajesz liczbę
całkowitą do wskaźnika za pomocą
operatora plus, po
prostu ustawisz adres tego wskaźnika o tak wiele bajtów.
Jeśli odejmiesz
liczbę od wskaźnika, ustawisz
ten adres minus pewną liczbę bajtów.
Możesz także zwiększyć dekrementację o jeden, a
zobaczysz, że jest to bardzo powszechne, gdy używamy go w tablicach.
Więc kiedy jesteś w tablicy i chcesz przejść do następnego elementu zamiast korzystać z indeksu, możesz użyć
wskaźnika i po prostu zwiększyć go o jeden.
Jeśli chcesz przejść do poprzedniego elementu zamiast korzystać z indeksu, możesz po prostu
zmniejszyć jego wartość, wskazując o jeden.
Arytmetyka wskaźnikowa, o której będziemy mówić wyłącznie, ale jest to
coś, co można zrobić za pomocą wskaźników.
I znowu, jest to szczególna notacja, która pozwala szybko poruszać
się w pamięci, być
bardziej wydajnym.
Możesz również znaleźć różnicę między 2 wskaźnikami.
Więc jeśli masz 2 wskaźniki, a elementy są w tej samej tablicy, być może
będziesz chciał dowiedzieć się, jak bardzo są
od siebie oddalone, elementy, możesz je odjąć.
Byłaby to różnica 2
wskaźników i. Więc jeśli masz indeks 5, a
następnie miałeś punkt wskaźnika w indeksie 5, miałeś inny wskazujący punkt
wskazujący indeks 15, możesz je
odjąć i to
dałoby ci, ile elementów są daleko, ponieważ byłoby adres szesnastkowy minus
inny adres szesnastkowy, zakładając tablicę w pamięci ciągłej,
która to jest, wyświetli, jak daleko są od siebie.
Możesz użyć operatorów relacyjnych, aby porównać wartości 2 wskaźników.
Wskaźniki muszą być tego samego typu. Więc jeśli obie wskazują liczby całkowite, muszą być
tego samego typu.
To tylko powie ci, czy adresy są większe lub mniejsze niż.
Ponownie, możesz zidentyfikować, w której części tablicy się znajdujesz.
Istnieją również 2 formy odejmowania.
Więc właśnie mówiłem o odejmowaniu, w
którym możesz faktycznie wziąć jeden wskaźnik i odjąć go od
innego, co
otrzymasz wskaźnik, lub możesz odjąć jeden wskaźnik
od liczby całkowitej, która da ci wskaźnik.
Odejmij więc jeden wskaźnik od drugiego, aby uzyskać liczbę całkowitą, lub
odejmij liczbę całkowitą od wskaźnika, aby uzyskać wskaźnik. To są 2 przypadki.
Więc będziemy mieć to na uwadze, przejdziemy przez więcej przykładów.
Zachowaj ostrożność, gdy używasz zwiększania lub zmniejszania wskaźników i powodując,
że tablica wykracza poza granice. Więc większość rzeczy, które robisz
z arytmetyką wskaźników, dotyczy operacji na tablicach.
Ponownie, pozwala na szybkie poruszanie się, jest to prosta składnia.
Ale pamiętaj, tablice mają błąd poza zakresem.
Więc jeśli zwiększysz zbyt daleko, wyjdziesz
poza granice tablicy. Jeśli zmniejszysz się za
daleko, będziesz miał dolną granicę, przechodzisz do liczby ujemnej.
Sam komputer nie będzie śledził, czy wskaźnik nadal wskazuje element tablicy.
Więc używając punktu arytmetyki na tablicach, musisz być ostrożny.
Jeśli chcesz korzystać ze wskaźników używanych w wyrażeniach, możesz to również zrobić.
Więc rozmawialiśmy o tym trochę wcześniej, wyłuskowaliśmy wskaźnik i dodaliśmy do niego
15. Myślę, że mamy 20.
Zatem wartość wskaźnika pi może być używana w wyrażeniach arytmetycznych.
Jeśli więc zdefiniowano zmienną jako wskaźnik typu do liczby całkowitej, to
jest ona oceniana przy użyciu zasad arytmetyki liczb całkowitych.
I pokazaliśmy to poprzez wyłuskiwanie. Więc jeśli masz
zmienną i masz wskaźnik, zainicjuj go na null, a następnie
podpisz wskaźnikowi adres tej zmiennej. Możesz wtedy po prostu powiedzieć gwiazdka pnumber plus równa się 25.
Pamiętaj, że gwiazda pnumber ma wartość, na którą wskazuje.
Oznacza to, że wartość pnumber równa się
wartości pnumber plus 25.
Więc wartość będzie równa 35.
To nie zmienia tego, na co wskazuje, ponieważ używamy gwiazdki, więc nie
zmienia adresu, na który wskazujemy,
nie zmienia samego adresu, ponieważ nie mamy adresu operatora.
Zmieniamy wartość, na którą wskazuje.
Więc zwiększając wartość zmiennej liczbowej o 25, gwiazdka wskazuje, że pytasz o
zawartość, na którą wskazuje zmienna o nazwie pnumber.
Jeśli wskaźnik wskazuje na zmienną x, ten
wskaźnik został zdefiniowany jako wskaźnik do tego samego typu danych, co x.
Więc jeśli utworzysz wskaźnik końcowy jako wskaźnik zmiennoprzecinkowy, możesz
mieć tylko adres typu danych,
który jest typu float lub int, w zależności od tego, który z nich zadeklarowałeś.
Zatem użycie operatora pośredniego w wyrażeniu
jest identyczne z użyciem x w tym samym wyrażeniu.
I to wszystko, co mówi, to to, że jeśli używasz float lub
masz int i używasz operatora pośredniego, wyodrębniasz go, możesz
zrobić wszystko, co możesz, jeśli masz w nim float, cokolwiek wskazuje.
Wiemy, że zmienna zdefiniowana jako wskaźnik do int może przechowywać
adres dowolnej zmiennej typu w.
Więc tutaj wartość int równa się 999, możemy przypisać ten int do pnumber,
o ile pnumber jest wskaźnikiem do int, a następnie możemy
do niego dodać.
Instrukcja będzie działać z nową wartością zmiennej,
a nowa wartość zawartości będzie wynosić 1024.
Pnumber wskazuje na wartość,
adres wartości. Więc jeśli zmienisz wskaźnik,
na co wskazuje wartość, zmienisz wartość tej zmiennej.
Więc pamiętaj tylko, że jest to koncepcja zmiany zmiennej w jednym miejscu
wskaźnika i zmieniasz ją w wielu zmiennych.
Wskaźnik może zawierać adres dowolnej zmiennej odpowiedniego typu.
Możesz więc użyć jednej zmiennej wskaźnikowej, aby zmienić wartości wielu różnych zmiennych, jeśli istnieją
wartości typu int, double lub float. Jeśli są typu zgodnego z typem wskaźnika,
możesz zmienić tę wartość. Nie możesz zmienić wartości
pnumbers, jeśli pnumber wskazuje na liczbę zmiennoprzecinkową, a wartość
jest liczbą całkowitą. Nie byłbyś w stanie tego zrobić. Wystąpiłby błąd kompilacji.
Kolejny przykład tutaj. Tutaj pokażemy,
jak uzyskać adres niektórych zmiennych. Więc tutaj, w tym głównym,
robimy trochę arytmetyki
wskaźnikowej na rzeczywistych wyrażeniach.
Więc nie robimy nic na tablicy, więc nie zwiększamy ani nie zmniejszamy wskaźników
w tym. Więc zmniejszamy wartości. Mamy więc 2
długie typy num1 i num2, a następnie mamy wskaźnik typu long, inicjalizujemy go na null.
Przypisujemy adres num1 do naszego wskaźnika pnum.
Następnie przypisujemy wartość pnum do L, co oznacza,
że num1 będzie teraz wynosić 2l.
Jeśli zwiększymy num2, teraz num2 nic jeszcze nie
zrobiliśmy.
Czyli wartość pnum to 2l, co oznacza, że wartość num1 to 2l.
L to tylko przyrostek oznaczający, że jest długi.
Jeśli teraz zwiększymy wartość long num2, to będzie
0l, to będzie 1l.
Jeśli powiemy, że num2 plus równa
się star pnum, pnum to 2l, więc to
będzie 1l dodać 2l, num2 będzie teraz równe 3l.
A jeśli przypiszemy adres num2 do pnum,
pnum będzie teraz równe adresowi num2.
Więc jeśli zwiększymy pnum,
na co wskazujemy, otrzymamy teraz 4l.
Przyjrzymy się temu wewnątrz zimnych bloków, ale
przeprowadzę cię przez to. Num1 będzie równe 2l.
Num2 będzie równe temu, które wskazuje
na pnum.
Pnum to 3l, więc myślę, że to będzie 3l.
Pnum będzie wynosić 3l, a następnie pneum plus num2 będzie
wynosić 6l.
Zobaczmy więc, czy mam rację, przejdźmy przez to w blokach kodu.
Więc jesteśmy w blokach kodu, próbowaliśmy przejść przez to ręcznie, ale
teraz chcemy zobaczyć, czy mamy rację. Użyjmy debuggera.
Umieścimy to tutaj i zobaczymy, jak przechodzimy przez program, jak zmieniają się wartości.
Zbudujmy to i debugujmy.
To się zacznie i zatrzyma na linii 10.
A jeśli wyświetlimy nasze okno zegarka, co
możemy zrobić, przechodząc do debugowania w zegarkach Windows, zegarkach, możemy zobaczyć
wartości w trakcie ich działania. Więc teraz num1
to zero, num2 to zero, pnum wskazuje na null.
Dlatego jest to 0x0,
ponieważ wiemy, że null reprezentuje 0 w pnumber.
I możemy tego użyć, porównując to w instrukcjach if.
Więc po wykonaniu linii 10 pnum powinno mieć teraz adres num1.
Więc po prostu przejdźmy.
Teraz pnum ma rzeczywisty adres 0x60ff08.
Po wykonaniu linii 11. powiemy, że wartość pnum jest równa 2l i tak naprawdę nie
zobaczymy niczego w oknie zegarka. Ale przejdźmy dalej i wykonajmy to, pnum
nadal ma ten sam adres.
Num1 jest teraz zmieniony, ponieważ pnum miał
adres num1 i zmieniliśmy go.
Więc teraz num1 to 2. Teraz w liniach 12. będziemy zwiększać num2.
Więc num2 stanie się po prostu 1l.
To będzie 1. Adres pnum jest nadal ten sam.
Teraz dodamy do num2 jakiekolwiek
było pnum. Pnum pamięta, że
wskazuje num1, więc num2 powinno wynosić 3.
Następnie przechodzimy do 14, num2 to 3, num1 to nadal 2.
Teraz w linii 15.
przypiszemy pnum nowy adres num2. Więc po
tym nie będziemy już 0x60ff08.
Będziemy 060ff04.
Więc teraz, jeśli zwiększymy wartość wskazywaną przez pnum, która jest liczbą 2,
a liczba 2 to 3, liczba 2 powinna teraz wynosić 4.
Widzimy, że to 4.
Teraz wydrukujemy wszystkie te wartości i zobaczysz, że wartość num1 powinna
wynosić 2.
Wartość num2 powinna wynosić 4.
Pnum wskazuje na num2, więc powinno być nadal 4.
A potem powiemy, że wartość pnum, która wynosi 4 plus, num2,
która wynosi 4, powinna wynosić 8.
Więc mogłem właściwie wyjść z 6.
Więc teraz nie będziemy mogli tego zobaczyć w oknie zegarka. Zagrajmy więc po
prostu i zobaczmy wynik jako instrukcje printf. Powinniśmy zobaczyć 2448.
Więc uruchommy to teraz w trybie bez
debugowania i widzimy 2 4 4 8.
Więc zaczynamy. Mam nadzieje że to miało sens.
Wróćmy i kontynuujmy naszą dyskusję.
A co powiesz na to, kiedy otrzymujemy dane wejściowe, jak możemy używać wskaźników w ten sposób.
Jak dotąd, kiedy używamy scanf do wprowadzania wartości, musimy używać operatora ampersand, gdy
mamy typ danych inter lub float. Mamy go tylko wtedy, gdy mieliśmy
tablicę znaków, a powodem tego jest fakt, że znak a
jest w rzeczywistości wskaźnikiem znaku, o którym będziemy mówić.
Ale jeśli używamy scanf do wprowadzania wartości i używamy ich na normalnych
typach danych, które nie są tablicami znaków, zawsze musieliśmy używać tego operatora add lub sub.
A powodem tego jest drugi argument programu scanf, który jest wskaźnikiem.
Musimy więc wysłać mu adres. Wskaźniki mają tylko wartości adresów.
Więc jeśli masz wskaźnik, który już zawiera adres, możesz użyć nazwy wskaźnika jako argumentu funkcji scanf.
Więc teraz nie musisz używać operatora ampersand, jeśli faktycznie utworzyłeś
wskaźnik, ponieważ drugim argumentem funkcji scanf jest wskaźnik, więc możesz
po prostu wypowiedzieć nazwę wskaźnika.
W przeszłości, gdybyśmy chcieli przechowywać wartość jako
wartość, używalibyśmy znaku
ampersand w drugim argumencie scanf.
Ale jeśli mamy utworzony przez nas wskaźnik i wskazuje
on już adres, tak jak w
tym przykładzie, to kiedy używamy scanf, wszystko, co mamy do powiedzenia, to nazwa
wartości, nazwa wskaźnika.
Oto przypadek, gdy mamy liczbę całkowitą, ale nie
musimy używać znaku ampersand, ponieważ mamy wskaźnik do liczby całkowitej.
Po prostu mówimy procent d, wartość p.
Są więc inne sytuacje, w których możesz
przekazać do skanowania zwykłe nazwy zmiennych oprócz tablic znaków,
jeśli masz wskaźnik.
Możesz również wydrukować to, co wprowadzili później, a zauważysz, że
wartość zostanie faktycznie zmieniona na to, co wprowadzili,
ponieważ wskazujemy na ten adres.
Testowanie pod kątem wartości null.
Jest jedna zasada, którą musisz wypalić w swojej pamięci. To jest bardzo ważne.
Rozmawialiśmy o dereferencji, ale co się stanie, jeśli wyłuskujesz niezamierzony
wskaźnik, wskaźnik, który nie wskazuje na nic
lub wskaźnik, który nie ma przydzielonej pamięci.
A co, jeśli zrobimy coś takiego. Deklarujemy wskaźnik, ale
tak naprawdę nigdy go nie zainicjowaliśmy, a następnie próbujemy go przypisać.
Odsyłamy i przypisujemy do niego, to duży błąd.
Zatem druga linia w zasadzie oznacza, że
chcesz przechowywać wartość 5 w miejscu, na które
wskazuje pt, ale pt nie wskazuje na nic, ponieważ nie został zainicjowany.
Zatem pt będzie teraz miało losową wartość.
Więc nie wiadomo, gdzie zostanie umieszczona 5, można ją umieścić w dowolnym miejscu pamięci.
To może być nieszkodliwe. Może przesłonić
istniejące dane lub istniejące zmienne stanu w programie.
Może to spowodować awarię programu. To bardzo problematyczne.
Więc kiedy tworzysz wskaźnik i przydzielasz pamięć do przechowywania samego wskaźnika, nie
przydziela pamięci do przechowywania danych.
Więc mówiąc instar pt, przydzieliło
pamięć dla wskaźnika, ale nie
przydzieliło żadnej pamięci dla wartości
wskaźnika, adresu. Pamiętaj, wskaźniki zawierają adresy.
Stworzył wskaźnik, ale nie utworzył pamięci dla tego, na co wskazuje.
Więc nie możesz tego wyodrębnić.
Dlatego nie przydziela pamięci do przechowywania danych.
Zanim użyjesz wskaźnika, należy mu przypisać miejsce w pamięci, które zostało już
przydzielone lub musisz użyć dynamicznej alokacji pamięci za pomocą malloc, o którym
będziemy mówić Więc albo przypisujesz adres istniejącej zmiennej do wskaźnika,
albo używasz malloc, czyli dynamiczna alokacja pamięci.
Ale pamiętaj, że za każdym razem, gdy użyjesz wskaźnika, jeśli go utworzyłeś
i był pusty, chcesz wcześniej sprawdzić, czy nie ma
null, porozmawiamy o tym za sekundę. Ale jeśli utworzyłeś wskaźnik,
który nie został zainicjowany, nie przypisuj mu niczego,
chyba że wskazuje inną zmienną zainicjowaną w ten sposób lub ma przydzieloną pamięć.
W tym miejscu do gry wchodzi null.
Możesz faktycznie przetestować wartość null, aby upewnić
się, że nie wystąpił błąd wyłuskiwania. Upewnij się, że program nie ulega awarii.
Więc już wiemy, że null w zasadzie nie
wskazuje na nic i powinniśmy zainicjować wszystkie nasze zmienne, nasze
wskaźniki na null.
Dlatego zawsze powinniśmy robić coś takiego.
Null to specjalny symbol w c, który reprezentuje wskaźnik równoważny
zeru ze zwykłymi liczbami.
Poniżej również ustawia wskaźnik na null przy użyciu zera.
Więc możemy również, jeśli nie chcemy go przekazać lub przypisać do null, możemy po prostu powiedzieć 0 i
to jest równoważne z powiedzeniem, że nie wskazuje na nic.
Tak więc, ponieważ null jest odpowiednikiem 0, jeśli
chcesz sprawdzić, czy wartość p jest równa null, możesz zrobić coś takiego,
jak użycie jawnych testów. Możesz powiedzieć, że wskaźnik jest równy null.
A jeśli wskaźnik jest równy null, co
oznacza zero, możesz coś zrobić.
Więc jeśli nie jest zero lub nie jest null, jest to jedna wartość prawda lub
inna, jest to sposób na sprawdzenie, czy nie występują awarie.
Upewnij się, że zanim spróbujesz
coś wyłuskać, dowiesz się, czy ma przydzieloną pamięć.
Chcesz więc sprawdzić wartość null przed wyłuskiwaniem wskaźnika.
Często robisz to w funkcjach,
ponieważ funkcje przyjmują wskaźniki jako parametry.
A jeśli wywołujący
przekazuje złe dane lub przekazuje wskaźnik nie ma przydzielonej żadnej pamięci,
może to spowodować awarię kodu. Więc to, co chcesz zrobić, to upewnić
się, że dzwoniący ma prawidłowy adres, abyś mógł sprawdzić, czy jest pusty.
Zasadniczo, co to oznacza, że zawsze je mieć, jeśli kontrole mówią, że
nie ma wartości p lub nie jest wskaźnikiem.
A jeśli nie jest to wskaźnik, możesz go usunąć.
Dużo mówimy o wskazówkach. Sporo dyskutowaliśmy.
Jestem pewien, że robi się to trochę przytłaczające. Zrób sobie przerwę i
przejrzyj poprzednie wykłady, spróbuj naprawdę zsynchronizować tę koncepcję w swojej głowie.
Wskaźniki zawierają tylko adresy.
Ale składa się z 3 części, więc może być problematyczny.
Ale bądź pozytywny. Trzymaj się ze mną, a
wiem, że to zagmatwane.
Rozmawialiśmy o pracy z adresami.
Rozmawialiśmy o pracy z wartościami. Rozmawialiśmy o pracy z samym
wskaźnikiem i rozmawialiśmy o pracy ze zmiennymi wskazującymi na wskaźnik.
Musisz więc pamiętać o
tych wszystkich rzeczach, które mogą być bardzo przytłaczające i frustrujące.
I zawsze trudno jest dowiedzieć się, co się dzieje i wymaga to dużo praktyki.
Najlepszym sposobem na zrozumienie tej koncepcji i wszystkiego, z czym rozmawialiśmy
do tej pory, jest
ćwiczenie, pisanie krótkich programów wykorzystujących wszystkie wspomniane przeze mnie pojęcia.
Więc napisz program, który pobiera
wartości za pomocą wskaźnika, może to być użycie
operatora pośredniego, wyłuskiwania. Napisz program, który zmienia wartości za pomocą wskaźników.
Więc byłoby to wyłuskiwaniem, pobieranie wartości przy użyciu wskaźników byłoby po
prostu nazwą wskaźnika, którym byłby adres. Podczas drukowania adresów przejrzeliśmy całą tę składnię.
Ćwicz te rzeczy w kółko, w ten sposób będziesz w tym dobry.
Musisz nabrać pewności siebie i szczegółowo przestudiować te koncepcje,
aby je opanować.
Nadal mamy dużo więcej do omówienia na temat wskaźników, ale może to być czas, aby po prostu odejść i
przejrzeć to, o czym rozmawialiśmy do tej pory, aby było to wwiercone w twoją głowę.
Dziękuję Ci.