Advertisement
Guest User

Untitled

a guest
Jul 26th, 2016
201
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.92 KB | None | 0 0
  1. 8. Wskaźniki
  2. Temat jako całość jest bardzo obszerny, skomplikowany i po prostu trudny. Dlatego opiszę jak bardzo prosto potrafię. Materiały które dokładniej wyjaśniają zagadnienie (które nie będzie Ci potrzebne do kontynuowania nauki) podam na koniec tego rozdziału
  3.  
  4. 8.1 Czym jest wskaźnik
  5.  
  6. Wskaźnik to takie coś co wskazuje na cos innego. Wyobraź sobie taką sytuację. W klasie podstawowej, Jasio rzucił papierkiem w Zosię. Zosia się odwróciła i zobaczyła, że Marysia pokazuje palcem na Jasia.
  7. Marysia w tym opisie jest wskaźnikiem.
  8. Jest to zmienna która istnieje w pamięci (tak samo jak istnieje Marysia) której zadaniem jest pokazywanie na coś innego (np. na Jasia). Przeważnie są to zmienne, ale też może być to funkcja!
  9. Definiujemy wskaźnik w następujący sposób
  10.  
  11. TYP *nazwa
  12.  
  13. na przykład
  14.  
  15. int *pointer;
  16. Foo *object;
  17. Bar *test;
  18.  
  19. Ważne jest to, że TYP to nie jest typ wskaźnika, ale jest to typ na jaki ma pokazywać wskaźnik. Jeżeli mamy obiekt Foo to musimy zdefiniować nasz wskaźnik na obiekt Foo.
  20.  
  21. 8.2 Do czego uzywa sie wskaznikow
  22.  
  23. Wskaźników używamy praktycznie wszędzie. Jedynym wyjątkiem są typy proste. Rzadko chcemy pokazywać na int, czy na float. Przeważnie pokazujemy na jakiś obiekt. Dlaczego? Więcej o tym w 8.2.2
  24.  
  25. 8.2.1 Wyjasnienie stosu i sterty (bez szczegółów)
  26.  
  27. Wszystko w "" jest wielkim uproszczeniem. Możesz w ten sposób myśleć, ale to nie działa tak jak jest opisane w "". Nadal, nie przeszkodzi Ci to w niczym by pisać programy.
  28.  
  29. Nasz program ma do dyspozycji dwie(*) sekcje pamięci. "Lokalną" oraz "Globalną". Lokalna jest stosem. Posiada ona mało pamięci (okolo 1mb (*)). Gdy potrzebujemy zdefiniować duży obiekt, to program wyrzuci nam błąd(*) StackOverflowExpcetion.
  30. Wszystkie lokalne zmienne oraz wywołania funkcji znajdują się na stosie. Obiekty tworzone za pomocą new zostaną stworzone na stercie.
  31. Operator new zwróci nam **wskaźnik**. Dlatego potrzebujemy znać wskaźniki gdy chcemy zdefiniować większe obiekty(a nawet jakiekolwiek obiekty).
  32.  
  33. Ok, zapytasz mnie jak to jest z std::string? Przecież normalnie definiujemy zmienna (bez żadnego operatora) i działa bez problemów. Nawet pliki kilkudziesięcio megabajtowe można wczytać do std::string. To jak to w końcu jest?
  34. std::string operuje na tablicy char. Klasa sama dba o alokowanie pamięci na stercie. Klasa do użytku ma być wygodna! Co to jest tablica pokaże w następnym rozdziale.
  35.  
  36. 8.2.2 Uzywanie new i delete
  37.  
  38. Przy wywoływaniu operatora new zostaje wywołany konstruktor. Przy wywołaniu operatora delete. zostaje wywołany destruktor.
  39.  
  40. Złota zasada. Ma być tyle samo delete co new. Gdy nie zwolnimy pamięci której zarezerwowaliśmy powstanie wyciek pamięci! Na przykładzie
  41.  
  42. class Foo
  43. {
  44. Foo();
  45. ~Foo();
  46. void Bar();
  47. };
  48.  
  49. Foo::Foo()
  50. {
  51. cout << "Constructor was called\n";
  52. }
  53.  
  54. ~Foo::Foo()
  55. {
  56. cout << "Destructor was called\n";
  57. }
  58.  
  59. void Foo::Bar()
  60. {
  61. cout << "a function";
  62. }
  63.  
  64. int main()
  65. {
  66. Foo *firstObject; // stworzylismy sam wskaznik!
  67. firstObject = new Foo(); // dzieki new tworzymy nowy obiekt i przypisujemy go do wskaznika (wskaznik pokazuje teraz gdzie on sie znajduje)
  68. firstObject->Bar(); // tutaj jest juz pierwsza wielka roznica. Zeby dostac sie do zmiennych/funkcji klasy uzywamy strzalki zamiast kropki!
  69. delete firstObject; // usuwamy obiekt i dzieki temu zwalniamy miejsce w pamieci. Jezeli tego nie zrobimy bedziemy miec wyciek pamieci
  70. }
  71.  
  72. 8.3 Dlaczego warto unikac wskaznikow
  73. Problem ze wskaźnikami jest taki, że są one dość trudne do zrozumienia na początek jak dokładnie one działaja. Przez to wielu początkujących popełnia błędy z których nawet sobie nie zdają sprawy. Od C++11 weszły mechanizmy do standardowego języka które bardzo ułatwiają pracę z dynamicznym alokowaniem pamięci
  74. 8.3.1 wyjasnienie UB oraz pokazanie kilku zlych praktyk
  75. UB - Undefined Behaviour. Standard języka nie precyzuje jak dana instrukcja będzie się zachowywać. Także znane pojęcie pod hasłem "demony nosa" ponieważ w teorii UB może doprowadzić, że demony wylecą z Twojego nosa ;) Jeżeli przeczytasz/usłyszysz, że Twój kod to UB. To nie ma żadnego znaczenia, że dla Ciebie działa. Musisz go zmienić. Dziś może działać, ale kiedyś może po prostu się beż zadnych objawów wyłączyć.
  76.  
  77. Bardzo częstym błędem jest taka praktyka
  78. int main()
  79. {
  80. Foo *firstObject; // stworzylismy sam wskaznik!
  81. firstObject->Bar(); // wskaznik nie zostal przypisany na nic więc ten kod... jest UB. W wiekszosci wypadkow powinien powodowac Access Violation, ale czasami bedzie dzialal (jezeli wskaznik bedzie pokazywal na obiekt PRZEZ PRZYPADEK)
  82. }
  83.  
  84. Na przykladzie realnym
  85. Mamy Jasia który rzuca papierkiem w Zosie, Zosia się odwraca i patrzy na Marysię. Marysia pokazuję na lampę która jest na suficie (gdy wskaźnik jest co dopiero stworzony to pokazuję na losowe miejsce w pamięci). Zosia obwinia lampę, że rzuciła papierkiem po czym próbuje rozmawiać z lampą i powiedzieć jej, że to nieładnie rzucać papierkiem w kogoś.
  86.  
  87. Zawsze gdy tworzysz obiekt (niezależnie jaki), jeżeli możesz to ustaw mu wartość na taką jaką powinien posiadać. Na przykładzie wskaźnika:
  88.  
  89. int main()
  90. {
  91. Foo *firstObject = new Foo(); // definicja i inicjalizacja wskaznika w jednym
  92. firstObject->Bar();
  93. delete firstObject;
  94. }
  95.  
  96. - praca na wskaźniku na którym zostało wywołane delete.
  97.  
  98. int main()
  99. {
  100. Foo *firstObject = new Foo();
  101. firstObject->Bar();
  102. delete firstObject;
  103. firstObject->Bar(); // UB!
  104. }
  105.  
  106. 8.3.2 przedstawienie smart pointerow
  107.  
  108. Dzięki C++11 w standardzie przyszły nam "mądre wskaźniki". Jako że polskie tłumaczenie jest po prostu słabe to będę je nazywał smart pointer'ami.
  109. Smart Pointer sam dba o tworzenie i usuwanie obiektu! W większości wypadków nie musisz się w ogóle przejmować odnosnie alokowania i zwalniania pamięci. Smart pointer'y zrobią to za Ciebie!
  110.  
  111. Teraz troszkę wywodu dlaczego ta zmiana jest świetna dla C++. Wielu zwolenników tak zwanego C z klasami powie "eee to bez sensu, może jeszcze dodadzą Garbage Collector do C++! Przecież to jest wolniejsze oraz wyręcza programistę z myślenia o swoim programie i jego zasobach!"
  112. Rozłoże, to na części pierwsze.
  113. C z klasami - Są to ludzie (w tym ja kiedy zaczynałem) którzy traktują C++ nie jako język obiektowy tylko jako rozszerzenie języka C. Co jest po prostu błędem. C oraz C++ to są dwa różne języki. Mają dwie rózne filozofie pisania programów. Jeden potrafi być przetłumaczony do Asemblera 1 do 1 (chodzi o C) natomiast drugi ma dość duży narzut (z jednej instrukcji powstaje wiele w asm). Jeżeli ktoś zargumentuje "No hej, ale przecież C jest kompatybyline z C++ wiec są podobne!" jest to kolejny nietrafiony argument. W C jak i w C++ możemy pisać w asemblerze. Czy asember jest zatem podobny do tych dwóch języków? A co z C++.NET? (tak zwanym CLI). Przeciez można łączyć platforme .NET z C++ a dzięki temu można dość łatwo mieszać C++ z C#. Czy te języki są podobne? Szczególnie od standardu C++11, język jest dużo bardziej nastawiony na obiektowość oraz by pomóc programiście nie przejmować się takimi rzeczami jak zasoby komputera. Programista ma być oczywiście ich świadom, ale przecież nie musi na samym początku nauki walić głową w mur właśnie przez zarządzaniem zasobami.
  114. Smart Pointery sa wolniejsze - Tak maja delikatny narzut jeżeli chodzi o pamięć i szybkość. Ten narzut jest tak mały, że w większości wypadków jest pomijalny. W grach (oraz w silnikach gier) smart pointer'y są powszechnie stosowane. Co samo w sobie dowodzi, że ten narzut jest pomijalny (w większości wypadków)
  115. No i ostatnie, Programista powinien wiedzieć wszystko co dokładnie dzieję się w jego programie. Mój kontr argument. Czy programisci wiedzą jaki dokładnie kod asemblerowy zostanie wygenerowany z ich kodu? Czy to też powinien programista wiedzieć? Jeżeli tak, to co z danymi architekturami procesora? Czy powinien wiedzieć również jak wykonaja sie instrukcje na nim? Gdzie jest granica która jest wystarczająca do programowania?
  116. Moim zdaniem (tak samo jak na początku kursu o tym wspomniałem) najpierw uczymy się jak używać młotka, później jak jest zbudowany.
  117.  
  118. 8.3.2.1 Wyjasnienie std::unique_ptr
  119. pełna dokumentacja znajduję sie pod tym linkiem http://en.cppreference.com/w/cpp/memory/unique_ptr
  120. Ja wymienie te najważniejsze rzeczy
  121. - używamy gdy obiekt jest unikalny (nie potrzebujemy go w dwoch miejscach, pamietaj ze kopia to jest inny obiekt!)
  122. - Usunie obiekt który zawiera gdy zasięg unique_ptr wyjdzie poza zakres (pokaże na przykładzie)
  123.  
  124. #include <iostream>
  125. #include <memory>
  126.  
  127. class Foo
  128. {
  129. public:
  130. Foo() { std::cout << "Foo::Foo\n"; }
  131. ~Foo() { std::cout << "Foo::~Foo\n"; }
  132. void bar() { std::cout << "Foo::bar\n"; }
  133. };
  134.  
  135. int main()
  136. {
  137. std::unique_ptr<Foo> p1(new Foo()); // jak widzisz, w <> podajemy jaki typ obiektu chcemy stworzyc nastepnie w nawiasach uzywamy new Foo.
  138. p1->bar(); // wywolujemy funkcje bar, zauwaz ze nie uzywamy kropki a strzalki. To bardzo wazne.
  139. // zaraz za ta linijka zostanie wywolany destruktor. dla p1
  140. }
  141.  
  142. Inny przyklad
  143.  
  144. #include <iostream>
  145. #include <memory>
  146. #include <string>
  147.  
  148. class Foo
  149. {
  150. public:
  151. std::string text;
  152. Foo(std::string t)
  153. {
  154. text = t;
  155. std::cout << "Constructor! of ";
  156. std::cout << text <<"\n";
  157. }
  158. ~Foo()
  159. {
  160. std::cout << "Destructor! of ";
  161. std::cout << text <<"\n";
  162. }
  163. void bar()
  164. {
  165. std::cout << "Foo::bar\n";
  166. }
  167. };
  168.  
  169. int main()
  170. {
  171. std::unique_ptr<Foo> p1(new Foo("I have bigger scope"));
  172. { // tworzymy lokalny zasieg. Robimy to teraz by pokazac jak cos dziala.
  173. std::unique_ptr<Foo> p2 = std::make_unique<Foo>("Hey I am local scope"); // jest to inny sposob tworzenia obiektów. Moim zdaniem ten sposob jest duzo wygodniejszy i czytelniejszy
  174. p2->bar();
  175. } // w tym miejscu zostanie zniszczony obiekt p2 oraz zwolni zasoby obiektu ktory trzyma
  176. p1->bar(); // wywolujemy funkcje bar, zauwaz ze nie uzywamy kropki a strzalki. To bardzo wazne.
  177. // zaraz za ta linijka zostanie wywolany destruktor. dla p1
  178. }
  179.  
  180.  
  181. 8.3.2.2 Wyjasnienie std::shared_ptr
  182. Pełna dokumentacja znajduję sie pod linkiem http://en.cppreference.com/w/cpp/memory/shared_ptr
  183. Najważniejsze cechy
  184. - używamy gdy chcemy żeby obiekt był dzielony (był w kilku miejscach na raz. Ten sam obiekt a nie jego kopia)
  185. - obiekt który jest przypisany do shared_ptr zostaje zniszczony gdy ostatnia referencja (jego ostatnie wystąpienie) przestanie istnieć
  186.  
  187. Nie będzie przykładów. Zasada działania jest podobna do unique_ptr. Użycie shared_ptr na początku raczej nie będzie nam potrzebne. Ale może Tobie się przyda. Funkcja która można tworzyć shared_ptr to std::make_shared
  188.  
  189. 8.4 Zadania (w tym napisanie bardzo prostej listy)
  190. - Przerób wszystkie dotychczasowe programy by używały smart pointerów gdy tworzymy obiekt jakieś klasy
  191. - przy smart pointerach uzyj kropki, zobacz co Ci podpowie IDE (jakie funkcje)
  192. -* napisz kontener dla stringów(tylko i wyłącznie. Można zrobić dla każdego typu, ale to dopiero za kilka rozdziałów). Powinien on bazować na smart pointer'ach. W rozdziale 10 pokaże jak to zrobić. Jest to bardzo trudne zadanie, ale bardzo dużo można sie przy nim nauczyć.
  193. - Pobaw się z std::move , oraz z funkcjami dostępnymi tutaj http://en.cppreference.com/w/cpp/memory/unique_ptr .Jeżeli będzie coś za trudnego to możesz napisać w komentarzu lub po prostu to zostawić. Najważniejsze są (na ten moment): swap, reset, release
  194.  
  195. 8.5 Materiał dodatkowy
  196. Gdybys chciał wiedzieć więcej już teraz o wskaźnikach polecam Ci ten filmik. https://www.youtube.com/watch?v=bewTJaboGIw Polecam oglądnać wszystkie 9 części. Do tego kursu ta wiedza nie jest wymagana, ale na pewno ta wiedza jest wymagana gdy będziesz pracował. Niestety bardzo często trzeba poprawiać stary kod który korzysta po prostu ze wskaźników.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement