Agenda na dzisiaj(środa)
● O mnie, o kursie, o Pascalu
● Programowanie imperatywne i strukturalne
● Klasy, czyli programowanie obiektowe
● (kilka tematów planowanych na później -> przenieśliśmy na czwartek)
Ogólnie o kursie
●Nauczymy się Pascala
● Postaram się zacząć od podstaw ale przejść też przez nie-podstawowe rzeczy
○ Aby było ciekawie, i zrozumiale, dla możliwie wszystkich :)
○ Zadania będą miały elementy opcjonalne/zaawansowane
○ Przykłady (także wstępne rozwiązania zadań) są na
https://github.com/michaliskambi/modern_pascal_course
● Trochę wykładu, trochę pracowni
● Pytajcie na Discordzie, audio albo tekstowo.
○ Jestem otwarty także na pytania poza czasem kursu, to jest OK (chociaż nie
gwarantuję wtedy jak szybko odpowiem :) ). Discord pozostaje dostępny.
5.
Ogólnie o Pascalu(1 / 3)
● Język obejmujący programowanie:
○ Imperatywne
■ Program to instrukcje wykonywane ~po kolei
○ Strukturalne
■ Dłuższy program warto podzielić na funkcje i procedury
○ Obiektowe
■ Obiekty (klasy i ich instancje) wyrazić koncepcje (rzeczy) i logikę na nich (akcje)
● Sensowny nacisk na "czytelny" kod,
○ który łatwo zrozumieć i modyfikować
○ kompilator pomaga aby kod był bezbłędny
○ Przede wszystkim: deklaracje typów (static typing), i dobry zestaw rozdzielonych typów (ew. boolean <> ints,
ints <> enums, arrays <> pointers etc.)
● Natywna kompilacja - kod jest szybki "out of the box"
6.
Ogólnie o Pascalu(2 / 3)
● Nowoczesny i "praktyczny" język z wszystkimi features których można oczekiwać
○ Moduly
○ Klasy
○ Generics
○ Interfejsy
○ Wyjątki
● Ogólnego zastosowania
○ Zabawki konsolowe, GUI
○ Przetwarzanie dowolnych danych (JSON, XML, bazy danych SQL, NoSQL)
○ Blockchain
○ AI
○ Gry
○ Network (client, server, any level - TCP / UDP sockets, WebSockets, HTTP REST…)
○ Dostep do dowolnych bibliotek w innym języku (jak przez DLL/SO, ale też przez integrację z Pythonem
Python4Delphi)
7.
Ogólnie o Pascalu(3 / 3)
● Luźne (i bardzo przybliżone!) porównania do innych popularnych języków
○ C++ - również OOP i natywny.
■ Pascal stara się być "czystszy", lepiej zorganizowane typy, składnia prostsza do wielu rzeczy (&,
initializers), jedna sensowna metoda na wyrażenie czegoś.
○ C# - również OOP i static typing.
■ Ma garbage collector i (generalnie) działa w VM.
■ Anders Hejlsberg odpowiedzialny za design, ta sama osoba która dodała OOP w Delphi, i to widać
(pozytywnie!). Np. klasy mają properties.
○ Java - starszy C#, w skrócie :)
■ Wszystko jest klasą.
■ Nie ma callbacks, polega na interfejsach i klasach zagnieżdżonych do tego.
○ Dalsze porównania:
■ Python: scripting. Brak typów.
■ JavaScript - scripting, brak static typing, klasy dodane na wierzchu rekordów, wymieszane array i
klasy… TypeScript naprawia cześć z tych dziwactw.
● Nie pozwólnie mi się rozgadać na tym slajdzie!!!!!!!!!!!!!!!!!
● Nie wyglądająpięknie, ale są dobrym sposobem nauki, bo zaczynamy "od zera", nie trzeba od razu
rozumieć jak działa GUI (które od razu pokazuje nam klasy i inne elementy języka)
○ Bez obaw, pokażemy także GUI, ale później!
● Nowy projekt - "Console Application - Delphi"
● Pierwszy hello world:
○ program optional
○ {$APPTYPE CONSOLE} - Windows by default doesn't initialize stdin/stdout/stderr, so Writeln fails
○ Writeln - Write Line (s + newline)
○ begin .. end. (z kropką) wyznaczają program. Jak { } w językach C-like.
○ String w ' .
● Jak zobaczyć output?
○ Uruchom w konsoli (win + cmd, cd …, my_proj.exe; sa inne konsole, PowerShell, Tabby…)
○ Zalecane, bo mamy debugger: Albo dodaj Readln (tylko lepiej potem usuń, bo przeszkadza w uruchamianiu z
konsoli bez interakcji)
Proste programy konsolowe
10.
Programowanie imperatywne istrukturalne
● Zmienne
○ Deklarujemy typy, to jest static typing
● Stałe
● Operacje na nich
● Testy if, case
● Funkcje i procedury
○ Funkcje - zwróc wartość, Result lub Exit(xxx)
○ Procedury (funkcje bez zwracania wartości)
○ Funkcje i procedury mogą wywołać się nawzajem, też same siebie (pośrednio lub bezpośrednio), aka
recursion
○ Parametry by value, const, out, var
● Dobre nawyki
○ Im bardziej kawałek kodu jest "oczywisty do zrozumienia", tym lepiej, możemy łatwiej rozwijać program
○ Funkcje, deterministyczne, zależące tylko od argumentów -> są zazwyczaj najprostsze do zrozumienia
○ Dokumentujemy co robi dana funkcja (może nawet https://pasdoc.github.io/ )
11.
Więcej o intrukcjach
●if:
○ Wartość boolean w argumencie
○ Można podac cokolwiek co jest Boolean, także zmienną tego typu
● Uwaga na priorytet and, or. Dodaj ( )
○ if A = 0 and B <> 0 then ... // INCORRECT example
● Short-circuit
12.
Proste typy
● Integer,Int64, Cardinal…
○ Po co unsigned? Nie tylko po to aby mieć inny zakres (zazwyczaj bez znaczenia), ale też aby mieć dodatkowe
testy
○ Beware "var I: Cardinal; for I := 0 to Count - 1 do…"
■ albo I: Integer (zazwyczaj)
■ albo test Count > 0
○ 50.,100 to też jest typ!
● Floats (Single, Double, Extended unportable, Math.Float)
● String (Unicode, UTF-16 in modern Delphi),
● AnsiString (8-bit, possibly UTF-8 with proper initialization, or current Windows encoding).
○ IMHO mostly useful with proper setup to make it UTF-8:
■ SetMultiByteConversionCodePage(CP_UTF8)
■ To interact with external libs, also reflects most text files nowadays.
● Enum
○ {$scopedenums on}
13.
Tablice
● Klasyczne, statyczne,rozmiar znany:
○ X: array [1..10] of Integer
○ Dowolny zakres, od 0 lub 1 lub czegokolwiek innego
○ Zakres to de facto typ!
■ zakres integer (1..10) to tylko special case
■ typ (jak Byte , 0..255) też zadziała.
● Xxx: array [Integer] of Integer; // nie zadziała, bo data too large, max 2 GB, nawet w 64-bit
■ typ enum też zadziała, często useful
○ Low, High
● Dynamiczne
○ X: array of Integer
○ Typ to zawsze Integer, od 0
○ Dodatkowe SetLength, Length (po prostu High+1)
14.
Pętle
● for I:= 1 to 10 do …
○ for I := 10 downto 1 do
○ for I in MyArray do
● while ConditionToContinue do …
● repeat … until ConditionToStop;
Czy można szybciej?
Programowaniedynamiczne, pamiętamy wyniki w tablicy. (de facto ostatnie 2 wystarczą tutaj, ale dla
ilustracji robimy tablicę)
17.
Co z zakresem?Przy > 40, mamy problem
Nasz pierwszy kontakt z debuggerem:
● uses SysUtils; // aby zmienić error w ładne exception, debugger zatrzyma
● Call Stack, najwyższa linijka która jest nadal w "naszym" kodzie -> zazwyczaj najważniejsza, double click
● Watch list, Ctrl + F7
○ I
○ FiboValues[I - 1] + FiboValues[I - 2] - ups!
○ Dig deeper:
■ FiboValues[I - 1]
■ FiboValues[I - 2]
■ 2.9 miliardów
■ https://docwiki.embarcadero.com/RADStudio/Sydney/en/Simple_Types_(Delphi)
○ Confirm:
■ Writeln(High(Integer))
■ {$assertions on}
■ Assert(Int64(FiboValues[I - 1]) + Int64(FiboValues[I - 2]) < Int64(High(Integer));
○ Fix: Int64
■ Albo UInt64. Nie ma tak wielkiego znaczenia, zakres tylko 2x
18.
Udało nam siędojść do 92
Jest szybko i poprawnie. A co dalej? https://github.com/Xor-el/DelphiBigNumberXLib
19.
Ćwiczenie 1
Prosta przygodówkaparagrafowa.
1. Wprowadź do template dodatkową lokację, np. 2 komory w jaskinii, w jednej jest smok, w innej
skarb.
2. Dodaj pamiętanie przedmiotów. Jeśli posiadamy skarb - księżniczkę można poślubić, otherwise --
wyrzucają nas z zamku.
3. Dodaj losowość. Kiedy wyrzucają nas z zamku, jest 25% szansa że zetną nam głowę i gra się
zakończy. (Random(4)=0)
4. Dodaj losowy przedmiot zamiast po prostu skarb (użyj enum):
a. Np. miecz albo pierścien.
5. Ze smokiem można walczyć. Np. kto wyrzuci więcej (Random(6)+1 to rzut kostką) ten wygrywa.
a. Gracz ma +2 do rzutu jeśli znajdzie wcześniej miecz.
6. Dodaj punkty życia gracza, więcej wrogów, porażka gracza to tylko -1 do jego/jej życia.
7. Dodaj wyświetlanie prostej mapy na początku każdej lokacji, w formie ASCII.
a. Np "[zamek] - las - jaskinia - komora 1"
b. " - komora 2"
Podstawy (pola, metody,konstruktory,
destruktory)
● Pola (kolekcja zmiennych)
● Metody (jak procedury i funkcje, tylko mają dostęp do pól)
● Klasa to template, instancja klasy to dopiero coś "konkretnego"
○ (a słowa "obiekt", jak na ironię, unikam jako nie-1-znacznego :) )
● Tworzymy instancję z klasy przez wywołanie konstruktora
○ Werewolf := TCreature.Create;
○ Można zdefiniować co się wtedy dzieje definiując constructor
○ Pola zaczynają zawsze od 0, false, nil, 0.0 etc.
● Niszczymy wywołując destructor
○ Można zdefiniować co się wtedy dzieje definiując destructor Destroy, wirtualny, zawsze z overide
○ Wywołać można Destroy
○ … ale lepiej Free
○ … a jeszcze lepiej FreeAndNil
■ (ale beware że FreeAndNil nie oznacza że nigdy nie mamy dangling pointers, ale pomaga w prostych sytuacjach)
22.
Dziedziczenie
● Nowa klasa(descendant) posiada pola i metody klasy z której dziedziczy (ancestor)
● Sensowne gdy są pewne fakty o których nawet nie ma sensu mówić w przypadku klasy nadrzędnej
● Wszystko dziedziczy z TObject, samo "class" jest równoważne "class(TObject)"
23.
Metody wirtualne
Podstawowy mechanizm"czystego" skorzystania z dziedziczenia aby rozszerzyć funkcjonalność.
Wszystkie wywołania metody kierowane są do implementacji w faktycznej klasie danej instancji (bez
względu na to do której klasy wygląda na to że się odwołują w czasie kompilacji).
24.
Metody, c.d.
● Metodynie muszą być wirtualne, domyślnie nie są. Nie ma potrzeby jeśli nie planujemy ich overide.
(Będzie wtedy min szybciej, bo wywołanie jest "znane" w momencie kompilacji)
● "inherited" pozwala wywołać metodę przodka (ancestor).
○ Z lub bez parametrami.
● See przykład.
25.
is, as
● is:Testowanie czy instancja jest danej klasy ("if MyCreature is TVampyre then …")
● as: Traktowanie instancji jako daną klasę ("(MyCreature as TVampyre).FangsPoisonous := …")
○ Wyjątek jeśli tak nie jest
○ Jest także niebezpieczny cast "TVampyre(MyCreature).FangsPoisonous := …", niezalecany chyba że
dokładnie w linijce powyżej sprawdziliśmy że to dozwolone przez "is"
● Najczyściej jest nie polegać ani na "is", ani na "as".
○ Wszystko wygląda prościej kiedy jest opakowane w metody wirtualne.
○ Ale w praktyce, co jakiś czas "is" i "as" mają sens. Po prostu zwracajcie uwagę że nie używacie ich zbyt często,
jeśli tak - może inne podejścia mają sens.
26.
Trochę (biased! tomoje opinie) teorii kiedy
używać klas i dziedziczenia (1 / 3)
Prosta reguła:
● Kiedy masz coś o czym myślisz jak o rzeczy, i można na niej robić akcje -> klasa
● Kiedy "A jest zawsze B" (np. jabłko to owoc, vampir to potwór…) to klasa A dziedziczy z B
Ta prosta reguła jest czasem krytykowana, czasem słusznie. Bo to tylko prosta reguła, i nie należy jej
używać ślepo :)
● Są koncepcje które ma sens ując jako klasy (rzeczy albo i nie rzeczy, np. jakaś funkcjonalność jak
downloader czy factory czy business logic jak reporter, converter… też mają sens).
○ Ale nie wszystkie. Nie ma potrzeby robić klasy A tylko dlatego że nam to przyszło do głowy :)
○ Ma sens wtedy kiedy chcesz mieć różne instancje tej koncepcji, z różnymi wartościami pól, z potencjalnym
dziedziczeniem.
○ Czyli definicja klasy ma sens wtedy kiedy ta definicja pomaga nam sprawniej wyrazić rozwiązanie
problemu.
27.
Ponadto, co do"A jest zawsze jak B" - więc "A = class(B)":
● Są relacje które warto ująć dziedziczeniem.
○ Reguła "A jest zawsze B" to warunek konieczny aby to miało sens, ale nie wystarczający.
○ Np. zawieranie funkcjonalności,
■ component systems
■ parent-child visual relationships,
■ behaviors (Castle Game Engine),
■ a nawet entity component systems
….pozwalają dodać funkcjonalność (i dane) bez dziedziczenia.
Uważajmy na sytuacje gdzie dziedziczenie można nam ułatwić jedną kwestię, a utrudnić inną.
● W realnym świecie, można wyróżnić wiele hierarchi.
○ Np. potwór jest postacią, wampir jest potworem, gracz jest postacią.
○ Ale też: postać 2-nożna jest postacią, wampir jest postacią 2-nożną, gracz jest postacią 2-nożną.
○ Nie ma uniwersalnej odpowiedzi "który aspekt powinien zdeterminować dziedziczenie, a który aspekt rozwiązać inaczej'.
Trochę (biased! to moje opinie) teorii kiedy
używać klas i dziedziczenia (2 / 3)
28.
● Refactor kiedywidzisz że inne struktury pozwolą Ci wyrazić rozwiązanie łatwiej.
● Relacje pomiędzy realnymi koncepcjami można wyrazić różnie.
○ Dziedziczenie to tylko "jedno narzędzie", nie jedyne.
○ Różne wartości pól (np. IloscNog) to inne oczywiste narzędzie.
○ Zawieranie to kolejne narzędzie. Np. ListaZachowan. Albo ListaKonczyn.
○ Interfejsy to jeszcze inne narzędzie, o tym jutro…
● Celem programu jest rozwiązanie problemu, a nie opisanie całego świata za pomocą struktur
danych.
Trochę (biased! to moje opinie) teorii kiedy
używać klas i dziedziczenia (3 / 3)
29.
Zawieranie i zarządzaniepamięcią klas
● Klasy mogą odnosić się do innych klas ("zawieranie"), po prostu pole klasy jest typu innej klasy
● Kiedy tworzymy, kiedy niszczymy?
○ Ogólnie, nasz wybór.
○ Sugestia: najlepiej prosto i konwekwentnie. Tworzymy dzieci w konstruktorze rodzica, niszczymy w
destruktorze rodzica.
30.
Zarządzanie pamięcią klas- listy i ownership
● Standardowa biblioteka zawiera kontenery
○ Contnrs, z TObjectList, TComponentList
○ Generics.Collections z TObjectList<T>
● Korzystajmy z nich aby lista sama zwalniała usuwane dzieci
● Pierwszy parametr konstruktora listy (default true) mówi czy lista ma zwalniać dzieci
● Beware! Listy nie wiedzą (pozostanie dangling pointer) jeśli ich dziecko zostanie zwolnione w inny
sposób, chyba że to lista TComponentList
31.
Zarządzanie pamięcią klas- TComponent
● TComponent może być właścicielem (owner) i mieć właściciela
● Idea: Kiedy właściciel jest zwalniany, zwalnia także obiekty które "posiada"
● O ile jeszcze istniały.
○ Tzn. mechanizm jest inteligentny, dzięki "free notifications" (które możemy też używać sami). Jeśli coś
zostanie zwolnione w inny sposób, to właściciel się o tym dowie i nie będzie próbował zwalniać tego 2gi raz
(co byłoby błędem, bo oznaczałoby próbę zwolnienia pamięci na dangling pointer, może nawet reużytym do
innej instancji).
● Owner to 1wszy parametr konstruktora.
● Owner może być zawsze nil, nie ma problemu, to znaczy że sami troszczymy się o wywołanie
destructora
● To najbliższe do ogólnego mechanizmu "stwórz i zapomnij"
Wszystkie opisane powyżej podejścia można łączyć. Bo TComponent ma inne zastosowania (nie tylko do
ownership), listy się też przydają często (nie tylko do ownership).
32.
Ćwiczenie 2
Przerabiamy naszągrę aby mieć
1. Klasę TLocation
a. metody wirtualne
i. functin BeforeVisit: Boolean;
1. W bazowej klasie: true.
2. W podklasach: pozwala dodać test, np. czy posiadasz przedmiot lub walkę z Creatures, listę Creatures można
zmodyfikować podczas tej walki - creatures mogą zginąć lub stworzyć nowe. np. królowa pająków rodzi pająki.
ii. procedure CoreVisit;
1. W bazowej klasie: nic
2. W podklasach: pyta co robić dalej. Jeśli oznacza to przejście do nowej lokacji, ustawia NewLocation:=...
2. Zmienne globalne
a. CurrentLocation, NewLocation: TLocation
b. Stan: GameOver, HasKey
3. Gra to pętla: jeśli jest NewLocation, to spróbuj do niego wejść (NewLocation.BeforeVisit), jeśli się uda to zmien
CurrentLocation:=NewLocation. Potem zrób CurrentLocation.CoreVisit.
4. Podklasy TLocation: TZamek, TJaskinia, TLas…
a. Mogą mieć warunki (jak pokonanie smoka, posiadanie klucza)
b. Mogą mieć stan (czy smok jeszcze tu jest?)
5. No memory leaks!