Lekce 8 - Aréna s mágem (dědičnost a polymorfismus)
V minulé lekci, Dědičnost a polymorfismus, jsme si vysvětlili dědičnost a polymorfismus.
Dnes máme slíbeno, že si dědičnost a polymorfismus vyzkoušíme v praxi.
Bude to opět v naší aréně, kde z bojovníka podědíme mága. Tento C# .NET
tutoriál již patří k těm náročnějším a bude tomu tak i u dalších.
Proto si práci s objekty průběžně procvičujte, zkoušejte si naše
cvičení a také vymýšlejte vlastní aplikace, abyste si zažili základní
věci. To, že je tu přítomen celý online kurz, neznamená, že ho celý
najednou přečtete a pochopíte Snažte se programovat průběžně.
![Mág - Objektově orientované programování v C# .NET](images/5/csp/mag.png)
Než začneme něco psát, shodněme se na tom, co by měl mág umět. Mág
bude fungovat stejně jako bojovník. Kromě života bude mít však i
manu. Zpočátku bude mana plná. V případě plné many
může mág vykonat magický útok, který bude mít
pravděpodobně vyšší damage než útok normální (ale samozřejmě
záleží na tom, jak si ho nastavíme). Tento útok manu vybije na
0
. Každé kolo se bude mana zvyšovat o 10
a mág
bude podnikat jen běžný útok. Jakmile se mana zcela doplní, mág bude moci
magický útok opět použít. Mana bude zobrazena grafickým ukazatelem stejně
jako život.
Vytvoříme tedy třídu Mag.cs
, podědíme ji z
Bojovnik
a dodáme jí atributy, které chceme oproti bojovníkovi
navíc. Třída tedy bude vypadat takto (opět si ji okomentujte):
class Mag: Bojovnik { private int mana; private int maxMana; private int magickyUtok; }
V mágovi zatím nemáme přístup ke všem proměnným, protože jsou v
bojovníkovi nastavené jako privátní. Musíme proto třídu
Bojovnik
lehce upravit. Změníme modifikátory
private
u atributů na protected
.
Budeme potřebovat jen kostka
a jmeno
, ale jako
protected
klidně nastavíme všechny atributy charakteru, protože
se v budoucnu mohou hodit, kdybychom se rozhodli podědit další typy
bojovníků. Jako protected
však není vhodné nastavovat atribut
zprava
, protože nesouvisí s bojovníkem, ale s vnitřní logikou
třídy. Třída tedy bude vypadat takto:
protected string jmeno; protected int zivot; protected int maxZivot; protected int utok; protected int obrana; protected Kostka kostka; private string zprava; ...
Přejděme ke konstruktoru.
Konstruktor potomka
C# konstruktory nedědí! Je to pravděpodobně z toho důvodu, že jazyk předpokládá, že potomek bude mít navíc nějaké atributy a původní konstruktor by u něj byl na škodu. To je i náš případ, protože konstruktor mága bude brát oproti tomu z bojovníka navíc dva parametry (manu a magický útok).
Definujeme si tedy konstruktor v potomkovi, který bere parametry potřebné pro vytvoření bojovníka a několik parametrů navíc pro mága.
V konstruktorech potomků je nutné vždy volat konstruktor předka. Je to z toho důvodu, že bez volání konstruktoru nemusí být instance správně inicializovaná. Konstruktor předka nevoláme pouze v případě, že žádný nemá. Náš konstruktor musí mít samozřejmě všechny parametry potřebné pro předka plus ty nové, které má potomek navíc. Některé potom předáme předkovi a některé si zpracujeme sami. Konstruktor předka se vykoná před naším konstruktorem.
V C# .NET existuje klíčové slovo base
,
které je podobné this
, jež už známe. Na rozdíl od
this
, které odkazuje na konkrétní instanci třídy,
base
odkazuje na předka. Můžeme tedy zavolat
konstruktor předka s danými parametry a poté vykonat navíc inicializaci pro
mága. V C# se volání konstruktoru předka píše do hlavičky metody.
Konstruktor mága bude tedy vypadat takto:
public Mag(string jmeno, int zivot, int utok, int obrana, Kostka kostka, int mana, int magickyUtok): base(jmeno, zivot, utok, obrana, kostka) { this.mana = mana; this.maxMana = mana; this.magickyUtok = magickyUtok; }
Stejně můžeme volat i jiný konstruktor v téže třídě (ne
předka), jen místo base
použijeme this
.
Přesuňme se nyní do Program.cs
a druhého bojovníka (Shadow)
změňme na mága, např. takto:
Bojovnik gandalf = new Mag("Gandalf", 60, 15, 12, kostka, 30, 45);
Změnu samozřejmě musíme udělat i v řádku, kde bojovníka do arény
vkládáme. Všimněme si, že mága ukládáme do proměnné typu
Bojovnik
. Nic nám v tom nebrání, protože bojovník je mágův
předek. Stejně tak si můžeme typ proměnné změnit na Mag
.
Když aplikaci nyní spustíme, bude fungovat úplně stejně jako předtím.
Mág vše dědí z bojovníka a zatím tedy funguje jako bojovník.
Polymorfismus a přepisování metod
Bylo by výhodné, kdyby objekt Arena
mohl s mágem pracovat
stejným způsobem jako s bojovníkem. My již víme, že takovémuto mechanismu
říkáme polymorfismus. Aréna zavolá na objektu metodu
Utoc()
se soupeřem v parametru. Nestará se o to, jestli bude
útok vykonávat bojovník, nebo mág, bude s nimi pracovat stejně. U mága si
tedy přepíšeme zděděnou metodu Utoc()
z
předka tak, aby útok pracoval s manou. Hlavička metody však zůstane
stejná.
Abychom mohli nějakou metodu přepsat, musí být v předkovi označena jako
virtuální. Toho se nemusíme nijak obávat, jednoduše
pomocí klíčového slova virtual
C# sdělíme,
že si přejeme, aby potomek mohl tuto metodu přepsat. Hlavičku metody v
Bojovnik.cs
tedy změníme na:
public virtual void Utoc(Bojovnik souper)
Když už jsme u metod, budeme ještě jistě používat metodu
NastavZpravu()
, ta je však privátní. Označme ji proto jako
protected
:
protected void NastavZpravu(string zprava)
Při návrhu bojovníka jsme samozřejmě měli myslet na to,
že se z něj bude dědit, a již označit vhodné atributy a metody jako
protected
, případně metody jako virtuální. Klíčovým slovem
virtual
je označena metoda, kterou lze v potomkovi přepsat, jinak
to není možné. V tutoriálu k bojovníkovi jsme vás tímto však nechtěli
zbytečně zatěžovat, proto musíme modifikátory změnit až teď, kdy jim
rozumíme
Metoda Utoc()
v bojovníkovi bude tedy
public virtual
. Nyní se vraťme do potomka a pojďme metodu
přepsat. Normálně ji definujeme v Mag.cs
tak, jak jsme zvyklí.
Za modifikátorem public
však ještě použijeme klíčové slovo
override
. To značí, že jsme si vědomi toho,
že se metoda zdědila, ale přejeme si změnit její chování.
public override void Utoc(Bojovnik souper)
Podobně jsme přepisovali metodu ToString()
u našich objektů.
Každý objekt v C# je totiž poděděný ze System.Object
, který
obsahuje čtyři metody, jedna z nich je i ToString()
. Při její
implementaci tedy musíme použít override
.
Chování metody Utoc()
nebude nijak složité. Podle hodnoty
many provedeme buď běžný útok, nebo útok magický. Hodnotu many potom buď
zvýšíme o 10
, nebo naopak snížíme na 0
v
případě magického útoku.
public override void Utoc(Bojovnik souper) { int uder = 0; // Mana není naplněna if (mana < maxMana) { mana += 10; if (mana > maxMana) mana = maxMana; uder = utok + kostka.hod(); NastavZpravu(String.Format("{0} útočí s úderem za {1} hp", jmeno, uder)); } else // Magický útok { uder = magickyUtok + kostka.hod(); NastavZpravu(String.Format("{0} použil magii za {1} hp", jmeno, uder)); mana = 0; } souper.BranSe(uder); }
Kód je asi srozumitelný. Všimněme si omezení many na
maxMana
, může se nám totiž stát, že tuto hodnotu
přesáhneme, když ji zvyšujeme o 10
. Když se nad kódem
zamyslíme, pochopíme, že útok výše v podstatě vykonává původní metoda
Utoc()
. Jistě by tedy bylo přínosné zavolat podobu metody na
předkovi místo toho, abychom chování opisovali. K tomu opět použijeme
base
:
{CSHARP_OOP} class Mag: Bojovnik { private int mana; private int maxMana; private int magickyUtok; public Mag(string jmeno, int zivot, int utok, int obrana, Kostka kostka, int mana, int magickyUtok): base(jmeno, zivot, utok, obrana, kostka) { this.mana = mana; this.maxMana = mana; this.magickyUtok = magickyUtok; } public override void Utoc(Bojovnik souper) { // Mana není naplněna if (mana < maxMana) { mana += 10; if (mana > maxMana) mana = maxMana; base.Utoc(souper); } else // Magický útok { int uder = magickyUtok + kostka.hod(); NastavZpravu(String.Format("{0} použil magii za {1} hp", jmeno, uder)); souper.BranSe(uder); mana = 0; } } } {/CSHARP_OOP}
{CSHARP_OOP} {CSHARP_MAIN_BLOCK} // vytvoření objektů Kostka kostka = new Kostka(10); Bojovnik zalgoren = new Bojovnik("Zalgoren", 100, 20, 10, kostka); Bojovnik gandalf = new Mag("Gandalf", 60, 15, 12, kostka, 30, 45); Arena arena = new Arena(zalgoren, gandalf, kostka); // zápas arena.Zapas(); Console.ReadKey(); {/CSHARP_MAIN_BLOCK} {/CSHARP_OOP}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } public int hod() { return random.Next(1, pocetSten + 1); } public override string ToString() { return String.Format("Kostka s {0} stěnami", pocetSten); } } {/CSHARP_OOP}
{CSHARP_OOP} class Bojovnik { protected string jmeno; protected int zivot; protected int maxZivot; protected int utok; protected int obrana; protected Kostka kostka; private string zprava; public Bojovnik(string jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maxZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; } public bool Nazivu() { return (zivot > 0); } public string GrafickyZivot() { string s = "["; int celkem = 20; double pocet = Math.Round(((double)zivot / maxZivot) * celkem); if ((pocet == 0) && (Nazivu())) pocet = 1; for (int i = 0; i < pocet; i++) s += "#"; s = s.PadRight(celkem + 1); s += "]"; return s; } public virtual void Utoc(Bojovnik souper) { int uder = utok + kostka.hod(); NastavZpravu(String.Format("{0} útočí s úderem za {1} hp", jmeno, uder)); souper.BranSe(uder); } public void BranSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; zprava = String.Format("{0} utrpěl poškození {1} hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zemřel"; } } else zprava = String.Format("{0} odrazil útok", jmeno); NastavZpravu(zprava); } protected void NastavZpravu(string zprava) { this.zprava = zprava; } public string VratPosledniZpravu() { return zprava; } public override string ToString() { return jmeno; } } {/CSHARP_OOP}
using System.Threading; {CSHARP_OOP} class Arena { private Bojovnik bojovnik1; private Bojovnik bojovnik2; private Kostka kostka; public Arena(Bojovnik bojovnik1, Bojovnik bojovnik2, Kostka kostka) { this.bojovnik1 = bojovnik1; this.bojovnik2 = bojovnik2; this.kostka = kostka; } private void Vykresli() { Console.Clear(); Console.WriteLine("-------------- Aréna -------------- \n"); Console.WriteLine("Zdraví bojovníků: \n"); Console.WriteLine("{0} {1}", bojovnik1, bojovnik1.GrafickyZivot()); Console.WriteLine("{0} {1}", bojovnik2, bojovnik2.GrafickyZivot()); } private void VypisZpravu(string zprava) { Console.WriteLine(zprava); Thread.Sleep(500); } public void Zapas() { // původní pořadí Bojovnik b1 = bojovnik1; Bojovnik b2 = bojovnik2; Console.WriteLine("Vítejte v aréně!"); Console.WriteLine("Dnes se utkají {0} s {1}! \n", bojovnik1, bojovnik2); // prohození bojovníků bool zacinaBojovnik2 = (kostka.hod() <= kostka.VratPocetSten() / 2); if (zacinaBojovnik2) { b1 = bojovnik2; b2 = bojovnik1; } Console.WriteLine("Začínat bude bojovník {0}! \nZápas může začít...", b1); Console.ReadKey(); // cyklus s bojem while (b1.Nazivu() && b2.Nazivu()) { b1.Utoc(b2); Vykresli(); VypisZpravu(b1.VratPosledniZpravu()); // zpráva o útoku VypisZpravu(b2.VratPosledniZpravu()); // zpráva o obraně if (b2.Nazivu()) { b2.Utoc(b1); Vykresli(); VypisZpravu(b2.VratPosledniZpravu()); // zpráva o útoku VypisZpravu(b1.VratPosledniZpravu()); // zpráva o obraně } Console.WriteLine(); } } } {/CSHARP_OOP}
Opět vidíme, jak můžeme znovupoužívat kód. S dědičností je spojeno opravdu mnoho technik, jak si ušetřit práci. V našem případě dědění ušetří několik řádků, ale u většího projektu by mělo obrovský význam.
Aplikace nyní funguje tak, jak má.
Konzolová aplikace
-------------- Aréna --------------
Zdraví bojovníků:
Zalgoren [############# ]
Gandalf [################# ]
Gandalf použil magii za 52 hp
Zalgoren utrpěl poškození 36 hp
Aréna nás však neinformuje o mágově maně, pojďme to napravit.
Přidáme mágovi veřejnou metodu GrafickaMana()
, která bude
obdobně jako u života vracet string
s grafickým ukazatelem
many.
Abychom nemuseli logiku se složením ukazatele psát dvakrát, upravíme
metodu GrafickyZivot()
v Bojovnik.cs
. Připomeňme si,
jak vypadá:
public string GrafickyZivot() { string s = "["; int celkem = 20; double pocet = Math.Round(((double)zivot / maxZivot) * celkem); if ((pocet == 0) && (Nazivu())) pocet = 1; for (int i = 0; i < pocet; i++) s += "#"; s = s.PadRight(celkem + 1); s += "]"; return s; }
Vidíme, že metoda není kromě proměnných zivot
a
maxZivot
na životě nijak závislá. Metodu přejmenujeme na
GrafickyUkazatel()
a dáme jí dva parametry: aktuální hodnotu a
maximální hodnotu. Proměnné zivot
a maxZivot
v
těle metody poté nahradíme za aktualni
a maximalni
.
Modifikátor bude protected
, abychom metodu mohli použít v
potomkovi:
protected string GrafickyUkazatel(int aktualni, int maximalni) { string s = "["; int celkem = 20; double pocet = Math.Round(((double)aktualni / maximalni) * celkem); if ((pocet == 0) && (Nazivu())) pocet = 1; for (int i = 0; i < pocet; i++) s += "#"; s = s.PadRight(celkem + 1); s += "]"; return s; }
Metodu GrafickyZivot()
v Bojovnik.cs
naimplementujeme znovu, bude nám v ní stačit jediný řádek, a to zavolání
metody GrafickyUkazatel()
s příslušnými parametry:
public string GrafickyZivot() { return GrafickyUkazatel(zivot, maxZivot); }
Určitě jsme mohli v tutoriálu s bojovníkem udělat metodu
GrafickyUkazatel()
rovnou. Chtěli jsme si však ukázat, jak se
řeší případy, kdy potřebujeme vykonat podobnou funkčnost vícekrát. S
takovouto parametrizací se v praxi budete setkávat často, protože nikdy
přesně nevíme, co budeme v budoucnu od našeho programu požadovat.
Nyní můžeme vykreslovat ukazatel tak, jak se nám to hodí. Přesuňme se
do Mag.cs
a naimplementujme metodu GrafickaMana()
:
public string GrafickaMana() { return GrafickyUkazatel(mana, maxMana); }
Jednoduché, že? Nyní je mág hotový, zbývá nám jen naučit arénu
zobrazovat manu v případě, že je bojovník mágem. Přesuňme se tedy do
Arena.cs
.
Rozpoznání typu objektu
Jelikož se nám nyní vykreslení bojovníka zkomplikovalo, vytvoříme si
pro něj samostatnou metodu VypisBojovnika()
, jejímž parametrem
bude daná instance bojovníka:
private void VypisBojovnika(Bojovnik b) { Console.WriteLine(b); Console.Write("Zivot: "); Console.WriteLine(b.GrafickyZivot()); }
Nyní pojďme reagovat na to, jestli je bojovník mágem. Minule jsme si
řekli, že k tomu slouží operátor is
:
private void VypisBojovnika(Bojovnik b) { Console.WriteLine(b); Console.Write("Život: "); Console.WriteLine(b.GrafickyZivot()); if (b is Mag) { Console.Write("Mana: "); Console.WriteLine(((Mag)b).GrafickaMana()); } }
Bojovníka jsme museli na mága přetypovat proto, abychom se dostali k
metodě GrafickaMana()
. Samotný Bojovnik
ji totiž
nemá. To bychom tedy měli. VypisBojovnika()
budeme volat v
metodě Vykresli()
, která bude vypadat takto:
using System.Threading; {CSHARP_OOP} class Arena { private Bojovnik bojovnik1; private Bojovnik bojovnik2; private Kostka kostka; public Arena(Bojovnik bojovnik1, Bojovnik bojovnik2, Kostka kostka) { this.bojovnik1 = bojovnik1; this.bojovnik2 = bojovnik2; this.kostka = kostka; } private void Vykresli() { Console.Clear(); Console.WriteLine("-------------- Aréna -------------- \n"); Console.WriteLine("Bojovníci: \n"); VypisBojovnika(bojovnik1); Console.WriteLine(); VypisBojovnika(bojovnik2); Console.WriteLine(); } private void VypisBojovnika(Bojovnik b) { Console.WriteLine(b); Console.Write("Život: "); Console.WriteLine(b.GrafickyZivot()); if (b is Mag) { Console.Write("Mana: "); Console.WriteLine(((Mag)b).GrafickaMana()); } } private void VypisZpravu(string zprava) { Console.WriteLine(zprava); Thread.Sleep(500); } public void Zapas() { // původní pořadí Bojovnik b1 = bojovnik1; Bojovnik b2 = bojovnik2; Console.WriteLine("Vítejte v aréně!"); Console.WriteLine("Dnes se utkají {0} s {1}! \n", bojovnik1, bojovnik2); // prohození bojovníků bool zacinaBojovnik2 = (kostka.hod() <= kostka.VratPocetSten() / 2); if (zacinaBojovnik2) { b1 = bojovnik2; b2 = bojovnik1; } Console.WriteLine("Začínat bude bojovník {0}! \nZápas může začít...", b1); Console.ReadKey(); // cyklus s bojem while (b1.Nazivu() && b2.Nazivu()) { b1.Utoc(b2); Vykresli(); VypisZpravu(b1.VratPosledniZpravu()); // zpráva o útoku VypisZpravu(b2.VratPosledniZpravu()); // zpráva o obraně if (b2.Nazivu()) { b2.Utoc(b1); Vykresli(); VypisZpravu(b2.VratPosledniZpravu()); // zpráva o útoku VypisZpravu(b1.VratPosledniZpravu()); // zpráva o obraně } Console.WriteLine(); } } } {/CSHARP_OOP}
{CSHARP_OOP} class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int VratPocetSten() { return pocetSten; } public int hod() { return random.Next(1, pocetSten + 1); } public override string ToString() { return String.Format("Kostka s {0} stěnami", pocetSten); } } {/CSHARP_OOP}
{CSHARP_OOP} class Bojovnik { protected string jmeno; protected int zivot; protected int maxZivot; protected int utok; protected int obrana; protected Kostka kostka; private string zprava; public Bojovnik(string jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maxZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; } public bool Nazivu() { return (zivot > 0); } protected string GrafickyUkazatel(int aktualni, int maximalni) { string s = "["; int celkem = 20; double pocet = Math.Round(((double)aktualni / maximalni) * celkem); if ((pocet == 0) && (Nazivu())) pocet = 1; for (int i = 0; i < pocet; i++) s += "#"; s = s.PadRight(celkem + 1); s += "]"; return s; } public string GrafickyZivot() { return GrafickyUkazatel(zivot, maxZivot); } public virtual void Utoc(Bojovnik souper) { int uder = utok + kostka.hod(); NastavZpravu(String.Format("{0} útočí s úderem za {1} hp", jmeno, uder)); souper.BranSe(uder); } public void BranSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; zprava = String.Format("{0} utrpěl poškození {1} hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zemřel"; } } else zprava = String.Format("{0} odrazil útok", jmeno); NastavZpravu(zprava); } protected void NastavZpravu(string zprava) { this.zprava = zprava; } public string VratPosledniZpravu() { return zprava; } public override string ToString() { return jmeno; } } {/CSHARP_OOP}
{CSHARP_OOP} class Mag: Bojovnik { private int mana; private int maxMana; private int magickyUtok; public Mag(string jmeno, int zivot, int utok, int obrana, Kostka kostka, int mana, int magickyUtok): base(jmeno, zivot, utok, obrana, kostka) { this.mana = mana; this.maxMana = mana; this.magickyUtok = magickyUtok; } public string GrafickaMana() { return GrafickyUkazatel(mana, maxMana); } public override void Utoc(Bojovnik souper) { // Mana není naplněna if (mana < maxMana) { mana += 10; if (mana > maxMana) mana = maxMana; base.Utoc(souper); } else // Magický útok { int uder = magickyUtok + kostka.hod(); NastavZpravu(String.Format("{0} použil magii za {1} hp", jmeno, uder)); souper.BranSe(uder); mana = 0; } } } {/CSHARP_OOP}
{CSHARP_OOP} {CSHARP_MAIN_BLOCK} // vytvoření objektů Kostka kostka = new Kostka(10); Bojovnik zalgoren = new Bojovnik("Zalgoren", 100, 20, 10, kostka); Bojovnik gandalf = new Mag("Gandalf", 60, 15, 12, kostka, 30, 45); Arena arena = new Arena(zalgoren, gandalf, kostka); // zápas arena.Zapas(); Console.ReadKey(); {/CSHARP_MAIN_BLOCK} {/CSHARP_OOP} }
Hotovo
Konzolová aplikace
-------------- Aréna --------------
Bojovníci:
Zalgoren
Život: [########## ]
Gandalf
Život: [##### ]
Mana: [############# ]
Zalgoren útočí s úderem za 28 hp
Aplikaci ještě můžeme dodat hezčí vzhled. Vložili jsme ASCIIart nadpis
Arena, který jsme vytvořili touto aplikací: http://patorjk.com/software/taag.
Navíc jsme obarvili ukazatele pomocí barvy pozadí a popředí. Metodu k
vykreslení ukazatele jsme upravili tak, aby vykreslovala plný obdélník
místo #
(obdélník napíšete pomocí Alt +
219). Výsledek může vypadat takto:
Konzolová aplikace __ ____ ____ _ _ __ /__\ ( _ \( ___)( \( ) /__\ /(__)\ ) / )__) ) ( /(__)\ (__)(__)(_)\_)(____)(_)\_)(__)(__) Bojovníci: Zalgoren Život: ████████████████████ Gandalf Život: ████████████████████ Mana: ████████████████████ Gandalf použil magii za 48 hp Zalgoren utrpěl poškození 33 hp
Kód máte k dispozici v příloze. Pokud jste něčemu nerozuměli, zkuste si článek přečíst vícekrát nebo pomaleji, jsou to důležité praktiky.
V následujícím cvičení, Řešené úlohy k 5.-8. lekci OOP v C# .NET, si procvičíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 3489x (40.04 kB)
Aplikace je včetně zdrojových kódů v jazyce C#