- 42: https://cs.wikipedia.org/wiki/42_(odpov%C4%9B%C4%8F)
- 50: https://en.wikipedia.org/wiki/50_(number)#In_mathematics
- 1337: https://cs.wikipedia.org/wiki/Leetspeak
- 1729: https://bankexamshorttricks.quora.com/Why-is-1729-a-magic-number / https://1729.com/
jméno = řetězec znaků, které mají zastupvat jiný objekt
speciální slova
- klíčové slovo = slovo se speciálním významem v určitém kontextu
- rezervované slovo = název, který nelze použít jako jméno (Java:
const
agoto
- ale v reálu to není využité)
= oblast v paměti, která má:
- jméno
- hodnota
- typ
- doba života (lifetime)
- rozsah platnosti (scope)
Python: Při přiřazení se přesměruje odkaz.
C: Přiřazení mění hodnotu.
= spojení mezi dvěma věcmi
statická vazba = vazba, která vzniká před během programu a během něj už se nemění (např. v C)
dynamická vazba = vazba, která vzniká při běhu programu a může se během něj měnit
(Výčet "Okamžik vzniku vazby" nezkouší.)
Dřívější vazba je výhodnější z hlediska efektivity, pozdní z hlediska pružnosti jazyka.
= více jmen má vazbu na stejný objekt
Python: [Node()] * 3
vytvoří tři uzly ukazující na stejné místo v paměti (tz. když změním jeden, změní se i ostatní) - jsou to aliasy. Případně jednoduše a = [1, 2, 3]
, a pak a = b
- b
je alias a
.
doba života vazby = doba mezi vznikem a zánikem vazby
doba života objektu = doba mezi vznikem a zánikem objektu
Doba života vazby a objektu se může lišit.
statické objekty = objekty s pevnou adresou, která se během běhu programu nemění
objekty na zásobníku = objekty alokované typicky ve vztahu k rekurzi a volání funkcí
objekty na haldě = objekty, které se mohu (de)alkokovat kdykoli (dražší správa paměti)
- vhodné pro mutable objekty s předem neznámou délkou
- objekty na haldě jsou přístupné přes odkazy nebo referenční proměnné
- problém s fragmentací
- garbage collection
C: (de)alokace na haldě pomocí malloc
a free
Python: vše alokováno na haldě
C#: referenced types se alokují na hadě, value types na zásobníku
mutable objekt = objekt, jehož hodnota se může měnit (Python: seznamy)
immutable objekt = objekt, jehož hodnota se měnit nemůže (Python: int
, C: klíčové slovo const
zakáže objekt měnit)
Haskell: všechno je immutable
= oblast, kde platí konkrétní vazba (napárování jména proměnné a místa v paměti)
= maximální oblast,kde se vazba nemění (C: {...}
, Pyton: tělo funkce)
= hodnoty proměnných záleží na struktuře programu - hledám v úrovni syntakticky výš (Python, C, C++ Java)
- Statický rozsah je běžně používaný v programovacích jazycích.
- Rozsahy mají proměnné, ale i názvy funkcí.
- Vazby jednoduše určím už během přehledu.
Python: Nový rozsah zavádí: modul (globální rozsah), definice třídy, definice funkce. Rozsahem není if else
a bloky (odsazení); modifikátory proměnných: global
, nonlocal
, pravidlo LEGB (Local, Enclosing, Global, Buildins) říká, kde se postupně hledá
C: Nový rozsah zavádí: soubor, definice funkce, blok (příkazy uzavřené v {}
- takže i if then
větve), funkční prototyp.
Haskell: na vnořené rozsahy narazím pokud vnořuji let
deklarace = zavedení jména a indikace rozsahu
- na začátku bloku/procedury (Lisp, C89)
- kdekoli (C99, C++, Java, Python, C#)
Rozsah deklarace může být:
- ode místa deklarace (C99, C++, Java)
- od začátku bloku, ve kterém je deklarace (Python, C#) - použití nesmí předcházet deklaraci ve stejném bloku!
Následující kód spadne:
c = 42
def foo():
print(c)
c = 13
foo()
definice = kompletní informace, včetně implementace
C: Rozsah je až do definice dál. Nastal by problém s dvěmi navzájem volajícími se funkcemi, proto vzniklo oddělení definice a deklarace.
JavaScript: (= zlo); překlep v proměnné - nový název bez var
mi může vytvořit novou proměnněnnou, bez vyhození chyby - variable hoisting. V JS je možné použít use strict
, které zakáže vytváření nových proměnných bez let
.
"Přepiš všechny var
na let
a uvidíš" - může pomoct
Poznámka k poslednímu slajdu: scaled_score
bude počítat s max_score
z procedury foo
.
= hodnoty proměnných záleží na pořadí volání, použiji hodnotu proměnné, kterou jsem použila naposledy
- používám zásobník volání (dřív Lisp, TeX, Bash, PowerShell)
- vazba nemůže být rozhodnuta v době překladu
- je možné je nahradit globálními proměnnými nebo implicitními parametry
= množina hodnot spolu s předdefinovanými operacemi na těchto hodnotách
- detekce chyb
- lepší čitelnost
- podpora modularizace (kontext pro operace:
"a" + "b"
->"ab"
,1 + 2
->3
)
- množina vestavěných typů
- mechanimy pro definici nových typů
- množina pravidel pro>
- rovnost typů
- kompatibilitu typů (dokážeme sečíst
int
areal
) - odvozování typů
hodnotové typy = proměnná obsahuje přímo data (C: vše; Java: primitivní typy - numerické + bool, C#: bool, int, enum, struct,... ale pozor, jsou to jen synonyma pro typové třídy, mají implicitní konstruktory bez parametru ale nemusím volat konstruktor explicitně)
odkazové typy = odkaz na místo, kde jsou uložena data (Python: vše, C# class string, object,..., Java: vše krom numerických a bool)
= základní stavební kameny typového systému (int, bool, float)
Definice primitivního typu záleží na jazyku.
C: string je to složený typ.
Python: string je to primitivní typ.
- reálná čísla
double
- typ s dvojtou přesnostífloat
- celá čísla
- C:
char
(číslený typ!),short
,int
,long
,long long
+ unsigned varianty - Python:
int
- nekonečný - C#:
byte
(1 byte),short
(2 byty),int
(4 byty),long
(8 bytů) + unsigned varianty
- C:
- decimální
- C#:
decimal
pevný počet decimálních míst (C#:decimal
)
- C#:
Python: podtyp int
C: dřív nebyl (int 0/1), teď _Bool
C#: má bool a nelze automaticky konvertovat na int
- typ pro uložení jediného znaku
C: 'a'
je typu int
Python: char
chybí
C#: char
odpovídá UTF-16 a je na 16 bitech
= komplení výčet hodnot, většinou implementovaný pomocí integerů
C: podtyp int
C#: enum
a volím, jakého typu jsou hodnoty enum nazev typHodnot {nazev1, nazev2 = hodnota2}
Python: třída enum.Enum
C: omezeně dynamická délka, složený datový typ, modifikovatelné, ukončeno 0
C++: dynamická délka, modifikovatelné
C#: pevná délka, předdefinovaná třída objektů, nemodifikovatelné
Python: pevná délka, primitivní datový typ, nemodifikovatelné
Java - String
: pevná délka, předdefinovaná třída objektů, nemodifikovatelné
Java - StringBuffer
: dynamická délka, předdefinovaná třída objektů, modifikovatelné
JS: dynamická délka
= proces ověření a vynucování typových pravidel a omezení
Programovací jazyky dělíme na:
- silně typované = zamezují situaci, kdy by se operace měla provést na objektu, pro který není definována
- slabě typované = ty, co nejsou silně typované
- staticky typované = kontrola proběhne již během překladu
- dynamicky typované = kontrola probíhá za běhu
slabě typované | silně typované | |
---|---|---|
staticky typované | C, C++ | Java, Haskell, C# (moderní jazyky) |
dynamicky typované | Perl, PHP, JavaScript | Python, Ruby |
= kdy jsou dva typy stejné?
jmenná ekvivalence (Pascal, Ada, Java C#) = typy jsou stejné, pokud byly definovány ve stejné deklaraci, nebo v deklaracích používajících stejné jméno typu
- striktní jmenná ekvivalence = různé typy (to, že je to stejného typu v programovacím jazyce neznamená, že to má i stejný význam, tz. nelze to zaměňovat)
- volná jmenná ekvivalence = stejné typy (Haskell, C, Pascal)
strukturální ekvivalence (Algol, Modula, ML) = jejich typy jsou identické struktury (konkrétní výklad ještě záleží na programovacím jazyku)
alias = možnost vytvořit synonymum pro nějaký typ
"Jazyk může být silně typovany i když je možné všude vrazit var
."
======= * striktní jmenná ekvivalence = různé typy (to, že je to stejného typu v programovacím jazyce neznamená, že to má i stejný význam, tz. nelze to zaměňovat) * volná jmenná ekvivalence = stejné typy (Haskell, C, Pascal)
strukturální ekvivalence (Algol, Modula, ML) = jejich typy jsou identické struktury (konkrétní výklad ještě záleží na programovacím jazyku)
alias = možnost vytvořit synonymum pro nějaký typ
"Jazyk může být silně typovany i když môžem všude vrazit var."
= programátorem vynucená změna jednoho typu na druhý
- Můžu a nemusím ztratit nějakou informaci.
slajd 26 - řešení: result = 2.0
, result1 = 2.5
, result2 = 2.0
= ke konverzi nedochází, hodnotu interpretujeme jako by byla jiného typu (např. float
čtu, jako by to byl int
)
- na slajdu: nedefinované chování, nikdo nám nezaručí, že to bude fungovat
- využití (dříve):
- vypočítání
1/sqrt(x)
(dnes už je na to instrukce v procesoru) - rychlé porovnání dvou floatů - porovnám je jako dva unsigned inty
- vypočítání
- definice kompatibility se liší mezi různými jazyky
koerce = implicitní typová konverze pro kompatibilní typy
- Předpokládám, že překladač ví, jak se změnou typu naložit.
- Představuje zásadní oslabení typového systému! Protože mě to nehlídá a můžu se nevšimnout, že něco přiřadím do něčeho s nižší přesností a ztratím nějakou informaci.
C: integer promotion = když mohou být všechny hodnoty reprezentovány rozsahem intu, bude to int
, když ne, tak to bude unsigned int
Python: complex, float, int - pokud je jeden complex, převede se i druhý na komplex, obdobně s floatem, jinak jsou oba inty
= typy vznikající kombinací primitních datových typů
- položky (members, fields)
- přístup přes tečkovou notaci
- je možné je vnořovat
C: struct
C++: class
Python: není, místo toho se používá class
Java: není, místo toho se použije třída
C#: hodnotvý typ (bool
, int
a double
jsou taky struct
)
- umožňují různým proměnným sdílet stejnou paměť
- sloužili k šetření paměti (dnes je paměti dost, takže už se moc nepoužívají)
- type-unsafe - přiřadím int, pak přečtu float a to už bude jiné číslo
Python, C#, Java: nejsou
discriminated unions (tagged unions) = před přiřazením použiju tag, který řekne, co to je
= záznam s pevným počtem nepojmenovaných položek
- použití: funkce vracející více položek
Python: lze převádět na pole tam a zpět
Haskell: speciální přístup pro dvojice - fst
a snd
= zobrazení nazev_pole(index) -> prvek
- pole jsou obvykle homogenní = všechny prvky stejného typu
- indexace: většinou pomocí
int
- kontrola mezí byla dřív drahá (C), dnes to není problém
- většinou se indexuje od 0
Python: pole není, místo toho seznam, který je implementován jako pole proměnné délky
"Fortran vypadá jako komentovaný Assembler."
= pole polí (C), nebo pole odkazů (C#)
- tvary polí:
- obdélníkové pole
- schodovité pole (délka řádků se může lišit)
- uložení v paměti:
- souvislý blok - snadná adresace
- pole odkazů na řádky - možnost mít schodovitá pole
řez (slice) = vytváří nové pole (a kazí složitost)
- statické = při překladu vím, ve kterém bloku paměti bude umístěno
- dynamické = alokace během zavádění do paměti
- s proměnnou délkou:
- stack-dynamic - elaborace
- heap-dynamic - vazba za běhu
- s fixní délkou:
- fixed stack-dynamic - elaborace, ale meze jsou statické
- fixed heap-dynamic - vazba za běhu
- s proměnnou délkou:
C: staticky alokované pole (static
) , fixed-heap dynamix (malloc
, free
), fixed stack-dynamic
Java: fixed heap-dynamic
C#: fixed heap-dynamix, heap-dynamic (List
class)
= uspořádaná sekvence prvků
- induktivní definice:
- prázdný seznam
- hlava seznamu (head) + zbytek seznamu (tail)
homogenní seznam = všechny prvky stejného typu (Haskell, ML)
heterogenní seznam = prvky různých typů (Python, List)
list comprehensions = vytvoření seznamu ze seznamu
LISP má moc závorek. (slajd 56 spíš pro srovnání, ale když to uvidíme, měli bychom se v tom orientovat)
- zpřehledňuje program
- jsme líní
- podpora abstrakce (dostaneme nejobecnější řešení)
Python: automatické typové odvození
C#: musím psát typy, odvození lze vynutit pomocí var
- přímá = dostanu chybu
- nepřímá = zjistím typy a vidím, že to není to, co chci
- Každému výrazu a podvýrazu se přiřadí nový typ (vhodné rozkreslit pomocí stomu, kořen podstromu je vždy výsledek)
- Generování typových rovností - aplikace typových pravidel (když vidím 0, vím, že to je
int
; když vidím seznam intů - je to seznam čísel apod.; podle toho, co je parametren funkcí apod.) - Vzniklou soustavu rovností vyřešíme pomocí unifikace typů.
- unifikace = nalezení substitucí za typové proměnné tak, aby várazy byly po substituci idnetické
- unifikátor = výsledná množina unifikací
- substituce = přiřazení typových výrazů jednotlivým proměnným
- Robinsonův unifikační algoritmus (slajd 81)
- Výsledek typového odvození
a. existuje právě jedno řešení
b. soustavu nelze řešit
c. existuje více řešení
- polymorfismus (např. kvůlu identitě)
- nejednoznačnost (např.
+
v F#, protože může být jak pro inty, tak pro seznamy)
Typový výraz se skládá z:
- primitivní typy (Int, Bool,...)
- typové proměnné
- typový konstruktor pole = vezme typ T a vrátí [T]
- typový konstruktor n-tice = vezme typ a vrátí
- funkční typ =
T1 -> T2
typový konstruktor = vezme typ a vrátí jiný typ
(minule na zkoušece: co to je typová proměnná, typový výraz)
= paměťová adresa
- známe z C (přiřazení
p = &a
, dereference*p
) - využití:
- nepřímá adresace
- správa dynamicky alokohované paměti (na haldě)
visící ukazatele = ukazatel ukazující tam, kde nic není (proměnná byla dealokována); v případě, že je na adresu alokována nová proměnná, může vzniknout kolize se správcem paměti
ztracené proměnné = proměnné na haldě, ke kterým není jak přistoupit; důsledkem je memory leak (pokud nemám garbage collectioin); vzniká, když do ukazatele přiřadím jinou adresu a obsah té původní nesmažu
C/C++: mohu ukovazovat kamkoli, ukazatelová aritmetika
= hodnota, která umožňuje programu nepřímo přistoupit k datům = (odkaz je ukazatel, na který si uživatel nemůže sáhnout)
- nelze s nimi dělat pointerovou aritmetiku
- často se implementuje pomocí ukazatelů, ale není to to stejné!
Java: má odkazy i ukazatele
= příkaz, který mění tok řízení v programu
- nepodmíněný skok (
goto
) - podmíněný příkaz
- cykly
Přečíst: http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html
= základní prostředek pro vyjádření výpočtu
- skládají se z:
- jednoduchých objektů (kontanty, proměnné)
- aplikací funkcí/operátorů na jiné výrazy
- závorek
operátor = vestavěná funkce se speciální syntaxí
operand = argument operátoru
- priorita = přednost vyhodnocení výrazu
- asociativita = pravidla pro sdružování operátorů (nikoli operandů!) se stejnou prioritou (
8/2/2
->((8/2)/2)
nebo( 8/(2/2)
)
int a = 10, b = 3, c = 2, d = 4, result
a + a * - b / c % d + c * d
10 + 10 * -3 / 2 % 4 + 2 * 4
10 + (10 * -3 / 2 % 4) + (2 * 4)
10 + (((10 * (-3)) / 2) % 4) + (2 * 4)
10 + ((-30 / 2) % 4) + 8
10 + ((-15) % 4) + 8
10 + (-15 % 4) + 8
10 + (-3) + 8
10 - 3 + 8
7 + 8
= 15
x + x >= y * 2 || y % 2 && z++ % 2
?
--(p++)
(p++)++
(bude na zkoušce :))
Haskell: mám možnost definovat si vlastní prioritu a asociativitu
- chytré jazyky, když to nemají definované, tak vyhodnocují zleva doprava
- závorky nikdy neškodí
= možnost vyhodnocení vvýrazu bez vyhodnocení všech jeho částí
- C, ML, F#, Python
přetížený operátor = jeden operátor značí víc operací
- např. * bude fungovat s čísli, ale i s maticemi
podmíněné cíle přiřazení (ani po nás nebude chtít)
a = a + b
-> a += b
= inkrementace: ++
, dekrementace: --
- možnosti zápisu:
- jako prefix
++a
- hodnota výrazu jea + 1
(je to pouze syntaktický cukr, nepřidává to žádnou novou funkcionalitu oprotix += 1
) - jako postfix
a--
- hodnota výrazu jea
- jako prefix
- (neplést se složeným přiřezním)
a, b = b, a
a, b = foo(x)
= přiřazení + vrácení naráz (b + (a = 42)
)
= Funkce či výraz mimo vrácení hodnoty (main effect) má také pozorovantelný vliv mimo své lokální prostředí.
referential transparency = libovolný výraoz mohu nahradit jeho hodnotou bez vlivu na chování programu
= vybírání mezi dvěma možnostmi
- možný problém: syntaktické vnořování (když nejsi Pyton, nevíš, ke kterému ifu patří else)
C: nemá implicitní skok na konci větvě
C#: musím na konci každé větve explicitně říct, jestli končím (break
), nebo pokračuji
Python: chybí
- podle typu podmínky
- smyčky s čítačem (nesprávně se jim říká forcykly)
- smyčky s logickou podmínkou
- kombinované cykly - používají logickou podmínku, ale zároveň mohu snadno enumerovat
- podle umístěním testu
- na začátku
- na konci
break
,continue
= zobecnění pricipu iterace přes prvky aritmetické posloupnosti
- vrací postupně jednotlivé prvky z nějaké množiny
- má historii, takže při dalším zavolání vrátí prvek následující po tom, který vrátil v předchozím volání
- implementováno pomocí odděleného vlákna řízení
Python: range
není iterátor, ale je iterable
(něco, přes co mohu iterovat), iterátor vytvořím pomocí iter()
, prvky získávám pomocí next()
, na konci vyhodí výjimku StopIteration
C#: IEnumerable
, IEnumerable<T>
, IEnumerator
, IEnumerator<T>
+ yield return
, rozhraní IEnumerable
generátor, generátorová funkce = výpočet - yield
(na chvíli se zastaví) - při dalším zavolání pokračuje tam, kde skončila, je to obecnější, lze je použít na výrobu iterátor
= procedura, funkce (mnoho definicí)
- zlepšení čitenosti
- podpora abstrakce
hlavička podprogramu = typ + jméno + seznam parametrů
profil parametrů = počet, pořadí a typ formálních parametrů
protkol = profil parametrů + typ návratové hodnoty
procedura = podprogram bez návratové hodnoty
funkce = podprogram v návratovou hodnotou (ta ale může být i void
)
Python: lze vrátit cokoli
C: pole a funkce lze vrátit pouze prostřednictím ukazatelů
C#: metody vracet nelze
formální parametry = a
a b
pro foo(a, b)
aktuální parametry (argumenty) = 55
a 6
pro foo(55, 6)
při volání funkce
poziční parametry = vazba aktuálních parametrů na formální parametry je určena pořadím
pojmenované parametry = parametry s explicitně uvedeným jménem
parametry s implicitní hodnotou = foo(a, b, c=0)
, v případě, že nedodám hodnotu parametru, použije se výchozí hodnota
proměnný počet parametrů = funkce bere předem neznámý počet parametrů
- vstupní parametry
- výstupní parametry
- vstupně-výstupní parametry
- Předávání vícerozměrných polí je problematické, protože potřebuji znát rozměry daného pole.
- C: rozměry ve formálním parametru
- Ada: jsou k dispozici informace o rozsahu
= v okamžiku volání překopíruju hodnotu do formálního parametru
C: všechno hodnotou - můžu předat hodnotu ukazatele, čímž můžu nasimulovat předávání odkazem
C#: hodnotové typy
= předávám odkaz na aktuální parametr, formální parametr bude odkazovat na stejné místo v paměti
- efektivní na čas i paměť
- vracení hodnot prostřednictvím aktuálních parametrů
C#: ref
, in
nelze modifikovat (C# >=7.2), out
- Riziko vzniku kolize:
- mezi aktuálními parametry
- při použití polí
- formálních a globálních proměnných
read-only parametry = jejich hodnota se nesmí uvnitř funkce měnit -> vyšší bezpečnost, rychlost (C#: in
, C: const
)
= funkci pošlu proměnnou (bez hodnoty), do které se v průběhu funkce zapíše výsledek
- možné problémy:
- kolize hodnot
- volba okamžiku vazby
C#: out
= nakopíruji hodnotu aktuálního parametru do formálního parametru, následně s tím normálně pracuji jako by to bylo předávání hodnotou a na konci nakopíruji výsledek zpátky do hodnoty argumentu
= pokud je aktuální parametr odkaz (ne hodnota), tak sdílíme s volanou funkcí tento odkaz (hodnotu na něm), zároveň není možné přesměrovat tento odkaz někam jinam (lze pouze měnit hodnotu)
C#: reference types (ale říká se tomu taky předávání hodnotou)
Python: taky to používá
= předává se to, co bylo v době volání jako jméno proměnné (např. u foo(x)
- foo(s[i])
se vždycky při změně x
podívám, co je za hodnotu v i
- řídím se tím jménem)
C: makra
Haskell: podobné call-by need
přetížený podprogram = existuje jiný podprogram se stejným jménem (musí se lišit protokolem - typem a počtem parametrů)
- použití: konstruktory
- problémy s přetíženými podprogramy:
- koerce = možnost shody s více profily (např.
int
může být idouble
) - je nutné určit prioritu - implicitní hodnoty - mění možný počet parametrů
- koerce = možnost shody s více profily (např.
Python: jde to, ale defaultní chování je, že se bere jen poslední definice funkce
= mnohotvarost; více tvarů toho samého, musím nějaký vybrat
- přetěžování
- při aktivaci se vybere ta definice programu, která podpovídá aktuálním parametrům
- podrogram má obecné typy formálních parametrů
- jedna definice, která je nezávislá na typu
- Haskell - např sort seznamu libovolného typu
= explicitní parametrický polymorfismus
- (implicitní parametrický polymorfismus (Haskell))
- nějaký typ
<T>
, za který se při konkrétním volání dosadí něco konkrétního
C++: template function
Java: generické parametry musí být třídy, mohu je navíc omezit pomocí nějakého rozhraní
C#: podobné jako Java, ale umí pracovat i s primitivními typy
- u OOP
Duck typing = Jestli to vypadá jako kachna, plave to jako kachna, kváká to jako kachna, tak je to pravděpodobně kachna. (používá např. Python)
= speciální typ programů na stejné úrovni, které se střídají v běhu (bez řídící autority) a pamatují si mezi tím svůj stav
-Hraju karty, vynesu kartu, předám řízení dalšímu h ráči, atd. (třeba Kvarteto)
- použití: simulace systémů (lišky a zajíci), pipelines, event loops
- Asynchronní funkce nejsou koprogramy (mají nějaké omezení).
- pseudosouběžnost
Python: nelze předávat řízení libovolně, ale jen volajícímu (jsou to skoro koprogramy, ne úplně), použiju generátorovou funkci (yield
. next()
, .send()
, .close()
), nejedná se o čisté koprogramy!
abstrakce = pohled na entitu, které ponechává pouze nejdůležitější atributy
- procesová abstakce - podprogramy (viz předchozí přednáška)
- datová abstrakce
UHP = Univerzální Hnědý Pták abstraktní datový typ = matematický model datové struktury, která je definována svými hodnotami a operacemi na nich
uživatelsky definovaný ADT = datový typ se skrytou reprezentací, která má deklarované rozhraní, které není závislé na implementaci (např. zásobník)
- creators
- producers = operace na typu, která vrátí nový objekt stejného typu
- observers
- mutators
mutable ADT - např. zásobník
immutable ADR - string v Pythonu
= určité části znepřístupníme klientskému kódu
- spolehlivost
- zjednodušení pro programátora
- menší riziko konfliktu jmen
- možnost změnit vnitřní reprezentaci
= jazyková konstrukce, která umožňuje spojit pod jedním jménem spolu související data a operace na nich (často zahrnuje i skrývání informace)
třída zásobníku
funkce, která pracuje s objetem zásobníku
^ to není zapouzdřené
třída zásobníku
metoda této třídy
^ to je zapouzdření
třída = šablona, podle které se vyrábí objekty a definuje typ, udává formát dat a metody pro přísutp k těmto datům, může obsahovat i vlastní data
objekt = instance třídy
člen třídy = atributy (datové členy) a metody (funkční členy)
- zapouzdření: metody ve třídě - metody jsou propojeny s datovou strukturou
- information hiding
- metody, které by neměly být používány mají začínat
_
- private metody začínají dvěmi podtržítky
__
, a pak je obtížnější je volat (je potřeba před to dát i název struktury:_JmenoTridy__jmenoMetody
)
- metody, které by neměly být používány mají začínat
class
__init__
není konstruktor- přístup k sobě samému:
self
- zapouzdření:
class
= referenční typ ukládaný na haldě, lze děditstruct
= hodnotový typ ukládaný na zásobníku, nelze dědit - jednodušší a menší reprezentace (např. nepotřebuji virtuální tabulku metod)
- information hiding: modifikátory
public
= přísutpné komukoliprivate
= přístupné pouze z kódu ze stejné třídy (implicitní)
- přístup k sobě samému:
this
+ tečková notace (ale jen když to potřebuji, např. při kolizi jmen)
getters/setters = metody sloužící k získávání a nastavování atributů, mohu je použít pro validaci
= atributy s výhodami metod
Auto-implemented properties
class Person {
public string name { get; set; } = "N/A";
public int age { private get; set; } = 0;
}
Person me = new Person();
me.name = "Dominika";
me.age = 1;
{get; set}
-> public get i set{private get; set}
-> private get, public set{get}
-> public get, set jen v rámci konstruktoru- mohu mít dvě properties, které používají jeden atribut
= třída, která funguje s různými datovými typy (podobné jako generická metoda)
- např.
class Stack<T>
= speciální funkce, která je volána při vytváření instance třídy
- jmenuje se stejně jako třída
- neuvádí se návratový typ
- C#: bez definování je vytvořen default constructor
- může jich být víc různých (přetěžování)
= uvolňuje alokovanou paměť, zavírá soubory apod.
- volán na konci života objektu
- omezená použitelnost v jazycích s garbage collection
- v C# finalizers - nemůže jej zavolat programátor, ten může použít
Dispoze
= existuje pouze v jedné kopii, nezávisle na počtu instancí
- statický atribut = je společný pro všechny objekty třídy
- statická metoda = nezávisí na konkrétním objektu - může přistupovat pouze ke statickým atributům!
- využívám oddělený překlad
- skrývání informace: pomocí odděleného hlavičkového souboru
- zapoudření: všechno je spolu v tom oddělém souboru
- pomocí definice modulu
- skrývání informace: naven exportuji jen metody, ke kterým chci, aby měl přístup uživatel
objekt
- má data a funkce
- implementace bývá skrytá
- objekty komunikují skrze rozhraní (zasíláním zpráv)
- zapouzdření
- abstrakce
- dědičnost
- dynamická vazba/polymorfismus
encapsulaiton = zapouzdření + abstrakce
- class-based OOP = každý objekt je instancí třídy
- prototype-based OOP = nemají třídy, jen objekty
- skoro čistě objektový programovací jazyk je SmallTalk
= Umožňuje definovat novou podtřídu na základě existující rodičovské třídy. Vytváří hierarchii tříd.
Liskov Substitution Principle (LSP) = Objekt typu T
může být, bez negativních důsledků, kdekoliv
nahrazen objektem typu S
, kde S
je podtřídou T
. (Původně definováno pro abstraktní datové typy.)
- potomek/podtřída
- odvozaná tříd
- rodič
- dědí
- rozšiřuje
- tranzitivní dědičnost
- podtřída může přidat atributy/metody k těm zděděným z rodičovské třídy
- podtřída může mít znepřístupněné atributy/metody z rodičovské třídy
- podtřída může modifikovat chování zděděných metod; dochází k tzv. překrytí (override) metody
C#: public
- členy přístupné kdekoli, private
- jen v aktuální třídě, protected
- v aktuální třídě a jejích podtřídách
Když vytvářím objekt podtřídy, implicitně nejdřív zavolám konstruktor rodičovské třídy, a pak si tam mohu doplnit další věci v konstruktoru podtřídy.
C#: base()
- zavolání konstruktoru rodičovské třídy
Destruktory jsou volány v opačném pořadí než konstruktory (od dítěte k rodičům).
Pokud má potomek i rodičovská třída metodu ze stejným jménem.
Device d = new SoundDevice();
d.PrintStatus();
= Použije se deklarovaný typ proměnné (za překladu)
- je rychlejší
C#: implicitní, ale používá se klíčové slovo new
= Použije se skutečný typ objektu (za běhu). Překrývání metod.
C#: musím označit virtual
- metoda v rodičovské třídě, override
- metoda v odvozené třídě
= mechanizmus používaný pro podporu dynamického výběru funkcí za běhu
- obsahuje odkazy na metody
- tabulka pro každou třídu, objekty v ní ukazují na stejnou tabulku
= třída, kterou nikdy neinstanciuji, pouze z ní dědím (např. třída Expression
s podtřídami Addition
nebo Multiplication
)
- mohou obsahovat metody s implementací nebo abstraktní metody bez implementace
C#: klíčové slovo abstract
, abstraktní metody jsou implicitně virtuální a nemají implementaci
= Případ, kdy jedna třída dědí z více rodičovských tříd.
C#: zakázaná vícenásobná dědičnost
Python: dané pořadí deklarací
= "abstraktní třída" ve velmi omezené verzi, která definuje, jaká funkcionalita musí být ve tříde, která implementuje toto rozhraní
- obsahuje jen deklarace metod a properties
- všechny členy jsou implicitně a nastálo
public
- nelze deklarovat static, abstract, virtual
- není tam žádná implementace (do verze C# 8.0)
- všechny členy jsou
public
, protože rozhraní definuje metody, co budou k dispozici na venek (takže nechceme, aby bylyprivate
) - nemůžu použít
static
,abstract
,virtual
- Třída implementuje rozhraní, může implementovat i více rozhraní.
- S rozhraními lze zacházet jako s typy - mohu vytvářet proměnné typu interface, i parametry mohou být typu interface.
= rozhraní, která je parametrizované nějakým typem <T>
Haskell: rozhraní je podobné typové třídě
C#: např. IComparable<T>
, List<T>
Zobáčky znamenají generika.
= Rozhraní od sebe můžou navzájem "dědit" - rozšiřují své rodičovské rozhraní.
- Protože nekopírujeme žádné implementace, tak nemám diamond problem.
- Pro každé rozhraní, které implementuji mohu mít vlastní implementaci metody (pomocí tečkové notace) a následně je potřeba instanciovat objekt, který je typu jednoho nebo druhého rozhraní a podle toho se potom vybere, která metoda se použije.
kořen objektové hierarchie = třída, ze které dědí úplně všechno
C#: třída System.Object
Pokud nevím, jak napsat hash, je doporučené použít xor všech mých objektů.
C#: is
- stejná nebo rodičovská třída, typeof() == GetType()
- ekvivalence
casting = objekt mohu přetypovat na typ rodičovské třídy (C#: a as B
)
explicitní přetypování = C#: (B) a
= proces konverze nějakého hodnotového typu na typ object
- využití: heterogenní seznam (seznam objektů - např.
ArrayList
neboStack
), metodaConcat
umí spojovat hodnoty různých typů
int i = 42;
object o = i; // boxing
int j = (int) o; //unboxing
= dodatečná funkcionalita, kterou využívám v různých třídách mimo logickou strukturu hierarchie dědičnosti
- nemusím řešit komplikovanou hierarchii tříd
- nemusím duplikovat kód
- přehlednost
- porušuje principy OOP
Python: když chci funkcionalitu mixinu, použiju výcenásobnou dědičnost
C#: prostřdnictvím rozhraní s implementací (od verze 8.0), při volání je potřeba přetypovat na typ rozhraní
= ošetření chyb, které program vygeneruje za běhu
- výjimky lze vnořovat
- čitejnější a čistější kód
- zjednodušuje zpracování chyb
- drahá operace
C#: všechny výjimky jsou (nepřímými) potomky třídy Exception
Java: dovoluje použít break
, continue
nebo return
ve finally
bloku všechny výjimky jsou podtřídou třídy Throwable
- kontrolované výjimky = specifikuji v hlavičce metody, jaké výjimky se z ní mohou teoreticky propagovat výše
- nekontrolované výjimky = výjimky, které se v rámci programu nehlídají
Python: except
místo catch
, raise
pro vyvolání výjimky, else
blok pro případ že k výjimce nedošlo
try
- blok kódu, kde může potenciálně nastat chybacatch
- blok, který specifikuje, jakou výjimku zachytávám a co se má stát, když ji zachytím- vybere se první blok, který odpovídá vyhozené výjimce
- bez parametrů se chová jako
catch (Exception e)
finally
- spustí se vždy, i kdy nastane výjimka, i když žádná výjimka nenastane (např. vždy chci zavřít soubor)
throw new Exception
- Mohu vytvořit svoji výjimku prostřednictvím třídy, které dědí z
Exception
. - Třída má min. 3 konstruktory: bez argumentů, s jedním argumentem se zprávou, a zprávou a vnitřní výjimkou
- pokud výjimka není ošetřena, propaguje se do volající funkce
= podmínka během odchytu výjimky
try {...}
catch (SomeException e) when (index > 0) {...}
catch (SomeOtherException e)
{...}
- výjimky slouží pouze na výjimečné stavy (ne bežné řízení programu)
- zbytečně nedefinovat nové výjimky
- používat co nejkonkrétnější výjimku
= throw
bez paramentru pošle výjimku do nadřezeného bloku
- přibalení originální výjimky pro nadřezený blok jako info navíc
try {...}
catch (Exception e) {
throw new Exception("message", e);
}
- C# má odvozování typů
- C# pokud nechci aby se v řetězci braly v potaz speciální znaky, napíšu před řetězec
@
- C# vyřaduje ve
switch
explicitníbreak
, aby se vykonala právě jen jedna větev
Autor: Uncle Bob
- SRP Single Responsibility Principle = třída se má starat jen o jednu jednou věc
- OCP Open/Closed Priciple = mělo by být jednoduché třídě přidat novou funkcionalitu bez toho, aniž bych musela ve třídě něco měnit
- LSP Liskov Substitution Principle = objekt může být nahrazen instancí podptypu
- ISP Interface Segregation Principle = lepší více konkrétnějších rozhraní než jedno, které dělá všechno
- DIP Dependency Inversion Principle = obecné třídy by neměly záviset na specializovaných třídách; je vhodné používat abstraktní třídy
rigidita = pro malou změnu funkcionality musíme změnit hodně kódu
fragilita = malé změny mohou rozbít program na mnoha jiných místech
immobilita = kód se dá jen těžko recyklovat