Nowoczesny obiektowy Pascal:
część 1 (środa)
Michalis Kamburelis
https://www.bsc.com.pl/szkolenie-nowoczesny-obiektowy-pascal/
https://github.com/michaliskambi/modern_pascal_course
https://castle-engine.io/modern_pascal
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)
O mnie
● Castle Game Engine - https://castle-engine.io/
● PasDoc - https://pasdoc.github.io/
● Modern Object Pascal Introduction -
https://castle-engine.io/modern_pascal
● Standardy 3D
○ X3D (Web3D member)
○ glTF (Khronos liaison)
○ IFC
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.
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"
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)
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!!!!!!!!!!!!!!!!!
Pierwsze kroki:
Programowanie imperatywne i
strukturalne
● 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
Programowanie imperatywne i strukturalne
● 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/ )
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
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}
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)
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;
Przykłady praktyczne
https://pl.wikipedia.org/wiki/Ci%C4%85g_Fibonacciego
Prosto, działa, ale nieakceptowalnie wolno dla dużych numerów.
Czy można szybciej?
Programowanie dynamiczne, pamiętamy wyniki w tablicy. (de facto ostatnie 2 wystarczą tutaj, ale dla
ilustracji robimy tablicę)
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
Udało nam się dojść do 92
Jest szybko i poprawnie. A co dalej? https://github.com/Xor-el/DelphiBigNumberXLib
Ćwiczenie 1
Prosta przygodówka paragrafowa.
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"
Klasy, czyli programowanie
obiektowe
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)
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)"
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).
Metody, c.d.
● Metody nie 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.
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.
Trochę (biased! to moje 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.
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)
● Refactor kiedy widzisz ż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)
Zawieranie i zarządzanie pamię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.
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
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).
Ć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!
Dziękuję na dzisiaj!
Kontakt:
Discord
michalis@castle-engine.io

Nowoczesny obiektowy Pascal (1) - kurs BSC

  • 1.
    Nowoczesny obiektowy Pascal: część1 (środa) Michalis Kamburelis https://www.bsc.com.pl/szkolenie-nowoczesny-obiektowy-pascal/ https://github.com/michaliskambi/modern_pascal_course https://castle-engine.io/modern_pascal
  • 2.
    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)
  • 3.
    O mnie ● CastleGame Engine - https://castle-engine.io/ ● PasDoc - https://pasdoc.github.io/ ● Modern Object Pascal Introduction - https://castle-engine.io/modern_pascal ● Standardy 3D ○ X3D (Web3D member) ○ glTF (Khronos liaison) ○ IFC
  • 4.
    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!!!!!!!!!!!!!!!!!
  • 8.
  • 9.
    ● 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;
  • 15.
  • 16.
    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"
  • 20.
  • 21.
    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!
  • 33.