VIDEO TRANSCRIPTION
No description has been generated for this video.
Przejście do kroku Witam serdecznie, z tej strony Mirosław Z acontec. odcinek dziewiąty, kursu programowania będzie dotyczył stosowania w swoich kodach źródłowych tzw. własnych funkcji. Jest to odcinek niezwykle ważny, a zarazem nieco trudniejszy niż zazwyczaj, bo wymagający konceptualnego przyswojenia zupełnie nowego podejścia w programowaniu. Dokonamy dziś przejścia o jeden poziom wyżej w kulturze pisania programów. Zmienimy niejako paradygmat, spojrzymy na program komputerowy z zupełnie nowej perspektywy. Dlatego jeżeli jesteś teraz zmęczony, czy zmęczona, to może przełóż oglądanie tego epizodu na inny termin. Postaraj się też maksymalnie skupić i ograniczyć wszelkie dystrakcje w postaci czatów, Facebooka, głośnej muzyki. Ok, zatem do dzieła. Kiedy rozpoczynamy naszą przygodę z programowaniem, to po prostu radośnie piszemy instrukcje linia za linią wewnątrz tzw.
funkcji głównej z angielskiego main. Nie przejmujemy się za bardzo optymalizacją kodu, frajdę sprawia nam poznawanie coraz to nowych instrukcji, bo potrafimy dzięki nim sprawić, że komputer wykonuje różne wpisane przez nas działania. To taka faza czystej, niczym nieskrępowanej radości z odkrywania nowych możliwości komputera. Jesteśmy w tej fazie nauki trochę jak dzieci, mamy olbrzymi entuzjazm i cechuje nas tzw. świeże spojrzenie, które każdy potem z czasem traci wsiąkając już w daną dziedzinę. Bo z czasem takie początkowe, entuzjastyczne podejście przestanie nam wystarczać. A dokładniej w momencie, gdy będziemy pisać coś większego, jakiś duży program, i to najlepiej nie samodzielnie, a w grupie programistów.
Wówczas odczujemy potrzebę poukładania programu, jak to mówimy podzielenia go na podprogramy, tak żeby można było podzielić się pracą na kilka osób. I właśnie własna funkcja w Ceptus TUSie to tak naprawdę podprogram, czyli jakaś konkretna rzecz do wykonania przez komputer, pewien fragment. A główny program tak naprawdę służy jedynie do zarządzania funkcjami. Ale pora na konkretny przykład. Posłuchaj tego. Załóżmy, że w kilka osób piszemy właśnie grę komputerową Super Mario Bros. I ty aktualnie masz za zadanie napisać kod odpowiedzialny za odgrywanie animacji, kiedy gracz, jak to mówimy, skuje się, straci jedno życie w grze. Pamiętamy jak to było w grze Mario.
Kiedy giniemy, to Mario staje do nas przodem i spada z ekranu po takiej powiedzmy krzywej. No i rzecz jasna, liczbę kontynuacji zmniejszamy wtedy o 1. No i my mamy za zadanie napisać ten kod odgrywający animacje i odejmujący jedno życie graczowi. Analizując ten problem dochodzimy do wniosku, iż Mario może w tej grze zginąć na wiele sposobów. Na przykład może zderzyć się z grzybkiem. Może go uderzyć żółw. Może go zjeść ten wredny kwiatek. Albo trafi go pocisk wystrzelony przez smoka. I tak dalej i tak dalej. Umówmy się, że Mario może zginąć w tej grze na 10 sposobów.
Gdy spojrzymy sobie w dużym oddaleniu na kod naszej gry, to po prostu w kodzie będzie w różnych miejscach usytuowanych 10 ifów, które będą sprawdzać czy nie zdarzyło się przypadkiem tak, że współrzędne naszego bohatera i danego rodzaju przeciwnika się pokrywają. Jeśli tak, to ma zostać odegrana nasza animacja i odjęty jeden punkt kontynuacji graczowi. Ponieważ nie zabrakło nam w pracy kawy, czy tam zielonej herbaty, to udało nam się stworzyć ten kod odgrywający animacje i odejmujący życie. Zajmuje on powiedzmy 50 linii. No to pozostaje nam wkleić te 50 linii w tych 10 miejscach. Niby wszystko ok, nawet się to kompiluje. Niestety zauważasz po czasie małego buga.
W jednym miejscu widocznie wstawił się znak minus, bo Mario zamiast spadać normalnie w dół przez chwilę leci znowu w górę. No ewidentny błąd i wygląda to przekomicznie śmiesznie. Ale udało Ci się znaleźć ten niepotrzebny minus w kodzie. Tylko, że teraz musisz dokonać poprawki w każdym z tych 10 miejsc w kodzie. Trochę niewygodne, prawda? A nie daj Boże przeoczysz jednego ifa i na przykład dla zderzenia z grzybkiem animacja odegra się prawidłowo, ale już zderzenie z żółwiem spowoduje odegranie tej felernej animacji. Błędy, błędy, błędy. W dodatku lubią one propagować w kodzie. Mamy jakby efekt motyla, bo czasem błąd w jednym miejscu w kodzie spowoduje też błąd w innym miejscu.
Inna rzecz, że jak odejdziesz na chwilę od komputera i zastanowisz się nad kodem gry, to ni stąd, ni zowąd, na przykład kiedy będziesz brać prysznic, zapytasz siebie, what the hell, co ja najlepszego robię, przecież 10 razy wrzucam do źródła dokładnie ten sam kod mający 50 linii. To daje razem 50 razy 10, 500 linii kodu. To jest bez sensu, za dużo nadmiarowych linii. Powinienem raczej wrzucić ich tylko 50 jeden raz i unikać redundancji. I zapewne w podobnych okolicznościach narodził się pomysł użycia podprogramów, czyli właśnie funkcji. Parafrazując film Bad Boys 2, Mike, wieś mnie do firmy, mam pomysła.
Czy nie lepiej byłoby zrobić tak? Wyjąć raz te 50 linii do osobnego podprogramu i jakoś nazwać ten fragment? Nazwijmy go na przykład Game Over. W tych 10 ifach zapiszę tylko tak zwane wywołanie podprogramu, czyli po prostu nazwę funkcji. U nas Game Over. Dwoma nawiasami dam znać, że chodzi mi o funkcję, a nie o zmienną. Zmienne nie będą miały na końcu nawiasów, prawda? I program główny main na podstawie tego wywołania sam uda się we właściwe miejsce do podprogramu. I teraz mam 50 linii. No plus linie wywołań to jakieś 60, ale już nie 500. No i jak zdarzy się błąd, to mogę go poprawić w jednym tylko miejscu, bo program główny udaje się zawsze do tej funkcji.
I jak poprawię tutaj, to poprawię animacje dla grzyba, roślinki, żółwia, smoka. Słowem zawsze. Następnego dnia inni programiści patrzą na ten twój pomysł i mówią, stary, to jest świetne. Ten nasz kot, do którego każdy co chwila coś dorzucał, był jednym wielkim śmietnikiem. I tak naprawdę każdy z nas już się w tym powoli gubił. Stosując twoje podejście możemy się lepiej podzielić pracą. Jedna osoba zrobi animacje, inna detekcje zderzeń, inna dialogi, jeszcze inna system walki i rozwoju postaci. A każdy z nas może testować swój fragment zupełnie w oderwaniu od reszty. I dopiero gdy dany fragment będzie dopracowany, to dokleimy go jako podprogram do naszej głównej aplikacji.
Funkcja główna main będzie się zajmować tylko wywołaniem naszych podprogramów po nazwach i jakby zarządzać całością. Łatwiej też będzie złożyć to wszystko w całość, no bo mamy to podzielone według zadań. Unikniemy powtarzania tego samego po kilka razy, kod będzie miał mniej linii, gra niewątpliwie będzie działać też dzięki temu płynniej na danym sprzęcie. Ok, koniec przykładu z grą Mario. Widzisz teraz na pewno jak cenny, jak wartościowy jest ten pomysł użycia własnych funkcji. Trzeba jedynie poukładać sobie w głowie to nowe podejście, ten nowy paradygmat. Nie traktujemy już programu jako zestawu instrukcji, który wykonuje się z góry na dół. Teraz traktujemy program jako zespół współpracujących ze sobą podprogramów, czyli funkcji, gdzie dodatkowo jeden program jest główny i nosi nazwę main.
Ten główny program służy przede wszystkim do wywoływania poszczególnych funkcji do pracy. Takie programowanie nazywa się fachowo programowaniem proceduralnym, no bo wszystko jest poukładane według spełnianej funkcji, czyli według tego co dany fragment kodu ma do zrobienia. Ok, a teraz pora na konkretny przykład. Napiszmy aplikację, która będzie korzystać z trzech własnych funkcji zamieniających metry na jednostki anglosaskie. Cale, yardy i mile. Program zapyta użytkownika o odległość w metrach, a następnie przekonwertuje podaną wartość na cale, yardy i mile. Każda konwersja będzie obsługiwana przez inną funkcję. Zaczniemy od zrobienia funkcji zamieniającej metry na cale. Jeden metr to około 39,37 cala. Czyli architektura naszej aplikacji póki co będzie następująca. Mamy główny program main, który można porównać do szefa w małej firmie.
Ma on za zadanie zarządzać swoimi pracownikami i w razie potrzeby wywoływać ich do pracy. Sam szef nie zrobi wszystkiego, bo po to ma podwładnych, żeby odciążyli go od części obowiązków. Jest też podprogram, nazwijmy go ile cali. Ten podprogram to jakby pracownik czy najemnik, który zna się na zamianie metrów na cale. Te ilustracje pochodzą ze znanej kreskówki o nazwie Jetsonowie. To jest George Jetson, pracownik, a to jego szef pan Kosmoski, właściciel fabryki kół zębatych. Kosmoski wywołuje Jetsona po nazwisku, daje mu metal i mówi, ty zrób mi z tego koło zębatę. Jetson przyjmuje zlecenie, bierze się do pracy i po jej zakończeniu przekazuje z powrotem, mówimy, zwraca szefowi koło zębatę zgodnie z życzeniem.
W czasie, gdy Jetson tworzył koło zębatę, szef po prostu czekał na zakończenie przez niego pracy. Podobnie będzie w programowaniu. Szef, czyli funkcja główna main, wywołuje po nazwie pracownika, czyli podprogram, daje mu liczbę metrów i mówi, przelicz mi to na cale. Funkcja ile cali bierze się do pracy i po jej zakończeniu zwraca głównemu programowi liczbę cali zgodnie z życzeniem. W czasie, gdy funkcja ile cali dokonywała obliczeń, funkcja main po prostu czekała na ich zakończenie i wznowiła pracę po otrzymaniu wyniku. Czyli to jest to nowe podejście. Podział kodu na mniejsze fragmenty. A teraz jak to zapisać w C++? Zaczniemy standardowo. Stworzymy zmienną o nazwie metry typu float, czyli będzie to liczba z przecinkiem.
Główny program zapyta użytkownika cout, podaj ile metrów i wczytamy sinem z klawiatury. Do tej pory nic nowego, ale teraz chcemy dokonać konwersji i wykorzystamy już do tego własną funkcję, czyli podprogram. No, funkcja main zaczyna się tutaj, a kończy tutaj. Więc skoro ma to być osobny program, to trzeba go zapisać gdzieś poza tym blokiem. Najwygodniej będzie nam to uczynić pomiędzy using namespace a int main. Zróbmy trochę miejsca na funkcję. I teraz zapiszemy to tak. Najpierw trzeba powiedzieć jakiego typu wynik będzie zwracała nasza funkcja. U nas niewątpliwie wynikiem działania funkcji ile cali jest liczba cali, czyli typ float, liczba z przecinkiem, prawda? Potem mówimy jak się nazywa nasza funkcja, czyli ile cali.
Zasady nazywania funkcji są takie same jak przyzmiennych. Brak polskich ogonków, spacji, nie powinna też ona zaczynać się od cyfry. OK, potem są nawiasy okrągłe. Pamiętasz jak przy tablicach mówiłem, że nawiasy kwadratowe, o takie, są zarezerwowane dla tablic? No to nawiasy okrągłe są zarezerwowane dla funkcji. I nie tylko zresztą w C++, w zasadzie w każdym współczesnym języku programowania, jeśli widzisz za nazwą nawiasy okrągłe, to znaczy to, iż masz do czynienia z funkcją. A pamiętasz matematykę? f od x równa się na przykład x kwadrat. f to właśnie funkcja, a nawiasy mówiły tak naprawdę jak jest tzw. argument, czy też parametr funkcji, czyli to co do niej wysyłamy.
Czyli w tym zapisie dajemy funkcji f x, a ona zwraca nam tę liczbę podniesioną do kwadratu. Zdarzyło ci się popatrzeć kiedyś na ten matematyczny zapis z tej właśnie perspektywy? A w naszym programie, jak mówiłem, w nawiasach są argumenty, czyli to co dostaje ta funkcja, ten pracownik. Ano dostaje on od szefa liczbę metrów, czyli float i nazwijmy to sobie m. Ktoś zapyta, ale dlaczego nie napisałeś float metry? To bardzo słuszne pytanie. Oczywiście mogłem napisać tu metry, ale uwaga, pracownik wcale nie musi nazywać tej liczby identycznie jak nazywają szef. Zwróć uwagę, main oraz ile cali to dwa zupełnie niezależne od siebie programy. Nikt więc nie może łudzić się, że zawsze będą się zachowywały tak samo.
Na przykład wyobraź sobie kobietę. Jej ojciec powie o niej córka, jej brat określi ją mianem siostra, jej mąż nazwie ją Żabciu, a każdy z tych facetów mówi o tej samej kobiecie, prawda? Podobnie w programowaniu ta sama liczba, ale różne programy mogą ją nazywać inaczej, na swoje potrzeby. Main o tej liczbie powy metry. Ile cali tę samą liczbę nazwie m. Dlaczego to jest takie ważne? Ano, zauważ, że taką gotową funkcję obliczającą ilość cali na podstawie metrów, mogę sobie ponownie wykorzystać w jakimś innym programie. No bo funkcja jest gotowa, po co znowu wynajdywać koło, skoro raz już taką funkcję zrobiłem. Zatrudnię tego samego pracownika w innym programie.
Ba, to nawet nie muszę być ja, bo podeślę taką funkcję znajomemu programiście i u niego ona też powinna zadziałać. A powiedzmy, że jest tą programistą wyłącznie anglojęzycznym i nazwie swoją zmienną przechowującą metry meters. Gdyby nasz podprogram wymagał zgodności nazw z mainem, to już u niego ta funkcja by nie działała. Metry to inna nazwa zmiennej niż meters, prawda? A tak mamy swobodę. Niech dana funkcja main nazywa zmienne jak chce. Podprogram dla siebie na swoje potrzeby określi te otrzymane liczby na swój sposób. Wczuj się też w punkt widzenia szefa, czyli właśnie w funkcji main. Nazywamy to w programowaniu zjawiskiem tak zwanej czarnej skrzynki.
Chodzi o to, że z punktu widzenia szefa po prostu dajemy innemu programowi liczbę. On coś tam z nią robi i jakoś tam sobie ją nazywa. Nas jako szefa w ogóle to nie interesuje. Wiemy tylko, że to co ten pracownik nam oddał, mamy traktować jako liczbę przeliczoną już na cale. Bierzemy to na wiarę, bo ufamy naszemu programiście, który w tym cyfrowym świecie jest trochę jak Bóg. Dziwna metafora, lecz wchodzimy w obszary cyfrowej abstrakcji, tak to już jest w programowaniu. Dlatego właśnie ten argument nie musi się wcale nazywać metry. A nawet gdybym nazwał to metry, to i tak szef o tym nie wie.
Jest to po prostu przypadek zgodności nazw nie mający większego znaczenia. OK, mamy już typ zwracanej wartości, nazwę i argumenty. To wszystko razem określa się mianem nagłówka funkcji. Czemu tak? Przypuszczam, że chodzi o to, iż po głowie, czy raczej twarzy można rozpoznać pracownika, czyli funkcję. A resztę funkcji, czyli to co funkcja ma zrobić, nazywamy ciałem funkcji i zamykamy w klamrach. Odtąd dotąd jest ciało funkcji. Czyli ta sama metafora. W głowie siedzi info o tym co ta funkcja potrafi i jak wygląda, a reszta ciała, czyli ręce i inne kończyny te prace wykonują. Swoją drogą to widzisz teraz czemu cię ostrzegałem o konieczności skupienia się w tym odcinku.
Zobacz ile tu dziś terminologii i nowych definicji. Ok, co umieścić w ciele funkcji? Ano to co ta funkcja ma zrobić, wykonać dla szefa. Ma ona zamienić liczbę metrów na cale i tę policzoną liczbę zwrócić programowi main. My już wiemy jak zwracamy coś w C++. Wystarczy zajrzeć do funkcji main. Słowo return, prawda? Return, czyli zwróć. Swoją drogą to znając budowę funkcji, inaczej już patrzymy na te zapisy. Tutaj widać, że main ma zwrócić inta, a tutaj go zwraca. Ok, wróćmy do funkcji ile cali. Zwróć, return. No i co powinniśmy dać szefowi? Liczbę cali.
Zrobimy pomocniczą zmienną float cale, równa się, ilość metrów, a w tym programie mówimy na nią m, razy prawidłowy przelicznik, czyli 39. 37. Pamiętaj, że separator dziesiętny w C++ to kropka, a nie przecinek, jak na przykład w Excelu. Co ciekawe, zmienna cale została stworzona wewnątrz programu ile cali. Moje pytanie do Ciebie, jak myślisz, czy szef, czyli funkcja main, będzie widzieć tę zmienną czy nie? No, jak pewnie się domyślasz, nie. Czarna skrzynka. Szef nie będzie miał pojęcia, że taka zmienna w ogóle istniała. To bardzo ważne, mówimy o tym zasięg widoczności zmiennej, z angielskiego variable scope.
Mało tego, ta zmienna zostanie utworzona w pamięci RAM dopiero w momencie, kiedy szef zwróci się do pracownika o dokonanie konwersji, czyli wywoła funkcję do pracy. Zmienna ta posłuży pracownikowi do przechowania wyniku, a zaraz potem, kiedy szef dostanie już wynik, ta zmienna zostanie zniszczona. Zresztą ten pracownik też. I to jest kolejna zaleta użycia funkcji. Używamy ich tylko, kiedy potrzebujemy, a potem zaraz zwalniamy pamięć. Taka zmienna, widoczna tylko wewnątrz jednej funkcji, nazywa się zmienną lokalną. No bo jest widoczna lokalnie, a tak konkretnie to odtąd, dotąd można się nią posługiwać. Za to ta zmienna metry tutaj, zauważ, że nie znajduje się ona w klamrach żadnego programu.
Powiedzielibyśmy o niej zmienna globalna, no bo jest dostępna globalnie, czyli dla każdej funkcji. Nawet nasz pracownik, ile cali, mógłby ją bezpośrednio odczytać. Chyba, że zrobiłbym tak. Jeśli znajdowałaby się ona wewnątrz szefa, no to już nie byłaby widoczna nigdzie poza nim, prawda? Reguła czarnej skrzynki. Oczywiście sytuacja, w której funkcja ile cali korzystałaby ze zmiennej metry zamiast m, jest niepożądana. No bo już w programie mojego anglojęzycznego kolegi programisty nie byłoby żadnej zmiennej o nazwie metry, prawda? Dlatego jedyny bezpieczny sposób podawania zmiennej do podprogramu, to podanie jej jako argument w nawiasie o nazwie przyjętej na potrzeby pracownika, niezależnej od programu głównego. Ok, no to teraz return, no i chcemy zwrócić zmienną calę.
Choć tak naprawdę nie muszę w ogóle tej zmiennej w tak prostym podprogramie tworzyć. Szybciej zapiszę to tak. Przyznam się szczerze, że stworzyłem tą zmienną tylko po to, żeby mieć pretekst do wyjaśnienia ci różnicy pomiędzy zmienną lokalną, a globalną. No to teraz mamy już gotową funkcję o nazwie ile cali, czyli jest pracownik. Trzeba jeszcze tylko sprawić, żeby szef zatrudnił go do pracy, czyli jak mówimy w programowaniu, program główny musi wywołać pod program. Wywołujemy tak jak koleżankę z akademika, po imieniu, czyli w przypadku funkcji po jej nazwie. Pod wczytaniem metrów umieszczamy wywołanie funkcji.
Nosi ona nazwę ile cali, no i posyłamy jej wczytaną z klawiatury ilość metrów, a z perspektywy programu głównego ilością metrów jest zmienna metry, prawda? I teraz kolejne nazwy do zapamiętania. Otóż zwróć uwagę, iż to co wysyłamy do funkcji nazywamy jej argumentem. Czasem też niektórzy mówią parametr, zamiast argument. Może być jeden argument posłany do funkcji, może być ich po przecinku posłanych kilka. U nas jest jeden, ale argument, parametr jest zarówno w wywołaniu funkcji tutaj, jak i w jej nagłówku, czyli w definicji tutaj.
I teraz ten argument o nazwie m, czyli o takiej nazwie jaką posługuje się na swoje potrzeby pod program, nazywamy argumentem formalnym, czyli jak formalnie na swoje potrzeby nazywa daną wartość pod program. Ponadto argument formalny, czyli ta zmienna o nazwie m nie jest widoczna dla programu głównego, prawda? Wewnątrz maina nie możemy się posługiwać zmienną m. A ten argument funkcji, który znajduje się w jej wywołaniu nazywa się argumentem aktualnym. Łatwo to zapamiętać, jest to ten parametr, dla którego aktualnie wywołana została funkcja. Czyli jest to ten argument z punktu widzenia szefa po prostu, czyli parametr formalny to u nas m, a parametr aktualny to metry. Ok, tyle terminologii.
I teraz jak to zadziała? Ano szef, czyli funkcja main, zaprzęgnie do pracy, czyli wywoła pod program ile cali i podeśle mu wartość otrzymaną z klawiatury, na przykład 10 metrów. Pracownik weźmie tę dziesiątkę i będzie o tej pory nazywał ją jako zmienną m. Przemnoży m równe 10 razy 39,37, wyjdzie mu 393,7 i zwróci szefowi wynik. I tu uwaga, zwróci ten wynik pod swoją nazwą, czyli dokładnie w miejscu wywołania. Czyli komputer zamiast tego wstawi tę otrzymaną liczbę 393,7. A ponieważ my chcemy ją po prostu pokazać na ekranie, to zapiszemy sobie to wywołanie wewnątrz C-outa. Dopiszmy też odpowiedni komentarz. Zamiast tego pracownik wstawi obliczoną liczbę, a C-out pokaże ją na ekranie.
Dlaczego wymyślono to w ten sposób? Ano dlatego, że mogę dzięki temu używać nazw w funkcji wewnątrz równań matematycznych, bezpośrednio we wzorach. Ok, tyle teorii. Sprawdźmy czy to zadziała. Ok, działa jak należy. Oczywiście jest to tak prosty program, że łatwiej byłoby go nam napisać bez użycia funkcji, no ale to jest tylko przykład dydaktyczny służący do pokazania sposobu zapisu funkcji. Zróbmy sobie teraz drugi podprogram, który będzie zamieniał metry na yardy, czyli analogicznie na główek funkcji, co będzie ten pracownik zwracał? Float'a. Jak go nazwiemy? Niech będzie ile yardów. Jakie argumenty wyślemy funkcji? Ilość metrów. Powiedzmy, że ta funkcja nazwie tę liczbę na swoje potrzeby x.
Równie dobrze mogłaby to być nazwa metry, m, albo jakakolwiek inna. I tak jest to parametr formalny, czyli nazwa na potrzeby tego podprogramu niewidoczna dla innych programów. No i w ciele funkcji dokonuje konwersji pomiędzy jednostkami. Return przemnoże metry, czyli x, przez przelicznik prawidłowy dla yardów. I gotowe. W zasadzie analogicznie jak dla cali. Ale uwaga, posłużymy się teraz drugim sposobem definiowania funkcji. Otóż ciało tej funkcji jest bardzo krótkie, ma tylko jedną linijkę. No ale nie zawsze tak będzie. Niektóre podprogramy będą miały po kilkaset linii. A to z kolei sprawiłoby, że żeby w ogóle zobaczyć na ekranie funkcję main, musiałbym najpierw przewinąć te kilkaset linii. Mało wygodne niestety.
Dlatego mogę zrobić tak. U góry pozostawić jedynie nagłówek funkcji bez ciała, które może zajmować sporo linii. Tym razem zakończę nagłówek średnikiem, bo tak normalnie to średnika tu nie ma, prawda? A całą funkcję, czyli ciało plus nagłówek umieszczę pod moją funkcją main. U góry muszę zostawić ten nagłówek, no bo w środku maina znajdują się wywołania funkcji. Stąd daje jedynie znać kompilatorowi, że będzie istnieć w tym kodzie funkcja o nazwie ile yardów. Taki sposób definiowania przydaje się właśnie wtedy, gdy podprogramy zajmują wiele linii. Nie trzeba przewijać tego wszystkiego, żeby się dostać do maina po prostu.
Czyli albo nagłówek i ciało od razu, albo tylko nagłówek zakończony średnikiem, a cała funkcja znajdzie się pod mainem. To są dwa sposoby definiowania własnych funkcji. No okej, jest jeszcze trzeci sposób tak naprawdę, bo można daną funkcję wyciąć do biblioteki, czyli zapisać w pliku z rozszerzeniem. h. . h wzięło się od headers, czyli na główki. Na główki funkcji. Widzisz jak to wszystko układa się powoli w całość? Samo wycinanie funkcji do biblioteki zostawimy sobie na inną okazję, nie wszystko na raz. Okej, teraz kolejna sprawa. Pogadajmy o tym, w jaki sposób ta liczba metrów zostaje przesłana od szefa do pracownika, czyli od programu głównego do podprogramu.
Otóż z punktu widzenia szefa zbyt dużym ryzykiem byłoby dawać jakiemuś pracownikowi oryginalną zmienną, czyli u nas szufladkę na daną o nazwie metry. Tak naprawdę domyślnie szef nie ufa do końca pracownikowi i daje mu jedynie wartość tej zmiennej metry. Jest to tak zwana zasada ograniczonego zaufania. Trochę jak na drodze. Na przykład załóżmy, że ktoś wpisał z klawiatury 10 metrów i to siedzi w zmiennej metry. Kiedy następuje wywołanie przez maina funkcji ile cali, to tak naprawdę utworzona zostaje kopia wartości zmiennej, czyli wartość 10 zostaje zapisana w zmiennej m. Pracownik nie ma możliwości dostępu do oryginalnej szufladki o nazwie metry. Nie może jej dotykać, to jest własność szefa.
Takie przesyłanie argumentu nazywamy przesyłaniem przez wartość. Przez wartość, no bo program dostał jedynie skopiowaną wartość oryginału, ale nie ma prawa dotykać oryginalnego pojemnika na dane. Istnieje co prawda możliwość udostępnienia przez szefa oryginału, czyli zarówno wartości zmiennej jak i samego pojemnika. Nazywa się to wówczas przesyłaniem przez referencje. Trzeba jednak do tego użyć wskaźnika, a my jeszcze ich nie znamy. Niemniej jednak zaznaczam iż jest możliwe przyznać pracownikowi prawa do pracy na oryginalnej zmiennej, tylko że szef musi wystawić pracownikowi referencje, czyli specjalne upoważnienie.
Ktoś tu może zapytać, no ale dlaczego domyślnie następuje kopiowanie wartości zmiennej? Przecież kopiowanie trochę trwa, więc tracimy czas, nie mówiąc już o tym, że potrzebujemy kolejnej szufladki w pamięci na kopie, więc marnujemy też pamięć. Więc dlaczego domyślnie szef nie ufa pracownikom i każe im robić kopie? Ano dlatego, że takie błędy nieprzewidzianej podmiany zawartości oryginalnej zmiennej przez pracownika mogą być katastrofalne w skutkach. Na przykład szef daje pracownikowi oryginał, a ten przeliczył metry na cale, ale niechcący przez pomyłkę zapisał do oryginalnego pojemnika gdzie znajdowały się metry wartość 0. A szef nie wie co robił pracownik, pamiętamy czarna skrzynka. I niczego nieświadomy próbuje potem podzielić jakąś wartość przez zmienną metry. A tam jest 0.
Podzielenie przez 0 to błąd matematyczny i cały program się wysypuje. Smutny koniec. Ktoś złośliwy powie, gdyby tylko szef lepiej pilnował ważnych zmiennych, to takiej sytuacji by nie było. Teraz już wiesz czemu szef nie pozwala sobie na ryzyko dawania dostępu do zmiennych komu popadnie? To jest po prostu nieopłacalne, no bo znalezienie potem takiego błędu może trwać bardzo długo i wymagać na przykład ślęczenia nad debagerem, czyli programem, który pozwala nam widzieć wartości zmiennych w trakcie wykonania programu i pracować linia po linii w kodzie.
W końcu programista zauważy, aha, ten podprogram zeruje zmienną metry, ale ile straci na tym czasu i nerwów i to jeszcze pod warunkiem, że ten błąd wykryto przed wypuszczeniem programu na rynek. O przesywaniu oryginałów pogadamy w odcinku o wskaźnikach. Często korzysta się z przesyłania przez referencje, jeśli kopiowanie zmiennych trwałoby zbyt długo, na przykład gdybyśmy musieli wykonać kopie dużych tablic. O przesyłaniu tablic do funkcji jako argumentów oczywiście też porozmawiamy, ale jeszcze nie w tym odcinku. Ostatnia sprawa. Otóż oprócz funkcji mamy jeszcze tak zwane procedury. Cóż to znowu takiego? Spokojnie, procedura to po prostu taka funkcja, która nic nie zwraca głównemu programowi. I tyle. To jedyna różnica.
No bo rzeczywiście, nie zawsze taka potrzeba zwrócenia czegoś szefowi istnieje. Na przykład animacja odgrywająca skuchę w Mario. Po prostu odgrywamy ją na ekranie, nie ma tu co zwracać. Czyli procedura to po prostu fragment kodu, który został wycięty z głównego programu do podprogramu i który można wielokrotnie wywoływać po nazwie. Niech konwersja metrów na mile będzie zrealizowana właśnie za pomocą procedury. Funkcja, która nic nie zwraca. Więc co teraz? Przecież mam tu zapisać jaki typ zmiennej zwróci funkcja. Otóż w tym miejscu napiszemy w każdej procedurze słowo void. W języku angielskim void oznacza po prostu pustka, czyli zaznaczamy, że chodzi nam o procedurę. To słowo void jest krótkie i łatwe do zapamiętania.
Do tego stopnia, że wielu programistów C++ mówi często na przykład zrobię voida zamiast zrobię procedurę. Aczkolwiek w języku angielskim najczęściej powiemy jednak I'm gonna make a procedure. To głównie dlatego, że I'm gonna make a void wydaje się nie mieć sensu logicznego. Ok, no to teraz nazwa procedury. Niech będzie ile mil. Paramet formalny niech się nazywa m typu float. Ponieważ procedura nic nie zwraca to nie napiszemy w ciele słowa return. Zamiast tego zrobimy po prostu couta wewnątrz samej procedury, który wypisze wynik. Stąd wywołanie w programie głównym już nie potrzebuje być wewnątrz couta, no bo cout jest zawarty w samej procedurze. Zauważ też jedną rzecz.
Stosując procedurę tracimy możliwość stosowania jej nazwy wewnątrz równań matematycznych. No bo procedura nic nie zwraca pod swoją nazwą, stąd takie zapisy nie miałyby sensu. Reasumując wszystko do tej pory. Dzięki funkcjom. Nie powtarzamy tego samego kodu kilka razy, czyli unikamy redundancji i skracamy długość kodu. Łatwiej nam poprawić ewentualne błędy. Program jest poukładany i łatwiej go analizować niż taką programistyczną grochówkę, do której wrzucamy co popadnie. Łatwiej nam podzielić się pracą nad większymi projektami w zespołach programistów. Możemy ponownie wykorzystywać raz stworzone funkcje i oszczędzać dzięki temu czas. Łatwiej lokalizować miejsce występowania błędu podczas testów aplikacji. Lepiej używamy pamięci komputera. Jedyna wada? Programista musi poświęcić czas i energię na zrozumienie użycia funkcji.
W ogóle tego nowego podejścia do tego czym jest program i wszystkich tego konsekwencji. Tak naprawdę to już nie raz używaliśmy funkcji, tylko że nic jeszcze o nich wtedy nie wiedzieliśmy. Sama funkcja main jest funkcją, prawda? Podobnie na przykład gdy otwieraliśmy plik tekstowy, używaliśmy funkcji open, która jako parametry pobierała ścieżkę do pliku i tryb otwarcia. Albo funkcja sleep, która jako parametr przyjmowała liczbę milisekund na jakie wstrzymać wykonanie aplikacji. Przykłady oczywiście można mnożyć, no bo funkcje są nieodłącznym towarzyszem programisty. I to nie tylko programistyce plus plusa. A teraz podsumowanie zdobytych wiadomości na slajdach. Jeśli masz ochotę to weź kartkę papieru i zanotuj sobie co trzeba. Po slajdach będzie jeszcze krótkie słowo końcowe.
No to do dzieła. Oto najważniejsze pojęcia i fakty na temat funkcji. Ok, jeśli w pełni zrozumiesz wiedzę z tego odcinka i to nowe podejście proceduralne, to zrobisz olbrzymi krok w swojej umiejętności programowania. Ludzie to niesamowite bestie. Mają naturalną tendencję do samorozwoju i optymalizowania rzeczywistości wokół siebie. Obojednie czy będzie to naczynie gliniane wykonane przez indiankę, czy program komputerowy napisany przez programisty. Chcemy zawsze, żeby to co uczyniliśmy naszymi rękoma było piękne, solidne i odporne na zniszczenie. Dlatego dzielimy program na podprogramy i pieczołowicie układamy wszystko czyniąc go bardziej solidnym, dobrze zaprojektowanym, pięknym nawet. Choć przede wszystkim służącym lepiej ludziom, tworem naszych rąk i umysłów.
Na koniec tylko powiem, iż za jakiś czas czeka nas kolejny krok milowy i taki paradigm shift, kolejna zmiana sposobu patrzenia na to, czym jest program komputerowy. Stanie się to, gdy poznamy programowanie obiektowe. To będzie trzeci level abstrakcji. Można chyba to tak zabawnie ująć. Mnie pozostaje już powoli się pożegnać, powiem tylko tyle, iż robi się coraz ciekawiej w kursie. Niektóre osoby na pewno odczują odpływ początkowego entuzjazmu. Z czasem pewnie pozostaną ci tylko naprawdę zaangażowani w naukę. Na początku każdy musi się z funkcjami oswoić. Uwierz mi, że ty też dasz radę. To kwestia praktyki i czasu. A będzie tej praktyki naprawdę sporo w tym kursie.
Póki co jest teoria, ale tego etapu nie da się pominąć. To tyle na dziś. Do usłyszenia w jakimś innym, YouTube'owym streamie z R i 1. Pozdrawiam serdecznie. .