Witam i witam ponownie.
W tym wykładzie chcę omówić koncepcję zwaną przekazywaniem przez odniesienie.
Kiedy po raz pierwszy zaczęliśmy rozmawiać o wskaźnikach, wspomniałem o jednej z zalet:
nie musisz tak często używać zmiennych globalnych.
Ponieważ podczas przekazywania wskaźnika do
funkcji funkcje mogą używać tego parametru. A kiedy modyfikuje ten parametr, dane
zostaną zmodyfikowane poza funkcją.
Jest to więc cenna, wartościowa koncepcja.
Porozmawiamy o tym. To jest bardziej koncepcja. C właściwie tego nie używa.
Pokażę wam, jak to działa, i
podam kilka przykładów.
Więc istnieje koncepcja przekazywania wartości. Ma to związek z funkcjami.
Rozmawialiśmy o przekazywaniu danych do funkcji, ale w jaki sposób są one faktycznie
przekazywane pod względem języka w kompilatorze.
Jest więc koncepcja przekazywania wartości. I o tym
rozmawialiśmy do tej pory. Za każdym razem, gdy wywołujemy
funkcję oprócz funkcji scanf, ale za każdym razem, gdy wywołujemy tę
funkcję, kiedy modyfikujesz te dane wewnątrz funkcji, nie zostały
one zmodyfikowane poza funkcją.
Więc rozmawialiśmy o zmiennych lokalnych, to trochę inna koncepcja.
Ale kiedy przekazujesz dane do funkcji, możesz je przekazać na dwa sposoby.
Możesz przekazać to według wartości, możesz przekazać przez odniesienie.
C przekazuje wyłącznie przez wartość, podobnie jak
java, wszystkie
przekazują przez wartość. Ale symuluje przekazywanie przez odniesienie,
ponieważ kopiujesz adresy.
I chociaż ta koncepcja przekazywania
przez referencję nie jest specjalnie wykorzystywana przez c,
to ją symuluje. Aby zrozumieć, co oznacza przekazywanie przez
odniesienie i dlaczego jest to ważne, porozmawiamy o tym.
Ale przekazanie przez wartość ma miejsce, gdy funkcja kopiuje rzeczywistą wartość
argumentu do formalnego parametru funkcji.
I dlatego jeśli masz liczbę całkowitą,
zmiennoprzecinkową lub podwójną i przekazujesz ją jako argument do funkcji,
ta rzeczywista zmienna nie jest modyfikowana poza funkcją, ponieważ jest kopiowana.
Zatem zmiany dokonane w parametrze wewnątrz funkcji nie mają wpływu na argument.
Przekazywany argument nie jest modyfikowany.
Więc zachowuje się prawie jak zmienna lokalna, ponieważ jest kopią.
Jak to się właściwie dzieje pod maską, jest kopiowane i przekazywane do funkcji.
Program w C używa funkcji call by value do przekazywania argumentów.
Wszystko jest nazywane wartością przekazywaną przez wartość.
Te terminy są używane zamiennie.
Więc znowu oznacza to, że kod w funkcji nie może zmienić argumentów użytych do wywołania funkcji.
Nie ma zmian w wartościach, chociaż zostały zmienione w funkcji.
Wygląda na to, że są to zmienne lokalne.
I właśnie dlatego wcześniej, aż do tego
momentu, kiedy trzeba było tworzyć dane globalne, jeśli chciało się udostępniać dane z jednej funkcji do drugiej.
A tak działa c.
Ale co się stanie, gdy przekażesz
kopię adresu. Następnie może naśladować przekazywanie przez odniesienie.
Ale oto przykład tego, jak używaliśmy przekazywania przez wartość, tutaj jest bardzo dobrze zademonstrowany.
Ta procedura wymiany jest często używana do demonstrowania przekazywania
przez wartość, ponieważ tworzy tymczasowy plik tymczasowy i zamienia
parametry przekazywane argumentom.
Więc jeśli mamy funkcję o nazwie swap, która przyjmuje dwie
liczby całkowite, tworzymy lokalną zmienną temp.
Jeśli przypiszemy parametr x do temp, temp będzie
równa x. Jeśli powiemy, że x jest równe
y, y będzie teraz równe x. Jeśli powiemy, że y jest równe
temp, y będzie równe temp.
Ale kiedy wywołasz tę funkcję przekazując te wartości, x
i y będzie lokalna dla tej funkcji.
Te zmienne nie będą modyfikowane poza tą funkcją.
Ta funkcja pokazuje, że jest to bardzo przydatne, ponieważ
przypisujemy do y, a do x.
A x i y są lokalne dla tej funkcji, ponieważ przekazujemy
wartość, nie są one modyfikowane poza tą funkcją i dlatego musimy używać zmiennych globalnych.
Ale to liczby całkowite. Są to dwie liczby całkowite x i y. To nie są adresy,
to liczby całkowite i to jest istotne.
Więc jeśli zadzwonimy
do zamiany i zobaczymy, jak to wygląda,
zauważysz, że wartości się nie zmieniają.
Więc jeśli utworzymy zmienną o nazwie a 100
b z 200 i wypiszemy zmienne przed zamianą, 100
i 200, a następnie wywołamy
zamianę, zmienne nie zostaną zmienione
po ich ponownym wydrukowaniu.
Spójrzmy więc na to w blokach kodu, a to zademonstruje przekazywanie przez wartość.
Oto nasz przykład. Mamy swoją rutynę wymiany.
Zamieniamy wartości danych, które są przekazywane przez x i y.
Dzwonimy z 100 200.
Wydrukujemy przed wywołaniem funkcji.
Wydrukujemy później.
Zauważysz, że zmienne a i b nigdy się nie zmieniają.
Powodem tego jest to, że przekazujemy wartość.
Więc jeśli uruchomię ten program,
zobaczymy, że przed zamianą wartość a wynosiła 100, a po zamianie wartość a wynosiła 100 i
to samo dla b 200 i 200.
Powodem tego jest to, że przekazaliśmy wartości. Gdybyśmy przekazali przez referencję,
te zmienne a i b zmieniłyby się po wywołaniu zamiany.
A co jeśli zastosujemy tę samą technikę przekazywania wartości, ale
kopiujemy wskazówki, jak myślisz, co się stanie?
Tak więc wskaźniki i funkcje całkiem dobrze ze sobą współgrają.
Możesz przekazać wskaźnik jako argument do
funkcji, a funkcja może również zwrócić wskaźnik jako wynik.
Pokażemy, jak to zrobić.
Ale przekazanie przez odniesienie kopiuje adres argumentu do parametru formalnego.
Adres jest używany do uzyskania dostępu do faktycznego argumentu używanego w wywołaniu.
Oznacza to, że zmiany dokonane w
parametrze wewnątrz funkcji wpływają na argument zawarty w wywołaniu.
I nawet jeśli technicznie nie jest pod maską przekazaną przez
odniesienie, naśladuje lub symuluje przejście przez odniesienie.
Kopiujemy adres. Jeśli skopiujesz adres,
masz nowy adres i pamięć dla tej wartości adresu.
Ale jeśli spróbujesz przypisać do tego adresu, przypisze to adresowi.
Więc nawet jeśli jest przekazywana przez wartość, kopiuje.
A jeśli kopiujesz zwykłą liczbę całkowitą, nie wprowadzasz zmian. Jeśli kopiujesz adres, jest on
przechowywany w pamięci, więc wprowadzając zmiany, wprowadzasz zmiany w tym adresie.
Tak więc, aby przekazać wartość przez
odwołanie, wskaźniki argumentów są przekazywane do funkcji tak samo, jak każda inna wartość.
Musisz zadeklarować parametry funkcji jako typy wskaźników z gwiazdką.
Zmiany wewnątrz funkcji są również odzwierciedlane poza funkcją.
Nie musisz tak często używać zmiennych globalnych.
W przeciwieństwie do wywołania według wartości, w którym zmiany nie odzwierciedlają poza funkcją.
Nadal kopiujemy, nadal przekazujemy wartość, to tylko wartość kopii adresu.
Jest to więc ważna koncepcja do zrozumienia.
Teraz, kiedy tworzymy naszą funkcję
swap, utworzymy ją z deklaracją wewnątrz funkcji
asterix, star i star.
To znaczy, że ta funkcja wymaga wskaźników.
Teraz, kiedy przypisujemy wewnątrz tej funkcji, musimy
faktycznie użyć operatora pośredniego do wyłuskiwania, ponieważ
chcemy przypisać wartości. Zamierzamy przypisać wartość, którą
x, które x wskazuje, do temp.
Zamierzamy przypisać wartość x punktów do wartości, na
którą wskazuje y, a następnie przypiszemy wartość, na którą wskazuje y, do temp.
Temp jest liczbą całkowitą, więc nie musimy jej usuwać.
Teraz, gdy wywołujemy
go z a100 i b200,
przekazując a i b, a i b nie są wskaźnikami, więc
musimy przekazać adres, ponieważ funkcja przyjmuje wskaźniki.
Ilekroć masz funkcję, która pobiera wskaźniki i nie deklarujesz wskaźnika, musisz użyć
adresu operatora i jest to to samo, co w przypadku, gdy używamy scanf.
Ale teraz zauważysz, kiedy wywołamy
to po wywołaniu zamiany, zmienne a i b zostaną
zmienione, zostaną zamienione. A będzie równe 200 b będzie równe 100.
Spójrzmy teraz na ten przykład, w którym nasza metoda zamiany zmienia się na wskaźniki w blokach kodu.
Jesteśmy w blokach kodu. Zmodyfikowaliśmy funkcję swap, aby przyjmowała wskaźniki.
Więc musimy je wyłuskać, kiedy je przypisujemy.
Przekazujemy teraz również adresy a i b, ponieważ są to teraz liczby całkowite,
a zamiana przyjmuje teraz wskaźniki. Więc kiedy to uruchomimy,
zobaczymy, że wezwanie i później zostanie teraz zmienione.
Więc zanim wywołamy swap a jest równe 100 po wywołaniu swap to 200.
Zanim zadzwonimy do zamiany na b, jest to 200. Potem jest 100.
Więc jesteśmy w stanie zmienić wartości zmiennych wewnątrz
funkcji, ponieważ tworzymy kopię adresu. Bardzo, bardzo ważne i jedna
z największych zalet wskaźników.
Podsumujmy więc składnię. Już to zrobiłem, ale przejdźmy bardziej szczegółowo.
Do funkcji można przekazywać dwa rodzaje informacji o zmiennej.
Możesz przesłać wartość x, a
funkcja musi być zadeklarowana przy użyciu tego samego typu co x.
Wiemy to. Ilekroć przekazujemy dane do
funkcji, musimy mieć te same typy, kiedy ją wywołujemy.
I tutaj mamy int function1, aby ją wywołać i jest zdefiniowana jako liczba int.
Więc kiedy przekazujemy wartość x w funkcji, muszą one być zadeklarowane z tym samym typem.
A jeśli chcesz faktycznie wywołać adres x, definicja
funkcji musi zawierać wskaźnik do odpowiedniego typu.
Więc ilekroć używasz wskaźników, musisz upewnić się, że jeśli
przekazujesz dane, a dane nie są wskaźnikiem, musisz uzyskać ich adres.
Podsumowując, jeśli zdefiniujesz funkcję, która nie pobiera wskaźnika, możesz ją
po prostu wywołać i przekazać dane.
Upewnij się, że nie jest to wskaźnik.
Jeśli masz funkcję, która pobiera wskaźnik gwiazdki, kiedy
ją wywołujesz, musisz
wywołać ją za pomocą operatora add lub sub.
Musisz podać adres.
Taka jest składnia.
Teraz możesz również określić parametr funkcji za pomocą słowa kluczowego constant.
Oznacza to, że funkcja będzie traktować argument przekazany dla tego parametru jako stałą.
Jest to przydatne tylko wtedy, gdy parametr jest wskaźnikiem.
Ponieważ jeśli przekazujesz wartość i nie przekazujesz wskaźników, nie ma to znaczenia, ponieważ nie
wpłynie to na zmianę na zewnątrz funkcji, więc po
co miałbyś robić to stałą, kogo to obchodzi, jeśli to zmienisz .
Jedynym powodem, dla którego ustawiasz wskaźnik jako stałą, jest to, że nie chcesz,
aby wartości poza funkcjami ulegały zmianie.
Możesz więc zastosować słowo kluczowe constant do parametru, który jest
wskaźnikiem, aby określić, że funkcja nie zmieni wartości, na którą wskazuje argument.
Bardzo ważne.
Więc w tym przykładzie mamy funkcję wysyłania wiadomości. Zajmuje wskaźnik wiadomości
p.
Stałe słowo kluczowe umieszczamy przed gwiazdą char.
Więc ten typ komunikatu parametru p jest wskaźnikiem do stałego znaku.
Stała jest wartość znaku, a nie adres.
W tym przypadku to, co robimy, to wskazywany wskaźnik, który nie może się zmienić.
Możemy zmienić adres, ale nie możemy zmienić tego, co wskazuje na wartość.
Składnia jest podobna do tej, gdy tworzymy stałe na innych wskaźnikach.
Stałe słowo kluczowe mówi, że jest to pierwsza rzecz, która się wyświetla, wtedy nie możesz zmienić wartości.
Teraz możesz określić, że sam wskaźnik jest stały,
ale nie ma to sensu, ponieważ adres jest przekazywany przez wartość.
Nie można zmienić
wskaźnika renderowania w funkcji wywołującej. Więc to jest tylko jeśli spróbujesz zrobić to tak, aby adres nie mógł
zostać zmieniony, o co chodzi, ponieważ jest przekazywany przez wartość. Więc faktycznie tworzysz
nowe miejsce w pamięci
na ten konkretny adres. Więc jeśli to zmienisz, kogo to obchodzi, to nie zmieni tego na zewnątrz.
Chcesz tylko móc uczynić parametr stałą,
aby nie zmieniać wartości tego, na co wskazuje, i
tak nie
możesz zmienić oryginalnego wskaźnika i funkcji wywołującej, ponieważ jest przekazywana
przez wartość przekazywaną przez kopię.
Kompilator wie, że argument będący wskaźnikiem do stałych danych będzie
bezpieczny, więc ma to swoje zalety. Nie można przypadkowo zmienić tego, na co wskazuje wartość.
Możesz więc zrobić to ponownie, jeśli nie chcesz, aby zmiany były odzwierciedlone na zewnątrz funkcji.
Jeśli przekażesz wskaźnik do stałych danych jako argument parametru,
to parametr musi być użyty jak powyżej.
Kiedy wywołujesz funkcję i przekazujesz ją do stałej, musisz mieć
deklarację funkcji również przyjmującą stały wskaźnik znakowy, tak jak zwykłe
inne wskaźniki, które są stałymi.
Muszą być zdefiniowane, a definicje muszą pasować do prototypu.
Zwracanie wskaźników z funkcji.
Zwracanie wskaźnika z funkcji to szczególnie potężna możliwość.
Umożliwia zwrócenie nie tylko pojedynczej wartości, ale całego zestawu wartości.
Więc znowu jedną z innych zalet wskaźników, o
których wspomniałem, jest to, że możesz teraz zwrócić więcej danych z funkcji.
W przeszłości można było zwracać tylko jedną wartość.
Ale ze wskaźnikiem, ponieważ wskaźnik może
wskazywać na strukturę, o której jeszcze nie rozmawialiśmy, lub nawet
inne rzeczy, możesz zwrócić
wiele wartości. Więc wiemy, jak przekazywać wskaźniki do funkcji, teraz
chcemy porozmawiać o zwracaniu wskaźników.
Musiałbyś zadeklarować funkcję zwracającą wskaźnik. Więc deklaracja funkcji musi się zgadzać.
Bardzo łatwo to zrobić. Po prostu mówisz, że użyj gwiazdki,
kiedy deklarujesz funkcję lub definiujesz ją i masz typ zwracany dla tego wskaźnika, na co wskazuje.
Należy więc uważać, chociaż zwracanie wskaźnika wiąże się z określonymi zagrożeniami.
Użyj zmiennych lokalnych, aby uniknąć zakłócania zmiennej, na którą wskazuje argument.
Jest to nieco problematyczne, gdy zwracasz wskaźniki, ponieważ jeśli zmodyfikujesz ten wskaźnik wewnątrz funkcji,
a następnie go zwrócisz, może
to być coś, czego nie zamierzasz robić.
Dlatego zawsze używaj zmiennych lokalnych, jeśli nie chcesz wpływać na zmienną, na
którą wskazuje Twój argument.
Dlatego w tym wykładzie opisano, w
jaki sposób można przekazywać wskaźniki
do funkcji i jak zwracać wskaźniki z funkcji.
Używając tego mechanizmu przekazywania wskaźników do
funkcji, demonstruje lub naśladuje przekazywanie przez
referencję, ponieważ kopiujesz adres.
A kiedy kopiujesz adres, jeśli napiszesz na ten adres, zaktualizujesz
wszystko, co wskazuje
na ten adres, co w praktyce oznacza, że zmieniasz
wartość danych wewnątrz funkcji, które odzwierciedlają to poza funkcją.
Aby to zademonstrować, używamy procedury zamiany.
To bardzo potężna koncepcja. I chcesz naśladować przebieg przez odniesienie,
możesz użyć wskaźników, aby to zrobić i chcesz to zrobić, gdy chcesz zmienić rzeczy poza funkcją.
Dziękuję Ci.