Programání informačních systémů
|
|
- Marcela Černá
- před 6 lety
- Počet zobrazení:
Transkript
1 VŠB-TU OSTRAVA Programání informačních systémů Ing. Ivo Špička, Ph.D Ostrava 2017
2 POKYNY KE STUDIU Programání informačních systémů Pro předmět Programání informačních systémů jste obdrželi studijní balík obsahující integrované skriptum pro kombinované studium obsahující i pokyny ke studiu. 1. Prerekvizity Pro studium tohoto předmětu se předpokládá absolvování předmětu Počítačová technika II. 2. Cílem předmětu a výstupy z učení Student porozumí struktuře a parametrům programovatelných logických automatů (PLC), jejich konfiguracím a programování. Po prostudování předmětu by měl student být schopen: výstupy znalostí: Student bude ovládat různé způsoby programování PLC. Bude znát hardwarové a softwarové řešení předních výrobců PLC. Hlavní důraz je kladen zejména na dovednosti v aplikování programovatelných automatů pro řešení standardní technické úkoly. Student zvládne základní úroveň programování. Pro koho je předmět určen Předmět je zařazen do magisterského studia na FMMI, ale může jej studovat i zájemce z kteréhokoliv jiného oboru, pokud splňuje požadované prerekvizity. Studijní opora se dělí na části, kapitoly, které odpovídají logickému dělení studované látky, ale nejsou stejně obsáhlé. Předpokládaná doba ke studiu kapitoly se může výrazně lišit, proto jsou velké kapitoly děleny dále na číslované podkapitoly a těm odpovídá níže popsaná struktura. Při studiu každé kapitoly doporučujeme následující postup: Čas ke studiu: xx hodin Na úvod kapitoly je uveden čas potřebný k prostudování látky. Čas je orientační a může vám sloužit jako hrubé vodítko pro rozvržení studia celého předmětu či kapitoly. Někomu se čas může zdát příliš dlouhý, někomu naopak. Jsou studenti, kteří se s touto problematikou ještě nikdy nesetkali a naopak takoví, kteří již v tomto oboru mají bohaté zkušenosti.
3 Cíl: Po prostudování tohoto odstavce budete umět popsat... definovat... vyřešit... Ihned potom jsou uvedeny cíle, kterých máte dosáhnout po prostudování této kapitoly konkrétní dovednosti, znalosti. Výklad Následuje vlastní výklad studované látky, zavedení nových pojmů, jejich vysvětlení, vše doprovázeno obrázky, tabulkami, řešenými příklady, odkazy na animace. Shrnutí pojmů Na závěr kapitoly jsou zopakovány hlavní pojmy, které si v ní máte osvojit. Pokud některému z nich ještě nerozumíte, vraťte se k nim ještě jednou. Otázky Pro ověření, že jste dobře a úplně látku kapitoly zvládli, máte k dispozici několik teoretických otázek. Úlohy k řešení Protože většina teoretických pojmů tohoto předmětu má bezprostřední význam a využití v databázové praxi, jsou Vám nakonec předkládány i praktické úlohy k řešení. V nich je hlavní význam předmětu a schopnost aplikovat čerstvě nabyté znalosti při řešení reálných situací hlavním cílem předmětu. Způsob komunikace s vyučujícími: Student má možnost se obrátit na přednášejícího i cvičícího i mimo výuku a komunikovat s nimi elektronickou poštou. Kontakty na pedagogy jsou dostupné na internetových stránkách školy.
4 1. Struktura programu Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět definovat proměnou určitého datového typu, seznámí se typy operátorů Výklad Struktura programu. Začíná příkazem: using System; System je základní jmenný prostor rámce.net. Jmenný prostor sdružuje související třídy a další typy. Například úplný název třídy z tohoto prostoru jmen System.Console. Abychom mohli psát pouze psát Console, řekneme, které jmenné prostory hodláme užívat. Zde např. napíšeme na začátek programu using System a všechny třídy ze jmenného prostoru System můžeme používat se zkráceným názvem. Jelikož se jedná o příkaz, musíme za using System napsat středník. Středník ukončuje každý příkaz v jazyce C#. Klíčové slovo namespace definuje jmenný prostor, do kterého patří kód uvedený ve složených závorkách. C# je čistě objektově orientovaný jazyk, což znamená, že každá funkce (resp. metoda) musí být definována uvnitř třídy. Totéž platí i pro hlavní metodu, tedy metodou je Main. Main je vstupni bod programu.. Překladače jazyka C# jsou case sensitive. Rozlišují tedy velká a malá písmena. V jazyce C# bylo zavedeno několik konvencí. Jména balíků, tříd, rozhraní a většiny dalších položek začínají velkým písmenem. Malým začínají privátní a chráněné (protected) atributy, lokální proměnné a parametry. Více informací o používaných konvencích najdete v dokumentaci (Naming Guidelines).
5 Definice metody Main může mít tvar: static void Main(); static int Main(); static void Main(string[] args); static int Main(string[] args); Složené závorky značí tzv. tělo metody. Veškerý kód zde umístěný se provede. Snažme se programy, které budeme během našeho kurzu vyvíjet, strukturovat tak, že jmenným Jmenné prostory se mohou vnořovat. Napište program, který vypíše na příkazový řádek větu: Zadejte uživatelské jméno: Napište program, který bude mít metodu Main ve třídě s názvem VelmiDulezitaTrida a tato třída bude ve jmenném prostoru VelmiDulezityProstor, který bude podprostorem prostoru JesteDulezitejsiProstor. Program vypíše na příkazový řádek větu: "Pozdrav z velmi důležité třídy. Co značí Main? K čemu slouží klíčové slovo using? ukážeme si, co je to proměnná, jak ji deklarovat, inicializovat, co je to datový typ proměnné a další Proměnné Data ukládáme v proměnných. Proměnná představuje určitou část paměti, která je určitého datového typu, a kterou si vhodně pojmenujeme. Datový typ určuje, jakým způsobem jsou data obsažená v proměnné (tj. hodnota proměnné) uložena v paměti a jaké operace lze s danou proměnnou provádět.
6 Deklaraci proměnné (tzv. deklarační příkaz). Obecně napíšeme: datovýtyp názevproměnné; Datovým typem pro celá čísla je int (z naglického integer). Deklarace proměnné i může její deklarace vypadat následovně: int i; Dříve než se proměnná použije, musí mít přiřazenou hodnotu Do deklarované proměnné můžeme přiřadit hodnotu pomocí přiřazovacího příkazu. Obecně vypadá přiřazovací příkaz následovně: názevproměnné = hodnotaproměnné; Přiřaďme do proměnné i hodnotu 5: i = 5; Přiřazovací příkaz je dán operátorem přiřazení, což je =. Program již o existenci proměnné i ví a může jí tedy přiřadit hodnotu. Pokud bychom se snažili přiřadit do proměnné, která není deklarována, zahlásí kompilátor chybu. Abychom kód napsaný dříve pochopili v budoucnu rychleji, můžeme si do něj vkládat komentáře. Komentář je text ve zdrojovém kódu, který se nepřekládá, slouží tedy pouze jako naše poznámky. Řádkový komentář platný do konce řídky začíná dvojicí lomíteka // Deklarujeme proměnnou i typu int: int i; Pokud již při deklaraci proměnné víme, jaká má být hodnota této proměnnémůžeme ji incializovat a odpovídající příkaz se nazývá inicializační. Obecný tvar inicializačního příkazu vypadá takto: datovýtyp názevproměnné = hodnotaproměnné; Inicializujme proměnnou j na hodnotu 15:
7 int j = 15; Jména proměnných, jejich identifikátory mohou obsahovat alfnumerické znaky(písmena a čísla) a podtržítko. Proměnná by neměla začínat číslicí, není doporučeno používat diakritiku. Příklad 1.1. V metodě Main deklarujte proměnnou celkovypocetpolozek a inicializujte ji na 500. Hodnotu této proměnné vypište na příkazový řádek. Dále přiřaďte do proměnné hodnotu 1000 a opět vypište na příkazový řádek. V této kapitole si ukážeme, jak lze přečíst údaje, které zapsal uživatel na příkazový řádek. Pro tento účel slouží třída Console, obsahuje dvě (statické) metody pro čtení vstupu z příkazového řádku: Readline - sejme celý řádek, tzn. až po zadání klávesy Enter Read - sejme následující znak Řešené úlohy Napišme program, který požádá o zadání strany čtverce a následně vypíše jeho obsah : namespace Ctverec "); class Program static void Main() // Vyzveme k zadání strany čtverce: Console.Write("Zadejte celočíselnou délku strany čtverce: // Uložíme zadanou hodnotu do proměnné: int strana = int.parse(console.readline()); // A vypíšeme hlášení: Console.WriteLine("Čtverec o straně 0"+ " jednotek má obsah 1"+ " jednotek čtverečných.", strana, strana * strana); Console.ReadLine();
8 Metoda Write třídy Console vypíše na příkazový řádek výzvu pro uživatele. Metoda Write, na rozdíl od metody WriteLine, nepřidá na konec výpisu znak pro nový řádek, takže uživatel bude zadávat hodnotu přímo za dvojtečku s mezerou. Metoda ReadLine přečte vstupní text až po znak nového řádku (je to prakticky místo, kde byl zadán Enter) a vrátí řetězec znaků. My potřebujeme uložit zadanou hodnotu jako celé číslo int. Číselné datové typy mají (statickou) metodu Parse, která se snaží převést řetězec na daný číselný typ. Pokud se tento převod nezdaří, vyvolá metoda Parse výjimku, kterou je potřeba zachytit, což si ukážeme později. Zatím předpokládejme, že vstupy jsou správné. Řetězec znaků jako znakovou konstantu, literál, zapíšeme mezi znaky uvozovek. Každý příkaz můžeme rozdělit do více řádků. U řetězce to nejde, musíme jej rozdělit na kousky a ty pak spojit pomocí operátoru + do jednoho řetězce. Zde ve výsledku jedná o řetězec "Čtverec o straně 0 jednotek má obsah 1 jednotek čtverečných.". Namísto znaků 0 a 1 se na daná místa vypíší hodnoty proměnných strana a výrazu strana * strana. Příklad 1.2. V metodě Main vyzvěte uživatele k zadání šířky a délky obdélníka. Nakonec vypište formátované hlášení o obsahu daného obdélníka dle vzoru: Zadejte a obdélníka: 4 Zadejte b obdélníka: 5 Obdélník o a=4 a b=5 má obsah Operátory S proměnnými a konstantními hodnotami (literály) lze provádět různé operace. Ty se určují pomocí znaku či znaků operace (operátoru) a aplikuje se na proměnné a hodnoty, kterým říkáme operandy. Operátor provede operaci a vrátí výsledek operace. Například operátor + aplikovaný na čísla 2 a 3 vrátí výsledek operace 5, zápis této operace je Skupina vyjadřuje obecně výraz. Operátor + operuje se dvěma operandy. Dle počtu operandů dělíme operátory (operace) na: unární - operují s jedním operandem binární - operují se dvěma operandy ternární - operují se třemi operandy Naprostá většina unárních operátorů se zapisuje v prefixovém tvaru (před operandem): operátor operand Vyskytují se však i verze v postfixovém tvaru (za operandem):
9 operand operátor Binární operátory se zapisují v infixovém tvaru (mezi operandy): operand1 operátor operand2 Jediný ternární operátor je?:. Zapíše se následovně: operand1? operand2 : operand3 Operátor + slouží pro sčítání čísel, v případě řetězců má význam spojení (konkatenace) řetězců. Dělení operátorů (operací) do následujících kategorií: Kategorie operátorů Operátory v kategorii Aritmetické + - * / % Logické (booleovské a bitové) & ^! ~ && true false Sloučení řetězců + Inkrementace, dekrementace Bitový posuv >> << Relační (porovnávací) ==!= < > <= >= Přiřazovací = += -= *= /= %= &= = ^= <<= >>= Přístup k členům. Indexování [] Přetypování () Podmínkový?: Tvorba objektů new Typové informace as is sizeof typeof Řízení přetečení checked unchecked Dereference a adresa * -> [] & Většina operátorů může pracovat s více datovými typy operandů. Takové chování operátorů nazýváme přetížení operátoru (operátor je přetížen).
10 Příklad 1.3. Nechť uživatel zadá dvě hodnoty typu int. Vypište výsledky binárních operací +, -, <, > aplikovaných na dané hodnoty. Otázky k probranému učivu Co jsou unární operátory Co jsou binární operátory Co jsou ternární operátory Jaké kategorie operátorů máme Shrnutí pojmů kapitoly (podkapitoly) Proměnná představuje určitou část paměti, která je určitého datového typu, a kterou si vhodně pojmenujeme. Datový typ určuje, jakým způsobem jsou data obsažená v proměnné (tj. hodnota proměnné) uložena v paměti a jaké operace lze s danou proměnnou provádět. Operátory unární - operují s jedním operandem binární - operují se dvěma operandy ternární - operují se třemi operandy Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> How Do I Videos - Visual C# ->
11 us/vcsharp/bb aspx. Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 2 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět Definovat ruzné druhy datových typů Seznámi se s aritmetickými operátory Výklad
12 2.1. Datový typ Datový typ proměnné určuje, jak je proměnná uložena v paměti a jaké operace lze s proměnnou provádět. Veškeré datové typy můžeme rozdělit na: 1. Hodnotové typy (value types) 2. Odkazové (referenční) typy (reference types) Proměnná hodnotového typu má svou hodnotu přímo uloženu v paměti, kterou označujeme zásobník (stack): Příkladem hodnotového typu je typ int, který slouží pro celočíselné proměnné. Proměnná odkazového (referenčního) typu, mluvíme také zkrácene o odkazu má uložen pouze odkaz (referenci) na skutečnou hodnotu. Abychom mohli s takovou proměnnou pracovat, musí existovat i odkazovaná hodnota. Tyto hodnoty jsou ukládány do jiné oblasti paměti, která se nazývá spravovaná halda (managed heap). Naprostá většina datových typů na platformě.net tvoří odkazové typy. Na následujícím programu demonstrujme rozdíl mezi hodnotovými a odkazovými datovými typy Vestavěné hodnotové typy V následujícm text se budem věnovat celočíselným typům, typům s plovoucí řádovou čárkou, desítkovému typ, aritmetickým operátorům, booleovskému typu a booleovským operátorům a znakovému typu. Ukážeme si datový typ pro ukládání kódu znaku Celočíselné typy Uveďme si jednotlivé celočíselné datové typy přehledně v tabulce. Sloupec Název reprezentuje zkrácený název pro daný datový typ. Naopak ve sloupci Typ CTS je úplný název v rámci FCL (Framework Class Library - Knihovny tříd rámce.net). Tyto typy musí být kompatibilní na celé platformě, to znamená i mezi různými programovacími jazyky. Proto je každý typ popsán přesným rozsahem a velikostí, jak ukazují další sloupce. Název Typ CTS Popis Rozsah Velik ost
13 sbyte System. SByte 8-bitové číslo se znaménkem -128 až 127 (-2 7 až 2 7-1) 1 B short System. Int16 16-bitové číslo se znaménkem až (-2 15 až ) 2 B int System. Int32 32-bitové číslo se znaménkem až (-2 31 až ) 4 B long System. Int64 64-bitové číslo se znaménkem až (-2 63 až ) 8 B byte System. Byte 8-bitové číslo bez znaménka 0 až 255 (0 až 2 8-1) 1 B ushort System. UInt16 16-bitové číslo bez znaménka 0 až (0 až ) 2 B uint System. UInt32 32-bitové číslo bez znaménka 0 až (0 až ) 4 B ulong System. UInt64 64-bitové číslo bez znaménka 0 až (0 až ) 8 B 2.4. Typy s plovoucí řádovou čárkou Datový typ s plovoucí řádouvou čárkou je v paměti realizován odlišně než celočíselné datové typy. Ukládá se zvlášť mantisa a zvlášť exponent. double cislo1 = ; double cislo2 = E2; double cislo3 = E5; double gravitacnikonstanta = 6.67E-11; Pokud požadujeme zapsat konstantní hodnotu do proměnné typu float, musíme zapsat odpovídající literál s příponou f (jako float): float cislo = 6.13E-2f; Bez přípony se literál chápe jako double Datové typy v plovoucí řádové čárce slouží pro zadání desetinných čísel, extrémně velkých a extrémně malých čísel. Název Typ CTS Popis Počet platných číslic Rozsah (zaokrouhleno) Velik ost
14 float System. Single 32-bitové číslo s jednoduchou (single) přesností s plovoucí řádovou čárkou 7 +(-) až +(-) B double System. Double 64-bitové číslo s dvojitou (double) přesností s plovoucí řádovou čárkou (-) až +(-) B 2.5. Desítkový typ Narozdíl od ostatních typů v plovoucí řádové čárce má desítkový typ (decimal) větší přesnost a menší rozsah, čímž je upřednostňován pro finanční výpočty. Název Typ CTS Popis Počet platných číslic Rozsah (zaokrouhleno) Velik ost decimal System.D ecimal 128-bitové číslo s jednoduchou přesností s plovoucí řádovou čárkou 28 +(-) až +(-) B 2.6. Aritmetické operátory Aritmetické operátory slouží pro provádění operací s aritmetickými datovými typy. Uveďme si jejich výčet: + (plus) - provede součet číselných hodnot operandů vlevo a vpravo a vrátí na dané místo vypočtenou hodnotu - (mínus) - provede rozdíl číselných hodnot operandů vlevo a vpravo a vrátí na dané místo vypočtenou hodnotu * (krát) - provede součin číselných hodnot operandů vlevo a vpravo a vrátí na dané místo vypočtenou hodnotu / (děleno) - provede podíl číselných hodnot operandů vlevo a vpravo a vrátí na dané místo vypočtenou hodnotu % (modulo) - provede zbytek po dělení číselných hodnot operandů vlevo a vpravo a vrátí na dané místo vypočtenou hodnotu Všechny uvedené operátory jsou binární (pracují se dvěma operandy) a zapisují se ve známém infixovém tvaru (mezi operandy). Další skupinou operátorů, která v tomto kontextu stojí za zmínku, jsou operátory inkrementace a dekrementace. Inkrementace znamená zvýšení hodnoty o jedna, dekrementace snížení o jedna: ++ (inkrementace) - zvětší hodnotu proměnné o 1 -- (dekrementace) - zmenší hodnotu proměnné o 1
15 Z této definice je zřejmé, že se jedná o unární operátory - aplikují se na jeden operand, kterým je v tomto případě proměnná aritmetického typu. Vyskytují se však ve dvou verzích: prefixové - nejdříve se změní hodnota proměnné a pak se teprve vyhodnotí (s již změněnou hodnotou) postfixové - nejdříve se proměnná vyhodnotí (s původní hodnotou) a pak se teprve změní její hodnota Velmi často je potřeba změnit hodnotu proměnné, například zvětšit ji o deset anebo dvakrát. Příkaz, kterým zvětšíme hodnotu proměné i o deset, může vypadat následovně: i = i + 10; V tomto příkazu se nejdříve vyhodnotí pravá strana a tento výsledek se potom přiřadí proměnné i. Pro takovou operaci existuje zvláštní přiřazovací operátor +=. Uvedený příkaz lze tedy zapsat také takto: i += 10; Pro přiřazení proměnným aritmetického typu můžeme kromě prostého přiřazení také použít následující operátory: += k proměnné vlevo přičte hodnotu operandu vpravo -= od proměnné vlevo odečte hodnotu operandu vpravo *= proměnnou vlevo vynásobí hodnotou operandu vpravo /= proměnnou vlevo vydělí hodnotou operandu vpravo %= do proměnné vlevo uloží zbytek po dělení původní hodnoty této proměnné hodnotou operandu vpravo 2.7. Booleovský typ Booleovský typ (boolean) slouží pro reprezentaci hodnot ve dvouhodnotové logice. Může tedy nabývat pouze dvě hodnoty: true a false. Tyto hodnoty obvykle reprezentují, zda je určitá podmínka (nebo stav) pravdivá (true) či nepravdivá (false). Název Typ CTS Popis Hodnoty bool System.Bool ean Reprezentuje boolevskou hodnotu true nebo false
16 Výsledkem porovnání dvou čísel může být true nebo false v závislosti na tom, zda čísla vyhovují 2.8. Booleovské operátory Booleovské (logické) operátory slouží pro provádění operací s booleovskými proměnnými a literály (true a false). Uveďme si jejich výčet:! (negace) - provede negaci hodnoty operandu vpravo a vrátí na dané místo vypočtenou hodnotu && (logické a - konjunkce) - provede konjunkci hodnoty operandů vlevo a vpravo a vrátí na dané místo vypočtenou hodnotu (logické nebo - disjunkce) - provede disjunkci hodnot operandů vlevo a vpravo a vrátí na dané místo vypočtenou hodnotu ^ (výlučné nebo) - provede "výlučné nebo" hodnot operandů vlevo a vpravo a vrátí na dané místo vypočtenou hodnotu 2.9. Znakový typ Znakový typ (char) slouží pro ukládání jedné znakové hodnoty, například písmena. Pracuje v kódování Unicode, může tedy obsahovat i znaky národních abeced. Literální (doslovné) hodnoty udáváme v apostrofech, například 'A'. Název Typ CTS Popis Hodnoty char System.C har Reprezentuje 16-bitový znak v Unicode Libovolný znak Unicode Demonstrujme užití typu char na následujícím příkladu: Některé znaky nelze zapsat do apostrofů přímo, jelikož mají zvláštní význam. Například nelze umístit mezi apostrofy apostrof, ačkoliv se jedná o normální znak. Pro zápis takových znaků slouží tzv. řídící sekvence. Každá řídící sekvence je uvedena zpětným lomítkem (\). Uveďme si některé z řídících sekvencí v tabulce: Řídící sekvence \' Znak Apostrof \" Uvozovka \0 Prázdný znak (null) \\ Zpětné lomítko \t Tabulátor \n Znak nového řádku
17 Otázky k probranému učivu Jaký je rozdíl mezi prefixovým a postfixovým operátorem Jaké máme datové typy, vyjmenujte Co znamená inkrementace, dekrementace Shrnutí pojmů kapitoly (podkapitoly) Datový typ proměnné určuje, jak je proměnná uložena v paměti a jaké operace lze s proměnnou provádět. Veškeré datové typy můžeme rozdělit na: 1. Hodnotové typy (value types) 2. Odkazové (referenční) typy (reference types) Datový typ s plovoucí řádouvou čárkou je v paměti realizován odlišně než celočíselné datové typy. Ukládá se zvlášť mantisa a zvlášť exponent. Narozdíl od ostatních typů v plovoucí řádové čárce má desítkový typ (decimal) větší přesnost a menší rozsah, čímž je upřednostňován pro finanční výpočty. Aritmetické operátory slouží pro provádění operací s aritmetickými datovými typy. Booleovský typ (boolean) slouží pro reprezentaci hodnot ve dvouhodnotové logice. Znakový typ (char) slouží pro ukládání jedné znakové hodnoty, například písmena. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team ->
18 C# Samples for Visual Studio > Visual C# Technical Articles -> How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 3 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět definovat vestavěné odkazové typy typu object a string definovat datovou strukturu pole
19 Výklad 3.1. Vestavěné odkazové typy V jazyce C# jsou dva vestavěné odkazové typy. Uveďme si je přehledně v tabulce: Název object string Typ CTS Syste m.object Syste m.string Popis Jedná se o kořenový typ hierarchie typů ve společném typovém systému CTS, což znamená, že od typu object jsou odvozeny ostatní typy. Jedná se o textový řetězec v kódování Unicode. Typ object Typ object (System.Object) tedy reprezentuje kořenový typ. Každý datový typ, ať vestavěný či uživatelsky definovaný, je odvozen právě z datového typu object. Odvozování tříd je základním principem objektově orientovaného programování(oop). Typ object již obsahuje několik základních metod Equals(), GetHashCode(), GetType() a ToString(). Veškeré datové typy mohou tyto metody překrýt (překrývání, overriding) tak, aby s daným datovým typem prováděly konkrétní činnost. Zaměřme se zatím pouze na metodu ToString(). Tato metoda má jediný úkol a to vrátit řetězec, který daný datový typ, popř. proměnnou daného datového typu, reprezentuje. Volat metodu ToString()lze pomocí operátoru přístupu (tečka). ToString() má za úkol vrátit textovou reprezentaci. Typ string Typ string (System.String) slouží pro práci s řetězci. Řetězec představuje určitou posloupnost (pole) znaků, které dohromady vytvoří určitý text, například slovo, větu apod. Konstantní řetězec (řetězcový literál) se zapisuje do uvozovek. System.String Obecné informace o typu System.String Název v stri C#: ng Veřejné konstruktory typu System.String public String (System.Char* value); public String (System.Char* value, int startindex, int length);
20 public String (System.SByte* value); public String (System.SByte* value, int startindex, int length); public String (System.SByte* value, int startindex, int length, System.Text.Encoding enc) ; public String (char[] value, int startindex, int length); public String (char[] value); public String (char c, int count); Veřejné položky typu System.String public static readonly string Empty; Chráněné položky typu System.String Veřejné vlastnosti typu System.String public char Chars get; public int Length get; Veřejné metody typu System.String public static string Join (string separator, string[] value); public static string Join (string separator, string[] value, int startindex, int count); public static bool Equals (string a, string b); public static int Compare (string stra, string strb); public static int Compare (string stra, string strb, bool ignorecase); public static int Compare (string stra, string strb, bool ignorecase, System.Globalization.CultureInfo culture); public static int Compare (string stra, int indexa, string strb, int indexb, int length); public static int Compare (string stra, int indexa, string strb, int indexb, int length, bo ol ignorecase); public static int Compare (string stra, int indexa, string strb, int indexb, int length, bo ol ignorecase, System.Globalization.CultureInfo culture); public static int CompareOrdinal (string stra, string strb); public static int CompareOrdinal (string stra, int indexa, string strb, int indexb, int len gth);
21 public static string Format (string format, object arg0); public static string Format (string format, object arg0, object arg1); public static string Format (string format, object arg0, object arg1, object arg2); public static string Format (string format, object[] args); public static string Format (System.IFormatProvider provider, string format, object[] args ); public static string Copy (string str); public static string Concat (object arg0); public static string Concat (object arg0, object arg1); public static string Concat (object arg0, object arg1, object arg2); public static string Concat (object arg0, object arg1, object arg2, object arg3); public static string Concat (object[] args); public static string Concat (string str0, string str1); public static string Concat (string str0, string str1, string str2); public static string Concat (string str0, string str1, string str2, string str3); public static string Concat (string[] values); public static string Intern (string str); public static string IsInterned (string str); public virtual string ToString (System.IFormatProvider provider); public virtual System.TypeCode GetTypeCode (); public virtual object Clone (); public virtual int CompareTo (object value); public virtual int GetHashCode ();
22 public virtual bool Equals (object obj); public virtual string ToString (); public bool Equals (string value); public void CopyTo (int sourceindex, char[] destination, int destinationindex, int count); public char[] ToCharArray (); public char[] ToCharArray (int startindex, int length); public string[] Split (char[] separator); public string[] Split (char[] separator, int count); public string Substring (int startindex); public string Substring (int startindex, int length); public string Trim (char[] trimchars); public string TrimStart (char[] trimchars); public string TrimEnd (char[] trimchars); public int CompareTo (string strb); public bool EndsWith (string value); public int IndexOf (char value); public int IndexOf (char value, int startindex); public int IndexOf (char value, int startindex, int count); public int IndexOfAny (char[] anyof); public int IndexOfAny (char[] anyof, int startindex); public int IndexOfAny (char[] anyof, int startindex, int count); public int IndexOf (string value); public int IndexOf (string value, int startindex);
23 public int IndexOf (string value, int startindex, int count); public int LastIndexOf (char value); public int LastIndexOf (char value, int startindex); public int LastIndexOf (char value, int startindex, int count); public int LastIndexOfAny (char[] anyof); public int LastIndexOfAny (char[] anyof, int startindex); public int LastIndexOfAny (char[] anyof, int startindex, int count); public int LastIndexOf (string value); public int LastIndexOf (string value, int startindex); public int LastIndexOf (string value, int startindex, int count); public string PadLeft (int totalwidth); public string PadLeft (int totalwidth, char paddingchar); public string PadRight (int totalwidth); public string PadRight (int totalwidth, char paddingchar); public bool StartsWith (string value); public string ToLower (); public string ToLower (System.Globalization.CultureInfo culture); public string ToUpper (); public string ToUpper (System.Globalization.CultureInfo culture); public string Trim (); public string Insert (int startindex, string value); public string Replace (char oldchar, char newchar); public string Replace (string oldvalue, string newvalue);
24 public string Remove (int startindex, int count); public System.CharEnumerator GetEnumerator (); public System.Type GetType (); Chráněné metody typu System.String protected virtual void Finalize (); protected object MemberwiseClone (); 3.2. Pole Pole je datová struktura, která obsahuje více položek stejného datového typu za sebou. K jednotlivým položkám se přistupuje pomocí indexu, což je celočíselná proměnná. První prvek pole má index 0, poslední pak n-1 v případě, že má pole n prvků. Pomocí indexu se s polem jednoduše pracuje, můžeme získat prvek pole s daným indexem, jednoduchý je také průchod pole prvek po prvku - zvyšujeme index a přistupujeme k prvkům. Deklarace pole vypadá následovně: datovýtyp[] názevpole; Pokud však požadujeme vytvořit i paměť pro prvky pole, nejen referenci (odkaz), musíme postupovat následovně: datovýtyp[] názevpole = new datovýtyp[početprvků]; K prvku s indexem i přistoupíme následovně: názevpole[i]; Přístup k prvkům pole demonstrujeme na následujícím obrázku: Pole v C# je odvozené od třídy System.Array. Třída System.Array obsahuje užitečné vlastnosti a metody pro práci s polem. Některé z nich demonstrujeme v následujícím programu: Je zřejmé, že můžeme pole inicializovat, vytvořit za běhu, zjistit počet prvků. Nemůžeme však za běhu měnit počet prvků. V tom případě bychom museli použít řídu System.Collections.ArrayList.
25 Pole můžeme také vytvořit vícerozměrné. O všech takových pokročilých postupech při práci s poli se zmíníme na různých místech při vytváření konkrétních aplikací. Otázky k probranému učivu Co je to pole Jak se definnuje pole K čemu slouží typ string K čemu slouží typ object Shrnutí pojmů kapitoly (podkapitoly) V jazyce C# jsou dva vestavěné odkazové typy. Typ object (System.Object) tedy reprezentuje kořenový typ. Typ string (System.String) slouží pro práci s řetězci. Pole je datová struktura, která obsahuje více položek stejného datového typu za sebou. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku ->
26 Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 4 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět Se seznámí s příkazy pro větvení programu Příkaz if Příkaz if/else Příkaz if/else if/else Příkaz switch Se seznámí s příkazy pro cyklus Příkaz for Příkaz while Příkaz do while Příkaz break Příkaz continue Výklad
27 4.1. Řízení toku programu Příkaz, který umožňuje rozvětvit tok programu podle určité podmínky, se nazývá příkaz větvení, resp. podmíněný příkaz. V mnoha případech se některé příkazy provádějí opakovaně. Například vyhledávání údaje v nějaké množině dat může probíhat tak, že je každý záznam porovnáván s vyhledávaným údajem. Jedná se o opakování stejného příkazu nebo několika příkazů. Takovým opakovaným příkazem je cyklus Příkazy větvení V případě, že požadujeme určitý blok příkazů provést pouze v případě, že je pravdivá určitá podmínka, použijeme příkaz if. Pokud se má program dle podmínky rozvětvit na dvě větve, přičemž první se provede, pokud je podmínka pravdivá, druhá větev se provede, pokud je podmínka nepravdivá, napíšeme příkaz if/else. V případě dělení do více větví dle podmínek zvolíme příkaz if/else if/else. A jestliže potřebujeme program rozvětvit podle hodnoty určité proměnné, je vhodné napsat příkaz switch. Příkaz if V případě, že požadujeme provést příkaz či blok příkazů jen tehdy, pokud je splněna daná podmínka, použijeme příkaz if. Příkaz if má následující syntaxi, které odpovídá diagram uvedený níže. if (podmínka) blok příkazů V případě, že je podmíněný příkaz jediný, nemusíme psát složené závorky. Zde je slíbený diagram činností (vývojový diagram, activity diagram):
28 Obr. Diagram činností příkazu if Podmínku musí tvořit logický výraz, tj takový, který se vyhodnotí jako true nebo false. Uveďme si všechny relační operátory: < menší - vyhodnotí se jako true právě tehdy, když levý operand je menší než pravý, > větší - vyhodnotí se jako true právě tehdy, když levý operand je větší než pravý, <= menší nebo rovno - vyhodnotí se jako true právě tehdy, když levý operand je menší nebo roven pravému, >= větší nebo rovno - vyhodnotí se jako true právě tehdy, když levý operand je větší nebo roven pravému, == rovno - vyhodnotí se jako true právě tehdy, když levý operand je roven pravému,!= nerovno - vyhodnotí se jako true právě tehdy, když levý operand není roven pravému. Pro podmínku, která se vyhodnotí jako true nebo false pak používáme relační výrazy, které tvoříme právě pomocí uvedených relačních operátorů. Relační výrazy můžeme také spojovat pomocí logických spojek (logických operátorů):! logická negace - vyhodnotí se jako true právě tehdy, když daný výrok (pravý operand) má hodnotu false, && logické a (logický součin) - vyhodnotí se jako true právě tehdy, když levý i pravý operand je současně true,
29 logické nebo (logický součet) - vyhodnotí se jako true právě tehdy, když oba operandy nejsou současně false. Příkaz if/else V některých situacích je také potřeba zdůraznit, které příkazy se mají provést v případě, že podmínka není splněna. Tehdy přidáváme sekci else (jinak). Syntaxe je pak následující: if (podmínka) blok příkazů, které se mají provést v případě vyhodnocení podmínky jako true else blok příkazů, které se mají provést v případě vyhodnocení podmínky jako false Uveďme si odpovídající vývojový diagram (diagram činností): Obr. Diagram činností příkazu if/else Příkaz if/else if/else V některých případech je potřeba program rozvětvit do více než dvou větví podle disjunktních podmínek. Tehdy druhá větev nebude případ else (jinak), místo toho bude
30 následovat další podmínka, příp. další podmínky, až poslední větev bude uvozena pomocí else: if (podmínka1) blok příkazů, které se mají provést v případě vyhodnocení podmínky1 jako true else if (podmínka2) blok příkazů, které se mají provést v případě vyhodnocení podmínky2 jako true... else if (podmínkan) blok příkazů, které se mají provést v případě vyhodnocení podmínkyn jako true else blok příkazů, které se mají provést v případě vyhodnocení všech podmínek jako false Kvadratická rovnice ax 2 + bx + c = 0 má v reálném oboru tři případy řešení, což zjistíme výpočtem diskriminantu D = b 2-4ac. Pak platí: Pokud je diskriminant kladný, rovnice má 2 kořeny. Pokud je diskriminant nulový, rovnice má 1 kořen. Pokud je diskriminant záporný, rovnice má 0 kořenů. Vytvořme program, který vyzve uživatele k zadání koeficientů kvadratické rovnice a, b, c a následně vypíše počet řešení této kvadratické rovnice: using System; namespace PrikazIfElseifElse.Priklad1 class VstupniBodProgramu static void Main() // Vyzveme uživatele k zadání koeficientů // a zadané hodnoty uložíme do proměnných: Console.Write("Zadejte a: "); double a = double.parse(console.readline());
31 Console.Write("Zadejte b: "); double b = double.parse(console.readline()); Console.Write("Zadejte c: "); double c = double.parse(console.readline()); // Spočítáme diskriminant: double D = b * b - 4 * a * c; // Můžeme si vytvořit proměnnou // pro počet řešení: double pocetreseni; // Nyní větvíme podle hodnoty D: if (D > 0) // Tento blok kódu se provede, // pokud je D > 0. pocetreseni = 2; else if (D == 0) // Tento blok kódu se provede, // pokud je D = 0. pocetreseni = 1; else // Tento blok kódu se provede // v ostatních případech, tzn. pokud je D < 0. pocetreseni = 0; // A vypíšeme závěr: Console.WriteLine("Daná kvadratická rovnice má 0 řešení.", pocetreseni); Console.ReadLine(); Příkaz switch Příkaz switch umožňuje provést jednu z větví na základě vyhodnocení testovaného výrazu. Syntaxe příkazu switch ja následující: switch (testovaný výraz) case hodnota1:
32 // Tento blok příkazů se provede, // jestliže testovaný výraz nabývá hodnotu hodnota1. break; case hodnota2: // Tento blok příkazů se provede, // jestliže testovaný výraz nabývá hodnotu hodnota2. break; //... default: // Tento blok příkazů se provede, // jestliže testovaný výraz nenabývá žádnou z předchozích hodnot. // Lze použít jako případ else. break; Příkazy větvení lze vnořovat do sebe. Představme si příklad, ve kterém uživatel-student zadá známku z předmětu a pohlaví a program, na základě těchto informací, vypíše formátované hlášení. Jednak se větví podle známky a pro každou známku pak podle pohlaví. Program může vypadat následovně: using System; Příkaz switch se také velmi často používá v souvislosti s výčtovými typy. O výčtových typech se zmíníme později Cykly Tato kapitola obsahuje následující podkapitoly: Cyklus for provádí blok příkazů provádět opakovaně v závislosti na hodnotě proměnné zvané čítač. Cyklus while provádí příkazy opakovaně, přičemž podmínka pro další opakování je umístěna na začátku smyčky. Cyklus do while provádí blok příkazů opakovaně, přičemž řídící podmínka pro další opakování je umístěna na konci smyčky. Příkaz break
33 umožní násilné ukončení opakování bloku příkazů v cyklu. Příkaz continue násilně ukončí aktuální iteraci cyklu a pokračovat následující iterací. Znázorněme si vývojový diagram (diagram činností) příkazu for: Obr. Diagram činností příkazu for Příkaz začne tzv. inicializací (inicializací čítače). Čítač (counter) je řídící proměnná cyklu - pro každou iteraci cyklu má určitou hodnotu a většinou řídí, kdy se cyklus ukončí. Pak přijde na řadu vlastní cyklus - opakování následujících úkonů: Vyhodnocení podmínky (testovací část) - podmínka většinou obsahuje hodnotu čítače; pokud se vyhodnotí jako true, následuje provedení těla cyklu, v opačném případě se příkaz ukončí a program pokračuje prvním příkazem za příkazem for. Provedení těla cyklu - tělo cyklu je blok příkazů; provedou se pouze tehdy, pokud je podmínka vyhodnocena jako true. Aktualizace (zpravidla aktualizace čítače) - hodnotu čítače nebo jiného identifikátoru stavu, na kterém je závislé vyhodnocení podmínky, je potřeba změnit, neboť v opačném případě by se podmínka stále vyhodnocovala stejně - v případě hodnoty true bychom dostali nekonečný cyklus. Následuje syntaxe příkazu for: for ( inicializace ; podmínka ; aktualizace )
34 tělo Cyklus while Cyklus while nemá definován čítač, jako příkaz for. Závisí pouze na podmínce, která se vyhodnotí vždy na začátku iterace, což můžeme demonstrovat na vývojovém diagramu (diagramu činností) příkazu while: Syntaxe příkazu while vypadá následovně: while (podmínka) tělo cyklu Obr. Diagram činností příkazu while Cyklus do while Cyklus do while je podobný cyklu while, podmínka se však v tomto případě vyhodnotí vždy na konci iterace, což můžeme demonstrovat na vývojovém diagramu (diagramu činností) příkazu do while:
35 Obr. Diagram činností příkazu do while Syntaxe příkazu do while vypadá následovně: do tělo while (podmínka); cyklu i Příkaz break Pomocí příkazu break lze násilně ukončit cyklus z jeho těla. Jakmile se v těle cyklu vyhodnotí příkaz break, cyklus se ukončuje a pokračuje se následujícím příkazem za cyklem. Obvyklé užití příkazu break můžeme opět demonstrovat na vývojovém diagramu (diagramu činností):
36 Obr. Diagram činností příkazu break V těle cyklu je podmínka, která řídí, zda se má cyklus ukončit nebo ne. Pokud je pravdivá skočí se do těla podmínky, kde je příkaz break. Tento příkaz pak ukončí cyklus. Příkaz continue Pomocí příkazu continue lze přeskočit zbytek provádění těla aktuální iterace cyklu a pokračovat následující iterací. Obvyklé užití příkazu continue můžeme taktéž demonstrovat na vývojovém diagramu (diagramu činností):
37 Obr. Diagram činností příkazu continue Otázky k probranému učivu Popište funkci příkazu if Popište funkci příkazu if/else Popište funkci příkazu for Popište funkci příkazu while Popište funkci příkazu break Shrnutí pojmů kapitoly (podkapitoly) Příkaz If V případě, že požadujeme provést příkaz či blok příkazů jen tehdy, pokud je splněna daná podmínka, použijeme příkaz if. Příkaz if/else V některých situacích je také potřeba zdůraznit, které příkazy se mají provést v případě, že podmínka není splněna. Tehdy přidáváme sekci else (jinak). Příkaz switch Příkaz switch umožňuje provést jednu z větví na základě vyhodnocení testovaného výrazu. Cyklus for provádí blok příkazů provádět opakovaně v závislosti na hodnotě proměnné zvané čítač.
38 Cyklus while provádí příkazy opakovaně, přičemž podmínka pro další opakování je umístěna na začátku smyčky. Cyklus do while provádí blok příkazů opakovaně, přičemž řídící podmínka pro další opakování je umístěna na konci smyčky. Příkaz break umožní násilné ukončení opakování bloku příkazů v cyklu. Příkaz continue násilně ukončí aktuální iteraci cyklu a pokračovat následující iterací. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, 2006.
39 Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 5 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět definovat vstupy a výstupy v konzolové aplikaci pracovat s rekurzi Výklad 5.1. Třídy Jelikož je jazyk C# objektově orientovaný, zavedení pojmu třída se nevyhneme. Více o tomto pojmu se dozvíte v kapitole objektově orientované programování, zde si uvedeme pouze základy. Třída je datový typ, pomocí něhož lze vytvářet objekty. Říkáme, že objekt je instancí třídy. Objekt je základním prvkem objektově orientovaného programu. Objektově orientovaný program se skládá z objektů, které mezi sebou komunikují a přitom každý z těchto objektů plní určitou funkci. Například jeden objekt načítá data z databáze, druhý tato data zobrazí uživateli. Objektově orientovaný program se vlastně chová jako komunikující reálné objekty Vstup a výstup v konzolové aplikaci Již víme, že stěžejní třída pro vstup a výstup v konzolové aplikaci v jazyce C# je třída System.Console. Pro čtení vstupu z příkazového řádku slouží metody:
40 Console.Read() Console.ReadLine() Metoda Console.Read() přečte další znak ze vstupního proudu a vrátí jeho kód (typu int). Pokud již žádný znak není, vrátí tato metoda hodnotu -1. Pro výstup na příkazový řádek slouží následující dvě metody: Console.Write() Console.WriteLine() Obě metody jsou mnohonásobně přetížené a vypíšou jakýkoliv datový typ, navíc mohou použít konverzi daného typu na řetězec pomocí metody ToString() definované pro daný datový typ. Liší se v tom, že metoda Console.WriteLine() navíc přidá nakonec znak nového řádku (new line) a znak pro návrat vozíku (carriage return). Metody Console.Write() i Console.WriteLine() nabízejí také formátovaný výstup, přičemž prvním parametrem je řetězec obsahující indexy ve složených závorkách, další parametry tvoří hodnoty, které nahradí indexy ve formátovacím řetězci. První index je 0 - odpovídá prvnímu argumentu za formátovacím řetězcem. Opět následuje krátká ukázka: Ve složené závorce lze čárkou oddělit dvě celočíselné hodnoty. První je zmíněný index, druhou je šířka. Vyzkoušejte zarovnání výstupu v následujícím programu: Navíc je možné použít speciální formátovací řetězce pro znak měny (C), desetinný formát (D), vědecký formát (E), formát s pevnou deseti desetinnou čárkou (F), číselný formát (N), procentuální formát (P), hexadecimální formát (H) a další, což opět ukážeme na krátkém programu: 5.3. Rekurze Rekurze znamená, že funkce volá sebe samu. Takovou funkci pak nazýváme rekurzivní. Rekurze je elegantní metoda řešení problému ve smyslu rozděl a panuj (divide and conquer). Nejdříve zavolaná rekurzivní funkce rozdělí problém na zpravidla více stejných problémů nižšího řádu. Zavolá se tedy stejná funkce pro problém nižšího řádu až se dojde k funkci, která již má triviální řešení (dané podmínkou pro úroveň řádu). Rekurze může být: přímá - funkce volá přímo samu sebe ve svém těle nepřímá - funkce volá další funkci, ta může volat další atd., až některá z tohoto řetězce funkcí zavolá opět původní funkci Podle počtu volání sebe sama ve svém těle dělíme rekurzi na: lineární - funkce volá sebe samu právě jednou (přímo či nepřímo) větvená (stromová) - funkce volá sebe samu vícekrát (přímo či nepřímo)
41 Lineární rekurze vytváří řetěz volání, který je lineární (odtud název). Jako příklad uveďme funkci pro výpočet faktoriálu. Z matematiky víme, že faktoriál čísla n (pro n > 0) se spočítá jako n krát faktoriál čísla n-1, neboli: n! = n(n-1)! Avšak faktoriál čísla n-1 se spočítá (pro n > 1) jako n-1 krát faktoriál čísla n-2, tj.: (n-1)! = (n-1)(n-2)! Tak bychom mohli pokračovat, až bychom dostali faktoriál čísla 1 (nebo 0), který je již definován jako 1. Přepišme tuto úvahu do definice pro funkci Faktorial: Matematika.cs using System; namespace MatematickeTridy public class Matematika // Metoda pro výpočet faktorialu pomocí rekurze: public static double Faktorial(int n) // Pokud je n již 1 nebo 0, vrátí se přímo výsledek 1, // jinak se vypočítá n * faktorial čísla o 1 menšího: if (n <= 1) return 1; else return n * Faktorial(n - 1); A pojďme tuto metodu otestovat, zda přece jen nebude fungovat tak, jak si slibujeme: VstupniBodProgramu.cs using System; using MatematickeTridy; namespace Rekurze.Priklad1 class VstupniBodProgramu static void Main() // Vyžádáme nezáporné číslo: Console.Write("Zadejte nezáporné celé číslo pro výpočet faktorialu: "); int n; try
42 n = int.parse(console.readline()); catch Console.WriteLine("Nesprávné zadání. Ukončení aplikace."); return; if (n < 0) Console.WriteLine("Bylo zadáno záporné číslo. " + "Bude se počítat s jeho absolutní hodnotou"); n = -n; Console.WriteLine("0! = 1", n, Matematika.Faktorial(n)); Console.ReadLine(); Stromová rekurze Stromová rekurze je hojně využívána ve smyslu rozděl a panuj. Jako příklad si představme takovou situaci. Na začátku máme čerstvě narozený 1 pár (= samec + samice) králíků. Každý pár po narození měsíc čeká (do plnoletosti, tzn. musí projít pubertou apod.) a pak má každý měsíc jeden pár (= samec + samice). Otázkou je, kolik párů budeme mít v n- tém měsíci? Nakresleme si odpovídající strom (stávající pár jde do levého potomka, pravý potomek je tvořen nově narozeným párem). V uzlech budeme označovat pořadové číslo páru: Dostaneme strom, ze kterého můžeme shrnout závěry do následující tabulky:
43 n (měsíc) Počet párů Vidíme, že každý další měsíc (generaci) je dán počet párů součtem počtu párů, který byl minulý měsíc, a počtu párů, které byly před dvěma měsíci, neboť to je počet nově narozených párů (páry po jednom měsíci již mohou rodit). Dostáváme tzv.fibonacciho posloupnost: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,... Naším úkolem bude napsat funkci, která vrátí počet párů v n-té generaci. Nabízí se opět rekurzivní řešení. Přidejme tuto rekurzivní metodu do třídy Matematika, která bude nyní vypadat následovně: Matematika.cs using System; namespace MatematickeTridy public class Matematika // Metoda pro výpočet členu Fibonacciho // posloupnosti pomocí rekurze: public static double Fibonacci(int n) if (n <= 2) return 1; else return Fibonacci(n-2) + Fibonacci(n-1); // Metoda pro výpočet faktorialu pomocí rekurze: public static double Faktorial(int n) // Pokud je n již 1 nebo 0, vrátí se přímo výsledek 1, // jinak se vypočítá n * faktorial čísla o 1 menšího: if (n <= 1) return 1; else return n * Faktorial(n - 1); A hned tuto metodu otestujme:
44 VstupniBodProgramu.cs using System; using MatematickeTridy; namespace Rekurze.Priklad4 class VstupniBodProgramu static void Main() // Vyžádáme nezáporné číslo: Console.Write("Zadejte nezáporné celé číslo " + "pro výpočet členu Fibonacciho posloupnosti: "); int n; try n = int.parse(console.readline()); catch Console.WriteLine("Nesprávné zadání. Ukončení aplikace."); return; if (n < 0) Console.WriteLine("Bylo zadáno záporné číslo. " + "Bude se počítat s jeho absolutní hodnotou"); n = -n; try Console.WriteLine("Fibonacci(0) = 1", n, Matematika.Fibonacci(n)); catch (StackOverflowException e) Console.WriteLine(e.Message); Console.ReadLine(); Výstup tohoto programu po zadání pořadového čísla 5 vypadá následovně: Zadejte nezáporné celé číslo pro výpočet členu Fibonacciho posloupnosti: 7
45 Fibonacci(7) = 13 Rekurze je efektní ne však nutně efektivní technika. Toto tvrzení obzvláště platí u stromové rekurze, což můžeme demonstrovat na následujícím příkladě. Počítejme členy Fibonacciho posloupnosti pomocí předcházející rekurzivní metody cyklicky od prvního členu až po pořadové číslo zadané uživatelem: VstupniBodProgramu.cs using System; using MatematickeTridy; namespace Rekurze.Priklad5 class VstupniBodProgramu static void Main() // Vyžádáme nezáporné číslo: Console.Write("Zadejte nezáporné celé číslo: "); int n; try n = int.parse(console.readline()); catch Console.WriteLine("Nesprávné zadání. Ukončení aplikace."); return; if (n < 0) Console.WriteLine("Bylo zadáno záporné číslo. " + "Bude se počítat s jeho absolutní hodnotou"); n = -n; try for (int i = 1; i <= n; i++) Console.WriteLine("Fibonacci(0) = 1", i, Matematika.Fibonacci(i)); catch (StackOverflowException e) Console.WriteLine(e.Message); Console.ReadLine();
46 Po spuštění programu zadejte číslo 100 a čekejte, co se bude dít. Od určitého pořadového čísla se dokončení výpočtu členu vůbec nedočkáme (výpočet každého dalšího členu je pomalejší a pomalejší): Fibonacci(17) = 1597 Fibonacci(18) = 2584 Fibonacci(19) = 4181 Fibonacci(20) = 6765 Fibonacci(21) = Fibonacci(22) = Fibonacci(23) = Fibonacci(24) = Fibonacci(25) = Fibonacci(26) = Fibonacci(27) = Fibonacci(28) = Fibonacci(29) = Fibonacci(30) = Fibonacci(31) = Fibonacci(32) = Fibonacci(33) =
47 Fibonacci(34) = Fibonacci(35) = Fibonacci(36) = Fibonacci(37) = Fibonacci(38) = Fibonacci(39) = Fibonacci(40) = _ Napišme program, ve kterém budeme při volání rekurzivní funkce Fibonacci počítat, kolikrát se volaly tyto funkce pro určité n. Definujme speciální testovací třídu: TestRekurzeVeFibonacci.cs using System; namespace MatematickeTridy public class TestRekurzeVeFibonacci // Deklarujeme pole pro uchování jednotlivých volání: public int[] polepoctuvolani; // číslo pro výpočet členu Fibonacciho posloupnosti: private int n; // Konstrukce: public TestRekurzeVeFibonacci(int n) this.n = n; polepoctuvolani = new int[n]; // Metoda, která vypíše informace o počtu volání // jednotlivých funkcí Fibonacci: public void VypisInfoOVolanich() Console.WriteLine("Volám Fibonacci(0):", n); Fibonacci(n); for (int i=0; i<n; i++)
48 Console.WriteLine("Fibonaccii(0) voláno: 1 krát. ", i + 1, polepoctuvolani[i]); // Rekurzivní metoda Pro výpočet členu // Fibonacciho posloupnosti: public double Fibonacci(int n) polepoctuvolani[n-1]++; if (n <= 2) return 1; else return Fibonacci(n-2) + Fibonacci(n-1); V klientském kódu spusťme vlastní testování: VstupniBodProgramu.cs using System; using MatematickeTridy; namespace Rekurze.Priklad5 class VstupniBodProgramu static void Main() // Vyžádáme nezáporné číslo: Console.Write("Zadejte nezáporné celé číslo: "); int n; try n = int.parse(console.readline()); catch Console.WriteLine("Nesprávné zadání. Ukončení aplikace."); return; if (n < 0) Console.WriteLine("Bylo zadáno záporné číslo. " + "Bude se počítat s jeho absolutní hodnotou"); n = -n; // Vytvoříme testovací objekt:
49 TestRekurzeVeFibonacci test = new TestRekurzeVeFibonacci(n); try // Vypíšeme informace o počtu volání: test.vypisinfoovolanich(); catch (StackOverflowException e) Console.WriteLine(e.Message); Console.ReadLine(); Nyní zkusme zadat číslo 20 a objeví se následující: Zadejte nezáporné celé číslo: 20 Volám Fibonacci(20): Fibonaccii(1) voláno: 2584 krát. Fibonaccii(2) voláno: 4181 krát. Fibonaccii(3) voláno: 2584 krát. Fibonaccii(4) voláno: 1597 krát. Fibonaccii(5) voláno: 987 krát. Fibonaccii(6) voláno: 610 krát. Fibonaccii(7) voláno: 377 krát. Fibonaccii(8) voláno: 233 krát. Fibonaccii(9) voláno: 144 krát. Fibonaccii(10) voláno: 89 krát. Fibonaccii(11) voláno: 55 krát.
50 Fibonaccii(12) voláno: 34 krát. Fibonaccii(13) voláno: 21 krát. Fibonaccii(14) voláno: 13 krát. Fibonaccii(15) voláno: 8 krát. Fibonaccii(16) voláno: 5 krát. Fibonaccii(17) voláno: 3 krát. Fibonaccii(18) voláno: 2 krát. Fibonaccii(19) voláno: 1 krát. Fibonaccii(20) voláno: 1 krát. Tomu se samozřejmě nebudeme divit, pokud si vykreslíme strom volání těchto rekurzivních funkcí (pro jednoduchost pouze pro n = 5). V kořeni stromu je 5, Fibonacci(5) volá Fibonacci(4) a Fibonacci(3) atd. V uzlech jsou uvedena n příslušného volání, červeně jsou naznačeny návratové hodnoty: Takový strom se nazývá Fibonacciho strom. Všimněte si, že se skládá z kořene a Fibonacciho stromu řadu n-1, který tvoří levého potomka a Fibonacciho stromu řádu n-2, který tvoří pravého potomka. Ověřme tento strom testovacím programem: Zadejte nezáporné celé číslo: 5 Volám Fibonacci(5):
51 Fibonaccii(1) voláno: 2 krát. Fibonaccii(2) voláno: 3 krát. Fibonaccii(3) voláno: 2 krát. Fibonaccii(4) voláno: 1 krát. Fibonaccii(5) voláno: 1 krát. Zmíněný problém v efektivitě rekurze se často odstraňuje pomocí cyklu nebo pomocí dynamického programování, ve kterém si již spočítané hodnoty nižších řádů ukládáme, aby se nemusely počítat znovu. S těmito postupy se setkáte při vypracování úkolů. Otázky k probranému učivu Coje to třída Jaká metoda se využívá pro čtení vstupu z příkazové řádky Co znamená Shrnutí pojmů kapitoly (podkapitoly) Třída je datový typ, pomocí něhož lze vytvářet objekty. Říkáme, že objekt je instancí třídy. Rekurze znamená, že funkce volá sebe samu. Takovou funkci pak nazýváme rekurzivní. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team ->
52 us/vcsharp/aa aspx. C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 6 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět Pracovat s vyjímkami, vyvolání, zachycení, ošetření vyjímek
53 Výklad 6.1. Výjímky Dříve či později zjistíte, že přes všechny vaše snahy o zabránění chybám za běhu nastane vámi neočekávaná situace, která pád programu způsobí. Může jít o přístup k souboru, který neexistuje, přístup k prvku pole mimo jeho rozsah, nebo může být požadováno více paměti, než je možné uvolnit apod. Zachycení (ošetření) výjimek (Exception handling) představuje mocný mechanismus pro zotavení programu z (většinou) neočekávaných stavů. V okamžiku vzniku výjimečného stavu je vyvolána výjimka pomocí objektu, který obsahuje informace o daném stavu, aby bylo možné nalézt zdrojový problém. Tento objekt je instancí třídy výjimky, která může být uživatelsky definovaná, nebo lze využít třídy výjimek z knihovny tříd rámce.net (FCL - Framework Class Library, někdy se také užívá zkratka BCL - Base Class Library). Uveďme aspoň některé z těchto základních tříd výjimek v následujícím diagramu tříd (tříd výjimek existuje mnohem více, než uvádí tento diagram):
54 Věnujme se jednotlivým třídám podrobněji: System.Exception - nejobecnější třída výjimky odvozená od třídy System.Object. Tato třída neobsahuje specifické informace o konkrétní výjimečné události, proto bychom měli pro vyvolání výjimky použít konkrétní, k dané výjimce určenou, třídu výjimky. System.SystemException - třída výjimek, které vyvolává rámec.net. Od této třídy je odvozeno mnoho tříd výjimek. System.ApplicationException - bázová třída pro uživatelsky definované třídy výjimky. Pokud v aplikaci nastává specifický výjimečný stav, který nepokrývá množina tříd výjimek z platformy.net, odvoďte třídu výjimky právě odsystem.applicationexception. System.ArgumentException - odpovídající výjimka je vyvolána, jestliže alespoň jeden argument metody neodpovídá předepsaným specifikacím.
55 System.ArgumentNullException - odpovídající výjimka je vyvolána, jestliže alespoň jeden argument metody představuje nulovou referenci, přičemž by měl obsahovat referenci na existující instanci. System.ArgumentOutOfRangeException - odpovídající výjimka je vyvolána, jestliže alespoň jeden argument metody nemá platnou (dovolenou) hodnotu. System.UnauthorizedAccessException - odpovídající výjimka je vyvolána, jestliže operační systém zabrání přístupu k určitému prostředku z pohledu bezpečnosti. System.StackOverflowException - odpovídající výjimka je vyvolána, jestliže dojde k přetečení zásobníku, například z důvodu neukončené rekurze. System.ArithmeticException - odpovídající výjimka je vyvolána, jestliže dojde k neplatné aritmetické operaci či přetypování. System.DevideByZeroException - odpovídající výjimka je vyvolána, jestliže dojde k dělení nulou. System.OverflowException - odpovídající výjimka je vyvolána, jestliže dojde k přetečení aritmetického typu v kontextu checked. System.IO.IOException - odpovídající výjimka je vyvolána, jestliže dojde k chybě typu I/O, například přístupu k adresáři, souboru apod. System.IO.FileNotFoundException - odpovídající výjimka je vyvolána, jestliže dojde k chybě při přístupu k souboru, který neexistuje. System.IO.DirectoryNotFoundException - odpovídající výjimka je vyvolána, jestliže nemůže být nalezena část souboru nebo adresáře. System.IO.FileLoadException - odpovídající výjimka je vyvolána, jestliže je sestavení nalezeno, ale nemůže být nataženo. System.IO.EndOfStreamException - odpovídající výjimka je vyvolána, jestliže je čteno za koncem souboru. Zachycení výjimky Na úvod vyzkoušejme následující příklad. Uživatel má zadat celé číslo. Tento vstup je reprezentován jako řetězec. Struktura System.Int32 má metodu Parse, která má za úkol převést řetězec na celé číslo daného typu. V případě, že uživatel zadal číselnou hodnotu, se daný převod zdaří, jinak vyvolá zmíněná metoda výjimku, kterou je potřeba zachytit. Pro takovou konverzi lze také použít třídu Convert, která má statické metody pro převod na různé datové typy. V tomto příkladu v cyklu požadujeme, aby uživatel zadal celé číslo, případně Enter, čímž program ukončí. Kód, ve kterém může být vyvolána výjimka, umístíme do bloku try. Pokud je vyvolána výjimka, pokračuje program blokem catch. Na konci lze umístit blok finally, který je vykonán buď po bloku try, pokud nedošlo k žádné výjimce, nebo po
56 bloku catch, jestliže výjimka byly vyvolána. Blok finally často slouží k úklidu použitých prostředků, jako například k uzavření databázového připojení. Následuje daný program: using System; namespace Vyjimky.Priklad1 class VstupniBodProgramu static void Main() // Pro zachycení uživatelského vstupu: string vstup; while (true) try Console.Write("Zadejte celé číslo (nebo Enter pro ukončení): "); vstup = Console.ReadLine(); // Enterem cyklus ukončíme: if (vstup == String.Empty) break; // V bloku try zkusíme převést zadaný řetězec // na celé číslo - metoda Parse může vyvolat výjimku // v případě, že řetězec nebude možné zkonvertovat: int cislo = int.parse(vstup); // Alternativně můžeme konvertovat takto: // int cislo = Convert.ToInt32(vstup); Console.WriteLine("Zadali jste 0.", cislo); catch (Exception e) // V bloku catch zachytíme výjimku a vypíšeme // text výjimky: Console.WriteLine("Zachycená výjimka: 0", e.message); finally // Kód finally se provede buď přímo po bloku try, pokud // nedošlo k vyvolání výjimky, nebo po bloku catch v případě, // že výjimka byla vyvolána. V tomto bloku může dojít // k závěrečnému úklidu, příp. k příkazům, které by byly na konci // bloku try i catch. Blok finally lze vynechat. Console.WriteLine("Děkujeme za zadání.");
57 Console.WriteLine("Ukončení aplikace."); Console.ReadLine(); Syntakticky vypadá postup při zachycení výjimek následovně: try // Kód programu podezřelý na vyvolání výjimky. catch // Zachycení (ošetření) výjimky. finally // Úklid prostředků. Proces zachycení výjimky může vypadat takto:
58 V uvedeném programu jsme v bloku catch uvedli jako parametr typ výjimky, v tomto případě System.Exception. Instance výjimky nese významné informace o výjimce, v programu jsme vypsali jednu z nich - Message výjimky, což je hlášení popisující výjimku. V případě, že nezadáte číselnou hodnotu, zpráva odpovídající výjimky v českém jazyce zní: Vstupní řetězec nemá správný formát. Bloků catch může být více pro ošetření více výjimek různých typů. V následujícím příkladu má uživatel nejen zadat celé číslo, ale pomocí tohoto čísla se má přístupovat k prvku pole pomocí indexu. Samozřejmě v takovém případě je možné ošetřit přístup k poli přímo pomocí podmíněného příkazu, ukažme si však řešení zachycením výjimky. v případě, že je bloků catch více, provede se vždy první z nich, který odpovídá typu vyvolané výjimky. Umistěme tedy do prvního bloku ošetření výjimky typuindexoutofrangeexception a následně obecného typu výjimky: using System; namespace Vyjimky.Priklad2 class VstupniBodProgramu static void Main() // Pole, ke kterému chceme přistupovat: int[] poctydnuvmesici = 31,28,31,30,31,30,31,31,30,31,30,31; // Pro zachycení uživatelského vstupu: string vstup; while (true) try Console.Write("Zadejte číslo měsíce (1 až 12) nebo Enter pro ukončení: "); vstup = Console.ReadLine(); // Enterem cyklus ukončíme: if (vstup == String.Empty) break; // V bloku try zkusíme převést zadaný řetězec // na celé číslo - metoda Parse může vyvolat výjimku // v případě, že řetězec nebude možné zkonvertovat: int mesic = int.parse(vstup); // Zde přistupujeme k prvkům pole - je možné, že index // bude mimo rozsah pole, pak by byla vyvolána výjimka // IndexOutOfRangeException: Console.WriteLine("Počet dní v daném měsíci nepřestupného roku je 0.",
59 poctydnuvmesici[mesic - 1]); catch (IndexOutOfRangeException e) // V tomto bloku zachytíme výjimku typu // IndexOutOfRangeException - vypíšeme // text výjimky: Console.WriteLine("Zachycená výjimka typu IndexOutOfRangeException: 0", e.message); catch (Exception e) // V tomto bloku zachytíme výjimku typu // Exception - vypíšeme // text výjimky: Console.WriteLine("Zachycená výjimka typu Exception: 0", e.message); catch // Zde zachytíme jiný typ výjimky: Console.WriteLine("Zachycená výjimka."); finally Console.WriteLine("Děkujeme za zadání."); Console.WriteLine("Ukončení aplikace."); Console.ReadLine(); Vyvolání výjimky Z předchozích kapitol umíme zachytit výjimku. Nyní se však můžeme zeptat, kde se vůbec výjimka vyvolala a jakým způsobem. Zkusme tedy vyvolat výjimku ve vlastním kódu a dospějeme k odpovědi. Výjimka se vyvolá pomocí klíčového slova throw. Například takto: throw new IndexOutOfRangeException(zpráva); Samozřejmě pokud vyvoláme výjimku v metodě třídy, nemusí být zachycena uvnitř této metody, ale v libovolném kódu programu, který postupně spěje k volání dané metody. Tuto skutečnost můžeme demonstrovat na následujícím příkladu: using System;
60 namespace Vyjimky.Priklad5 public class TridaA public void MetodaA() try // Voláme metodu B: TridaB instanceb = new TridaB(); instanceb.metodab(); catch (IndexOutOfRangeException e) // Zde zachycujeme výjimku typu // IndexOutOfRangeException. Console.WriteLine("Zachycení výjimky IndexOutOfRangeException " + "v metodě A: " + e.message); public class TridaB public void MetodaB() try // Voláme metodu C: TridaC instancec = new TridaC(); instancec.metodac(); catch (ArithmeticException e) // Zde zachcujeme výjimku typu // ArithmeticException - nelze zachytit // výjimku IndexOutOfRangeException. Console.WriteLine("Zachycení výjimky ArithmeticException " + " v metodě B: " + e.message); public class TridaC public void MetodaC() //...
61 // Vyvoláme za určité situace výjimku typu IndexOutOfRangeException: throw new IndexOutOfRangeException( "Metoda C vyvolává výjimku IndexOutOfRangeException."); //... Zde metoda MetodaA instance třídy TridaA volá metodu MetodaB instance třídy TridaB. Metoda MetodaB instance třídy TridaB volá metodu MetodaC instance třídy TridaC a tato metoda vyvolá výjimku typu IndexOutOfRangeException. MetodaMetodaC se volá v bloku try metody MetodaB, za kterým následuje blok catch, který však zachytí pouze výjimku typu ArithmeticException. Mechanismus zachycení výjimek tedy pokračuje hledáním odpovídajícího bloku catch ve volající metodě, tedy metodě MetodaA a tam nachází možnost zachycení. Pokud by došlo k takovému zpětnému průchodu, aniž by se našel odpovídající blok catch, byl by program ukončen operačním prostředím.net a byla by vypsána informace o nezachycené výjimce. VMain pouze zavolejme metodu MetodaA: using System; namespace Vyjimky.Priklad5 class VstupniBodProgramu static void Main() TridaA instancea.metodaa(); instancea = new TridaA(); Console.ReadLine(); Zkuste v metodě MetodaA změnit typ výjimky například na InvalidCastException a výjimka IndexOutOfRangeException nebude zachycena a dostanete podobný výpis. Mechanismus zachycení výjimky v uvedeném příkladu ještě znázorněme na obrázku:
62 Otázky k probranému učivu Jakým způsobem se dá vyvolat vyjímka Co to jsou vyjímky Shrnutí pojmů kapitoly (podkapitoly) Zachycení (ošetření) výjimek (Exception handling) představuje mocný mechanismus pro zotavení programu z (většinou) neočekávaných stavů. Výjimka se vyvolá pomocí klíčového slova throw.
63 Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 7
64 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět definovat vícenásobné delegáty, události definovat vlastního delegáta události Výklad 7.1. Vícenásobné delegáty V předcházejícíh kapitolách jsme se zabývali jednoduchými delegáty. Takové delegáty zapouzdřují pouze odkaz na jednu metodu. Jednoduché delegáty jsou odvozené od třídy System.Delegate. Dále uvidíme, že delegáty mají jako jedno z hlavních využití odkazovat na obslužnou metodu určité události, která se stane v systému. Například při stisku tlačítka myši je potřeba patřičně reagovat, tzn. spustit určitou obslužnou metodu. Jak jinak se na tuto metodu odkázat než pomocí delegátu. Určitá událost však může spustit více metod, které ji obslouží. Je tedy nutné mít více delegátů v seznamu a při spuštění události všechny takové metody zavolat. A k tomu právě slouží vícenásobné delegáty. Vícenásobné delegáty mají většinou návratový typ odkazovaných metod prázdný (void). Kam by se jinak všechny ty návratové hodnoty vracely? V případě potřeby je však možné zapouzdřit i metodu s návratovou hodnotou - viz příspěvek v diskuzi. Jakmile tedy vytvoříme delegáta typu void, automaticky se bere jako vícenásobný delegát. Vícenásobné delegáty jsou odvozené od třídy System.MulticastDelegate, která je odvozená od System.Delegate: Do vícenásobného delegátu přidáme odkaz na funkci pomocí operátoru +=, odebereme pak pomocí -=. Odebrání se však používá velmi zřídka. Vícenásobného delegáta nejvíce oceníme u událostí, zde vyzkoušejme pouze modelový příklad. Budeme deklarovat třídu OperaceSPrvkyPole, která bude mít položkou pole typu double, které půjde odkázat v konstruktoru. Další položkou bude delegát na metodu s jedním parametrem předávaným referencí na double a delegát na metodu se dvěma parametry, z nichž první je předávaný referencí na double, druhým je přímo parametr typu double. Dále budou ve třídě metody,
65 které provedou operace odkazované pomocí zmíněných delegátů s každým prvkem pole. Třída bude vypadat následovně: OperaceSPrvkyPole.cs using System; namespace Delegaty.OperaceSPrvkyPoleVicenasobnyDelegat // Vícenásobné delegáty musí mít návratový typ void - kam by se // jinak všechny ty hodnoty vracely? // Delegát funkce která má jeden parametr typu double // předaný pomocí reference - může ho modifikovat: public delegate void FunkceJednePromenne(ref double a); // Delegát funkce která má dva parametry typu double // - první je předaný pomocí reference (může se modifikovat), // druhý hodnotou: public delegate void FunkceDvouPromennych(ref double a, double b); public class OperaceSPrvkyPole // Reference na pole typu double: private double[] pole; // Delegáty funkcí: public FunkceJednePromenne Funkce1; public FunkceDvouPromennych Funkce2; // Konstruktor uloží referenci na pole: public OperaceSPrvkyPole(double[] pole) this.pole = pole; // Metoda, která provede na každém prvku pole // operaci, která je delegována pomocí Funkce1: public void ProvedOperaci() if (Funkce1 == null) return; for (int i = 0; i < pole.length; i++) Funkce1(ref pole[i]); // Metoda, která provede na každém prvku pole // operaci, která je delegována pomocí Funkce2 // (argument2 je dodatečný argument): public void ProvedOperaci(double argument2)
66 if (Funkce2 == null) return; for (int i = 0; i < pole.length; i++) Funkce2(ref pole[i], argument2); // Řetězcová reprezentace - vypíše prvky pole: public override string ToString() string str = "[ "; for (int i = 0; i < pole.length; i++) str += pole[i]; str += i < pole.length - 1? " ; " : " "; str += "]"; return str; V testovacím programu definujeme několik metod, které souhlasí s delegáty, vytvoříme pole a přiřadíme metody delegátům. Pak budeme v cyklu provádět operace s polem a sledovat výsledky: VstupniBodProgramu.cs using System; namespace Delegaty.OperaceSPrvkyPoleVicenasobnyDelegat class VstupniBodProgramu static void Main() // Pole typu double: double[] pole = 3.2, -1.2, 2.5 ; OperaceSPrvkyPole operacesprvkypole = new OperaceSPrvkyPole(pole); // Vypíšeme: Console.WriteLine("Na začátku:"); Console.WriteLine(operaceSPrvkyPole);
67 // Nasadíme více delegátů: operacesprvkypole.funkce1 += new FunkceJednePromenne(VynasobDvema); operacesprvkypole.funkce1 += new FunkceJednePromenne(ZaokrouhlenySinus); operacesprvkypole.funkce1 += new FunkceJednePromenne(NaDruhou); operacesprvkypole.funkce1 += new FunkceJednePromenne(ExponencialniFunkce); operacesprvkypole.funkce2 += new FunkceDvouPromennych(Vynasob); operacesprvkypole.funkce2 += new FunkceDvouPromennych(Umocni); // Budeme provádět operaci v cyklu, dokud uživatel nezadá k: do Console.WriteLine("Po provedení operací pomocí vícenásobného delegátu:"); operacesprvkypole.provedoperaci(); operacesprvkypole.provedoperaci(1.5); // Co všechno jsme s prvky pole provedli??? // Vypíšeme: Console.WriteLine(operaceSPrvkyPole); while (Console.ReadLine().ToLower()!= "k"); // Můžete experimentovat dále... Console.ReadLine(); // Umocní na druhou: public static void NaDruhou(ref double a) a = a * a; // Vynásobí dvěma: public static void VynasobDvema(ref double a) a = 2 * a; // Provede sinus a zaokrouhlí na 6 desetinných míst: public static void ZaokrouhlenySinus(ref double a) a = Math.Round(Math.Sin(a), 6); // Provede exponenciální funkci: public static void ExponencialniFunkce(ref double a) a = Math.Exp(a);
68 // Vynásobí a b: public static void Vynasob(ref double a, double b) a = a * b; // Umocní a na b: public static void Umocni(ref double a, double b) a = Math.Pow(a, b); // Můžete přidat další metody pro delegáty... Otázkou je: "Co se v tomto programu provede v jedné iteraci cyklu s každým prvkem pole?" Výstup na příkazovém řádku je takový: Na začátku: [ 3,2 ; -1,2 ; 2,5 ] Po provedení operací pomocí vícenásobného delegátu: [ 1, ; 3, ; 7, ] Po provedení operací pomocí vícenásobného delegátu: [ 2, ; 5, ; 6, ] Po provedení operací pomocí vícenásobného delegátu: [ 2, ; 6, ; 2, ] Po provedení operací pomocí vícenásobného delegátu: [ 5, ; 6, ; 5, ] Po provedení operací pomocí vícenásobného delegátu: ] [ 6, ; 6, ; 3,
69 Po provedení operací pomocí vícenásobného delegátu: ] [ 6, ; 1, ; 2, Po provedení operací pomocí vícenásobného delegátu: [ 1, ; 3, ; 3, ] 7.2. Události Událost (event) je prostředek, kterým oznamuje generátor události (event generator) spotřebiteli události (event consumer), že nastala určitá specifická situace (událost). Tuto událost pak může spotřebitel obsloužit. Uveďme některé příklady událostí: kliknutí myší pohyb myší stisknutí klávesy zavření okna změna velikosti okna překreslení okna a mnoho, mnoho dalších... Na nižší úrovni komunikují jednotlivé části systému Windows pomocí zpráv (messages). Programování v.net však podporuje událostní model. Generátor spustí událost, spotřebitel ji obslouží. Jak jednoduché! (Navenek) V jazyce C# jsou události jen zvláštním případem delegátů. Zde je potřeba dát pozor na skutečnost, že pojem událost má v tomto kontextu dva významy: 1. určitá specifická situace, která nastane v systému 2. člen, který má význam pro obsloužení události V předcházejících kapitolách jste se dozvěděli, že delegáty jsou typově bezpečná zapouzdření odkazu na metodu. Reprezentovaná metoda může mít jakoukoliv signaturu. Delegát události má však vždy dva parametry: Prvním parametrem je odkaz na objekt, který událost generoval (spustil). Tento parametr je tedy typu object a obvykle je nazván sender. Druhý parametr představuje dodatečné informace o události. Typ tohoto parametru je buď System.EventArgs nebo třída odvozená
70 z System.EventArgs. System.EventArgs je použito v případě, že žádné dodatečné informace nejsou. V mnoha případech je však užitečné poskytnout doplňující údaje - například při pohybu myši potřebujeme znát její souřadnice, při stisku klávesy její kód apod. K tomu slouží právě třídy odvozené od System.EventArgs, jako je např.system.windows.forms.mouseeventargs, System.Windows.Forms.KeyEventArgs. Delegát události je typu void, díky čemuž je vícenásobný. Událost tedy může být vícenásobně obsloužena. Základní delegát události, který má druhý parametr typu System.EventArgs a neobsahuje tudíž žádné dodatečné informace o události, je již definován pod názvem System.EventHandler: public delegate void EventHandler(object sender, EventArgs e); Dost teorie, pojďme si ukázat praktický příklad. Generátorem naší události bude objekt, v němž bude možné spustit generování pseudonáhodných čísel. V objektu bude zadán interval generovaných čísel a také interval čísel, ze kterého když padne číslo, vyvolá se událost. Tato událost nebude mít pro začátek žádné dodatečné informace. Ukažme si nejdříve celou definici třídy: using System; namespace Udalosti.OznamovaniNahodnychVysledku public class GeneratorNahodnychVysledku // Generátor pseudonáhodných čísel: private Random rand; // Index generovaného čísla: private int index; // Dolní mez: private int dolnimez; // Horní mez: private int hornimez; // Dolní sledovaná mez: private int dolnisledovanamez; // Horní sledovaná mez: private int hornisledovanamez; // Počet generovaných čísel: private int pocetgenerovani; // Událost: public event EventHandler CisloJeVIntervalu; // Konstruktory:
71 public GeneratorNahodnychVysledku() // Vytvoříme generátor pseudonáhodných čísel a // zamícháme ho dle aktuálního času: rand = new Random(unchecked((int)DateTime.Now.Ticks)); this.dolnimez = int.minvalue; this.hornimez = int.maxvalue; this.dolnisledovanamez = int.minvalue; this.hornisledovanamez = int.maxvalue; this.pocetgenerovani = 1000; public GeneratorNahodnychVysledku(int dolnimez, int hornimez, int dolnisledovanamez, int hornisledovanamez, int pocetgenerovani) : this() this.dolnimez = dolnimez; this.hornimez = hornimez; this.dolnisledovanamez = dolnisledovanamez; this.hornisledovanamez = hornisledovanamez; this.pocetgenerovani = pocetgenerovani; // Vlastnosti: public int DolniMez get return dolnimez; set if (value < hornimez) dolnimez = value; public int HorniMez get return hornimez; set if (value > dolnimez) hornimez = value;
72 public int DolniSledovanaMez get return dolnisledovanamez; set if (value < hornisledovanamez) dolnisledovanamez = value; public int HorniSledovanaMez get return hornisledovanamez; set if (value > dolnisledovanamez) hornisledovanamez = value; public int PocetGenerovani get return pocetgenerovani; set if (value >= 0) pocetgenerovani = value; // Metoda, která spustí generování: public void Generuj() index = 0; while (index < pocetgenerovani) int cislo = rand.next(dolnimez, hornimez); if ((dolnisledovanamez <= cislo) && (cislo <= hornisledovanamez))
73 // Číslo je ve sledovaném intervalu: // Pokud existuje obsluha události, // zavoláme ji: if (CisloJeVIntervalu!= null) CisloJeVIntervalu(this, EventArgs.Empty); index++; // Zpomalíme pomocí uspání vlákna: System.Threading.Thread.Sleep(1); Předně je potřeba se zaměřit na veřejný člen, který tvoří událost: // Událost: public event EventHandler CisloJeVIntervalu; Do tohoto členu si spotřebitel události uloží jednu nebo více metod pro obsloužení události. Dalším důležitým okamžikem je spuštění události. V našem generátoru spustíme událost, když je v metodě Generuj vygenerované číslo uvnitř sledovaného intervalu: if ((dolnisledovanamez <= cislo) && (cislo <= hornisledovanamez)) // Číslo je ve sledovaném intervalu: // Pokud existuje obsluha události, // zavoláme ji: if (CisloJeVIntervalu!= null) CisloJeVIntervalu(this, EventArgs.Empty); Zde je nejdůležitější tento řádek: CisloJeVIntervalu(this, EventArgs.Empty); Tím zavoláme obslužnou metodu (metody), která je uložena ve členu CisloJeVIntervalu. Prvním argumentem je objekt, který vyslal událost, to je přímo aktuální objekt, ve kterém je metoda Generuj, což je this. V druhém argumentu budou prázdné informace typu EventArgs, což lze vytvořit například pomocí statické položky pro čtení Empty. Nezapomeňte vždy zjistit, zda nějaká obslužná metoda existuje. Měla by existovat, chceme ji přece zavolat. Takto vypadá volání i s ověřením: if (CisloJeVIntervalu!= null) CisloJeVIntervalu(this, EventArgs.Empty);
74 Nyní máme představu, jak vypadá generování události, pojďme tedy událost spotřebovat (obsloužit). Taková činnost je mnohem častější. V mnoha případech budete obsluhovat události již existujících generátorů (myši, klávesnice apod.). Pro náš generátor může vypadat obsloužení následovně. Opět nejdříve vypíšeme celý kód: using System; namespace Udalosti.OznamovaniNahodnychVysledku class VstupniBodProgramu static void Main() // V programu bude určitý objekt spotřebitele: SpotrebitelUdalosti spotrebitel = new SpotrebitelUdalosti(); // Pomocná metoda spotřebitele pro zadání hodnot // o generátoru: spotrebitel.zadej(); // Tímto spotřebitel spustí generátor: spotrebitel.generuj(); Console.WriteLine("Konec"); // Třída spotřebitele události - to může být například formulář (okno), public class SpotrebitelUdalosti // Generátor náhodných výsledků: private GeneratorNahodnychVysledku generator; // Konstruktor: public SpotrebitelUdalosti() // Vytvoříme generátor: generator = new GeneratorNahodnychVysledku(); // Zaregistrujeme obslužnou metodu události: generator.cislojevintervalu += new EventHandler(OnCisloJeVIntervalu); public void Zadej() Console.Write("Zadejte dolní mez: "); generator.dolnimez = int.parse(console.readline()); Console.Write("Zadejte horní mez: "); generator.hornimez = int.parse(console.readline()); Console.Write("Zadejte dolní sledovanou mez: "); generator.dolnisledovanamez = int.parse(console.readline()); Console.Write("Zadejte horní sledovanou mez: "); generator.hornisledovanamez = int.parse(console.readline());
75 Console.Write("Zadejte počet generování: "); generator.pocetgenerovani = int.parse(console.readline()); public void Generuj() // Uvnitř volání této metody se spouští události: generator.generuj(); // Obslužná metoda události - pouze vypíšeme hlášení // na příkazový řádek: private void OnCisloJeVIntervalu(object sender, EventArgs e) Console.Write("Číslo je v intervalu. "); Zde se při každém vygenerovaném čísle z daného intervalu vypsalo "Číslo je v intervalu". Nyní k důležitým bodům programu. Třída SpotrebitelUdalosti má jako položku generátor události: // Generátor náhodných výsledků: private GeneratorNahodnychVysledku generator; V konstruktoru tento generátor vytvoříme a zaregistrujeme obslužnou metodu události: // Konstruktor: public SpotrebitelUdalosti() // Vytvoříme generátor: generator = new GeneratorNahodnychVysledku(); // Zaregistrujeme obslužnou metodu události: generator.cislojevintervalu += new EventHandler(OnCisloJeVIntervalu); Obslužnou metodu musíme samozřejmě definovat s odpovídající signaturou: // Obslužná metoda události - pouze vypíšeme hlášení // na příkazový řádek: private void OnCisloJeVIntervalu(object sender, EventArgs e) Console.Write("Číslo je v intervalu. "); Všechny ostatní metody jsou pouze pomocné. Při každém spuštění události generátorem se provede kód obslužné metody - vypíše se hlášení. Dalším krokem v reakci na událost by mohlo být určení, jaké číslo se vygenerovalo a kolikáté bylo v pořadí. Museli bychom k události definovat dodatečné informace, tzn. vlastní třídu odvozenou od System.EventArgs a definovat svůj vlastní delegát události. Tím se budeme zabývat v následující kapitole Vlastní delegát události
76 Pokud je nutné předat obslužné metodě události dodatečné informace o této události, je třeba definovat vlastního delegáta události. Jak již bylo popsáno, takový delegát musí být typu void, musí mít dva parametry - první typu object (objekt, který spustil událost), druhý parametr musí mít typ odvozený od System.EventArgs. Právě v tomto druhém parametru jsou předávány zmíněné doplňující informace ke spuštěné události. Odtud vyplývá, že nejdříve musíme definovat třídu dodatečných argumentů události. Vraťme se k příkladu z minulé kapitoly, ve kterém instance třídy GeneratorNahodnychVysledku spouštěla událost CisloJeVIntervalu, pokud bylo vygenerované pseudonáhodné číslo v zadaném intervalu hodnot. Tato událost pouze oznámila, že došlo k určité situaci, ale neposkytovala žádné bližší vztahující se informace. Nyní chceme tuto událost blíže popsat tak, že předáme následující údaje: index vygenerovaného čísla, tj. jeho pořadové číslo číslované od nuly vygenerované číslo Začneme tím, že definujeme třídu argumentů události, kterou nazveme například GeneratorEventArgs. Odvodíme ji od třídy System.EventArgs a doplníme specifické vlastnosti. To je docela jednoduchá úloha: // Vlastní argumenty události s dodatečnými // informacemi k události: public class GeneratorEventArgs : EventArgs // Index generovaného čísla: private int indexgenerovanehocisla; // Generované číslo: private int generovanecislo; // Konstruktor: public GeneratorEventArgs(int indexgenerovanehocisla, int generovanecislo) this.indexgenerovanehocisla = indexgenerovanehocisla; this.generovanecislo = generovanecislo; // Vlastnosti: public int IndexGenerovanehoCisla get return indexgenerovanehocisla; set indexgenerovanehocisla = value; public int GenerovaneCislo get return generovanecislo; set generovanecislo = value; Dále musíme definovat vlastního delegáta události. Nazvěme ho GeneratorEventHandler:
77 // Vlastní EventHandler události: public delegate void GeneratorEventHandler(object sender, GeneratorEventArgs e); Všimněte si signatury delegáta! Nyní jsme připraveni upravit třídu, která spouští událost. Její celý kód může vypadat takto: public class GeneratorNahodnychVysledku // Generátor pseudonáhodných čísel: private Random rand; // Index generovaného čísla: private int index; // Dolní mez: private int dolnimez; // Horní mez: private int hornimez; // Dolní sledovaná mez: private int dolnisledovanamez; // Horní sledovaná mez: private int hornisledovanamez; // Počet generovaných čísel: private int pocetgenerovani; // Událost: public event GeneratorEventHandler CisloJeVIntervalu; // Konstruktory: public GeneratorNahodnychVysledku() // Vytvoříme generátor pseudonáhodných čísel a // zamícháme ho dle aktuálního času: rand = new Random(unchecked((int)DateTime.Now.Ticks)); this.dolnimez = this.dolnisledovanamez = int.minvalue; this.hornimez = this.hornisledovanamez = int.maxvalue; public GeneratorNahodnychVysledku(int dolnimez, int hornimez, int dolnisledovanamez, int hornisledovanamez, int pocetgenerovani) : this() this.dolnimez = dolnimez; this.hornimez = hornimez; this.dolnisledovanamez = dolnisledovanamez; this.hornisledovanamez = hornisledovanamez; this.pocetgenerovani = pocetgenerovani;
78 // Vlastnosti: public int DolniMez get return dolnimez; set if (value < hornimez) dolnimez = value; public int HorniMez get return hornimez; set if (value > dolnimez) hornimez = value; public int DolniSledovanaMez get return dolnisledovanamez; set if (value < hornisledovanamez) dolnisledovanamez = value; public int HorniSledovanaMez get return hornisledovanamez; set
79 if (value > dolnisledovanamez) hornisledovanamez = value; public int PocetGenerovani get return pocetgenerovani; set if (value >= 0) pocetgenerovani = value; public void Generuj() index = 0; while (index < pocetgenerovani) int cislo = rand.next(dolnimez, hornimez); if ((dolnisledovanamez <= cislo) && (cislo <= hornisledovanamez)) // Číslo je ve sledovaném intervalu: // Pokud existuje obsluha události, // zavoláme ji: if (CisloJeVIntervalu!= null) CisloJeVIntervalu(this, new GeneratorEventArgs(index, cislo)); // V tomto volání jsme použili vlastní argumenty, // které obsahují index generovaného čísla a // generované číslo index++; // Zpomalíme pomocí uspání vlákna: System.Threading.Thread.Sleep(1); V této třídě jsou z našeho pohledu nejdůležitější kroky: 1. Deklarace události, která již nebude obecného typu EventHandler, nýbrž GeneratorEventHandler: // Událost: public event GeneratorEventHandler CisloJeVIntervalu;
80 2. Spuštění události, ve kterém zavoláme delegovanou obslužnou metodu i s vlastními dodatečnými argumenty události: // Číslo je ve sledovaném intervalu: // Pokud existuje obsluha události, // zavoláme ji: if (CisloJeVIntervalu!= null) CisloJeVIntervalu(this, new GeneratorEventArgs(index, cislo)); // V tomto volání jsme použili vlastní argumenty, // které obsahují index generovaného čísla a // generované číslo Všechny třídy můžeme definovat v jednom souboru, který tedy bude vypadat takto: GeneratorNahodnychVysledku.cs using System; namespace Udalosti.VlastniEventHandler // Vlastní argumenty události s dodatečnými // informacemi k události: public class GeneratorEventArgs : EventArgs // Index generovaného čísla: private int indexgenerovanehocisla; // Generované číslo: private int generovanecislo; // Konstruktor: public GeneratorEventArgs(int indexgenerovanehocisla, int generovanecislo) this.indexgenerovanehocisla = indexgenerovanehocisla; this.generovanecislo = generovanecislo; // Vlastnosti: public int IndexGenerovanehoCisla get return indexgenerovanehocisla; set indexgenerovanehocisla = value; public int GenerovaneCislo get return generovanecislo; set generovanecislo = value;
81 // Vlastní EventHandler události: public delegate void GeneratorEventHandler(object sender, GeneratorEventArgs e); public class GeneratorNahodnychVysledku // Generátor pseudonáhodných čísel: private Random rand; // Index generovaného čísla: private int index; // Dolní mez: private int dolnimez; // Horní mez: private int hornimez; // Dolní sledovaná mez: private int dolnisledovanamez; // Horní sledovaná mez: private int hornisledovanamez; // Počet generovaných čísel: private int pocetgenerovani; // Událost: public event GeneratorEventHandler CisloJeVIntervalu; // Konstruktory: public GeneratorNahodnychVysledku() // Vytvoříme generátor pseudonáhodných čísel a // zamícháme ho dle aktuálního času: rand = new Random(unchecked((int)DateTime.Now.Ticks)); this.dolnimez = this.dolnisledovanamez = int.minvalue; this.hornimez = this.hornisledovanamez = int.maxvalue; public GeneratorNahodnychVysledku(int dolnimez, int hornimez, int dolnisledovanamez, int hornisledovanamez, int pocetgenerovani) : this() this.dolnimez = dolnimez; this.hornimez = hornimez; this.dolnisledovanamez = dolnisledovanamez; this.hornisledovanamez = hornisledovanamez; this.pocetgenerovani = pocetgenerovani;
82 // Vlastnosti: public int DolniMez get return dolnimez; set if (value < hornimez) dolnimez = value; public int HorniMez get return hornimez; set if (value > dolnimez) hornimez = value; public int DolniSledovanaMez get return dolnisledovanamez; set if (value < hornisledovanamez) dolnisledovanamez = value; public int HorniSledovanaMez get return hornisledovanamez; set
83 if (value > dolnisledovanamez) hornisledovanamez = value; public int PocetGenerovani get return pocetgenerovani; set if (value >= 0) pocetgenerovani = value; public void Generuj() index = 0; while (index < pocetgenerovani) int cislo = rand.next(dolnimez, hornimez); if ((dolnisledovanamez <= cislo) && (cislo <= hornisledovanamez)) // Číslo je ve sledovaném intervalu: // Pokud existuje obsluha události, // zavoláme ji: if (CisloJeVIntervalu!= null) CisloJeVIntervalu(this, new GeneratorEventArgs(index, cislo)); // V tomto volání jsme použili vlastní argumenty, // které obsahují index generovaného čísla a // generované číslo index++; // Zpomalíme pomocí uspání vlákna: System.Threading.Thread.Sleep(1); Díky této práci máme možnost v klientovi třídy GeneratorNahodnychVysledku získat nejen potuchu o tom, že vygenerované číslo je ve sledovaném intervalu (nastala událost), ale také přímo vygenerované číslo a jeho index, podobně jako v obslužné metodě
84 události MouseMove (spouští se při pohybu myší v aplikaci pro Windows) lze získat pozici ukazatele myši. Pojďme na to: VstupniBodProgramu.cs using System; namespace Udalosti.VlastniEventHandler class VstupniBodProgramu static void Main() // Vytvoříme spotřebitele události: SpotrebitelUdalosti spotrebitel = new SpotrebitelUdalosti(); // Nechť uživatel zadá vlastnosti generátoru: spotrebitel.zadej(); // Nechť generátor začne s generováním: spotrebitel.generuj(); Console.WriteLine("Konec"); Console.ReadLine(); // Třída spotřebitele události: public class SpotrebitelUdalosti // Generátor náhodných výsledků: private GeneratorNahodnychVysledku generator; // Konstruktor: public SpotrebitelUdalosti() // Vytvoříme generátor: generator = new GeneratorNahodnychVysledku(); // Zaregistrujeme obslužnou metodu události: generator.cislojevintervalu += new GeneratorEventHandler(OnCisloJeVIntervalu); // Pomocná metoda pro zadání vlastností generátoru: public void Zadej() Console.Write("Zadejte dolní mez: "); generator.dolnimez = int.parse(console.readline()); Console.Write("Zadejte horní mez: "); generator.hornimez = int.parse(console.readline()); Console.Write("Zadejte dolní sledovanou mez: "); generator.dolnisledovanamez = int.parse(console.readline()); Console.Write("Zadejte horní sledovanou mez: "); generator.hornisledovanamez = int.parse(console.readline());
85 Console.Write("Zadejte počet generování: "); generator.pocetgenerovani = int.parse(console.readline()); // Metoda, která spustí generování: public void Generuj() generator.generuj(); // Obslužná metoda události - signatura musí odpovídat delegátovi // události: private void OnCisloJeVIntervalu(object sender, GeneratorEventArgs e) Console.WriteLine("Index 0: číslo 1", e.indexgenerovanehocisla, e.generovanecislo); V tomto kódu jsou důležité kroky: 1. Zaregistrování obslužné metody. Obslužná metoda musí mít signaturu určenou delegátem GeneratorEventHandler: // Zaregistrujeme obslužnou metodu události: generator.cislojevintervalu += new GeneratorEventHandler(OnCisloJ evintervalu); 2. Obsloužení události. Obslužná metoda události musí mít odpovídající signaturu: // Obslužná metoda události - signatura musí odpovídat delegátovi // události: private void OnCisloJeVIntervalu(object sender, GeneratorEventArgs e) Console.WriteLine("Index 0: číslo 1", e.indexgenerovanehocisla, e.generovanecislo); V této metodě pouze vypíšeme dodatečné argumenty události na příkazový řádek. Vlastní užitečné delegáty události budeme definovat v aplikacích typu Windows Forms a ASP.NET. Otázky k probranému učivu
86 Jak se definuje vlastní delegát události Co je to událost K čemu slouží vícenasobné delegáty Shrnutí pojmů kapitoly (podkapitoly) Vícenásobné delegáty jsou odvozené od třídy System.MulticastDelegate, která je odvozená od System.Delegate: Událost (event) je prostředek, kterým oznamuje generátor události (event generator) spotřebiteli události (event consumer), že nastala určitá specifická situace (událost). Tuto událost pak může spotřebitel obsloužit. Pokud je nutné předat obslužné metodě události dodatečné informace o této události, je třeba definovat vlastního delegáta události. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, 2006.
87 Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 8 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět Seznámí se s objektovým programováním, strukturovaným programovaním a komponentním programováním Výklad 8.1. Teorie objektového programování úvod Během uplynulého půlstoletí se v informatických vědách uskutečnil takový progres, jaký se v jiných oblastech lidských činností zaznamenává zpravidla za několik století. Ze sálových počítačů se staly mikropočítače s ohromným výkonem připravené pomáhat. Spolu s převratným technologickým tempem držela krok i teoretická informatika, která definovala metodiky, postupy a paradigmatu vývoje počítačového softwaru Vývoj počítačového softwaru nebyl vždy takové vyspělé úrovni jako v současnosti. Začátky se spojují s přímým programováním počítačů pomocí ryze technických úkonů, které byly později nahrazeny tvorbou programů ve strojovém kódu, které se vyznačovalo velmi malou pracovní produktivitou, ale i náročností na abstraktní myšlení a technickou preciznost. Přesun od strojového kódu směrem k jazykům symbolických adres přinesl ve své době významnou úroveň abstrakce. Dnešní programovací jazyky se vyznačují vyšší mírou abstrakce od hardwarové infrastruktury. Strukturované programování umožnilo naplno
88 rozvinout myšlenku problémové dekompozice, kdy se problém rozložil na více algoritmicky řešitelných podproblémů. Strukturované programování se stalo první vývojovou větví. O perspektivnosti strukturovaného programování svědčí jeho stále velká praktická oblíbenost, a to i přesto, že od uvedení tohoto stylu vývoje softwaru uplynulo už několik desetiletí. Velký posun vpřed zaznamenalo objektové paradigma, která se stala základním pilířem objektově orientovaného programování. Data a operace, které jsou s daty uskutečňované, jsou zapouzdřeny do logických jednotek, kterým říkáme objekty. Virtuální objekty vznikají v procesu objektového modelování, přičemž odráží vlastnosti a schopnosti skutečných objektů tvořících reálný svět. Dnes se cení zejména vysoká pracovní produktivita s vysokou přidanou hodnotou. Komponentové programování představilo komponentní technologii vývoje softwaru, podle níž jsou aplikace tvořeny kolekcí vhodně nakonfigurovaných počítačových součástek, tedy komponentů. Komponentové programování využívá všechny silné stránky objektově orientovaného programování. Výzvou současných a určitě i budoucích let je paralelní programování, které lze realizovat v součinnosti s objektově orientovaným a komponentového programováním. Cílem je vysvětlit principy objektově orientovaného programování v jazyce C# Teoretické základy objektově orientovaného programování Vývojové větve a paradigmatu programování Při bližší analýze můžeme determinovat tři základní větve programování: 1. Strukturované programování. 2. Objektově orientované programování. 3. Komponentové programování. Každá z uvedených větví přichází s vlastní paradigmatem vývoje počítačového softwaru Strukturované programování Strukturované (procedurální) programování se soustřeďuje na návrh programů, které implementují algoritmy prostřednictvím tří základních řídících konstrukcí, kterými jsou: sekvence, selekce a iterace. Návrh strukturovaných programů zpravidla probíhá postupným zjemňováním podrobnosí, kdy komplexní problém se rozkládá na menší podproblémy a jednodušší. Tento rozklad je založen na využití základních řídících konstrukcí. Strukturované programování využívá strukturované algoritmy. Strukturovaný algoritmus takto: 1. Každý jednotlivý krok je strukturovaným algoritmem. 2. Základní řídící konstrukce (sekvence, selekce a iterace) strukturovaných algoritmů představují také strukturovaný algoritmus. 3. Strukturovaným algoritmem je všechno to, co získáme opakovaným použitím 1. a 2. kroku tohoto postupu.
89 Jedna z funkcí strukturovaného programu má výsadní postavení, přičemž se klasifikuje jako hlavní funkce programu. Hlavní funkce představuje vstupní bod programu, což znamená, že exekuce programu začíná voláním a zpracováním této funkce. Hlavní funkce je automaticky aktivována operačním systémem vždy, když je zaregistrována požadavek na spuštění strukturovaného programu. V rámci implementace strukturovaných algoritmů je ideální, pokud jedna funkce programu řeší právě jeden algoritmus. Vizuální kompozice strukturovaného programu je znázorněna na obr Legenda Volanie funkcie ^ Poskytnutie návratovej hodnoty ^ Obr. 1: Štruktúrovaný program
90 Mezi hlavní funkcí a ostatními, tzv.. uživatelsky definovanými, funkcemi existují těsné datové vazby. Při volání uživatelsky definované funkce je možné této funkci předat množství vstupních dat. Vstupní data specifikovaná při volání funkce se nazývají argumenty. V roli argumentů mohou vystupovat datové objekty, které jsou na syntaktické úrovni reprezentovány hodnotami primitivních datových typů, resp. instancemi uživatelsky deklarovaných datových typů. Prostředkem pro uchování argumentů jsou v strukturovaných programech formální parametry, které z technického hlediska představují lokální proměnné. Funkce strukturovaného programu alespoň s jedním formálním parametrem je parametrickou funkci. Naproti tomu, funkce s bez parametrů se označují termínem bezparametrické funkce. Strukturované programovací jazyky obvykle implementují dva základní mechanismy předávání argumentů formálním parametrem: 1. Mechanismus předávání argumentů hodnotou. 2. Mechanismus předávání argumentů odkazem. Při aplikaci mechanismu předávání argumentů hodnotou je vždy vytvořena kopie argumentu (původního datového objektu), která je následně poskytnuta volané funkci. Volána funkce získá identický duplikát argumentu, který může použít při své práci. Dokonce může získaný duplikát i modifikovat, ale tyto modifikace nijak neovlivní původně předáván argument. Mechanismus předávání argumentů hodnotou je bezpečnějším mechanismem předávání datových objektů volaným funkcím. Za jistých okolností se použití mechanismu předávání argumentů hodnotou váže s nežádoucí postranní režií, především tehdy, pokud volající funkce poskytuje volané funkci kapacitně náročné datové objekty. Možností, jak eliminovat paměťové zatížení systému, je upřednostnit mechanismus předávání argumentů odkazem. Na druhou stranu, při mechanismu předávání argumentů odkazem je volané funkci poskytnuta paměťová adresa, na níž je argument (datový objekt) situovaný. Jelikož volána funkce zná paměťovou adresu argumentu, má k němu přímý přístup a je schopna jej libovolně Volána funkce může po vyřešení problému, resp. po dokončení své činnosti, podat o jejím výsledku zprávu hlavní funkci. Tento komunikační model je realizován pomocí předávání tzv.. návratové hodnoty volané funkce. Funkce bez návratové hodnoty jsou ze sémantického hlediska totožné s procedurami. Podobně, jak může komunikovat hlavní funkce se všemi uživatelsky definovanými funkcemi, mohou kooperovat i jednotlivé uživatelsky definované funkce mezi sebou. Přitom platí všechny pravidla, které jsme rozebrali výše. Manipulace s argumenty a návratovou hodnotu tvoří základní princip pro řízení toku dat v strukturovaných programech. Pro úplnost dodejme, že existují i jiné mechanismy, které umožňují předávání dat mezi funkcemi navzájem. Typickým příkladem je použití globálních
91 dat. K takovým proměnným mají přístup všechny funkce, které se nacházejí v příslušné překladové jednotce zdrojového kódu. Pokud dojde k vyvolání funkce, řízení programu je přesměrováno na počátek kódu funcke, na vstupního bod těla funkce. Pokud jsou funkci předávány argumenty, alokuje se prostor pro formální parametry, do kterých jsou argumenty uloženy. Následně je zahájeno provádění příkazů, které tvoří tělo volané funkce. Po zpracování všech příkazů je vrácena návratová hodnota funkce a řízení programu je předáno zpět do volající funkce. Uživatelsky definované funkce strukturovaného programu mohou být kromě přímého volání volány i rekurzivně. Při přímé rekurzi je vytvořena nová instance identické funkce předtím, než se ukončí zpracování všech příkazů v těle této funkce. Při přímé rekurzi volaná funkce v určitém okamžiku svého zpracování aktivuje "sama sebe". Z technického hlediska není žádný rozdíl mezi voláním nerekurzívní a rekurzivní funkce. Přímá rekurze probíhá opakovaně, přičemž při každém opakování je volána nová instance funkce s modifikovanou soupravou argumentů (rekurzivně funkce budou vždy parametrické). Řetězec jednotlivých instancí volané funkce musí být vždy konečný (bez ohledu na to, zda pracujeme s přímou, nebo nepřímou rekurzí). Při každém vytvoření nové instance funkce se tato funkce musí rozhodnout, zda parciální část problému, kterou řeší, představuje rekurzivní, nebo základní případ. Pokud jde o rekurzivní případ, tak v procesu dekompozice stále nebyla dosažena taková úroveň problému, kterou by bylo možné okamžitě vyřešit. Podproblém na příslušné úrovni je proto nutné opět dekomponovat na jednodušší problémy. Tento případ znamená vytvoření nových a nových instancí funkce v operační paměti. Pokud se v průcesu dekompozice jedná o základní případ, funkce se musí ukončit. Základní případ je takový podproblém, který dokáže funkce v dané instanci vyřešit. Samozřejmě, otázkou zůstává, za jak dlouho bude schopna rekurzivně volána funkce dosáhnout základní případ. Po dosažení základního případu se rekurzivně volána funkce "vynořuje zpět", přičemž každá z vytvořených instancí poskytuje svému nadřazenému protějšku výsledek své činnosti. Nepřímá rekurze je jen variací přímé rekurze: při ní rekurzivně volána funkce aktivuje jinou uživatelsky definovanou funkci a tato zase zpětně volá původní funkci. Ačkoliv je zápis rekurzivně volané funkce přirozené v mnoha případech elegantní, vždy dochází kapacitní náročnost je způsobena generováním instancí rekurzivně volaného funkce, které musí být alokovány v operační paměti počítače. Vyšší zátěž je vázána také s výkonem komunikačního mechanismu mezi jednotlivými instancemi rekurzivně volané funkce Objektově orientované programování
92 Přestože základy paradigmatu objektově orientovaného vývoje počítačového softwaru byly teoreticky položeny již v 60. letech 20. století, praktický rozmach zaznamenal tento model tvorby softwaru přibližně o třicet let později. Objektově orientované programování se snaží řešit jeden ze základních problémů strukturovaného programování, kterým je vztah datových objektů a funkcí (podprogramů), které s těmito datovými objekty manipulují. Obecná teorie objektově orientovaného programování vychází z následujících axiomů: 1. Objekt je základní logická entita, která je charakterizována svým identifikátorem, vlastnostmi a činnostmi. Identifikátor objektu je jednoznačné určení objektu v systému, resp. v objektově orientovaném prostředí. Vlastnosti objektu jsou dány jeho atributy a představují znaky, kterými objekt disponuje. Činnosti objektu jsou reprezentovány jeho metodami a definuje jeho vnější chování. Definují tedy styl chování objektu vůči klientům, resp. vůči jiným. 2. Objekt je výsledkem objektového modelování, tedy procesu, jehož prostřednictvím se vytvářejí virtuální ekvivalenty fyzických objektů, se kterými pracujeme v reálném světě. 3. Protože fyzické objekty jsou zpravidla velmi složité, během objektového modelování se uplatňuje princip abstrakce, kdy pomíjíme ty atributy a činností fyzických objektů, které pro nás nejsou důležité (obr. 2). Objektová abstrakce nám dovoluje soustředit se jen na ty vlastnosti a činnosti objektu, které jsou významné z hlediska vytvářeného systému, resp. aplikace. Reálny objekt objekt Obr. 2: Objektové modelovanie a objektová abstrakcia 4. Každý objekt je svéprávnou jednotkou, která obsahuje vlastní atributy a metody. Atributy jsou reprezentovány datovými položkami. Metody představují činnosti vykonávané
93 objektem. Metody mohou přímo pracovat s atributy objektu. Principem zapouzdření je chápání objektu jako kontejneru (obálky, schránky), ve kterém se nacházejí atributy a metody, (obr. 3). Obr. 3: Atributy a metody jsou zapouzdřeny do objektu Objekt je z vnějšího pohledu "černou skříňkou", protože jiné objekty nevidí jeho atributy, ani metody. Není tedy možné, aby byla jeho data modifikovaná potenciálně nebezpečným způsobem jiným objektem, resp. jinou entitou vně objektu. V obecné teorii objektově orientovaného programování tento princip je nazáván ukrývání dat. 6. S daty objektu mohou přímo zacházet jen metody objektu. Ty jsou navrženy tak, aby se datová část objektu nikdy nedostala do nekonsistentního stavu. Podobně jako atributy, i metody jsou v objektu ukryté. Tento princip se nazývá ukrývání implementace a umožňuje uživateli využívat služeb objektu i bez znalosti toho, jak jsou tyto služby implementovány. Metody jsou vvyvolány pomocí protokolu zpráv, kde každá zpráva je vázána na jedinou metodu. 7 Objekt tedy ví o své existenci, jeho aktuální stav určují hodnoty jeho atributů. 8. Komunikace ve vztazích "uživatel -> objekt" a "objekt -> jiný objekt" se uskutečňuje pomocí mechanismu zpráv. Každý objekt je schopen přijmout a zpracovat jistou množinu zpráv. Pokud přijde zpráva od uživatele, resp. jiného objektu, objekt ji zachytí a zpracuje (obr. 4). Proces dále pokračuje aktivováním metody, která je s přijatou zprávou asociována. Objekt provede požadovanou činnost a s jejím výsledkem seznámí uživatele. Obr. 4: Komunikácia s objektom pomocou protokolu správ
94 9. Zprávy, které jsou objektu zasílány mohou být bezparametrické a parametrické. Bezparametrické zprávy s sebou nenesou žádná data a jsou určeny pouze pro zjištění aktuálního stavu objektu. Naopak, parametrické zprávy obsahují data, které modifikují aktuální stav objektu. 10. Z hlediska čekání na výsledek zaslání zprávy můžeme zprávy klasifikovat na synchronní a asynchronní. Budeme uvažovat abstraktní komunikační modely s více objekty. První objekt označíme identifikátorem A, druhému objektu přiřadíme identifikátor B a třetí objekt pojmenujeme identifikátorem C. Zaměříme se na zkoumání dvou situací: 1. Analýza synchronního komunikačního modelu. 2. Analýza asynchronního komunikačního modelu. V synchronním komunikačním modelu pracujeme s dvěma objekty: A a B. Komunikace mezi zmíněnými objekty začíná tím, že objekt A zašle synchronní zprávu objektu B. Objekt B zprávu zpracuje a synchronně spustí příslušnou metodu. Objekt B tedy začne provádět operace, které jsou naprogramovány v těle metody, kterou si objekt A přál spustit. Objekt A nemůže realizovat žádné další aktivity (např. rozesílat jiné zprávy), pokud metoda objektu B nedokončí svou činnost. Objekt A musí čekat na ukončení synchronně aktivované metody objektu B. Synchronní komunikační model vytváří pevnou vazbu mezi komunikujícími objekty. Časová latence, která vzniká po doručení zprávy, je značná. Znázornění viz obr. 5.
95 Obr. 5: Synchrónny komunikačný model medzi objektmi Komentář k synchronnímu komunikačnímu modelu (obr. 5): V době t1 zasílá objekt A synchronní zprávu (SS) objektu B. Objekt B zajišťuje zpracování synchronní zprávy (SSS). Po diagnostice zprávy objekt B zahajuje synchronní aktivaci přidružené metody (SAM). Volána metoda je zpracovávány synchronně (SSM), což znamená, že objekt A čeká až do okamžiku, kdy tato metoda ukončí svou činnost. Po vrácení návratové hodnoty (VNH) a její zpracování (SNH) v době tn se řízení vrací zpět objektu A. Přímá časová závislost (PCZ), vyjadřující vzájemnou součinnost objektu A, je dána intervalem <t1, tn>. Objekt A nemůže zaslat další zprávu objektu B (nebo jinému objektu) dříve, než v době tn +1. Asynchronní komunikační model eliminuje citelnou časovou latenci synchronnho komunikačního modelu. V tomto modelu se komunikace mezi zúčastněnými objekty odehrává následujícím způsobem: Objekt A zašle asynchronní zprávu objektu B. Řízení se v této chvíli okamžitě vrací zpět objektu A, který může zahájit provedení dalších operací. Objekt B zaslanou zprávu přijme, a asynchronně spustí příslušnou metodu. Když metoda objektu B skončí svou činnost, bude o jejím výsledku informován objekt A. Jelikož objekt zasílající asynchronní zprávu není přerušení přepravy během výkonu metody poptávaného objektu, smí pokračovat ve své práci. Vizuální interpretaci asynchronního komunikačního modelu mezi objekty znázorňuje obr. 6. Komentář k asynchronního komunikačnímu modelu (obr. 6): Nyní musíme analyzovat více než dva objekty. V našem modelu použijeme tři objekty s identifikátory A, B a C. V době t1 zasílá objekt A objektu B první asynchronní zprávu (AS1). Objekt B zpracuje asynchronní zprávu (SAS1) a asynchronně spustí metodu spojenou s touto zprávou (AAM1). Jelikož objekt A nečeká na dokončení asynchronní aktivované metody objektu B, může v době t2 zaslat další asynchronní zprávu (AS2) objektu C. Objekt C doručenou asynchronní zprávu zpracuje (SAS2) a asynchronní spustí svázanou metodu (AAM2). Obě asynchronní aktivované metody objektů B a C pracují paralelně. Jejich finální exekuční čas je variabilní: v našem modelu předpokládáme, že asynchronní aktivována metoda objektu B vrátí svou návratovou hodnotu v čase tn, zatímco asynchronní aktivována metoda objektu C nám poskytne návratovou hodnotu v čase tn + s. V asynchronním komunikačním modelu je tedy přímá časová závislost (PCZ) menší než v synchronním komunikačním modelu. Jelikož
96 objekt A po zaslání zprávy objektu B nečeká na zpracování příslušné metody, může již v době t2 (přičemž platí, že t2 <tn) vytvořit a zaslat jinou asynchronní zprávu. 11. Protokol zpráv představuje veřejně přístupné rozhraní a poskytuje kompletní aparát, jehož prostřednictvím mohou uživatelé využívat všechny služby objektu. 12. Objekty s funkčně spřízněnými atributy a metodami patří do stejné třídy objektů. Třídu objektů můžeme charakterizovat jako množinu funkčně ekvivalentních objektů. Objekty jsou instancemi stejné dané třídy. Každý objekt třídy má své vlastní atributy a metody. 13. Objekty mohou dědit vlastnosti a činnosti od jiných objektů. To se děje v procesu dědičnosti, kdy potomci přebírají charakteristické rysy svých rodičů. Třídy objektů mohou vytvářet různé varianty dědičnosti. Pokud potomek dědí své schopnosti jen od jednoho rodiče, jde o jednoduchou dědičnost. Naopak, když potomek zdědil své schopnosti od několika rodičů současně, říkáme o vícenásobné dědičnosti. Potomci však nejsou odkázáni pouze na ty schopnosti, které zdědily po svých rodičích. K již nabytým (zděděným) vlastnostem a činnostem mohou přidávat nové vlastnosti a činnosti. Ppotomek se může vyskytnout všude tam, kde je může být i jeho rodič. Opačně tento proces nefunguje. Totiž tam, kde je očekávaný potomek, nemůže být dosazen rodič tak. Je to proto, že rodič není schopen v plné míře zastoupit svého potomka, protože ten může být funkčně vyspělejší než on. 14. Vztahy mezi objekty nemusí být generovány pouze na základě dědičnosti. Nehledě na dědičnosti, je možné modelovat vazby mezi objekty i pomocí asociativních relací, a to agregace a kompozice. Jejich podstata spočívá v tom, že objekt může vzniknout složením z jiných objektů. V tomto kontextu rozlišujeme hlavní (nadřazený) objekt a množinu podobjektů, které nadřazený objekt obsahuje. Agregační a kompoziční vztahy se využívají především při konstrukci složitých objektů, které vykonávají širokou paletu činností.
97 Obr. 6: Asynchrónny komunikačný model medzi objektmi Obr. 7: Agregačno-kompozičné vzťahy medzi objektmi
98 15. Jestliže dva objekty reagují na zaslání totožné zprávy odlišným způsobem, říkáme, že se chovají polymorfně. Polymorfismus je aspekt objektově orientovaného programování, který úzce souvisí s dědičností a umožňuje vzájemnou substituci objektů (potomci jsou schopni zastoupit svých předků). Obr. 8: Polymorfné správanie inštancií tried Vizualizaci programu, který byl vytvořen v ryze objektově orientovaném programovacím jazyce, uvádí obr. 9.
99 Obr. 9: Rýdzo objektovo orientovaný program Většina v praxi rozšířených programovacích jazyků patří do kategorie hybridních programovacích jazyků, protože v sobě kombinuje možnosti pro vývoj strukturovaného a objektově orientovaného počítačového softwaru. Je zdezájem i na zachování zpětné kompatibility. K hybridním programovacím jazykem patří C #, C + +, Visual Basic, Delphi a Java. Vedle hybridních programovacích jazycích existují i ryze objektově orientované jazyky, ke kterým patří zejména dynamické jazyky řešící úkoly pro potřeby umělé inteligence, simulace, počítačové grafiky atd. Patří k nim např. Smalltalk, CLOS, Eiffel, ESP a Object- Prolog. Program vytvořený v ryze objektově orientovaných jazycích není závislý od hlavní funkce, resp. metody, která zahajuje zpracování programů vytvořených v hybridních programovacích jazycích. V ryze objektových programech se exekuce začíná zasláním zprávy z rozhraní programu objektem, které na tuto zprávu příslušným způsobem reagují Komponentní programování
100 Komponentové programování je modulární nadstavbou objektově orientovaného programování, využívá možnosti objektově orientovaného programování, kdy pomocí objektů vytváří komponenty. Komponenty můžeme chápat jako softwarové jednotky vyššího kvalitativního stupně. Základní princip komponentového programování spočívá ve vytváření komponent, které dokáží realizovat skupinu příbuzných činností aplikace, resp. počítačového systému. Komponentové programování vychází z předpokladu, že software by měl být vytvářen přesně tak, jak produkty průmyslové výroby, které se vyrábějí hromadně v pásové výrobě. Komponentové programování říká, že iv softwarovém světě lze zhotovit samostatné programové součástky, ze kterých se pak zkonstruuje finální program. Aby toto bylo možné musí být splněny následující předpoiklady: 1. Aplikace (A) je konečnou neprázdný množinou komponent (K), kterou můžeme matematicky definovat takto: A - K - kltk2i..., kn 2. Jeden z komponentů aplikace představuje tzv. primární komponentu. Primární komponenta ta, která zajišťuje základní funkcionalitu komponentové aplikace. 3. Kromě primárního dílu lze aplikace tvořena libovolnou přípustnou množinou dalších (komplementárních) komponentů. Každý z komplementárních komponentů je zodpovědný za provádění určité množiny činností aplikace. Protože komponenty jsou navrženy na modulární bázi, můžeme vytvořit rozmanitou kolekci adekvátních komponent. Modularita komponent je výhodná nejen z pohledu vývojářů, ale i cílových uživatelů. V závislosti na svých potřeb mohou uživatelé přímo ovlivnit konečnou komponentní skladbu aplikace. Softwarové firmy často produkují různé verze komponentových aplikací, které se odlišují množstvím instalovaných komponent. Uživatel se může rozhodnout, které komponenty zakoupí a které ne. Softwarová firma může zase kategorizovat komponentové aplikace podle počtu implementovaných komponent (např. základní, pokročilá zda luxusní verze komponentové aplikace). Vizuální podobu komponentové aplikace můžeme vidět na obr. 10.
101 Obr. 10: Komponentová aplikácia 4. Každá součást je charakterizován těmito vlastnostmi: Identifikace. Komponenta musí být jednoznačně identifikovatelný. Zapouzdření. Komponenta zapouzdřuje veškerou požadovanou funkcionalitu v sobě. Rozhraní. Podobně jako objekty, i komponenty mají své veřejně přístupné rozhraní, pomocí kterých využívají uživatelé jejich služeb.. Připravenost k použití. Komponenta působí za všech okolností jako hotová softwarová součástka, která poskytuje komplexní automatizaci vybraných činností. Opětovná použitelnost. Pokud je komponenta vyvinutý, odladěný, otestován a optimalizovaný, můžeme ho použít tolikrát, kolikrát potřebujeme.. Anonymita uživatelů. Funkcionalita komponentu je zcela oddělena od jeho aplikace. Interoperabilita. Součásti musí být schopny mezi sebou spolupracovat i tehdy, když byly vytvořeny v různých integrovaných vývojových prostředích a programovacích jazycích. 4. Vybrané komponenty smějí vytvářet celky, které samy o sobě mohou stát komponenty.
102 . Obr. 12: Skladanie komponentov Otázky k probranému učivu Co znamená pojem objektové programování Čím se vyznačuje strukturované programování Co je to objekt Co je to komponentní programování Shrnutí pojmů kapitoly (podkapitoly) Strukturované (procedurální) programování se soustřeďuje na návrh programů, které implementují algoritmy prostřednictvím tří základních řídících konstrukcí, kterými jsou: sekvence, selekce a iterace. Objektově orientované programování se snaží řešit jeden ze základních problémů strukturovaného programování, kterým je vztah datových objektů a funkcí (podprogramů), které s těmito datovými objekty manipulují. Komponentové programování je modulární nadstavbou objektově orientovaného programování, využívá možnosti objektově orientovaného programování, kdy pomocí objektů vytváří komponenty. Komponenty můžeme chápat jako softwarové jednotky vyššího kvalitativního stupně. Základní princip komponentového programování spočívá ve vytváření komponent, které dokáží realizovat skupinu příbuzných činností aplikace, resp. počítačového systému.
103 Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, 2007.
104 9. Téma 9 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět Základy objektově orientovaného programování v jazyce C C # 3.0 Deklarovat a nvrhnout třídu Pracovat s Konstruktory Skalární instanční vlastnosti třídy Výklad 9.1. Objektově orientované programování v jazyce C # 3.0 Programovací jazyk C # byl vytvořen společností Microsoft, která jeho první verzi uvedla na softwarový trh v roce C # je hybridním programovacím jazykem, protože implementuje syntaktická-sémantické konstrukce strukturovaného i objektově orientovaného programování. Jazyk C # byl vytvořen speciálně pro potřeby vývojově-exekuční platformy Microsoft.NET Programovací jazyk C # byl standardizován organizacemi ISO a ECMA5, podobně jako i základní komponenty vývojově-exekuční platformy Microsoft.NET Framework. Genezi programovacího jazyka C # zobrazuje obr generace jazyka C # 2. a 3. generace jazyka C 1. generácia jazyka C# 2. a 3. generácia jazyka C#
105 Obr. 13: Vývoj programovacieho jazyka C# Třída jako abstraktní objektový datový typ Všechny hybridní programovací jazyky vytvářejí objekty ze tříd, které jsou v těchto prostředích považovány za abstraktní objektové datové typy. Třída představuje abstraktní datový typ, jehož deklarace specifikuje vlastnosti a činnosti objektů, které z této třídy vzniknou. Třídy jsou uživatelsky deklarovanými datovými typy, což znamená, že nejprve musme deklarovat třídu a až následně můžeme vytvářet instance tété třídy Deklarace třídy Třída jako abstraktní, objektový a odkazový uživatelsky deklarovaný datový typ se v jazyce C # 3.0 se deklaruje takto: class T // T je identifikátor třídy Deklarace třídy je složena z hlavičky třídy a jejího těla. V hlavičce se musí nacházet klíčové slovo class, podle kterého dáváme kompilátoru na známost, že se chystáme deklarovat třídu jako nový uživatelsky deklarovaný datový typ. Za klíčovým slovem class je umístěn identifikátor třídy, který jednoznačné pojmenovává třídu v dané oblasti platnosti. Abychom předešli potenciálním jmenným konfliktům, můžeme deklaraci třídy vložit do předem připraveného jmenného prostoru. Pokud není určeno jinak, deklarovaná třída má interní přístupné práva, tedy je třídě implicitně přiřazen přístupový modifikátor internal, který vymezuje viditelnost a oblast platnosti třídy. Znamená to, že je třída přístupná pro všechny ostatní datové typy (ať už hodnotové, nebo odkazové), které jsou situovány v daném sestavení aplikace.net. Kromě modifikátoru internal můžeme deklarovat veřejně přístupné třídy (modifikátor public). Veřejně přístupná třída je viditelná pro jakýkoliv externí, a tedy klientský zdrojový kód.
106 Tělo třídy je ohraničeno složenými závorkami (), v nichž se nacházejí definice členů třídy. K základním členem třídy patří atributy (datové položky) a metody. Kromě nich však smějí členy třídy být také vlastnosti či delegáti. Můžeme vytvořit následující deklaraci třídy: class Učitel // Definice atributů (datových položek). private string jméno; private string příjmení; private int mzda; private int odpracovanéroky; // Definice konstruktoru public Učitel(string jméno, string příjmení, int mzda, int roky) this.jméno = jméno; this.příjmení = příjmení; this.mzda = mzda; odpracovanéroky = roky; // Definice metod. public string PřečtiJméno() return jméno; public void NastavJméno(string jméno) this.jméno = jméno; public string ČtiPříjmení() return příjmení; public void NastavPříjmení(string příjmení) this.příjmení = příjmení; public int ČtiMzdu() return mzda; public void NastavMzdu(int mzda) this.mzda = mzda; public int ČtiRoky() return odpracovanéroky; public void NastavRoky(int roky) odpracovanéroky = roky;
107 Třída deklaruje abstraktní datový typ Zaměstnanec, který je charakterizován svými atributy a metodami. Datové položky musí být soukromé, abychom dodrželi princip ukrývání dat. U zaměstnance sledujeme atributy: jméno, příjmení, mzdu a počet odpracovaných let. Všechny uvedené atributy odpovídají příslušný datovým položkám. Z definic je zřejmé, že jméno a příjmení zaměstnance budeme uchovávat v podobě textových řetězců, které budou instancemi systémové třídy System.String. Na uložení mzdy a počtu odpracovaných let nám poslouží datové položky typu int. Následuje Konstruktor. Konstruktor je speciální metoda třídy, která zodpovídá za korektní inicializaci její zakládaných instancí. Jedná seze syntaktického hlediska je konstruktor veřejně přístupnou metodou. Identifikátor konstruktoru se shoduje s identifikátorem třídy, v níž je konstruktor definován. Konstruktor nevrací žádnou návratovou hodnotu (v hlavičce konstruktor nesmí být použity ani klíčové slovo void, návratovou hodnotu prostě vůbec nespecifikuje). Pokud by v těle třídy nebyl explicitně definován žádný konstruktor, kompilátor jazyka C # by vygeneroval implicitní konstruktor, který hby byl veřejně přístupný a bezparametrický. Implicitní konstruktor zajistí implicitní inicializaci všech datových položek instance třídy. Pokud do těla třídy jistý konstruktor explicitně zavedeme, kompilátor nepoužije jeho implicitní verzi. V těle veřejně přístupného parametrického instančního konstruktor dochází k inicializaci instančního datových položek, které jsme definovali v datové sekci V hlavičce statického konstruktor nachází navíc modifikátor static. Statický konstruktor se věnuje inicializaci statických datových položek třídy. Statické datové položky nenáleží objektu ale přímo s samotnou třídě. Konstruktor může být na rozdíl od statického přetížen. Statický Konstruktor je vždy bezparametrický a vždy bez přístupového modifikátor. V rámci třídy je kterákoli z datových položek je přímo dostupná prostřednictvím svého identifikátoru. Na datovou položku se lze odvolat použitím klíčového slova this, za kterým následuje operátor přímého přístupu (.) a identifikátor požadované datové položky. Takto je zajištěno rozlišení, která entita v případě shodnosti identifikátorů představuje datovou položku a která formální parametr s inicializační hodnotou. Klíčové slovo this je identifikátorem speciální případ reference, tedy proměnné, ve které je uložena objektová reference určující aktuální instanci třídy. Aktuální instance třídy je vždy ta, s níž je prováděna jistá činnost (např. inicializace její datových položek, volání metody a pod.). Jelikož objekt má zachovat konzistenci a integritu svých dat, nemůže dovolit vnějšímu zdrojovému kódu svévolný přístup k datům objektu. Přitom však musí existovat způsob, jakým by uživatel mohl získat o objektu požadované informace. Právě veřejné přístupové metody umožňují předem naprogramovaný, ověřený a zejména bezpečný přístup k datům objektu. Ke každé datové položce proto navrhujeme dvě přístupové metody. Jedna z pro získání hodnoty datové položky, druhá pro její modifikaci.
108 9.3. Vizualizace deklarace třídy a Návrhář tříd (Class Designer) Integrované vývojové prostředí (Integrated Development Environment, IDE) Microsoft Visual Studio 2010 umožňuje vizualizovat jakoukoliv správně deklarovanou třídu. Pro automatické vygenerování souboru s grafickým modelem třídy musíme udělat toto: 1. V podokně Solution Explorer klikneme pravým tlačítkem myši na zdrojový soubor deklarací třídy (*. Cs), jejíž vizuální model chceme sestavit. 2. Z kontextové nabídky vybereme příkaz View Class Diagram.
109 3. Prostředí IDE vytvoří nový soubor s příponou *. Cd a vloží do něj grafický model, který vizuálně charakterizuje deklarovanou třídu a všechny její členy. Grafická podoba představuje tzv. diagram třídy (Class Diagram). Diagramy tříd sestavujemepomocí Návrháře tříd (Class Designer).
110 Diagram tříd je na obrázku zobrazen v rozšířeném režimu. Lze jej zobrazit i v kompatnéí formě. Přepínat lze pomocí tlačítka s ikonou dvojité šipky (zmiňované tlačítko se nachází v pravém horním rohu diagramu tříd). Z grafického hlediska se diagram tříd velmi podobá diagram tříd unifikovaného modelovacího jazyka UML. Podobně jako v jazyce UML, i v prostředí produktu Visual Studio 2008 je diagram tříd složen ze tří základních částí:
111 1. Identifikační část (podává informace o názvu třídy). 2. Datová část (podává informace o datových členech třídy). 3. Procedurální část (podává informace o metodách třídy). Návrhář tříd umožňuje dvoucestné modelování. Z deklarace třídy lze vytvořit grafický diagram a naopak. Pomocí jednoduchých grafických nástrojů a přidružených konfiguračních prostředků navrhneme diagram tříd, a pak necháme Návrháře tříd, aby vygeneroval syntaktická-sémanticky korektní deklaraci třídy. Programátor ale musí do těla třídy doplnit zdrojový kód vlastních metod.. Kromě deklarací tříd smíme vizualizovat i vztahy mezi třídami, které jsou založeny na agregaci, kompozici a dědičnosti Instance třídy a její použití Deklarováním třídy určíme naše požadavky, které mají mít instance, které z této třídy vzniknou. Proces zrodu instance třídy (objektu) se jmenuje instanciace třídy (vytvoření objektu instance na základě dané třídy. Třída virtuální,, instance je je skutečná ). Vytvoření instance tvoří několik etap. Objekt vždy vzniká (je instancován) užitím operátoru new. Generický tvar vytvoření instance třídy má tvar: T obj = new T ();
112 kde: T je identifikátor třídy. obj je identifikátor proměnné typu odkaz (reference). Jedná se tedy o definiční inicializaci proměnné obj referenčního typu. Jelikož jde o přiřazovací příkaz, můžeme jej rozdělit na tři části: 1. Výraz nacházející se na levé straně od přiřazovacího operátora (=). 2. Přiřazovací operátor. 3. Výraz nacházející se na pravé straně od přiřazovacího operátora. Začněme analýzou výrazu nalevo od operátoru =. Výraz T obj. představuje definici proměnné typu odkaz s identifikátorem obj. Jelikož typem definované odkazu je třída do okazu bude uložen okaz na objekt(nebo reference), která je nasměrovaný na instanci třídy T. Inicializační hodnotou odkazu je hodnota výrazu, který se nachází napravo od přiřazovacího operátora. Výraz new T () znamená použití operátora new s identifikátorem deklarované třídy T. Operátor new vytvoří instanci třídy T, přičemž provede tyto činnosti: 1. Na řízené haldě vyhledá a alokuje dostatečný paměťový prostor pro novou instanci třídy T. 2. Vytvoří instanci třídy, umístí ji do předem připraveného alokačního prostoru a inicializuje ji. Inicializaci provede veřejně přístupný instanční Konstruktor, který je volán operátorem new. Inicializaci však samozřejmě může provádět také parametrický instanční Konstruktor. Potom by však výraz na pravé straně přiřazovacího příkazu vypadal takto: T obj = new T (a1, a2, an); kde v závorkách jsou argumenty, které budou předány formálním parametrům parametrického instančního konštruktoru. 3. Vrátí typově silnou objektovou referenci na vytvořenou instanci třídy. Jediným způsobem, jak s instancí třídy pracovat, je použít objektovou referenci, kterou nám poskytne operátor new. Objektová reference jednoznačně identifikuje pozici instance třídy na řízené haldě. Objektová referencia, ktorá je návratovou hodnotou operátora new, je teda hodnotou celého výrazu new T(), resp. new T(ai, a2, an). Je samozrejme veľmi dôležité, aby sme poskytnutú objektovú referenciu uchovali, pretože len prostredníctvom nej môžeme využívať služby vytvorenej inštancie triedy. To sa koniec koncov aj deje: navrátenú objektovú referenciu ukladáme do definovanej odkazovej premennej obj. Vravíme, že táto odkazová premenná je inicializovaná objektovou referenciou identifikujúcou inštanciu triedy na riadenej halde.
113 Obr. 15: Vizualizácia inštanciácie triedy Vždy, když budeme chtít pracovat s vytvořeným objektem, musíme použít odkaz odkazující na daný objekt. K veřejně přístupným metodám objektu přistupujeme pomocí operátoru tečka (.). Následující fragment zdrojového kódu ukazuje vytváření instancí třídy Zaměstnanec a její praktické použití: static void Main(string[] args) // Vytvoření instance třídy Ucitel. Učitel uč1 = new Učitel("Emil", "Detektiv", 30000, 10); // Volání služeb vytvořené instance. Console.WriteLine("Údaje o učiteli \n" + "Jméno: " + uč1.přečtijméno() + "\n" + "Příjmení: " + uč1.čtipříjmení() + "\n" + "Plat: " + uč1.čtimzdu() + "\n" + "Odpracované roky: " + uč1.čtiroky(); Console.ReadLine(); Na rozdíl od jazyka C + +, vývojáři v jazyce C # 3.0 nejsou povinni dynamicky založené objekty z řízené haldy explicitně uvolňovat. Odstraňování nepotřebných objektů má na starosti automatický správce paměti (Garbage Collector, GC), který dokáže rozpoznat ty objekty, které se již nepoužívají. Generický syntaktický instanciační příkaz se dá rozdělit do dvou samostatných příkazů, samozřejmě se zachováním původní funkcionality: T obj;
114 obj = new T (); První příkaz vytváří nový odkaz obj. Do takto definované proměnné můžeme uložit objektovou referenci na instanci třídy T. Bude ale tato proměnná implicitně inicializována? Abycom byli schopno opovědět, musíme zjistit více informací o oblasti platnosti a charakteru proměnné typu odkaz. Platí, že pokud by tato proměnná vystupovala jako lokální proměnná (definovaná například v těle hlavní metody Main), pak by nebyla implicitně inicializována. Kdyby se ale definiční příkaz vytvářející odkaz nacházel v těle třídy, implicitní instanční Konstruktor by zajistil její implicitní inicializaci. Implicitní inicializační hodnota referenční proměnné by pak byla nulová reference, reprezentovaná klíčovým slovem null. Pokud by proměnná nebyla vůbec inicializována, kompilátor jazyka by nás na tuto skutečnost upozornil. V každém případě je nepřípustné používat neinicializované proměnné. Inicializace definované referenční proměnné se odehrává ve druhém příkazu, který je spojen s vytvořením instance třídy T Konstruktory Konstruktory lze rozdělit do několika skupin: Instanční Konstruktor. Implicitní instanční Konstruktor. explicitní instanční Konstruktor. Bezparametrický explicitní instančního Konstruktor. Parametrický explicitní instančního Konstruktor. Statický Konstruktor. Pokud do těla instanční (nestatickej) třídy nevložíte žádný Konstruktor, kompilátor automaticky vygeneruje implicitní instanční Konstruktor. Třída s implicitním instančním konstruktorem: class A // Tělo třídy je prázdné. Kompilátor do něj automaticky // vloží implicitní bezparametrický instanční Konstruktor. Třída s explicitním parametrickým instančním konstruktor: class C public C (int x)
115 // Tělo parametrického konstruktoru. Je povoleno, aby se ve třídě nacházelo několik definic explicitního instančního konstruktor. Bezparametrická verze explicitního instančního konstruktor smí být právě jedna. Naopak, parametrická verze explicitního instančního konstruktor se může vyskytovat ve více verzích,, pokud se ale tyto verze liší seznamy svých formálních parametrů. Tedy, že různé definice explicitního parametrického instančního konstruktor se mohou lišit: počtem formálních parametrů, datovými typy formálních parametrů nebo pořadím formálních parametrů. Pokud v těle třídy existuje explicitní bezparametrický instanční konstruktor a několik definic explicitního parametrického instančního konstruktor, tak říkáme, že instanční Konstruktor třídy je přetížen. Klient třídy si tedy může vybrat z více exemplářů konstruktoru a inicializovat tak vytvářené instance třídy různými způsoby. O tom, která z různých definic instančního konstruktor bude zavolána, rozhoduje počet a typy skutečných argumentů. class D class C public D () // Tělo bezparametrického konstruktoru. public D (int x) // Tělo 1. verze přetíženého parametrického konstruktoru. public D (int x, E y) // Tělo 2. verze přetíženého parametrického konstruktoru. V těle třídy D vidíme jednu definici explicitního bezparametrického instančního konstruktor a dvě verze explicitně definovaného parametrického instančního konstruktor. Konstruktor třídy D je tedy přetížen. První parametrická verze konstruktor pracuje pouze s jedním formálním parametrem, který očekává 32bitový celočíselný argument. Druhá parametrická verze konstruktoru má složitější strukturu: kromě celočíselného formálního parametru se v ní nachází i druhý formální parametr s typem, jehož identifikátor je E. Z deklarace třídy D nelze určit, co typ E představuje. Může jít o název: struktury, enumeračného typu,
116 třídy, delegáta, nebo rozhraní. Pokud E reprezentuje strukturu, tak formální parametr bude inicializován instancí této struktury. Pokud je E identifikátorem enumeračného typu, tak formální parametr bude inicializován čítačem. Pokud bude E třída, tak formální parametr získá odkaz na instanci této třídy. Když bude identifikátor E představovat delegáta, formální parametr bude schopen akceptovat odkaz na instanci tohoto delegáta (instance delegáta přitom obsahuje odkaz na cílovou metodu, která bude pomocí delegáta nepřímo aktivována). V případě, že bude E rozhraním, tak formální parametr bude moci přijmout odkaz na instanci jakékoliv třídy, která rozhraní E implementuje Statický Konstruktor a statické členy instančního tříd Význam statického konstruktor spočívá v inicializaci statických datových členů třídy. Statické datové členy třídy (také statické datové položky třídy) jsou definovány pomocí modifikátoru static. Statické datové položky a statický Konstruktor se smějí vyskytovat v instančních a statických třídách. Když jsou datové položky třídy statické, existují vždy v právě jediném exempláři bez ohledu na to, kolik instancí třídy existuje. Naopak, všechny instance třídy mohou využívat statické datové položky třídy. Říkáme, že statické datové položky třídy jsou instancemi třídy sdílené. I zde se zachovává princip ukrývání dat, a proto jsou (implicitně) definovány jako soukromé. Vedle datových položkech se mohou v tělech tříd vyskytovat i jiné statické členy, například metody nebo vlastnosti. Obecná definice statického konstruktoru v těle instanční (a také statické) třídy je následující: static T () // Tělo statického konstruktor. T je název instanční, resp. statické třídy, ve které je deklarace statického konstruktoru. Při práci se statickým konstruktorem musíme vědět toto: 1. Statický Konstruktor je implicitně soukromý (explicitně nesmí obsahovat žádné přístupové modifikátory).
117 2. Statický Konstruktor je vždy bezparametrický. 3. Statický Konstruktor nemůže být přetížen. Kromě bezparametrické verze, nejsou v těle třídy přípustné žádné jiné verze statického konstruktor. 4. Statický Konstruktor není nikdy explicitně aktivován. Je to proto, že statický konstruktor je automaticky volán v těchto případech: Před vytvořením první instance třídy (platí pouze pro instanční třídy se statickým konstruktorem). Před prvním pokusem o přístup k statickému členu třídy. 6. Statický Konstruktor se může vyskytovat pouze v tělech tříd, ne struktur. V ukázce kódu je uvedena deklarace instančníh třídy se statickou datovou položkou, statickým konstruktor a statickou metodou. Zde pomocí statické datové položky lze zjistit, kolikrát byla volána její instanční metoda M. class K // Definice statické datové položky. static int početvolaní; // Definice statického konstruktor. static K () početvolaní = 0; // Definice instančního metody. public void M () početvolaní ++; // Definice statické metody. public static int ZjistiPočetVolaní () return početvolaní; Všechny statické členy třídy jsou uvedeny modifikátorem static. Statický Konstruktor inicializuje statickou datovou položku. Zde je smysluplná inicializační hodnota je nula, protože v době inicializace statické datové položky ještě nebyla instanční metoda ani jednou zavolána. Vždy, když bude zmíněná metoda zavolána, inkrementuje se hodnota statické datové položky třídy. Pro zjištění aktuálního počtu spuštění metody využijeme statickou metodu, která nám na požádání poskytne aktuální hodnotu statické datové položky. Deklarovanou třídu použijeme v dalším fragmentu zdrojového kódu programovacího jazyka C # 3.0:
118 static void Main (string [] args) K obj = new K (); for (int i = 0; i <20; i + +) obj.m (); Console.WriteLine ("Metoda byla zavolána" + K.ZistiPočetVolaní () + "krát."); Instanční metody třídy smí přistupovat k instančním a statickým datovým položkám třídy. Protože statické datové položky sdílejí všechny instance třídy.statické metody mohou přistupovat jen k statickým, ale už ne k instančním datovým položkám třídy. Vzhledem k tomu, že statická metoda může být zavolána bez nutnosti vytvoření třídy, pokus o přístup k instančním datovým položkám by skočil abnormálním ukončením programu, jelikož ješte neexistují Mechanismus typové inference (MTI) Kompilátor jazyka dokáže automaticky určit datový typ lokální proměnné nebo odkazu proměnné podle jejího inicializačního výrazu. Implicitní stanovení datového typu lokální proměnné uskutečňuje mechanismus typové inferencie (MTI). Obecný zápis instanciačního příkazu třídy, který pracuje s MTI, je následující: var obj = new T (); nebo var obj = new T (p1, p2, pn); var je klíčové slovo určující, že typ lokální proměnné bude implicitně inferovaný. obj je identifikátor lokální proměnné. T je název třídy, jejíž instance je vytvářena. p1, p2, pn jsou argumenty parametrického konstruktoru třídy T. Jakmile MTI vyhodnotí výraz na pravé straně od přiřazovacího operátora, určí typ lokální proměnné. V této souvislosti je důležité uvést, že datový typ lokální proměnné je pomocí MTI určen vždy v době překladu zdrojového kódu. MTI smí být aplikován i při vytváření lokálních polí instancí tříd. Generický zápis zdrojového kódu se pak změní takto: var poleobjektu = new [] new T (), new T () ; nebo var poleobjektu = new [] new T (p1,p2,... pn), new T (p1,p2,... pn) ;
119 V 1. případě vytváříme pole dvou instancí třídy T, které jsou inicializovány bezparametrickým instančním konstruktorem. Ve 2. případě vytváříme opět pole dvou instancí třídy T, přičemž obě jsou inicializované parametrickými instančními konstruktoru. // Deklarace třídy. class T // Definice statické datové položky třídy. static int t; public T () Console.WriteLine ("Instance č. 0", + + t); class Program static void Main (string [] args) // Implicitní určení datového typu // lokální proměnné pomocí MTI. var poleobjektu = new [] new T (), new T (); Console.Read (); Při vytváření pojmenovaných tříd je použití MTI pouze volitelnou pomůckou, zásadně jiná je situace při vytváření instancí anonymních tříd. Identifikátor anonymní třídy nám není znám, nevíme explicitně určit datový typ lokální proměnné, do které bude uložen odkaz na vytvořenou instanci anonymní třídy. Zde vždy musíme použít anönymní typ s kláčovým slovem var Skalární instanční vlastnosti třídy Skalární instanční vlastnost je programovou konstrukcí, která zapouzdřuje dvě přístupové metody get a set, jejichž úkolem je získání, resp. úprava hodnoty požadované instanční datové položky. Podívejme se, jak by vypadala deklarace třídy Zaměstnanec, pokud abychom množinu přístupových metod nahradili skalárními instančními vlastnostmi: class Učitel
120 private string jméno; private string příjmení; private int mzda; private int odpracovanéroky; public Učitel (string jméno, string příjmení, int mzda, int roky) this.jméno = jméno; this.příjmení = příjmení; this.mzda = mzda; odpracovanéroky = roky; // Definice skalárních instančních vlastnosti. public string Jméno get return jméno; set jméno = value; public string Příjmení get return příjmení; set příjmení = value; public int Mzda get return mzda; set mzda = value; public int OdpracovanéRoky get return odpracovanéroky; set odpracovanéroky = value; V těle třídy se nacházejí definice čtyř skalárních instančních vlastností: Jméno,
121 Příjmení, Mzda a OdpracovanéRoky. Všechny skalární instanční vlastnosti jsou veřejně přístupné a sdílejí podobnou syntaktickou formu. Každá z těchto skalárních instančních vlastností je svázana s jednou datovou položkou. Například skalární instance vlastnosti Jméno:. public string Jméno get return jméno; set jméno = value; Skalární instanční vlastnost má svůj datový typ, který je shodný s datovým typem datové položky, se kterou je tato vlastnost propojena. V případě skalární instanční vlastnosti Jméno je to primitivní odkazový datový typ string. Skalární instanční vlastnosti mohou získávat, resp. modifikovat hodnoty přidružených datových položek. Pokud je vlastnost schopna nejen získávat, ale i upravovat hodnoty datových položek, označuje se jako "vlastnost určená pro čtení a zápis". Skalární instanční vlastnost Jméno je určena pro čtení i zápis, ve svém těle obsahuje dvě přístupové metody get a set. Přístupová metoda get obsahuje zdrojový kód, který poskytne hodnotu datové položky uživateli, přístupová metoda set změní hodnotu datové položky. V těle přístupové metody set je zapsán přiřazovací příkaz, který inicializuje datovou položku. Samotná inicializační hodnota je uložena v proměnné value. Tato proměnná je speciální proměnnou jazyka, nikdy nevytvářmeí. Typ proměnné value je implicitně inferovaný tak, aby byl totožný s typem skalární instanční vlastnosti. Dodejme, že definici přístupové metody get, resp. přístupové metody set, můžeme v těle skalárních instančního vlastnosti vynechat (ne však obě současně). Pokud se bude v těle vlastnosti nacházet jen přístupová metoda get, vytvoříme vlastnost, která bude určena jen "pro čtení" hodnoty cílové datové položky. Analogicky, pokud v těle vlastnosti bude definována jen přístupová metoda set, půjde o vlastnost určenou "jen za zápis" hodnoty do cílové datové položky. Fragment zdrojového kódu jazyka C # 3.0, který instancí třídy Zaměstnanec a využívá množinu skalárních instančních vlastností, vypadá takto: static void Main (string [] args) Učitel uč1 = new Učitel ("Emil", "Detektiv", 30000, 10); Console.WriteLine ( "Údaje o zaměstnanci: \n" + "Jméno:" + uč1.jméno + "\n" + "Příjmení:" + uč1.příjmení + "\n" +
122 "Mzda:" + uč1.mzda + "\n" + "Odpracováno roky:" + uč1.odpracovanéroky; Nepochybně, práce s instancí třídy pomocí skalárních instančních vlastností je elegantnější a přirozenější. Z pohledu uživatele se vlastnost jeví jako inteligentní datová položka, i když ve skutečnosti jde o programovou konstrukci, která zapouzdřuje dvojici přístupových metod. Otázky k probranému učivu Jakým způsobem je deklarovana třída Co je to instance třídy a kde ji lze použít Je Statický Konstruktor parametricky Co je to mechanismus typove inference Shrnutí pojmů kapitoly (podkapitoly) Deklarace třídy je složena z hlavičky třídy a jejího těla. V hlavičce se musí nacházet klíčové slovo class, podle kterého dáváme kompilátoru na známost, že se chystáme deklarovat třídu jako nový uživatelsky deklarovaný datový typ. Za klíčovým slovem class je umístěn identifikátor třídy, který jednoznačné pojmenovává třídu v dané oblasti platnosti. Tělo třídy je ohraničeno složenými závorkami (), v nichž se nacházejí definice členů třídy. Význam statického konstruktor spočívá v inicializaci statických datových členů třídy. Statické datové členy třídy (také statické datové položky třídy) jsou definovány pomocí modifikátoru static. Statické datové položky a statický Konstruktor se smějí vyskytovat v instančních a statických třídách. Kompilátor jazyka dokáže automaticky určit datový typ lokální proměnné nebo odkazu proměnné podle jejího inicializačního výrazu. Implicitní stanovení datového typu lokální proměnné uskutečňuje mechanismus typové inferencie (MTI). Skalární instanční vlastnost je programovou konstrukcí, která zapouzdřuje dvě přístupové metody get a set, jejichž úkolem je získání, resp. úprava hodnoty požadované instanční datové položky. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification ->
123 download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 10 Čas ke studiu: xx hodin
124 Cíl Po prostudování tohoto odstavce budete umět vytvořit automaticky implementovné skalární instanční vlastnosti třídy používat metodu třídy finalizér Stavba řízené haldy a řídící algoritmy automatického správce paměti Výklad Automaticky implementované skalární instanční vlastnosti třídy Standardní chování přístupových metod get a set skalárních instančních vlastností třídy můžeme charakterizovat takto: 1. Přístupová metoda get získává hodnotu soukromé datové položky instance třídy. 2. Přístupová metoda set modifikuje hodnotu soukromé datové položky instance třídy. Pokud nemáme zájem o pokročilou konfiguraci přístupových metod get a set (např. za účelem zapracování pokročilé aplikační logiky), můžeme přenechat kompletní sestavení těl přístupových metod vlastností a vytvoření definic spřízněných datových položek kompilátoru. Tak vytvoříme automaticky implementovné skalární instanční vlastnosti třídy. Dále uvádíme praktickou ukázku třídy, jejíž vlastnosti jsou automaticky implementovány. // Deklarace třídy, která využívá automaticky implementované // skalární instanční vlastnosti. class tablet public string Značka get; set; public ushort KapacitaPamětiRam get; set; public byte InterniPamět get; set; Instance této třídy budou reprezentovat oblíbené tablety. U každého tabletu nás budou zajímat tři atributy: značka, kapacitu interní paměti v GB a kapacitu paměti RAM v MB. V těle třídy jsou definovány tři skalární instanční vlastnosti, které budou kompilátorem automaticky implementovány. Automatické impůlementace skalárních instančních vlastností do těla třídy docílíme tím, žedo těl příslušných vlastností umístíme jen deklarace (ne definice) přístupových metod get a set. Kompilátor provede následující operace: Tělo třídy rozšíří o definici množiny soukromých datových položek. Vztah mezi datovými položkami a vlastnostmi zůstává nezměněn a popisuje ho relace typu 1:1.
125 Vytvoří definice původně pouze deklarovaných přístupových metod get a set. Vygenerované definice budou fungovat podle standardního modelu: metoda get bude získávat hodnotu datové položky, zatímco metoda set bude tuto hodnotu měnit. Kompilátor vytvoří i veřejně přístupný implicitní instanční Konstruktor. O tom, že kompilátor provedl konstatováno operace, se můžeme přesvědčit při pohledu na MSIL kód předmětné třídy Deklarace třídy s automaticky prováděnými vlastnostmi je funkčně ekvivalentní kompletní deklaraci stejnojmenné třídy: class tablet1 public string Značka get return Značka; set Značka = value; public ushort KapacitaPamětiRam get return KapacitaPamětiRam; set KapacitaPamětiRam = value; public byte InterniPamět get return InterniPamět; set InterniPamět = value; Kompletní deklarace třídy se odlišuje explicitním definováním soukromých datových položek, které jsou pro členy třídy vždy přístupné. Datové položky, které kompilátor
126 samočinně generuje při automatické implementaci skalárních instančního vlastností však nejsou nijak dosažitelné. Programátor tedy nemá možnost s nimi přímo zacházet. Použití instance je pak již jednoduché: tablet GoClever = new tablet (); GoClever.Značka = "GoClever TAB R "; GoClever.KapacitaPamětiRam = 4096; GoClever.InterniPamět = 16; Console.WriteLine ("Produktové informace o" + "tabletu: \n" + "Značka:" + GoClever.Značka + "\n" + "Kapacita paměti (MB):" + GoClever.KapacitaPamětiRam + "\n" + "Kapacita interní paměti GB:" + GoClever.InterniPamět); Console.Read (); Indexované instanční vlastnosti třídy Kromě skalárních instančních vlastností smíme rozšířit tělo třídy také o definice indexovaných instančních vlastností. Indexovaná vlastnost nám umožňuje inicializovat instanci třídy podobně, jako kdyby byla tato instance polem. Indexovaná instančního vlastnost se definuje následujícím způsobem: // Deklarace třídy, která využívá automaticky implementované // skalární instanční vlastnosti. public T1 this [T2 i] get / * Zdrojový kód přístupové metody get. * / set / * Zdrojový kód přístupové metody set. * / T1 je datový typ indexované vlastnosti a současně i datový typ množiny datových položek, s nimiž je indexovaná vlastnost asociována. this je identifikátor indexované vlastnosti. T2 je datový typ formálního parametru indexované vlastnosti. i je identifikátor formálního parametru indexované vlastnosti. Tento formální parametr uchovává index určující požadovanou datovou položku instance třídy. Pro indexované instanční vlastnosti platí tato pravidla: 1. Definice indexovaných vlastností se mohou nacházet v deklaracích tříd a struktur. 2. Indexované vlastnosti mohou být přetížené. Je tedy možné vytvořit sadu definic stejnojmenné indexované vlastnosti. Pochopitelně, jednotlivé verze přetížené indexované vlastnosti se musí odlišovat svojí signaturou (seznamem formálních parametrů).
127 Třída Vektor, jejíž deklaraci přibližuje následující fragment zdrojového kódu jazyka C # 3.0, disponuje jednou indexovanou instančního vlastností. class Souradnice3D private int x, y, z; // Definice indexované instanční vlastnosti. public int this [int i] get switch (i) case 0: return x; case 1: return y; case 2: return z; default: throw new System.ArgumentOutOfRangeException (); set switch (i) case 0: x = value; break; case 1: y = value; break; case 2: z = value; break; default: throw new System.ArgumentOutOfRangeException (); Komentář ke třídě: Instance třídy představuje souřadnice bodu v prostoru. Vzhledem k tomu, že datovým obsahem instance je trojice vektorových složek, jako rozumné řešení se jeví zapracovat indexovanou vlastnost, která nám dovolí inicializovat instanci třídy (vektor) jako jednorozměrné pole. Indexovaná vlastnost je veřejně přístupná a parametrická, protože v její signatuře je umístěna definice jednoho formálního parametru. Podle hodnoty tohoto formálního parametru bude indexovaná vlastnost buď získávat hodnoty vektorových složek, nebo je upravovat. V těle indexované vlastnosti vidíme definice dvou přístupových metod get a set. Nejprve popišme přístupovoui metodu get.
128 Programový kód metody get indexované vlastnosti se vykoná, pokudbude požadavek na získání hodnoty jisté souřadnice bodu. Protože bod obsahuje 3 složky, můžeme je indexovat hodnotami z intervalu <0, 2>. Pro přístup prostřednictvím indexované vlastnosti je důležitá hodnota indexu, protože určuje, se kterou složkou vektoru budeme pracovat. Implemenrace je řešena příkazem switch, s hodnotou formálního parametru i. Pokud je i == 0, tak přístupová metoda get vrátí hodnotu datové položky x (tedy hodnotu 1. složky vektoru). V případě zvyšující se hodnoty indexu bude metoda get vracet vždy složku vektoru s vyšším pořadovým číslem. Vrácení hodnoty příkazem return již nevyžaduje v příslušné větvi case ukončovací příkaz break. Pokud se nedopatřením stane, že hodnota formálního parametru i bude mimo hranice intervalu <0, 2>, bude vykonán kód ve větvi default který vyvolá chybovou výjimku. Účelem existence přístupové metody set je nastavení hodnoty datové položky, která bude určena svým číselným indexem. Vícecestné rozhodování probíhá obdobně jao o metod get. Rozdíl spočívá v tom, že vybranou datovou položku inicializuje v rámci přiřazovacího příkazu. Jelikož přiřazovací příkaz neukončuje zpracování příkazů ve větvi case, musíme za něj přidat samostatný ukončení příkaz break. Následující program ukazuje, jaké praktické implikace s sebou přináší použití indexovaných instančního vlastností třídy. static void Main (string [] args) Souradnice3D bod = new Souradnice3D (); // Inicializace instance třídy pomocí indexované vlastnosti. bod [0] = 1; bod [1] = 1; bod [2] = 1; Console.WriteLine ("Bod má souřadnice [0, 1, 2]", bod [0], bod [1], bod [2]); Všimněme si, jak je inicializována vytvořená instance třídy Souřadnice3D. Za identifikátorem odkazu zapisujeme hranaté závorky a specifikujeme index datové položky (složky vektoru), se kterou si přejeme manipulovat. Syntakticky použití indexované vlastnosti opravdu vypadá, jako kdybycom s instancí třídy Souřadnice3D pracovali jako s jednorozměrný polem. V závislosti na tom, kde se výraz bod [i] nachází, bude zpracována buď přístupová metoda get, nebo přístupová metoda set indexované vlastnosti. Indexované vlastnosti prokazují své silné stránky i při práci s poli vektorů: static void Main (string [] args) Souradnice3D [] polyline = new Souradnice3D [10]; Random generátor = new Random (); for (int i = 0; i <10; i ++) polyline [i] = new Souradnice3D ();
129 Console.Write ("Bod č. 0 má souřadnice", i + 1); for (int m = 0; m <3; m ++) polyline [i][m] = generátor.next (1, 101); Console.Write ("\t 0 \t", polyline [i][m]); Console.Write ("\n"); Console.Read (); Tento zdrojový kód vytváří pole deseti instancí třídy Souřadnice3D, které mohou představovat souřadnice krajích bodů úseček lomené čáry. Naším záměrem je všechny sestrojené body inicializovat pomocí jejich indexovanými vlastnostmi. Inicializačními hodnotami budou pseudonáhodná celá čísla z intervalu <1, 100>. V dalším textu se budeme věnovat jen kritickým místům programu: 1. První příkaz vytváří jednorozměrné pole, jehož prvky budou moci byt inicializovány odkazy na instance třídy Souřadnice3D. Je důležité si uvědomit, že po vykonání tohoto příkazu existuje pole, ale není naplněno odkazy na žádné objekty (body). 2. Body jsou vytvořny až v těle nadřazeného cyklu for. Ve vnořeném cyklu for používáme indexované vlastnosti bodů na jejich inicializaci pseudonáhodných celými čísly. Výraz polyline [i] [m] znamená spuštění indexované vlastnosti i-té instance třídy Souřadnice3D Finalizér Finalizér je speciální metoda třídy, jehož úkolem je řídit dokončovací práce předtím, než bude instance třídy uvolněna z řízené haldy. Generický tvar finalizéru je následující: ~T () // Tělo finalizéru. T je identifikátor třídy, ve které těle se finalizér nachází. Pro finalizér platí tyto zásady: Finalizér se může vyskytovat pouze v tělech tříd (odkazových datových typů), nikdy ne v tělech struktur (hodnotových datových typů). Finalizér nemá žádnou návratovou hodnotu (podobně jako při konstruktor nelze použít ani klíčové slovo void). Finalizér musí být vždy bezparametrický. Nemůže tedy definovat žádné formální parametry. Finalizér se nesmí přetížit, což znamená, že jedna třída může definovat právě jeden finalizér.
130 Pokud bázová třída explicitně definuje svůj finalizér, tento není děděn odvozenou třídou. Finalizér nesmí být volán přímo klientským zdrojovým kódem. Je to proto, že finalizér je v případě potřeby implicitně aktivován automatickým správcem paměti virtuálního exekučního systému. Pokud do těla třídy umístíme definici finalizéru, kompilátor automaticky vloží do jeho těla kód, který bude volat finalizér primární systémové bázové třídy System.Object. Pokud je ukončena instance odvozené třídy, nejdříve je volán její finalizér. Po zpracování zdrojového kódu, který je uložen v těle finalizéru odvozené třídy dochází k aktivaci finalizéru bázové třídy a exekuci v něm uložených programových příkazů. Jelikož finalizace instancí tříd na řízené haldě je nedeterministická, nelze přesně predikovat okamžik, kdy bude finalizér spuštěn. Na rozdíl od konstruktoru, finalizér nesmí být statický. Podle toho, zda instance vznikla ze třídy, která explicitně definuje svůj finalizér nebo ne, rozlišujeme dva základní modely finalizace instancí tříd: implicitní a explicitní finalizace. Implicitní finalizace se týká pouze těch instancí, které vznikly z tříd, které explicitně nedefinují své finalizéry. Stane-li se taková instance třídy nepotřebnou, automatický správce paměti provede její okamžitou dealokaci. Na druhou stranu, pokud instance vzešla z třídy s explicitně definovaným finalizérom, bude vyžadovat explicitní finalizaci Destruktory v jazyce C + + a finalizéry v jazyce C # 3.0 Přesto, že syntaktický obraz finalizéra jazyka C # 3.0 je stejný jako vzhled Destruktorů v jazyce C + +, mezi oběma entitami existují významné sémantické rozdíly. Vůbec nejdůležitějším rozdílem je skutečnost, že zatímco v jazyce C + + je finalizace instancí tříd deterministický, v prostředí jazyka C # 3.0 pracujeme s nedeterministickým dokončením. Pokud v jazyce C + + vytvoříme dynamicky instanci třídy (pomocí operátoru new), můžeme na ni kdykoliv aplikovat operátor delete. Použití operátoru delete implikujeme okamžitou aktivaci Destruktoru, který je zodpovědný za realizaci množinu finalizačních operací předtím, než bude instance třídy dealokovaná. Je tedy zřejmé, že programátor v jazyce C + + je schopen explicitně vyvolat destruktor na požádání kdykoli, když to uzná za vhodné. V jazyce C # 3.0 nemůžeme finalizér aktivovat přímo, protože tuto kompetenci za vývojářů přebírá virtuální exekuční systém. V jazykové specifikaci C # se nenachází žádný operátor delete, nebo jiný, funkčně příbuzný operátor. automatickou dealokace. Analýza životních cyklů instancí tříd V této kapitole podáme exaktní vysvětlení analýzy životních cyklů instancí tříd jako uživatelsky deklarovaných odkazových datových typů. Životní cyklus instance třídy se skládá z těchto fází:
131 1. Vytvoření instance třídy. 2. Využívání služeb instance třídy. 3. Ukončení života instance třídy. Instance třídy je výsledkem inštanciačného procesu, který byl podrobně rozebrán v části Inštanciácia třídy a použití zrozené instance. V 1. stádiu životního cyklu jsou z pohledu dalšího života instance třídy důležité především dvě operace: alokace instance a inicializace instance. Jakmile je instance třídy alokována v řízené haldě a náležitě inicializována, může začít poskytovat služby svým klientům. Počínaje tímto okamžikem vstupuje instance třídy do 2. stadia svého životního cyklu, které je z pohledu celkové analýzy nejvýznamnější. Instance třídy vystupuje jako server, který na požádání plní požadavky konečné a neprázdného množiny klientů. Styl chování instance třídy je plně determinován komunikačním modelem klient - server. 3. etapa životního cyklu instance třídy je charakterizována provedením finalizačných prácí a dealokáciou instance z řízené haldy. Dealokace paměťového prostoru je spojena s likvidací instance, čímž končí její životní cyklus. 3.etapu životního cyklu instance třídy lze charakterizovat funkcí (/), která analyzuje počet operací (P) realizovaných instancí v průběhu určitého časového intervalu (t): / (T) = P, VTE <1,2,..., n> Počet operací prováděných instancí třídy je přímo závislý na počtu zpráv, které instance třídy přijímá od klientů. Vizuální interpretaci průběhu 2. etap vybraných instancí tříd uvádějí obr.
132 Obr.: Vizualizace 2. stadia životního cyklu instance třídy (1. ukázka) Obr.: Vizualizace 2. stadia životního cyklu instance třídy (2. ukázka) Obr.: Vizualizace 2. stadia životního cyklu instance třídy (2. ukázka) Přesto, že křivka grafu monitorující činnost instance třídy může mít variabilní průběh, vždy dokážeme spolehlivě identifikovat tři základní body: 1. Bod A se souřadnicemi [ti, Pá]. V tomto bodě instance vstupuje do 2. etapy svého životního cyklu. Můžeme tedy říci, že instance úspěšně překonala 1. stádium, ve kterém došlo
133 k jejímu alokaci a inicializaci. Podle charakteru inštanciačného procesu může existovat mezi 1. a 2. stádiem životního cyklu instance různé dlouhý časový interval. V některých případech je vzniklá časová latence minimální - tento jev zaznamenáváme zejména u instancích, které nevyžadují náročnou inicializaci. Naopak, pokud instance vytváří v inicializační fázi nové datové objekty, nebo alokuje paměť pro dynamické datové struktury, do 2. etapy svého životního cyklu vstupuje s citelněji zpožděním. 2. Bod B se souřadnicemi [t2, P2]. Toto je bod maximálního pracovního vytížení instance třídy. Bod maximální aktivity dosahuje instance třídy v momentě, kdy poskytuje své služby maximálnímu počtu klientů. Na rozdíl od bodu A je možné, aby instance třídy dosáhla bodu maximálního pracovního vytížení i vícekrát během 2. etapy svého životního cyklu. Podotknime, že instance třídy může být naprogramována tak, aby byla schopna sama flexibilně modifikovat množství maximálního pracovního vytížení, resp. maximální počet obsluhujících klientů Finalizace instancí tříd 3. stádiem životního cyklu instance třídy je finalizace. Jak jsme uvedli, v prostředí jazyka C # 3.0 rozlišujeme mezi implicitní a explicitní dokončením. Pokud instance třídy neplánuje v 3. stádiu svého životního cyklu provést rozsáhlé dokončovacích prací, její finalizace bude implicitní. S implicitní finalizací se setkáváme u všech instancích, které vznikly ze tříd, v tělech kterých se nenacházely definice finalizérov. Implicitní finalizace je výkonnostně příznivější, protože vyžaduje realizaci jen několika pracovních cyklů automatického správce paměti. Při implicitní finalizaci je instance třídy uvolněná z řízené haldy bez nutnosti spuštění jejího finalizéra. Pokud byla k vytvoření instance použita třída, která definuje svůj finalizér, tak taková instance bude vyžadovat explicitní finalizaci. To znamená, že než bude moci být instance třídy dealokovaná z řízené haldy, bude muset být spuštěn její finalizér a stejně budou muset být zpracovány všechny programové příkazy, které se v těle finalizéra nacházejí. Než budeme moci přesněji objasnit proces explicitní finalizace instancí tříd v jazyce C # 3.0, musíme se seznámit se stavbou řízené haldy a řídícími algoritmy automatického správce paměti Stavba řízené haldy a řídící algoritmy automatického správce paměti Ve fyzickém procesu aplikace.net je oblast dynamické paměti (řízená halda) kontrolována automatickým správcem paměti. Řízená halda je segmentován na objektových generací, které jsou identifikovatelné podle svého pořadového čísla. Jde o následující objektové generace: 1. Objektová generace č. 0 (1. objektová generace). 2. Objektová generace č. 1 (2. objektová generace). 3. Objektová generace č. 2 (3. objektová generace). Každá objektová generace disponuje alokačních kapacitou, určující počet objektů, které lze v příslušné objektové generaci uskladniť.19 Objekty jsou z hlediska času, během kterého pracují podle modelu klient - server, klasifikovány do tří generací: 1. Nejmladší objekty (objekty jen nedávno vytvořené). 2. Objekty se středně dlouhým životním cyklem.
134 1. objektová f 2. objektová t 3. objektová generace generace generace Obr.: Generační složení řízené haldy Jak si můžeme všimnout, objekty v objektových generacích jsou umístěny v sekvenční posloupnosti, čímž vyplňují souvislý paměťový blok. Takové uspořádání je efektivní, protože umožňuje velmi rychle vyhledat objekt, se kterým chceme pracovat. Řídicí algoritmy automatického správce paměti garantují, že mezi objekty nebudou vznikat žádné prázdná místa, jejichž přítomnost by snižovala efektivitu při práci s objekty. Automatický správce paměti vždy uchovává ukazatel identifikující paměťovou pozici, na které bude moci být přidělený nový objekt (obr.). 1. objektová i 2. objektová * 3. objektová generace generace generace Obr.: ukazatel determinující pozici nově přiděleného objektu v 1. objektové generaci Všechny objekty, které vzniknou, se z pohledu paměťového managementu nejdříve ocitají v 1. objektové generaci. 1. objektová generace je kapacitně projektována tak, aby dokázala absorbovat nově vznikající objekty. Když se kapacita 1. objektové generace zaplní a přesto
135 přijde od aplikace požadavek na sestrojení nového objektu, automatický správce paměti uskuteční tzv.. kolekci 1. objektové generace. V procesu kolekce dochází k proskenována 1. objektové generace, přičemž cílem této akce je určení množiny nedosažitelných objektů. Nedosažitelné objekty jsou nepoužívané objekty, na které nejsou nasměrovány žádné odkazy. Takové objekty mohou být podrobeny finalizaci. Když automatický správce paměti zahájí kolekci 1. objektové generace, předpokládá, že všechny přítomné objekty jsou nedosažitelné. Činnost správce paměti pokračuje procházením stromu kořenů a generováním grafu všech dosažitelných objektů. Strom kořenů je datová struktura, která obsahuje odkazy (objektové reference). Tyto odkazy mohou být uloženy na různých místech, ale nejčastěji jsou uskladněny v proměnných odkazových datových typů. Graf dosažitelných objektů seskupuje odkazy na objekty, které jsou "živé" - jde tedy o objekty, které aplikace.net stále využívá. Naopak, všechny ostatní objekty, na které se odkazy v grafu nenacházejí, může automatický správce paměti považovat za nedosažitelné objekty. Předpokládejme, že automatický správce paměti zjistí, že v 1. objektové generaci se nacházejí 3 nedosažitelné objekty. Jelikož tyto objekty již nejsou potřebné, mohou být finalizovány. Rozviňme naše úvahy dál a řekněme, že 2 ze zmíněné trojice objektů smí být implicitně finalizovány. Pokud je to tak, tyto objekty budou podrobeny destrukci a jejich alokační kapacita bude přístupná pro budoucí použití. Zůstává nám však jeden objekt, který vyžaduje explicitní finalizaci. Pokud založen objekt vzešel ze třídy, která explicitně definuje svůj finalizér, tak ještě před inicializací tohoto objektu (před aktivací konstruktor objektu) byl ukazatel něj přidán do seznamu objektů vyžadujících explicitní finalizaci (ZOVEF). Když automatický správce paměti diagnostikuje nedosažitelný objekt jako objekt, který vyžaduje explicitní finalizace, ví, že je nutné aktivovat jeho finalizér. Proto správce paměti přesune ukazatel na objekt ze seznamu objektů vyžadujících explicitní finalizaci do seznamu dosažitelných objektů (zdů). Po této operaci dochází k obnovení stavu objektu, protože z původně nedosažitelný objektu udělal automatický správce paměti dosažitelný objekt. V této souvislosti je velmi důležité upozornit na jednu zásadní skutečnost: správce paměti provádí obnovení stavu objektu pouze za účelem jeho explicitní finalizace. Smyslem obnovení stavu objektu je "oživení" původně nedosažitelných objektu. Tak získáváme "živý" objekt, na kterém může být spuštěn finalizér. (Z uvedeného jasně vyplývá, že aktivace finalizéra nemůže být provedena ve spojení s nedosažitelným objektům.) Finalizace objektu, resp. objektů, na které se odkazuje zdů, je realizována asynchronní na samostatném, tzv.. finalizačnom programovém vlákně. Ačkoliv pořadí volání finalizérov jednotlivých objektů není determinováno, automatický správce paměti garantuje, že budou zpracovány finalizéry všech objektů, které požadují svou explicitní finalizaci. Po provedení všech příkazů finalizéra bude objekt opět označen jako nedosažitelný, avšak tentokrát bude opatřen příznakem, který říká, že objekt již byl explicitně dokončen. Při následující kolekci bude nedosažitelný a už explicitně dokončen objekt jednoduše zlikvidován, a to bez jakýchkoliv dodatečných akcií. Při druhé kolekci je tedy analyzován objekt okamžitě uvolněný, podobně jako implicitně finalizovány objekty.
136 Samozřejmě, může se stát, že automatický správce paměti objeví v 1. objektové generaci množství dosažitelných objektů, které nelze finalizovat (prostě z toho důvodu, že stále existují klienti, kteří využívají služeb těchto objektů). Vzhledem k tomu, že cílem automatického správce paměti je uvolnit paměťovou kapacitu na alokaci nových objektů, budou objekty, které přežijí kolekci, transportovány do objektové generace s vyšším pořadovým číslem. Dosažitelné objekty z 1. objektové generace se dostanou do 2. objektové generace. Přesun objektů mezi objektovými generacemi má významné praktické implikace: 1. V objektové generaci s nižším pořadovým číslem vznikne po přemístění objektu prázdné místo. Toto prázdné místo představuje paměťovou mezeru, přičemž je ohraničené paměťovým prostorem, který před svým přemístěním do objektové generace s vyšším pořadovým číslem okupovala instance třídy. Je evidentní, že čím více objektů bude z nižší objektové generace propagovaných do vyšší objektové generace, s tím větší pravděpodobností budou vznikat paměťové mezery. Intenzivní kreace paměťových mezer zapříčiňuje fragmentaci objektové generace řízené haldy. V zájmu optimalizovaného přístupu k objektům je nutné paměťové mezery odstranit a zajistit sekvenční uspořádání objektů. Eliminace paměťových mezer se uskutečňuje v procesu defragmentace paměti objektové generace. Po defragmentaci budou objekty v objektové generaci uspořádány sekvenčně a přístup k nim bude stejně efektivní jako dříve. 2. Při přesunu objektů z 1. do 2. objektové generace se původní odkazy na tyto objekty (směřující do 1. objektové generace) stávají neplatnými. Jelikož objekty se po transportu ocitnou ve 2. objektové generaci, je třeba aktualizovat původní odkazy na ně. Aktualizaci odkazů na objekty provádí automatický správce paměti ve své vlastní režii. Jakmile jsou odkazy aktualizovány, objekty jsou i nadále přímo dosažitelné, a to i přesto, že se změnila jejich fyzická pozice na řízené haldě. Kolekce 1. objektové generace řízené haldy je nejčastěji provedena při zaplnění této objektové generace. Pokud se automatickému správci paměti podaří získat dostatek paměťového prostoru na alokaci nových objektů, bude 1. objektová generace jedinou, ve které se kolekce vykoná. Ačkoliv za běžných okolností lze získat dostatek paměťového prostoru kolekcí 1. objektové generace, existují situace, kdy není automatický správce paměti schopen uvolnit dostatečný alokační prostor. V těchto případech musí správce paměti realizovat kolekci iv generaci s vyšším pořadovým číslem, tedy v 2. objektové generaci. Během kolekce dochází k následujícím akcím: Diagnostikuje se kolekce nedosažitelných objektů 2. objektové generace. nedosažitelné objekty jsou finalizovány. Pokud objekt nevyžaduje explicitní finalizace, bude okamžitě uvolněn. Objekty vyžadující explicitní finalizaci budou explicitně dokončeny tak, jak jsme již vysvětlili. Dosažitelné objekty, které přežijí kolekci, patří k objektům s dlouhou dobou životnosti, a proto budou přeneseny do objektové generace s vyšším pořadovým číslem, tedy do 3. objektové generace. Objekty z 1. objektové generace, které přežily kolekci, budou přesunuty do 2. objektové generace. Ve výjimečných případech se může stát, že vyhovující paměťový prostor nebude nalezen ani v 1. objektové generaci a ani ve 2. objektové generaci. Tehdy automatický správce paměti
137 provede kolekce všech tří objektových generací a uvolní nedosažitelné objekty. Dosažitelné objekty 3. objektové generace v této generaci i nadále zůstávají, protože už žádná další objektová generace neexistuje. Pokud by se ani po 3-fázové kolekci nepodařilo automatickému správci paměti dealokovať potřebné množství paměťového prostoru, virtuální exekuční systém ohlásí chybu při pokusu o rozvržení a exekuce aplikace.net bude ukončena. Řídicí algoritmy automatického správce paměti při kolekci 1. objektové generace, implicitní finalizaci nedosažitelných objektů a alokaci nově vytvořených objektů představují obr objektová 2. objektová * 3. objektová generace generace generace Obr. 23: Výsledkem kolekce 1. objektové generace jsou 3 nedosažitelné objekty 1. objektová * 2. objektová * 3. objektová generace generace generace
138 Obr. 24: Implicitní finalizace nedosažitelných objektů 1. objektová 2. objektová + 3. objektová generace generace generace Obr. 25: Umístění nových objektů do 1. objektové generace Segmentace řízené haldy do tří objektových generací přináší tyto pozitiva: 1. Klasifikuje objekty podle času jejich životnosti. 2. Umožňuje spravovat objekty samostatně podle jejich příslušnosti k určité objektové generaci.
139 3. Napomáhá produktivnější zprávě řízené haldy. Je totiž vždy jednodušší provést kolekci jedné, nebo dvou objektových generací, jak provádět kolekci celé řízené haldy. 4. Přestože transporty objektů mezi různými objektovými generacemi jsou náročné na práci procesoru, automatický správce paměti eliminuje paměťovou roztříštěnost a vždy půjčí objekty v sekvenční posloupnosti, což produkuje minimální časové latence při práci s nimi. Objekty, jejichž alokační kapacita je větší než bajtů, jsou z pohledu automatického správce paměti považovány za tzv.. kapacitně náročné objekty. Objekty tohoto typu jsou alokované a spravována na samostatné řízené haldě. Jde o řízenou haldu pro kapacitně náročné objekty. V této řízené haldě jsou objekty uskladněny v souvislých paměťových segmentech. Na rozdíl od 3-generační řízené haldy, řízená halda pro kapacitně náročné objekty není členěna na objektové generace. Místo toho vystupuje jako monolitický celek, který je poskládaný z více paměťových segmentů. I životní cykly kapacitně náročných objektů jsou řízeny automaticky, ale s tím rozdílem, že se neuskutečňují žádné přesuny objektů. Vzhledem k tomu, že objekty jsou kapacitně náročné, jejich transporty by snižovaly výkon av konečném důsledku by působily kontraproduktivní. Otázky k probranému učivu Co je to finalizér Popište přístupové metody get a set Jaká pravidla platí pro indexované instanční vlastnosti Popište analýzu životních cyklů instancí tříd Shrnutí pojmů kapitoly (podkapitoly) Standardní chování přístupových metod get a set skalárních instančních vlastností třídy můžeme charakterizovat takto: 1. Přístupová metoda get získává hodnotu soukromé datové položky instance třídy. 2. Přístupová metoda set modifikuje hodnotu soukromé datové položky instance třídy. Indexované instanční vlastnosti třídy Kromě skalárních instančních vlastností smíme rozšířit tělo třídy také o definice indexovaných instančních vlastností. Indexovaná vlastnost nám umožňuje inicializovat instanci třídy podobně, jako kdyby byla tato instance polem. Finalizér je speciální metoda třídy, jehož úkolem je řídit dokončovací práce předtím, než bude instance třídy uvolněna z řízené haldy. Životní cyklus instance třídy se skládá z těchto fází: 1. Vytvoření instance třídy. 2. Využívání služeb instance třídy.
140 3. Ukončení života instance třídy. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, 2007.
141 11. Téma 11 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět Definovat dědičnost, abstraktní třídu, zapečetěné třídy, parciální třídy statické třídy Polymorfismus implementován pomocí veřejné jednoduché dědičnosti Polymorfismus implementován pomocí rozhraní Výklad Agregačně-kompoziční vztahy mezi třídami V programovacím jazyce C # 3.0 existuje několik možností, jak na syntaktické úrovni vybudovat mezi více třídami agregační, resp. kompoziční vztahy. Jedním řešením je aplikovat techniku vnořování tříd, kdy vložíme deklaraci jedné třídy do těla jiné třídy. V těle nadřazené třídy pak definujeme datovou položku, jejiž typem bude vnořená třída. V následující praktické ukázce vytvoříme model tabletu, který bude obsahovat procesor určitého typu. Příklad demonstruje vytvoření kompoziční vazby mezi třídou Tablet a vnořených třídou Procesor. Jde tedy o silnější typ agregace (kompozici), což je však pochopitelné, protože počítač bez procesoru nemůže fungovat. // Deklarace třídy Procesor. class Procesor // Definice datových položek třídy. string název; string vícejader; float frekvenceprocesoru; string technologiearm; // Definice veřejně přístupného parametrického instančního // konstruktor třídy. public Procesor (string název, string vícejader, float frekvenceprocesoru, string technologiearm) this.název = název; this.vícejader = vícejader;
142 this.frekvenceprocesoru = frekvenceprocesoru; this.technologiearm = technologiearm; // Definice veřejně přístupných instančních skalárních // vlastností třídy. public string Název get return this.název; set this.název = value; public string VíceJader get return this.vícejader; set this.vícejader = value; public float FrekvenceProcesoru get return this.frekvenceprocesoru; set this.frekvenceprocesoru = value; public string TechnologieARM get return this.technologiearm; set this.technologiearm = value; Pokračujeme sestrojením třídy Počítač, do těla jejíž vnoříme právě deklarovanou třídu Procesor: class Tablet // Zde se nachází deklarace vnořené třídy Procesor. class Procesor... // Definice datové položky, do které budeme moci uložit odkaz // na instanci třídy Procesor. Procesor procesor; // Definice veřejně přístupného bezparametrického instančního // konstruktor třídy. public Tablet () procesor = new Procesor ("Cortex A9", "Ano", 1.6f, "Ano"); // Definice veřejně přístupné instančního metody třídy. public void InformaceOProcesoruTabletu () Console.WriteLine ("Informace o procesoru tabletu:"); Console.WriteLine ("Název: 0 \nvícejádrový: 1 \n" + "Frekvence procesoru: 2 GHz \n" + "Technologie ARM: 3", this.procesor.název, this.procesor.vícejader, this.procesor.frekvenceprocesoru, this.procesor.technologiearm);
143 Vzhledem k tomu, že v těle třídy Počítač je umístěna deklarace vnořené třídy Procesor, můžeme v datové sekci třídy Počítač definovat soukromou proměnnou typu odkaz s identifikátorem procesor, jejíž datovým typem je vnořená třída. V těle konstruktor třídy Tabůet dochází k vytvoření instance vnořené třídy. Tímto způsobem zajistíme, že každá korektně vytvořena instance třídy Počítač bude disponovat svým procesorem. Ovšem, z hlediska lepší uživatelské přívětivosti bychom mohli Konstruktor třídy Počítač přetížit a nabídnout tak širší možnosti pro konfiguraci procesoru vytvářeného počítače. Jelikož oblast platnosti proměnné procesor je vymezena tělem nadřazené třídy, je zřejmé, že životnost objektu třídy Procesor se bude krýt s životním cyklem objektu třídy Počítač. Veřejně přístupná instančního metoda InformácieOProcesoruTabletu poskytuje klientům objektu třídy Tablet základní údaje o technických vlastnostech procesoru, kterým je tablet osazen. Vizuální model reprezentující diagram třídy Počítač je znázorněn na obr. 26. Inštanciácia třídy Počítač probíhá dle standardního postupu: class Program static void Main (string [] args) // Inštanciácia třídy Počítač. Tablet můjetabet = new Tabůet (); můjtabletc.informáceoprocesorutabůetu(); Console.Read (); V procesu inštanciácie bude nejdříve konstruován objekt třídy Tablet. Volání operátoru new způsobí dynamické založení objektu na řízené haldě. Operátor new implicitně vyvolá Konstruktor alokované instance třídy Tablet, který zajistí vytvoření vnořeného objektu třídy Procesor. Vnořený objekt není pro klientský programový kód přímo přístupný, což je v pořádku, protože naším cílem je dodržet základní principy objektově orientovaného programování.
144
145 Obr. : Vizuální ztvárnění kompozičního vztahu mezi třídami Počítač a Procesor Dědičnost Programovací jazyk podporuje veřejnou jednoduchou dědičnost. Chybí zde implementace vícenásobné dědičnosti s jejími problémy, které musely být řešeny virtuálním děděním. Jednoduchá dědičnost, se kterou se v jazyce C # setkáváme, je implicitně veřejná (nejsou vyžadovány a nakonec ani potřebné jiné varianty jednoduché dědičnosti, jako je soukromá nebo chráněná dědičnost). V procesu jednoduché dědičnosti vytváříme z bázové třídy odvozenou třídu (podtřídu). Odvozená třída dědí od své bázové třídy všechny atributy a metody. Přestože zděděné budou všechny členy bázové třídy, ne všechny musí být z těla odvozené třídy přímo viditelné. O tom, které zděděné členy budou viditelné, rozhodují jejich přístupová modifikátory, které jsou uvedeny v bázové třídě. Vztahy vybudované na základě veřejné jednoduché dědičnosti mají vždy generalizačně-specializovaný charakter. Bázová třída je obecnou šablonou, zatímco odvozená třída je její specifikovaná implementace. Pokud se vyskytuje v řetězci veřejné jednoduché dědičnosti několik tříd, platí pravidlo, podle kterého je každá následující podtřída vždy konkrétnějším případem své nadřazené třídy. Kromě opětné použitelnosti schopností bázové třídy může odvozena třída dodat novou, doplňkovou funkcionalitu. To se děje definováním nových členů, které se vyskytují pouze v odvozené třídě. Podtřída se může rovněž rozhodnout předefinovat chování zděděných členů bázové třídy. V tomto kontextu je možné překrýt zděděnou implementaci člena bázové třídy ve třídě odvozené, což je podstatou polymorfismu implementovaného pomocí veřejné jednoduché dědičnosti. Na druhou stranu, odvozená třída je schopna ukrýt zděděnou implementaci člena bázové třídy tak, že ho nahradí novou implementací. Tato technika je známá jako ukrývání zděděných členů bázové třídy v podtřídě. Generický syntaktický příkaz vytvoření odvozené třídy B, která vznikla v procesu veřejné jednoduché dědičnosti z bázové třídy A, má následující podobu: class B : A Zapsaná deklarace odvozené třídy předpokládá, že existuje třída s identifikátorem A, která není ani zapečetěna, ani statická. Když vytvoříme instanci odvozené třídy, tato bude obsahovat zděděný podobjekt bázové třídy. Generický syntaktický příkaz vytvářející instanci odvozené třídy vypadá takto: B obj = new B (); Jelikož instance odvozené třídy v sobě zapouzdřuje zděděný podobjekt bázové třídy, je nutné korektně zkonstruovat nejprve ten, a až pak může být řádně konstruována instance odvozené třídy. Tomuto principu odpovídá i řetězec volání instančního konstruktoru: nejdříve je aktivován instančního Konstruktor odvozené třídy, který však vzápětí (ještě před vstupem do svého těla) volá instanční Konstruktor bázové třídy. Když je zděděný podobjekt bázové
146 třídy zkonstruován a korektně inicializován, dokončí se konstrukce objektu odvozené třídy (zpracují se programové příkazy v těle instančního konstruktor odvozené třídy). Uvažujme deklaraci bázové třídy GrafickýObjekt: class GrafickýObjekt public void Vykreslit () // Zdrojový kód pro vykreslení grafického objektu. V dalším kroku odvodíme od třídy GrafickýObjekt novou podtřídu Čtverec: class Čtverec : GrafickýObjekt // Tělo odvozené třídy. Přestože jsme do těla odvozené třídy nevložili žádné členy, které by zaváděly jistou funkcionalitu, můžeme odvozenou třídu ihned instancovat a zavolat zděděnou veřejně přístupnou instanci metodu Vykreslit: static void Main(string[] args) Čtverec můjčtverec = new Čtverec(); můjčtverec.vykreslit(); Console.Readline(); Vizualizace vztahu mezi dvěma třídami, který byl vybudován na bázi veřejné jednoduché dědičnosti, je zobrazena na obr.
147
148 Obr. : Vizualizace vztahu veřejné jednoduché dědičnosti mezi třídami Odvozená třída se nemusí spokojit jen se zděděnými metodami, ale může doplňovat své schopnosti: class Polygon: GrafickýObjekt class Čtverec : GrafickýObjekt // Tělo odvozené třídy. public void Posunout (Vektor v) // Zdrojový kód provádějící posunutí polygonu. Deklaraci odvozené třídy jsme rozšířili o definici veřejně přístupné instanční parametrické metody Posunout, která dokáže posunout čtverec. Použití modifikované třídy Čtverec přibližuje následující fragment zdrojového kódu: static void Main(string[] args) Čtverec můjčtverec = new Čtverec(); můjčtverec.posunout(new Vektor(10, 2, 3)); Console.Readline();
149 Předpokládáme existenci třídy Vektor, který implementuje vlastnosti vektoru. Pokud si uvědomíme, že třírozměrný vektro má shodnou strukturu jako výše zmíměná třída Souřadnice3D, pak můžeme třídu Vektor přímo odovodit ze třídy Souřadnice3D Abstraktní třídy Abstraktní třída je v programovacím jazyce C # třída, která nemůže přímo vytvořit své instance Abstraktní třída vystupuje v pozici základní třídy, od které mohou být v procesu veřejné jednoduché dědičnosti odvozovány nové podtřídy. Získané podtřídy mohou být buď znovu abstraktní, nebo instanční. Pokud je podtřída bázové abstraktní třídy instančního charakteru, dokáže vytvářet své instance. V deklaraci abstraktní třídy se vyskytuje modifikátor abstract: [M] abstract class X // Tělo abstraktní třídy. kde: [M] je přístupový modifikátor abstraktní třídy. X je identifikátor abstraktní třídy. Pro abstraktní třídy platí tyto zásady: 1. Členy abstraktní třídy (metody a vlastnosti) mohou být abstraktní nebo instanční. 2. Abstraktní člen abstraktní třídy je implicitně virtuální. 3. Abstraktní člen je v těle abstraktní třídy pouze deklarovaný, jeho definice se objevuje až v těle instančního podtřídy, která je od bázové abstraktní třídy odvozená. Podívejme se na deklaraci abstraktní třídy KreditníKarta: // Deklarace abstraktní třídy. public abstract class KreditníKarta // Deklarace 1. abstraktní metody. public abstract void Uhradit (UINT částka); // Deklarace 2. abstraktní metody. public abstract void Vybrat (UINT částka); // Deklarace abstraktní skalárních vlastnosti, která je určena // jen pro čtení. public abstract UINT Stav get; V těle naší abstraktní třídy se nacházejí výhradně abstraktní členy: dvě abstraktní metody a jedna abstraktní skalární vlastnost. Je důležité poukázat na skutečnost, že abstraktní členy abstraktní třídy jsou zaváděny pouze prostřednictvím svých deklarací (prototypů). Kdybychom chtěli do těla deklarované abstraktní třídy umístit definice abstraktních členů, kompilátor by nás zastavil s hlášením udávajícím nemožnost uskutečnění takové operace. Na druhou stranu, v deklaraci abstraktní třídy se mohou nacházet i neabstraktné členy. Přitom
150 platí, že jakýkoli neabstraktný člen vyskytující se v těle abstraktní třídy musí být v této třídě řádně definován. Od deklarované abstraktní třídy odvodíme instanci třídy Kreditka: public class Kreditka: KreditníKarta // Definice soukromých datových položek třídy. private UINT stav, možnočerpat; private string číslo, majitel; // Definice parametrického instančního konstruktoru. public Kreditka (string číslo, string majitel, UINT možnočerpat, UINT nastavenýlimit) this.čislo = čislo; this.majiteľ = majitel; this.možnočerpat = možnočerpat; stav + = nastavenýlimit; // Definice 1. překrývající instančního metody. public override void Uhradit (UINT částka) stav + = částka; // Definice 2. překrývající instančního metody. public override void Vybrat (UINT částka) if ( (stav - částka > 0) && (částka < možnočerpat)) stav - = částka; // Definice překrývající instančního skalárních vlastnosti. public override UINT Stav get return this.stav; Všechny zděděné abstraktní členy abstraktní bázové třídy jsou v těle instančního odvozené třídy explicitně překryty. To je nezbytný předpoklad k tomu, abychom jednotlivé implementované členy mohli obdařit požadovanou aplikační logikou. Jelikož deklarovaná podtřída je instančního charakteru, můžeme vytvořit její instanci (bankovní účet) a provést několik bankovních transakcí: class Program static void Main(string[] args) // Instanciace podtřídy. Kreditka kredit = new Kreditka ( " ", "Johan Kepler", 500, 5000); // První výběr peněz z kreditní karty. kredit.vybrat (100);
151 kredit.stav); // První úhrada vyčerpaného kreditu. kredit.uhradit (40); // Druhá úhrada peněz z bankovního účtu. kredit.uhradit (60); Console.WriteLine ("Stav kreditního účtu: 0 USD.", Console.Read (); Vizualizace vztahu mezi bázovou abstraktní třídou a instanční odvozenou třídou je znázorněna na obr..
152 Obr. : Vztah mezi bázovou abstraktní třídou a odvozenou instanční třídou zapečetěné třídy Zapečetěna třída je v programovacím jazyce C # třída, kterou nelze použít jako bázovou třídu v procesu veřejné jednoduché dědičnosti. Není tedy možné vytvářet podtřídy zapečetěné
153 třídy. Schopnost vytvářet instance není nijak omezena. Jako zapečetěné jsou obvykle deklarované třídy, které disponují úplnou funkcionalitou. V deklaraci zapečetěné třídy se vyskytuje modifikátor sealed: [M] sealed class X // Tělo zapečetěné třídy. [M] je přístupový modifikátor zapečetěné třídy. X je identifikátor zapečetěné třídy. Následuje deklarace zapečetěné třídy NovýFormulár: using System.Windows.Forms; using System.IO; using System; namespace diz // Deklarace zapečetěné třídy. public sealed class NovýFormulár : Form // Definice soukromých datových položek třídy. private const int WM_CREATE = 0x0001; private const int WM_CLOSE = 0x0010; private const int WM_SIZE = 0x0005; private StreamWriter sw; // Definice instančního konstruktor třídy. public NovýFormulář() sw = new System.IO.StreamWriter (Application.StartupPath + "\\Soubor.txt"); sw.writeline("***** ZÁZNAMNÍK ZPRÁV OPERAČNÍHO" + "SYSTÉMU *****"); sw.writeline("začátek záznamu"); // Definice překrývající metody WndProc. protected override void WndProc(ref Message m) switch (m.msg) // Zachycení zprávy WM CREATE. case WM_CREATE: sw.writeline("okno aplikace bylo vytvořeno" + "(čas [" + DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second + "], zpráva" + "WM_CREATE)."); break; // Zachycení zprávy WM SIZE. case WM_SIZE: // Pokud bylo okno aplikace minimalizováno...
154 if ((int)(m.wparam) == 1) sw.writeline("okno aplikace bylo" + "minimalizované (čas [" + DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second + "], zpráva" + "WM_SIZE; WParam == 1)."); // Pokud došlo k maximalizaci okna aplikace... else if ((int)(m.wparam) == 2) sw.writeline("okno aplikace bylo" + "maximalizované (čas [" + DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second + "], zpráva" + "WM SIZE; WParam == 2)."); break; // Zachycení zprávy WM CLOSE. case WM_CLOSE: sw.writeline("okno aplikace bylo uzavřeno" + "(Čas [" + DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second + "], zpráva" + "WM_CLOSE)."); sw.writeline("konec záznamu"); sw.close(); break; // Volání metody WndProc bázové třídy. base.wndproc(ref m); // Definice metody Zobrazit, která zajišťuje // zviditelnění okna aplikace na obrazovce počítače. public void Zobrazit() this.show(); Instance deklarované zapečetěné třídy budou působit jako formuláře, které dokáží monitorovat průběhy vybraných akcií svých životních cyklů. Předmětem záznamu budou tyto akce: vytvoření formuláře, minimalizaci formuláře, maximalizaci formuláře a uzavření formuláře. Když nastane kterákoliv z vyjmenovaných akcí, její časový výskyt se uloží do externího textového souboru. Komunikace mezi operačním systémem a formulářem je uskutečňována na bázi mechanismu zpráv. Pokud dojde k určité události, operační systém podá formuláře informaci o vzniku události zasláním zprávy. Tuto zprávu následně filtruje procedura okna formuláře, která rozhodne, jaká bude reakce na příjem příslušné správy.
155 Diagram zapečetěné třídy je zobrazen na obr.. Obr. : Diagram zapečetěné třídy Instanciáci zapečetěné třídy můžeme realizovat například v těle zpracovatele události Click tlačítka, které se nachází na hlavním aplikačním formuláři: namespace WindowsFormsApplication2 public partial class Form1 : Form public Form1() InitializeComponent(); private void Form1_Load(object sender, EventArgs e) private void buttonzobrazformulář_click(object sender, EventArgs e) // Generování nové instance zapečetěné třídy (nového formuláře). NovýFormulář nf = new NovýFormulář (); Formulář s tlačítkem vypadá takto:
156 Obsah textového souboru s informacemi o průběhu životního cyklu formuláře můžeme vidět na obr.. Obr. : Monitoring životního cyklu formuláře jako instance zapečetěné třídy Parciální třídy V programovacím jazyce C # smíme deklaraci třídy rozdělit do více částí a tyto fragmenty mohou být uloženy v různých zdrojových souborech sestavení aplikace.net. V tomto případě mluvíme o parciální třídě. Při překladu musí být všechny zdrojové soubory s jednotlivými fragmenty deklarace parciální třídy dostupné a viditelné, protože kompilátor z nich sestaví finální deklaraci parciální třídy. V deklaračních fragmentech parciální třídy se vyskytuje modifikátor partial: [M] partial class X // 1. fragment parciální třídy. [M] partial class X // 2. fragment parciální třídy.
157 kde: [M] je přístupový modifikátor parciální třídy. X je identifikátor parciální třídy. Sestavení finální deklarace parciální třídy z oznamovacími fragmentů je schematicky znázorněno na obr... Obr.: Generování finální deklarace parciální třídy a její inštanciácia Následuje praktická ukázka deklarace parciální třídy Absolvent. Zdrojový kód parciální třídy je rozdělen na dvě části, z nichž každá je uložena v samostatném zdrojovém souboru. Deklaratorní fragment parciální třídy uložený v souboru Program.cs: namespace ParciálneTriedy / / 1. deklaratorním fragment parciální třídy. internal partial class Doktorand private string jméno, příjmení; private bytě kredityzavedeckúčinnosť; public Doktorand (string jméno, string příjmení, bytě kredityzavedu) this.meno = jméno; this.priezvisko = příjmení; this.kredityzavedeckúčinnosť = kredityzavedu;
158 Deklaratorním fragment parciální třídy uložený v souboru PT2.cs: using System; namespace ParciálneTriedy / / 2. deklaratorním fragment parciální třídy. internal partial class Doktorand public void ZapisaťSaNaDizertačnúSkúškuO if (this.kredityzavedeckúčinnosť <180) Console.WriteLine ("Nemůžete se zapsat na" + "Disertační zkoušku."); else Console.WriteLine ("Byli jste zapsán na" + "Disertační zkoušku."); Inštanciácia parciální třídy probíhá podle standardních pravidel v těle hlavní metody Main: namespace ParciálníTřída class ClassProgram static void Main (string [] args) // Vytvoření instance parciální třídy. Absolvent a1 = new Absolvent ("Ferda", "Mravenec", 105); a1.zapsatnazávěrečnouzkoušku(); Console.Read (); Při zobrazování grafických diagramů parciálních tříd pracuje Návrhář tříd tak, že nejdříve vyhotoví finální deklaraci parciální třídy a až potom zobrazí její vizuální obraz.
159 11.6. Statické třídy Obr. : Vizualizace parciální třídy ve vizuálním Návrháři tříd Počínaje verzí 2.0 programovacího jazyka C # je možné ve zdrojovém kódu vytvářet statické třídy. V deklaratorním příkazu statické třídy se nachází modifikátor static: internal static class X // Tělo statické třídy. Statická třída může obsahovat pouze definice statických členů, jako jsou statické datové položky, statický Konstruktor, statické metody či statické vlastnosti. Pro každou statickou třídu platí následující pravidla: 1. Statická třída nemůže být inštanciovaná. 2. Statická třída nemůže působit jako bázová třída. 3. Statická třída je implicitně zapečetěna. 4. Statická třída nesmí obsahovat definice instančního konstruktoru a finalizéru. UIkázka kódu implementuje vykreslování rastrových obrázků- bitmap: using System.Drawing; // Deklarace statické třídy. static class Obrázek // Definice soukromé statické datové položky. private static Image bitovámapa; // Definice statického konstruktor. static Obrázek () bitovámapa = null; // Definice statické metody. public static void Vykresli (string cesta, Graphics grafickýobjekt) bitovámapa = Image.FromFile (cesta); grafickýobjekt.drawimage (bitovámapa, new Point (10, 10)); bitovámapa.dispose(); V těle statické třídy jsou umístěny pouze statické členy. Parametrická statická metoda Vykresli má jako své parametry absolutní cestu k fyzickému souboru s obrázkem a odkaz na instanci třídy Graphics (tato instance slouží jako primární grafický objekt, který realizuje grafické operace na ploše aplikačního formuláře). Data rastrové bitové mapy načteme do instance třídy Image z jmeného prostoru System.Drawing. Načetní daz nám zprostředkuje statická parametrická metoda FromFile třídy Image. Metoda FromFile vytvoří instanci třídy Image a vrátí odkaz, který ukládáme do proměnné bitovámapa typu odkaz. Za vykreslení bitové mapy na plochu aplikačního formuláře provede instančního metoda DrawImage
160 primárního grafického objektugrafickýobjekt. parametry: Metodě DrawImage musíme předat dva 1. Odkaz na instanci třídy Image, která je uchovává bitovou mapu. 2. Instanci hodnotové struktury Point, která určí souřadnice bodu, na kterém začne vykreslování bitové mapy. Souřadnice bodu, který je reprezentován instancí struktury Point, budou totožné se souřadnicemi levého horního rohu obdélníkového regionu, do kterého bude bitová mapa vykreslena. Pokud všechny operace proběhnou úspěšně, na aplikačním formuláři se objeví požadovaná bitová mapa. Metoda neošertřuje možné chyby, předpokládá pouze korektní parametry. Po vykreslení již bitovou mapu nebudeme využívat, proto uvolníme alokované prostředky voláním metody metody Dispose. Praktické použití statické třídy je velmi jednoduché, protože nemusíme pracovat se žádnými objekty. Jednoduše spustíme statickou metodu: private void buttonkresli_click(object sender, EventArgs e) // Aktivace statické metody statické třídy. "Pictures\Jellyfish.jpg", this.creategraphics()); Statickou metodu voláme ve spojení se statickou třídou. Prvním předávaným argumentem je doslovný textový řetězec (doslovnost se zajistí zapsaným před řetězece a znamená, že všechny znaky se chápou jako znaky včetně zpětného lomítka) který udává absolutní cestu k souboru s bitovou mapou. Druhým argumentem je odkaz na primární grafický objekt, který je spojen s aktuálním aplikačním formulářem (aktuální instancí třídy Form, na níž odkazuje this).
161 11.7. Anonymní třídy a inicializátory instancí anonymních a pojmenovaných tříd Při objektově orientovaném programování v jazyce C # 3.0 mohou vývojáři pracovat se dvěma základními typy tříd. Jde o pojmenované a anonymní třídy. Pojmenovaná třída je každá třída, která je správně deklarována a má svůj identifikátor. Všechny třídy deklaroané programátorem jsou pojmenované třídy. Od verze C # 3.0 můžeme vytvářet i anonymní třídy. Pro anonymní třídy platí následující pravidla: anonymit třídě určí kompilátor. Na základě syntaktického předpisu sestrojí kompilátor kompletní deklaraci anonymní třídy. Anonymní třída nemá identifikátor, který by byl přímo přístupný pro programátorovi. Programátor nezná identifikátor implicitně vygenerované třídy, tato třída se mu jeví jako anonymní. Přestože z pohledu programátora je kompilátorem sestavena třída opravdu anonymní, přesto i anonymní třída má svůj identifikátor. Ten je však vytvářen automaticky kompilátorem. Instance anonymní třídy vytvoříme s pomocí operátora new. Po úspěšné instanciacii vrací operátor new odkaz na alokovanou a inicializováno instanci anonymní třídy. Vrácený odkaz bude uložen do odkazu, jehož datový typ bude muset být kompilátorem implicitně inferovaný, jelikož identifikátor anonymní třídy není znám. Instance anonymní třídy je inicializována pomocí množiny tzv.. Inicializátorů instancí anonymních tříd. Inicializátory jsou hodnoty, kterými budou inicializovány automaticky generované soukromé datové položky instance anonymní třídy. Kromě inicilaizace datových
162 položek kompilátor také vytvořil sadu veřejně přístupných skalárních instančních vlastností, které slouží pro přístup k soukromým datovým položkám. Veřejně přístupné skalární instanční vlastnosti slouží jen pro čtení hodnot soukromých datových položek.není implementována vlastnost set, a proto datové položky nemohou být modifikovány. Obecný vzor, popisující vytvoření instance anonymní třídy, je tento: var obj = new SIV1= IN1, SIV2= IN2,..., SIVN= INN; SIV1...N jsou identifikátory veřejně přístupných skalárních instančního vlastností anonymní třídy. IN1...N jsou inicializátory. Seznamme se s fragmentem zdrojového kódu jazyka C # 3.0, ve kterém dochází k inštanciácii anonymní třídy: // Inštanciácia anonymní třídy. var obj = new Kniha = "C # - praktické příklady", Autor = "Jan Hanák", Vydavatel = "Grada Publishing", RokVydání = 2006, PočetStran = 288, ISBN = " " ; Protože že operátor za new nenásleduje žádný identifikátor třídy, kompilátor rozezná, že bude muset sestavit deklaraci anonymní třídy. Nejprve vytvoří hlavičku a tělo třídy. Třídu samozřejmě pojmenuje, aby se mohl na ni přímo odkazovat. Kompilátor dále pokračuje definicí šestice soukromých datových položek, které budou uchovávat data instance anonymní třídy. Po vytvoření datových položek vytvoří kompilátor stejný počet veřejně přístupných skalárních instančních vlastností (pouze pro čtení). Dále vloží kompilátor do těla anonymní třídy i parametrický instanční Konstruktor, který je zodpovědný za korektní inicializaci založené instance anonymní třídy. Po úspěšném vytvoření instance, bude tato instance anonymní třídy alokovaná na řízené haldě a náležitě inicializována. Operátor new nám poskytne odkaz na vytvořenou instanci, který ukládáme odkazu s identifikátorem obj a datový typ proměnné obj je automaticky určen mechanismem typové inferencie. Pokud budeme chtít vypsat na obrazovku počítače informace o knize, použijeme automaticky vygenerované veřejně přístupné skalární instanční vlastnosti: Console.WriteLine ("Informace o knize: \ n" + "Název:" + obj.kniha + "\ NAutor:" + obj.autor + "\ NVydavateľ:" + obj.vydavatel + "\ NRok vydání:" + obj.rokvydania + "\ NPočet stran:" + obj.početstrán + "\ NISBN:" + obj.isbn);
163 Inicializátory instancí tříd smíme využít i při zakládání instancí pojmenovaných tříd. Nejdříve uvádíme deklaraci pojmenované třídy, kterou budeme vzápětí potřebovat při praktické ukázce Inicializátory: class Kniha private string název, autor, vydavatel, isbn; private ushort rokvydání, početstran; public string Název get return název; set název = value; public string Autor get return autor; set autor = value; public string Vydavatel get return vydavatel; set vydavatel = value; public ushort RokVydania get return rokvydání; set rokvydání = value; public ushort PočetStran get return početstran; set početstran = value; public string ISBN get return isbn; set isbn = value; Syntaktická skladba třídy je velmi jednoduchá. Inicializátory nám umožní efektivně zkonstruovat použitelnou instanci třídy: // Inicializace instance třídy pomocí Inicializátory. Kniha kniha = new Kniha () Název = "C # - praktické příklady", Autor = "Jan Hanák", Vydavatel = "Grada Publishing", RokVydání = 2006, PočetStran = 288, ISBN = " " ; Inštanciace třídy se řídí tímto předpisem: 1. Nejdříve je na řízené haldě přidělený paměťový prostor pro instanci třídy. 2. Dochází ke spuštění konstruktorui. O tom, jaký Konstruktor to bude, rozhoduje syntaktické znění instanciačního příkazu. Zde jsme použili výraz new Kniha (). Alokovaná instance třídy bude proto inicializována bezparametrickým implicitním instančním konstruktorem. Konstruktor je implicitní proto, že ho vytváří automaticky kompilátor
164 3. Po zpracování konstruktor je zahájena inicializace předepsána Inicializátory. Prostřednictvím veřejně přístupných skalárních instančních vlastností získáme přístup k soukromým datovým položkám instance třídy a uložíme do nich hodnoty Inicializátory. 4. Odkaz na alokovanou a inicializováno instanci třídy je uložen do odkazu. Inicializátory instancí tříd nepřebírají funkci konstruktoru, jen ji doplňují. Pokud by pojmenovaná třída, jejíž instanci chceme získat, definovala adekvátní parametrický Konstruktor, tak bychom mohli primárně využít ten a nemuseli bychom se spoléhat na inicializátory. Naopak, s výhodou můžeme inicializátory upotřebit při těch třídách, které žádný Konstruktor nedefinují (což je případ i naší výše uvedené třídy Polymorfismus implementován pomocí veřejné jednoduché dědičnosti Polymorfismus determinuje schopnost objektů reagovat jinak na příjem identické zprávy. V programovacím jazyce C # 3.0 může být polymorfismus implementován dvěma způsoby: 1. Polymorfismus implementovaný pomocí veřejné jednoduché dědičnosti. 2. Polymorfismus implementovaný pomocí rozhraní. Podstata polymorfismu implementovaného pomocí veřejné jednoduché dědičnosti v tomto spočívá v překrytí zděděného člena bázové třídy ve třídě odvozené. Zděděný člen bázové třídy můžeme překrýt stejným druhem člena, který bude disponovat odlišnou funkcionalitou. Abychom mohli v odvozené třídě překrýt zděděnou implementaci člena bázové třídy, musí být takový člen v bázové třídě definován jako virtuální. Když v těle bázové třídy definujeme virtuální metodu, říkáma, že tato metoda bude moci být v odvozené třídě překryta. Pokud má být jistá metoda bázové třídy virtuální, musí se v její hlavičce nacházet modifikátor virtual. Generická syntaktická ukázka deklarace bázové třídy s jednou virtuální metodou: class A public virtual void m () // Implementace virtuální metody bázové třídy. Metoda m bázové třídy A je explicitně virtuální, což znamená, že odvozená třída bude moci v případě zájmu překrýt zděděnou provedení této virtuální metody. V programovacím jazyce C # 3.0 nejsou instanční metody tříd implicitně virtuální V jazyce C # 3.0 však není implicitní technikou překrývání, ale ukrývání zděděných členů bázové třídy. Proto pokud chceme uplatnit překrytí zděděné instančního metody bázové třídy, musí být taková metoda virtuální. V těle odvozené třídy definujeme stejnojmennou metodu s modifikátorem override: class B: A public override void m () // Implementace instančního metody odvozené třídy, // Která překrývá stejnojmennou zděděnou metodu bázové třídy.
165 Překrývající metoda odvozené třídy s modifikátorem override musí být s překrýváním (virtuální) metodou bázové třídy shodná v těchto aspektech: signatura (identifikátor a seznam formálních parametrů), datový typ návratové hodnoty, přístupový modifikátor. Implementace překrývající metody bude samozřejmě odlišná. O tom, která metoda bude v souvislosti se založenou instancí aktivována, rozhoduje při polymorfismu implementovaném pomocí veřejné jednoduché dědičnosti typ zrozené instance. Prozkoumejme následující fragment zdrojového kódu jazyka C # 3.0: A obj = new B (); obj.m (); Rozeberme první instanciačný příkaz. V tomto příkazu definujeme odkaz s identifikátorem obj. Do této proměnné lze uložit objektovou referenci na instanci bázové třídy A. My však aplikujeme operátor new na odvozenou třídu, což znamená, že vytváříme její instanci. Jakmile je dokončen proces instanciacie odvozené třídy, operátor new vrací v podobě své návratové hodnoty objektovou referenci na vytvořenou instanci odvozené třídy. Tuto objektovou referenci je však možné uložit i do referenční proměnné, jejíž typem je bázová třída. Je to proto, že mezi třídami A a B existuje pouto vybudované na základě veřejné jednoduché dědičnosti, přičemž B je odvozenou třídou a A je bázovou třídou. Instanciační příkaz verifikuje korektnost tvrzení, podle kterého se potomek může vyskytnout všude tam, kde je očekáván jeho rodič. Z tohoto titulu je možné do referenční proměnné s typem bázové třídy uložit objektovou referenci na instanci odvozené třídy. Druhý příkaz představuje volání veřejně přístupné instanční metody m. Jelikož jsme řekli, že o tom, která metoda bude aktivována, rozhoduje typ vytvořené instance, tak musí být zřejmé, že tento příkaz volá překrývající metodu instance odvozené třídy. A to i přesto, že na vyvolání metody jsme použili referenční proměnnou, jejíž typem je bázová třída Polymorfismus implementován pomocí rozhraní Druhý způsob, podle kterého je možné navrhnout polymorfní chování instancí tříd v jazyce C # 3.0, reprezentuje odlišná implementace identického rozhraní několika třídami. Abychom však mohli kvalifikovaně hovořit o implementaci polymorfismu prostřednictvím rozhraní, musíme se nejdříve seznámit s pojmem rozhraní a jeho chápáním v jazyce C # 3.0. Rozhraní je předpis vymezující styl chování entit, které se rozhodnou příslušné rozhraní implementovat '. V jazyce C # 3.0 je rozhraní popisuje sadu odkazů metod, vlastností, událostí a delegátů. Rozhraní sice charakterizuje členy prostřednictvím oznamovacích příkazů (deklaruje je), ale už nedefinuje jejich implementáci. Všechny členy deklarované v rozhraní budou definovány ve třídě, která se rozhodne dané rozhraní implementovat. Řečeno jinak, rozhraní říká "co se má udělat", avšak už nezmiňuje konkrétní způsob "jak se to má udělat". Rozhraní deklarujeme podle následujícího generického modelu:
166 [M] interface IRozhraní // Deklarace členů rozhraní. kde: [M] je přístupový modifikátor, který určuje přístupová práva jednak samotného rozhraní, a taktéž i členů, které rozhraní deklaruje26. IRozhraní je identifikátor rozhraní. Uvažujme deklaraci rozhraní IPočítačováGrafika: public interface IPočítačováGrafika void ProvéstTransformáci (string zdrojovábitmapa, string cílovábitmapa); Toto rozhraní obsahuje deklaraci jedné veřejně přístupné parametrické metody, jejím úkolem bude zpracování grafické transformace bitové mapy, jejíž rastrová data budou uloženy ve vstupním grafickém souboru. Absolutní cesta ke vstupnímu grafickému souboru bude ve formě řetězcové konstanty uložena do prvního formálního parametru transformační metody. Po zpracování grafické transformace bude modifikovaná bitová mapa uložena do nového (výstupního) fyzického grafického souboru, který specifikuje druhý formální parametr transformační metody. Rozhraní samo o sobě představuje pouze předpis, podle kterého se budou muset chovat třídy, které se rozhodnou dané rozhraní implementovat '. Implementace rozhraní IPočítačováGrafika třídou Grafika vypadá v jazyce C # 3.0 takto: // Deklarace třídy, která implementuje rozhraní. class Grafika: IPočítačováGrafika // Definice metody, která je deklarována v rozhraní. public void VykonaťGrafickúTransformáciu (string zdrojovábitmapa,string cílovábitmapa) int i, j; // Konstrukce objektu třídy Bitmap, který bude zapouzdřuje // rastrová data načtené ze vstupního grafického souboru. Bitmap bitovámapa = new Bitmap (zdrojovábitmapa); Color barvaobrazovéhobodu, inverzníbarva; // Dvojice cyklů provádí inverzi bitové mapy. for (i = 0; i <bitovámapa.width; i ++) for (j = 0; j <bitovámapa.height; j ++) barvaobrazovéhobodu = bitovámapa.getpixel (i, j); inverzníbarva = Color.FromArgb (
167 255 - barvaobrazovéhobodu.r, barvaobrazovéhobodu.g, barvaobrazovéhobodu.b); bitovámapa.setpixel (i, j, inverzníbarva); // Po provedení grafické transformace je upravena bitová mapa // uložena do nového grafického souboru. bitovámapa.save (cílovábitmapa); // Protože s objektem třídy Bitmap už nebudeme pracovat, // Můžeme s ním asociované zdroje explicitně uvolnit. bitovámapa.dispose (); Ze syntaktického pohledu se technika implementace rozhraní třídou podobá aplikaci veřejné jednoduché dědičnosti. Přestože je ztvárnění zdrojového kódu identické s dědičností, v tomto případě jde o implementaci rozhraní. Jak jsme již zmínili, pokud se třída rozhodne jisté rozhraní implementovat, musí přijmout závazek definovat všechny členy, které příslušné rozhraní deklaruje. V naší praktické ukázce obsahuje rozhraní jen jednu metodu, jejíž definici deklarovaná třída zavádí. Zdrojový kód umístěný v těle metody VykonaťGrafickúTransformáciu realizuje inverzi vstupní bitové mapy. Po provedení inverze je transformována bitová mapa uložena do výstupního grafického souboru. Grafické modely rozhraní a implementující třídy zobrazené v Návrháři tříd jsou znázorněny na obr..
168 Obr. : Vizuální model rozhraní a implementující třídy Algoritmus vykonávající inverzi bitové mapy pracuje následujícím způsobem: 1. Pro každý obrazový bod, který je součástí bitové mapy, je nalezen jeho barevný vektor. Barevný vektor je vyjádřen v prostoru RGB, přičemž je tvořen uspořádanou trojicí hodnot (složek). Složky barevného vektoru podávají informace o intenzitě zastoupení tří barevných složek: R - červená, G - zelená a B - modrá. Intenzita zastoupení červené, zelené a modré složky v konečném důsledku určuje intenzitu výsledné barvy, kterou bude obrazový bod vykreslen na obrazovce počítače. Intenzita zastoupení složek barevných vektorů je běžně kvantifikována pomocí celočíselné nebo reálné stupnice hodnot. Při celočíselné stupnici může intenzita složky barevného vektoru nabývat hodnot z intervalu <0, 255>. Při reálné stupnici se zase používá interval <0, 1>. Například, obrazový bod s maximální intenzitou svých složek disponuje barevným vektorem ve tvaru [255, 255, 255] (v celočíselném vyjádření), resp. [,.1, 1] (v reálném vyjádření). Ze všech obrazových bodů bitové mapy vybereme jeden, se kterým budeme dále pracovat. Vybraný obrazový bod poéjmenujem P1 a jeho barevný vektor má tvoří složky P1 [R1, G1, B1]. Složky barevného vektoru analyzovaného obrazového bodu budeme určovat pomocí celočíselné stupnice hodnot. 2. Při inverzi jsou složky vektoru barev obrazového bodu P1 transformovány tak, že vznikne nový obrazový bod P2 s transformovaným (inverzním) vektorem barev. Výpočet jeho vektoru barev stanoví následující matematický vztah: P2 [R2, G2, B2] = [255 R1, 255 G1, 255 B1] Ukažme si příklad: Ať je vstupní obrazový bod P1 vykreslený odstínem červené barvy s barevným vektorem [237, 28, 36]. Po inverzi získáme nový barevný vektor [18, 227, 219], který představuje tyrkysovou barvu. 3. Algoritmus tedy pracuje tak, že nejdříve určí barevné vektory obrazových bodů a pak je převede na jejich inverzní ekvivalenty. Grafická transformace se uskutečňuje na bázi obrazového bodu, což znamená, že finální invertní obraz nezískáme dříve, než budou transformov8nz všechny obrazové body. Pokud počet obrazových bodů bitové mapy (jako objem vstupních dat) označíme proměnnou n, tak operace "zjištění aktuálního barevného vektoru, výpočet inverzního barevného vektoru a aplikace transformovaného barevného vektoru" budou muset být zpracovány n-krát. Pro běžnou bitovou mapu o rozměrech 1024x768 obrazových bodů to znamená realizaci více než 786 tisíc transformací. Vzhledem k tomu, že načtena bitová mapa je v objektu třídy Bitmap uložená ve formě dvojrozměrnýho pole, jako algoritmické nejjednodušší řešení se jeví použití dvojice cyklů, pomocí kterých manipulujeme s barevnými vektory jednotlivých obrazových bodů. Pro zjištění původních barevných vektorů používáme instance metodu GetPixel objektu třídy Bitmap, uložení transformovaných bodůprovede metoda SetPixel stejného objektu. Dodejme, že metody GetPixel a SetPixel patří k tzv. párovým metodám, které se vždy používají společně. Ve chvíli, kdy je inverze barevných vektorů obrazových bodů bitové mapy hotová, voláme instanční metodu Save objektu třídy Bitmap a modifikovanou bitovou mapu ukládáme do nového fyzického grafického souboru. Jelikož objekt třídy Bitmap už nemíníme nadále používat, můžeme požádat o explicitní dealokace systémových prostředků, které jsou s tímto objektem asociované. Na to nám slouží instančního metoda Dispose.
169 Třídu Grafika s implementovaným rozhraním IPočítačováGrafika bychom mohli prakticky použít například takto: using System.Diagnostics; class Program static void Main(string[] args) Grafika g = new Grafika(); Stopwatch sw = new Stopwatch(); sw.start(); Console.WriteLine("Vykonávam grafickou transformaci..."); sw.stop(); Console.WriteLine("Grafická transformace je hotová."); Console.WriteLine("Operácia trvala 0 s.", sw.elapsed.totalseconds); Console.Read(); Zpracování grafické transformace je za přispění naší třídy velmi jednoduché. Ve chvíli, kdy máme alokovanou a inicializováno instanci třídy Grafika, zavoláme její veřejně přístupnou instanci metodu ProvéstTransformaci, jíž předáme smysluplnou kolekci vstupních argumentů. Program informuje uživatele nejen o provedení grafické transformace, ale také o měřeném exekučním čase, který byl na její zpracování potřebný. Exekuční čas měříme pomocí objektu třídy Stopwatch z jmenného prostoru System.Diagnostics. O polymorfisuy implementovaném rozhraním mluvíme tehdy, pokud dvě třídy zavedou odlišnou implementaci identického rozhraní. To se děje tehdy, když navrhneme třídu Grafika2, a definice metody ProvéstTransformaci bude vykonávat odlišnou činnost, bude uskutečňovat jiný typ grafické transformace - my jsme zvolili převod bitové mapy do stupně šedi. Zdrojový kód jazyka C # 3.0, který zobrazuje deklaraci třídy, uvádíme dále: class Grafika2 : IPočítačováGrafika public void ProvéstTransformaci(string zdrojovábitmapa,string cílovábitmapa) int i, j, I; Bitmap bitovámapa = new Bitmap(zdrojováBitmapa); Color barvaobrazovéhobodu; for (i = 0; i < bitovámapa.width; i++) for (j = 0; j < bitovámapa.height; j++) barvaobrazovéhobodu = bitovámapa.getpixel(i, j); I = (int)(0.299 * barvaobrazovéhobodu.r * barvaobrazovéhobodu.g * barvaobrazovéhobodu.b); bitovámapa.setpixel(i, j, Color.FromArgb(I, I, I));
170 bitovámapa.save(cílovábitmapa); bitovámapa.dispose(); Algoritmus grafické transformace je při převodu obrazu do sivotónu založen na výpočtu intenzity jasu pro barvy vykresleny v odstínech šedé. Vycházíme přitom z matematického vzorce, který reflektuje míru citlivosti lidského oka na jednotlivé složky barevných vektorů: I = 0,299 x R + 0,587 x G + 0,114 x B I je výsledná intenzita jasu odpovídající obrazu ve stupních šedi. R, G a B jsou složky barevného vektoru zpracovávaného obrazového bodu. Vznesenými matematická rovnice vyjadřuje, s jakou intenzitou vnímá lidské oko jednotlivé barevné složky modelu RGB. Z použitých koeficientů je zřejmé, že nejcitlivější je vnímána zelená barva (koeficient 0,587), potom červená barva (koeficient 0,299) a nakonec modrá barva (koeficient 0,114). Řečeno přímočařejší, v barevném obraze si všímáme nejvíce intenzitu odstínů zelené barvy, o něco méně jsou pro nás signifikantní odstíny červené barvy a vůbec nejnižší citlivost prokazujeme vůči odstínem modré barvy. Číselné hodnoty modelu RGB, o kterých jsme se zmínili, nám podávají informace o intenzitě jasu určité barvy, resp. barevného odstínu. Například, nejjasnější je bezpochyby bílá barva, jejíž barevný vektor má hodnotu [255, 255, 255]. Výše uvedený vztah dokáže podle vstupních informací o složkách barevného vektoru obrazového bodu (nebo přesněji o intenzitě složek barevného vektoru) vypočítat celkový jas, který je typický pro obrazový bod vykreslen ve stupních šedi. Instance tříd Grafika a Grafika2 se chovají polymorfně, protože na stejný podnět (žádost o provedení grafické transformace) reagují odlišně (instance třídy Grafika vyhotoví inverzní obraz, zatímco instance třídy Grafika2 vytvoří obraz v odstínech šedi). Zdrojový kód jazyka C # 3.0, který instancí třídy Grafika2 a provádí tvorbu sivotónového obrazu, vypadá takto: class Program static void Main(string[] args) Grafika g = new Grafika(); Stopwatch sw = new Stopwatch(); sw.start(); Console.WriteLine("Transformuji..."); Pictures\GrayScaleJellyfish.jpg"); sw.stop(); Console.WriteLine("Transformace je hotová."); Console.WriteLine("Trasforamce trvala 0 s.", sw.elapsed.totalseconds); Console.Read();
171 Obr. : Schematické demonstrace polymorfního chování instancí tříd, které zavádějí odlišnou implementaci stejného rozhraní
172 Obr. : Schematická demonštrácia polymorfného správania inštancií tried, ktoré zavádzajú odlišnú implementáciu rovnakého rozhrania Otázky k probranému učivu Popište agregačně kompoziční vztahy mezi třídami Jaký je rozdíl mezi dědičnosti a vícenásobné dědičnosti Co je to abstraktní třída Co je to zapečetěná třída Co je to parciální třída Co jsou statické třídy Shrnutí pojmů kapitoly (podkapitoly) Abstraktní třída je v programovacím jazyce C # třída, která nemůže přímo vytvořit své instance Zapečetěna třída je v programovacím jazyce C # třída, kterou nelze použít jako bázovou třídu v procesu veřejné jednoduché dědičnosti. V programovacím jazyce C # smíme deklaraci třídy rozdělit do více částí a tyto fragmenty mohou být uloženy v různých zdrojových souborech sestavení aplikace.net. V tomto případě mluvíme o parciální třídě.
173 V deklaratorním příkazu statické třídy se nachází modifikátor static. Statická třída může obsahovat pouze definice statických členů, jako jsou statické datové položky, statický Konstruktor, statické metody či statické vlastnosti. Polymorfismus determinuje schopnost objektů reagovat jinak na příjem identické zprávy. V programovacím jazyce C # 3.0 může být polymorfismus implementován dvěma způsoby: 1. Polymorfismus implementovaný pomocí veřejné jednoduché dědičnosti. 2. Polymorfismus implementovaný pomocí rozhraní. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka
174 Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Téma 12 Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět se seznámi, delegáty, kovariaci a kontravariaci delagátů λ-výrazy v jazyce C # 3.0 λ,-výrazy a λ,-kalkul λ-příkazy v jazyce C # 3.0 Výklad Delegáti Delegát je v jazyce C # 3.0 uživatelsky deklarovaný odkazový datový typ, jehož instance v sobě zapouzdřuje odkaz na cílovou instanci nebo statickou metodu. Jakmile je instance delegáta propojena s cílovou metodou, lze danou metodu vyvolat nepřímo, prostřednictvím instance delegáta. Podle své praktické aplikace se instance delegáta podobá použití ukazatele na funkci, který je známý z prostředí programovacích jazyků C a C + +. S delegáty v jazyce C # 3.0 pracujeme podobně jako s ukazatelem na funkce v jazycích C a C + +. Jelikož delegát je uživatelsky deklarovaným referenčním datovým typem, prvním krokem je jeho deklarace. Následující deklaratorním příkaz zavádí deklaraci delegáta s identifikátorem Delegát: // Deklarace delegáta jako nového uživatelsky deklarovaného // odkazové datového typu. internal delegate int Delegát (string řetězec); Deklarovaný delegát disponuje interními přístupovými právy a jeho instance bude moci být asociována s cílovou instanční nebo statickou metodou, která vyhovuje těmto požadavkům:
175 1. Metoda vrací celočíselnou návratovou hodnotu primitivního datového typu int. 2. Metoda je parametrická, pracuje s jedním formálním parametrem, jehož typem je primitivní odkazový datový typ string. Formální parametr metody bude inicializován odkazem na instanci třídy System.String, ve které je zapouzdřen textový řetězec. Deklarace delegáta se syntakticky podobá definici ukazatele jazyka C / C + +, do které může být uložen ukazatel na funkci. Sémantický rozdíl však spočívá v tom, že delegát je novým datovým typem: jeho kompletní deklaraci provede kompilátor jazyka C # 3..0 ve své vlastní režii. V těle třídy delegátu jsou situovány definice pro instančního Konstruktoru a trojicei parametrických instančního metod s názvy BeginInvoke, EndInvoke a Invoke. Druhým krokem při práci s delegátem je definování cílové metody, která bude s jeho instancí spojená. Syntaxe obrazu definice metody SpočtiDélkuŘetězce vypadá takto: class Program internal delegate int Delegate (string řetězec); static void Main (string [] args) // Tělo hlavní metody je zatím prázdné. // Definice metody, která bude spojena s instancí delegáta. static int SpočtiDélkuŘetězce (string řetězec) return řetězec.length; Pro programátory, kteří přicházejí z jazyků C / C + +, je důležité sdělení, že v jazyce C # 3.0 pracujeme běžně jen s definicemi, ne s deklaracemi metod. Definována metoda je statická, což je dáno technickými požadavky na přehlednější zdrojový kód. Kdybychom totiž chtěli instanci delegáta spojit s instanční (a tedy nestatickou) cílovou metodou, museli bychom deklarovat třídu a v ní definovat požadovanou instanci metodu. Nakonec založíme instanci delegáta a uložíme do ní odkaz na cílovou statickou metodu: Jelikož je deklarován delegát typu odkaz, jeho instanci vytvoříme pomocí operátora new. Ve chvíli, kdy je na řízené haldě alokovaná instance delegáta, static void Main(string[] args) // Inštanciácia delegáta a asociování založené instance // s cílovou statickou metodou. Delegát del = new Delegát(SpočtiDélkuŘetězce); // Nepřímá aktivace cílové statické metody pomocí
176 // Vytvoření instance delegáta. int znaky = del("paralelní programování"); Console.WriteLine("Délka řetězce (znaky): 0.", znaky); Console.Read(); dochází k její inicializaci. V procesu inicializace je do instance zapouzdřen odkaz na cílovou statickou metodu s uvedeným identifikátorem. Konečně je odkaz na instanci delegáta uložen do proměnné, která je vytvořena v rámci instanciačního příkazu. Když máme inicializovánu instanci delegáta, můžeme prostřednictvím ní nepřímo zavolat cílovou statickou metodu. Všimněme si, že odkaz (přes který je instance delegáta dosažitelná) používáme podobně, jako by se jednalo o cílovou metodu, kterou chceme spustit. Po aktivaci metody budou provedeny všechny příkazy, které se v jejím těle nacházejí a metoda předá svou návratovou hodnotu, kterou ukládáme do předem připravené proměnné. Předchozí výpis zdrojového kódu jazyka C # 3.0 se dá přepsat následujícím způsobem: static void Main(string[] args) // Syntakticky zjednodušené vytvoření instance delegáta. Delegát del = SpočtiDélkuŘetězce; // Cílovou metodu voláme explicitně pomocí instanční // metody Invoke delegáta. int znaky = del.invoke("paralelní programování"); Console.WriteLine("Délka řetězce (znaky): 0.", znaky); Console.Read(); Liší se jen syntaktická stránka zdrojového kódu, finální efekt je stejný. Charakterizujme modifikace, které jsme v kódu uskutečnily: Za prvé, zdrojový kód využívá zjednodušenou formu vytvoření instance a inicializace delegátu. Kód explicitně nepoužívá operátor new, a tedy ani není vidět přímou aktivaci instančního konstruktor. Ačkoliv kód navozuje dojem, že do uvedené proměnné je přiřazen odkaz na vstupní bod těla cílové metody, není tomu tak. Do proměnné je totiž uložena reference na objekt, která je nasměrovaná na instanci vytvořeného delegáta. Tato instance leží na řízené haldě a obsahuje paměťovou adresu vstupního bodu těla cílové metody. Za druhé, ve zdrojovém kódu je explicitně aktivována metoda Invoke instance delegáta. Ve skutečnosti je metoda Invoke volána vždy, pokud není stanoveno jinak. V souvislosti s aktivací cílové metody je důležité uvést, že zdrojový kód této metody je zpracováván synchronně. To znamená, že provádění zdrojového kódu hlavní metody Main bude pozastaveno, dokud nebudou zpracovány všechny příkazy synchronně aktivované cílové metody. Exekuce kódu v těle hlavní metody bude pokračovat v okamžiku, kdy synchronní spuštěna cílová metoda vykoná celou svou činnost.
177 Dále si ukážeme, jak instanci delegáta propojit s cílovou instančního (nestatickou) metodou: // Deklarace třídy. internal class Mince // Definice soukromých datových položek třídy. private uint číslo, padlo; private uint líc; // líc == 1, rub == 0 String strana; private Random generátor; // Definice veřejně přístupného bezparametrického konstruktor. public Mince () generátor = new Random (); // Definice instanční metody třídy. public void Hodit (ushort počethodů) for (int i = 1; i <= počethodů; i ++) číslo = (uint) generátor.next (0, 1); JakáPadlaStrana(číslo); Console.WriteLine ("Bylo vrženo číslo 0.", číslo); if (číslo == líc) padlo ++; ; if (padlo == 0) Console.WriteLine(JakáPadlaStrana(číslo) + "nepadl ani jednou."); else Console.WriteLine(JakáPadlaStrana(číslo) + "padl 0 krát.", padlo); Console.WriteLine ("Vaše úspěšnost byla" + ((float) padlo / počethodů * 100).ToString ("0") + "%."); private string JakáPadlaStrana(uint padlo) if (padlo == 1) return " Líc "; else return " Rub "; public void ZvolStranu() bool opakuj = true; uint x = 0; String zvolil ; while (opakuj)
178 Console.Write("Zvol si stranu mince, rub = 0, líc = 1 :"); zvolil = Console.ReadLine(); opakuj =!uint.tryparse(zvolil,out x) && x > 0 && x < 2; líc = x; strana = JakáPadlaStrana(x); Asociování instance delegáta s instanční cílovou metodou je jednoduché, protože všechno, co musíme udělat, je odevzdat instančnímu konstruktoru delegáta odkaz na požadovanou metodu. Jelikož cílová metoda je instanční a ne statická, vždy musíme specifikovat odkaz, jehož prostřednictvím je příslušná instance dosažitelná. Operátorem tečka(.)určíme metodu, se kterou chceme pracovat. // Deklarace delegátů. internal delegate void Delegát (ushort x); internal delegate void VolbaStrany(); class Program static void Main (string [] args) uint strana; Mince mince = new Mince (); // Vytvoření instance delegáta a spojení // instance s cílovou instančního metodou. VolbaStrany prvýdelegát = new VolbaStrany(mince.ZvolStranu); Delegát druhýdelegát = new Delegát (mince.hodit); // Aktivace cílové instančního metody pomocí // Instance delegáta. prvýdelegát.invoke(); druhýdelegát.invoke (10); Console.ReadLine (); Zde jsme deklarovali dva delegáty, jelikož se liší počtem předávaných. Prvý delegát bude svázán s instanční metodou ZvolStranu instance mince, tato metoda vyzve hráče, aby si zvoli stranu mince, a volbu ve formě čísla i řetězce zanků metoda uloží do datových proměnných třídy mince. Druhý delegát svázaný s metodou metodou Hodit tyto proměnné pak využívá ik vyhodnocení pokusu a výpisu jednotlivých hodů Spojení instance delegáta s anonymní cílovou metodou Počínaje verzí 2.0 programovacího jazyka C # je možné propojit instanci delegáta nejen s pojmenovanou instančního nebo statickou cílovou metodou, ale také s anonymní metodou. class Program // Deklarace delegáta. delegate void Delegát (int min, int max); static void Main (string [] args)
179 // Instanciáce delegáta a spojení instance // s anonymní metodou. Delegát anonymnídelegát = delegate(int min, int max) Random generátor = new Random(); for (int i = 0; i < 10; ++i) Console.WriteLine("0. Číslo je 1.", i + 1, generátor.next(min, max + 1)); ; anonymnídelegát (1, 100); Console.Read (); Podívejme se blíže na příkaz, který zakládá novou instanci deklarovaného delegáta. Na pravé straně od operátora přiřazení se nachází klíčové slovo delegát s určením signatury anonymní metody. Jak si můžeme všimnout, v naší praktické ukázce bude anonymní metoda parametrická, přičemž její formální parametry budou moci přijmout dvě celočíselné hodnoty. Po specifikaci signatury anonymní metody dochází k definici těla této metody. Tělo anonymní metody je (podobně jako tělo pojmenované metody) tvořené programovými příkazy, které se nacházejí v bloku ohraničeném složenými závorkami (). Za uzavírací složenou závorkou musíme zapsat středník ukončující příkaz, který vytváří instanci a inicializuje delegát. Použití anonymních metod v souvislosti s instancemi delegátů je velmi užitečná technika, která nás zbavuje povinnosti za každých okolností definovat samostatné metody, které budou provázány s instancemi delegátů. Místo toho kompilátoru předáme požadovaný seznamu formálních parametrů a sadu programových příkazů, a kompilátor sám automaticky vygeneruje korektní anonymní metodu kovariance a kontravariance delegátů. Jazyk C # 2.0 (a stejně i jazyk C # 3.0) definuje dvě vlastnosti delegátů, které vymezují pravidla pro přípustné odlišnosti signatur. Tyto vlastnosti jsou známé jako kovariance a kontravariancia delegátů. Podle kovariance je možné, aby byl datový typ návratové hodnoty cílové metody specifičtější jako datový typ návratové hodnoty deklarovaného delegáta. Praktickou aplikaci kovariance demonstruje následující fragment zdrojového kódu jazyka C # 3.0: // Deklarace bázové třídy. class A // Definice virtuální metody bázové třídy. public virtual A m() return this;
180 // Deklarace odvozené třídy. class B: A // Definice instanční metody potomka public B w () return this; class Program // Deklarace delegáta. delegate A Delegát (); static void Main (string [] args) // Inštanciácia a inicializace delegáta. Delegát del = new Delegát(metoda); // Indirektná aktivace cílové metody. Console.WriteLine (del().tostring ()); Console.Read (); // Definice cílové metody. static B metoda () B b = new B (); return b.w(); Z deklarace delegáta vyplývá, že datovým typem jeho návratové hodnoty je bázová třída A. Z tohoto titulu můžeme prohlásit, že deklarovaný delegát bude moci být po svém vytvoření propojen s takovou cílovou metodou, která v podobě své návratové hodnoty vrací odkaz na instanci třídy A nebo odkaz na instanci jakékoliv podtřídy třídy A. Když se podíváme na definici cílové statické metody, se kterou je instance delegáta spojena, vidíme, že datovým typem návratové hodnoty této metody je třída B. Protože třída B je podtřídou třídy A, je podle pravidel kovariance spojení instance delegáta a cílové metody zcela korektní. Poté, co bude prostřednictvím instance delegáta vyvolaná cílová statická metoda, odehrají se tyto činnosti: V řízené haldě se alokuje instance podtřídy B. rozvržení instance podtřídy B je inicializována bezparametrickým instančním konstruktor. Aktivuje se metoda w instance podtřídy B. Metoda w vrátí odkaz na aktuální instanci podtřídy B. Statická metoda přijme poskytnutý odkaz na instanci podtřídy B a odešle jej zpět klientskému programovému kódu (hlavní metodě Main). V těle hlavní metody je navrácen odkaz na instanci podtřídy B konvertován do textové podoby. Výsledkem konverzního procesu je zobrazení kvalifikovaného jména třídy, ze které instance vznikla.
181 Kontravariancia delegátů se soustředí na rozdílnost mezi datovými typy formálních parametrů cílové metody a deklarovaného delegáta. Podle kontravariancie je možné, aby cílová metoda disponovala generickejšími datovými typy svých formálních parametrů ve srovnání s datovými typy formálních parametrů, které jsou uvedeny v deklaraci delegáta. Následuje praktická ukázka kontravariancie delegátů v jazyce C # 3.0: // Deklarace bázové třídy. class A // Definice překrývající metody bázové třídy. public override string ToString () return "A"; // Deklarace odvozené třídy. class B: A // Definice překrývající metody odvozené třídy. public override string ToString () return "B"; class Program // Deklarace delegáta. delegate string Delegát (B b); static void Main (string [] args) // Inštanciácia a inicializace delegáta. Delegát del = metoda; B obj. = new B (); // Nepřímá aktivace cílové metody. string s = del (obj.); Console.WriteLine (s); Console.Read (); // Definice cílové metody. static string metoda (A a) return a.tostring (); Komentář ke zdrojovému kódu: V kódu deklarujeme dvě třídy, A je bázová třída a B je odvozena třída. V těle bázové třídy A je definice překrývající metody s identifikátorem ToString, která překrývá stejnojmennou zděděnou metodu primární bázové třídy System.Object. Pokud tedy vytvoříme instanci bázové třídy A a zavoláme její metodu ToString, získáme textový řetězec "A". Odvozená třída B poskytuje další (komparativní) funkcionalitu Zde definovaná metoda ToString překrývá stejnojmennou zděděnou metodu bázové třídy A. Analogicky, ve spojení s instancí podtřídy B bude volána zmíněná překrývající metoda. Na výstupu tedy získáme textový řetězec "B".
182 Zajímavá je. deklaratorním příkaz delegáu, který definuje jeden formální parametr, jehož datovým typem je podtřída B. Instanci delegáta budeme moci spojit s jakoukoliv cílovou metodou, jejíž formální parametr dokáže přijmout odkaz na instanci podtřídy B. Vztah mezi deklarovaným delegátem a definovanou cílovou statickou metodou je vybudován na bázi kontravariancie. To znamená, že je dovoleno, aby cílová statická metoda definovala formální parametr, jehož datovým typem je bázová třída A. Když bude pomocí instance delegáta nepřímo spuštěna cílová statická metoda, její formální parametr získá odkaz na instanci podtřídy B. Tento odkaz bude implicitně konvertován na odkaz na instanci bázové třídy A, přičemž tímto převedeným odkazem bude inicializován formální parametr cílové statické metody. Tento mechanismus je proveditelný a vždy bude fungovat. Je to proto, že do formálního parametru, jehož datovým typem je bázová třída A může být kdykoli uložen odkaz na instanci odvozené třídy B, jelikož potomek se může vyskytnout všude tam, kde je očekáván jeho rodič λ,-výrazy a λ,-kalkul V kolekci Syntaktická-sémantických inovací jazyka C # 3.0 mají své pevné místo λ-výrazy (lambda-výrazy). Lambda-výrazy jsou součástí širší matematicko-informatické vědecké teorie, která se nazývá λ-kalkul (lambda-kalkul). λ-kalkul patří k jedné z paradigmat tvorby počítačového softwaru, které se říká funkcionální programování. Funkcionální programování se na stavbu softwaru dívá jinak než imperativní programování. Připomeňme, že v pojetí imperativní paradigmatu je program považován za konečnou a neprázdné množinu instrukcí, které jsou zpracovávány jedna za druhou a které formují jednotlivé kroky implementovaných imperativních algoritmů. Imperativní programování je vývojářům blízké, protože se s ním setkávají již od nepaměti: jazyky BASIC, C, C + +, C #, Visual Basic a mnohé další můžeme zařadit do kategorie imperativních jazyků. Na druhou stranu, funkcionální programování je postaveno na aparátu A-kalkulu a počítačový program definuje z matematického hlediska. Z pohledu funkčně programování je program indikován jako konečná a neprázdná množina funkcí. Na funkce, která zapouzdřuje požadované algoritmy, jsou aplikovány různé transformace (zejména redukce a konverze), díky kterým se program dopracuje k požadovanému výsledku, λ-kalkul intenzivně pracuje s λ-výrazy, jejichž prostřednictvím jsou funkce anonymně definovány. λ-kalkul ve svém jádru pracuje s unárnymi funkcemi, tedy s funkcemi, které jsou schopny pracovat pouze s jedním argumentem. Argumentem unárnej funkce může být další unárna funkce a unárna funkce může jinou unárnu funkci vracet také jako svou návratovou hodnotu. Unárna funkce je definována anonymně, což znamená, že jdeo anonymní funkci, která nemá vlastní identifikátor. Pro definici unárnych anonymních funkcí se používají λ,-výrazy. λ-výraz vytváří funkci a podává rovněž informace o jejím formálním parametry. Například matematický zápis funkce f (x) = x + 7 vyjádříme následujícím λ-výrazem: (λ x x + 7). λ-výrazy zapisujeme vždy do závorek, přesně tak, jak jsme uvedli. Každý λ-výraz formují tyto čtyři části: 1. lambda-symbol (λ), 2. formální parametr funkce (x),
183 3. vertikální oddělovač ( ), 4. tělo funkce, resp. tělo λ-výrazu [x + 7). V matematice bychom pro výpočet hodnoty funkce se vstupním argumentem 2 použili zápis f (2). V λ-kalkuluje zapíšeme výraz (λ x x + 7) 2, kde hodnota 2 představuje argument, který bude dosazen do formálního parametru (x) a použije se v těle funkce na stanovení její návratové hodnoty. Návratová hodnota funkce je ve skutečnosti hodnota λ-výrazu. Je zřejmé, že hodnotou λ-výrazu bude číslo 9. λ-výraz je z formální stránky definován následujícím způsobem: 1. X je proměnná, která je definována svým identifikátorem. 2. (λx V) je abstrakce, v jejímž rámci je X proměnná a V je tělo λ-výrazu. Termín "abstrakce" můžeme substituovat termínem "definice anonymní funkce". 3. (λx V) A je aplikace, tedy volání anonymní funkce s argumentem A. Identifikátorem proměnné X může být i symbol složený z více znaků. Proměnné, které se nacházejí v λ-výrazech, rozdělujeme na vázané a volné. Klasifikační kritérium je docela jednoduché. Pokud je proměnná asociována s lambda-symbolem, je pojata jako vázána. V opačném případě jde o volnou proměnnou. V λ-výrazu (λ, x 2x + 1) je x vázanou proměnnou. Když předchozí λ,-výraz upravíme na (λ x 2x + y), tak x zůstává i nadále vázanou proměnnou, ale proměnná y je volná (neexistuje žádný λ-symbol, který by byl s ní spojen). Jelikož λ-kalkul dokáže pracovat jen s unárními funkcemi, musíme reálnou funkci s více proměnnými (formálními parametry) v tomto prostředí modelovat jako kompozici jedné unární funkce, která jako argument přijímá další unárnu funkci. Uvažujme tento příklad: Nechť je počet tranzistorů umístěných v jednom vícejádrových procesorů daný funkcí f 1( n) = 10 8 n, kde n je počet exekučních jader uložených v procesorových balení. Řekněme, že budeme chtít sledovat celkový počet instalovaných tranzistorů v kolekci vícejádrových procesorů, která je vyrobena během jednoho výrobního cyklu. Této relaci ať odpovídá funkce f 2 (p, n) = p 10 8 n, kde p je celkový počet vyprodukovaných procesorů. Definici funkce f2 bychom mohli zapsat i takto: f2 (p, f1 (n)) = p f1 (n). Použitím nástrojů λ-kalkulu zapíšeme vztahy mezi funkcemi následujícím způsobem: 1. abstrakce: (λ n 10 8 λ n) 2. abstrakce: (λp (λn p (10 8 xn))) Aplikace pro 100 čtyřjádrových procesorů, které jsou vyrobeny v jednom cyklu, bude pak vypadat takto: (λp (λn p (10 8 n))) 100 4
184 Ve složeném λ-výrazu lze rozpoznat vnitřní (vnořenou) abstrakci: (λn p (10 8 n)) Vyhodnocení složeného λ,-výrazu probíhá v těchto krocích: 1. Nejdříve nahradíme vázané proměnné (p a n) konkrétními hodnotami: (λ p (λn 100 x (10 8 x 4))) Vyhodnotíme vnitřní a vnější abstrakci: (λ p (λn ( )) 3. Výsledná hodnota analyzovaného A,-výrazu je 4 x Na základě vypočtené hodnoty můžeme říci, že 100 procesorů osazených čtyřmi jádry obsahuje 40 miliard tranzistorů. Vyhodnocování λ-výrazů se provádí pomocí vyhodnocovacích pravidel, které se nazývají konverze. λ-kalkul zná tři základní typy konverzních operací: α-konverze, β-redukce a η- redukce λ-výrazy v jazyce C # 3.0 Implementace λ-výrazů do jazyka C # 3.0 je dalším evolučním stupněm použití anonymních metod. λ-výraz se v jazyce C # 3.0 definuje podle následujícího vzoru: (Formálně parametry) => tělo λ-výrazu Pokud je množina formálních parametrů jednoprvková, závorky v zápise nejsou povinné. V opačném případě musíme uvádět závorky, ve kterých je seznam formálních parametrů oddělených čárkami. Formální parametry jsou reprezentovány identifikátory, které mohou být doplněny explicitními specifikacemi datových typů. Pokud parametr nemá přímo zadaný datovým typ, kompilátor se pokusí implicitně jeho typ odvodit na základě kontextu, v jakém je parametr použit. Tuto akci provádí mechanismus typové inferencie. Bez ohledu na to, zda je datový typ parametru zadaný explicitně, nebo je implicitně inferovaný, typ formálního parametru je určen vždy v době překladu zdrojového kódu. Symbol => představuje λ-operátor. Tento operátor se používá pouze v λ-výrazech, přičemž působí jako oddělovač formálních parametrů a těl λ-výrazů. λ-operátor => má stejnou prioritu jako přiřazovací operátor (=) a je asociativní ve směru zprava doleva. Tělo λ-výrazu je tvořeno syntakticky a sémanticky správně zapsanou posloupností operandů a operátorů, které lze zpracovat a vyhodnotit. Jelikož λ-výrazy jsou velmi úzce propojeny s anonymními metodami, tak tělo λ-výrazu bude vloženo do automaticky vygenerované anonymní metody. Aby mohla být vygenerována anonymní metoda použitelná, musíme deklarovat a instancovat delegáta, který bude s anonymní metodou prostřednictvím syntaxe λ-výrazu asociovaný. Následující výpis zdrojového kódu jazyka C # 3.0 demonstruje elementární použití λ- výrazu: class Program // Deklarace delegáta. delegate int Del (int a); static void Main (string [] args)
185 // Inštanciácia delegáta a inicializace zrozené // instance delegáta λ -výrazem. Del d = new Del (x => x + 10 * 2); // Aktivace cílové anonymní metody. Console.WriteLine (d (10)); Console.Read (); Deklarace delegáta říká, že jeho instance lze asociovat s metodou s jedením 32bitovým celočíselným parametrem a stejně dlouhou návratovou hodnotu. V těle hlavní metody Main je významný první příkaz představující definiční inicializaci odkazové proměnné. Výraz, nacházející se napravo od přiřazovacího operátoru, vytváří instanci delegátu, kterou automaticky spojuje s implicitně generovanou anonymní metodou. V těle automaticky generované anonymní metody je uloženo tělo definovaného λ-výrazu. λ-výraz, který používáme, má v λ-kalkulu podobu: (λ x x ) Ekvivalentní λ,-výraz zapsaný podle syntaktických pravidel jazyka C # 3.0 vypadá takto: x => x + 10 * 2 Formálním parametrem je proměnná x, která nemá explicitně determinován datový typ. Pokud absentuje přímá specifikace datového typu, víme, že kompilátor využije mechanismus typové inferencie a datový typ určí implicitně. Tělo A-výrazu tvoří jednoduchý aritmetický výraz, jehož typ bude opět implicitně inferovaný. Z instanciačního příkazu je zřejmé, že λ-výraz je jako vstupní argumentu předán formálnímu parametru instančního konstruktoru delegáta. Ve skutečnosti bude λ-výraz bude převeden na parametrickou anonymní metodu jehož tělo bude totožné s tělem X-výrazu. Po instanciáci delegáta bude vytvořena instance spojená s anonymní metodou. Kompilátor ví, že je nutné sestavit parametrickou anonymní metodu, protože deklarovaný delegát je stejně parametrický. Následně Konstruktor instance delegáta získá odkaz na implicitně definovanou anonymní metodu. Tato metoda není spuštěna okamžitě - k aktivaci metody tím instance delegáta dochází až tehdy, když je metodě předán vstupní argument. To se stane teprve v příkaze: Console.WriteLine (d (10)); Výraz d (10) zahajuje synchronní aktivaci cílové anonymní metody a metodě je předána celočíselná konstanta. Ve vztahu k λ-kalkulu výraz d (10) odpovídí volání funkce. Při další analýze zjistíme, že proběhne tento řetězec operací: 1. Instance delegáta nepřímo vyvolá cílovou parametrickou anonymní metodu. 2. Formální parametr cílové anonymní metody se inicializuje kopií, kopií celočíselné konstanty Vyhodnotí se λ-výraz uložený v těle cílové anonymní metody. Hodnota λ-výrazu je 30 ( * 2), což je hodnota návratové hodnoty anonymní funkce.
186 4. 32-bitová návratová hodnota anonymní metody se konvertuje na posloupnost textových znaků, odešle se do výstupního datového proudu a zobrazí se na obrazovce počítače. Pro signaturu deklarovaného delegáta a formu definovaného λ,-výrazu platí tato pravidla: λ-výraz musí obsahovat tolik formálních parametrů, kolik formálních parametrů je definovaných v deklaraci delegáta. Datový typ každého formálního parametru λ,-výrazu musí být implicitně konvertovateľný na datový typ odpovídajícího formálního parametru definovaného v deklaraci delegáta. Datový typ návratové hodnoty λ-výrazu musí být implicitně konvertovateľný na datový typ návratové hodnoty, který je uveden v deklaraci delegáta. Díky flexibilní jazykové specifikaci C # 3.0 lze instanciační příkaz uvedený v předchozím fragmentu zdrojového kódu přepsat i tímto způsobem: // Syntakticky jednodušší zápis inštanciačného příkazu. Del d = x => x + 10 * 2; Přesto, že jsme modifikovali syntaxi příkazu, jeho původní sémantika zůstala zachována. Při studiu podobně navržených instanciačních příkazů delegátů využívající λ,-výrazy je nutno přesně znát praktické dopady plynoucí z takto zapsaných příkazů. V případě potřeby můžeme formální parametr uvést s explicitně určeným datovým typem: Del d = (int x) => x + 10 * 2; Zde zapíšeme datový typ formálního parametru společně s identifikátorem parametru uvnitř závorek (jinak kompilátor generuje chybové hlášení). Použití λ,-výrazu nás zbavuje povinnosti definovat samostatnou cílovou metodu, která bude spojena s instancí deklarovaného delegáta. Nemusíme dokonce definovat ani anonymní metodu, protože tu kompilátor vytváří automaticky podle znění λ-výrazu. λ-výraz smíme použít například při návrhu programu, který na základě vstupních dat klasifikuje nárůst výkonnosti původně sekvenčního algoritmu podle Amdahlovho zákona Praktická aplikace A,-výrazů v jazyce C # 3.0 Při určování přesnosti měření mnohdy využíváme normální rozdělení. Distribuční funkce normálního rozdělení (také známe jako Gaussovo rozdělení) je následující:. x je proměnná nabývající hodnoty od nekonečna do +nekonečna μ střední hodnota spočtené z jedotlivách údajú σ. je směrodatná odchylka
187 Máme spočítat, jaký je hodnota distribuční funkce pro hodnoty μ = 10, σ = 0,5 a hodnota proměnné x = 11. class Program delegate double Del(double mi, double sig, double x); static void Main (string [] args) Del p = (m, s, x) => 1 /(s* System.Math.Sqrt (2*System.Math.PI)*System.Math.Exp(-(x-m)*(x-m)/(2*s*s))); double hustotapravděpodobnosti = d (10, 1, 11); Console.WriteLine ("Hustota pravděpodobnosti" + "pro střední hodnotu 10, směrodatnou odchylu 0,5 a hodnotu 11 je 0.", hustotapravděpodobnosti.tostring ("0.00")); Console.Read (); λ-příkazy v jazyce C # 3.0 V programovacím jazyce C # 3.0 je možné pracovat is tzv.. A-příkazy, o kterých mluvíme tehdy, když tělo λ,-výrazu tvoří konečná a neprázdná množina programových příkazů. Generický vztah pro definici λ-příkazu má následující podobu: (Formální parametry) => příkaz 1; příkaz2; příkazní; λ-příkaz nachází uplatnění například při paralelní inicializaci čtvercové matice celočíselnými hodnotami: class Program static void Main(string[] args) int [,] pole = new int [10000, 10000]; Stopwatch sw = new Stopwatch (); sw.start(); Console.WriteLine ("Inicializace matice byla zahájena."); // Konstrukce Parallel.For využívá lambda-prikaz. Parallel.For (0, 10000, i => for (int j = 0; j <10000; j + +) pole [i, j] = 2 * i + j; ); sw.stop (); Console.WriteLine ("Matice je inicializována."); Console.WriteLine ("Exekuční čas: 0 s.", Sw.Elapsed.TotalSeconds); Console.Read ();
188 Tento program využívá řízenou knihovnu Parallel Extensions (známou také jako Parallel FX) společnosti Microsoft na podporu paralelního zpracování programových instrukcí. Abychom mohli využívat třídy pro podporu paralelizace, musíme provést dvě akce: 1. Do aktuálně otevřeného projektu jazyka C # 3.0 vložíme odkaz na sestavení knihovny Parallel Extensions. To provedeme takto: Z nabídky Project vybereme příkaz Add Reference. Když se objeví dialogové okno Add Reference, ujistíme se, že je vybrána záložka.net. V seznamu vyhledáme sestavení System.Threading, které odpovídá knihovně Parallel Extensions. Po kliknutí na tlačítko OK se odkaz na knihovnu Parallel Extensions vloží do projektu jazyka C # Do zdrojového souboru vložíme příkaz using a zabezpečíme import jmenného prostoru System.Threading. Jelikož máme za úkol inicializovat čtvercovou matici typu x 10000, budeme potřebovat 2 programové cykly. Pro potřeby tohoto praktického experimentu jsme se rozhodli paralelismu nadřazeného cyklus. Místo toho, abychom použili standardní cyklus for, aplikujeme jednu z množiny přetížených definic statické metody For třídy Parallel. Ta představuje cyklus for s možností paralelizace jeho iterací. λ-příkaz je umístěn v signature statické metody Parallel.For, která implementuje techniku imperativnío datového paralelismu. Praktické experimenty na počítači osazeném 2-jádrovým procesorem AMD Turion 64 X2 a 2 GB operační paměti ukázaly, že paralelizace snižuje exekuční čas potřebný na zpracování inicializační části algoritmu o přibližně 42%. Otázky k probranému učivu Co je to delegát v jazyce C Co znamená kovariance a kontravariance delegátů λ-výrazy v jazyce C # 3.0 Shrnutí pojmů kapitoly (podkapitoly) Delegát je v jazyce C # 3.0 uživatelsky deklarovaný odkazový datový typ, jehož instance v sobě zapouzdřuje odkaz na cílovou instanci nebo statickou metodu.
189 Jazyk C # 2.0 (a stejně i jazyk C # 3.0) definuje dvě vlastnosti delegátů, které vymezují pravidla pro přípustné odlišnosti signatur. Tyto vlastnosti jsou známé jako kovariance a kontravariancia delegátů. Použitá literatura, kterou lze čerpat k dalšímu studiu Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku -> Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer
190 Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, Cíl Po prostudování tohoto odstavce budete umět definovat algoritmy, co je algoritmus a co znamená jeho složitost Časová složitost algoritmu a asymptotická notace Bubble sort Heapsort Třídění Merge sort Výklad Algoritmy, Co je algoritmus a co znamená jeho složitost Jednoduše řečeno, algoritmus je návod k řešení nějakého problému. Počítače jsou stroje a ty nemyslí. Musíme tedy dopodrobna popsat všechny kroky algoritmu, které musí být elementární (skládat se z konečného počtu jednoduchých a snadno srozumitelných kroků, tedy příkazů). Co když budeme po počítači chtít, aby prohodil hodnoty dvou proměnných pojmenovaných například a, b? Jistě snadno poznáte, že následující kód nebude fungovat: a = b; b = a; Nejprve se do proměnné a se uloží obsah proměnné b. Do proměnné b se pak uloží obsah proměnné a. Promněnné a jsme již přiřadili obsah proměné b. Musíme si tedy pomoci další proměnnou c: c = a; a = b; b = c; Nyní se obsahy proměnných a a b prohodily. Tuto posloupnost příkazů můžeme chápat jako algoritmus prohození osahu dvou proměnných. Všimněme si, že nelze říci "prohoď čísla". Musíme dokonale popsat co má program dělat pomocí posloupnosti příkazů. Další důležitou vlastností algoritmu je, že musí být determinovaný. V každém kroku musí jít určit, zda algoritmus skončil a pokud ne, pak musí být jednoznačně řečeno, jaký bude dalčí krok. Algoritmus musí skončit se správným výsledkem a na všechny vstupy smysluplné vstupy. V případě algoritmu prohození čísel nezáleží na tom, zda bude a=10; b=20 či zda
191 bude a=999; b=102. Hovoříme tedy o tom, že algoritmus musí být obecný. Navíc algoritmus musí být ještě konečný, vždy musí skončit s konečným počtem kroků Časová složitost algoritmu a asymptotická notace Časová složitost algoritmu souvisí s dobou, po kterou daný algoritmus (tedy program) běží. Doba závisí na množství dat na vstupu algoritmu (třídění deseti čísel bude jistě rychlejší než třídění milionu čísel). Problém je v tom, že nemůžeme prohlásit: "Tomuto algoritmu výpočrt trvá 5 vteřin". Místo času tedy počítáme příkazy, které počítač vykoná, a to v závislosti na vstupu. Uveďme si algoritmus, který najde v poli minimum (tedy nejmenší prvek). Na počátku předpokládáme, že je minimum první prvek a prodeme pole prvek poprvku od začátku do konce. Pokud nalezneme menší úrvek, než je minumum, prohlásíme jej za nové minimum a pokračujeme dále. Na konci pole budeme znát minimální prvek. Pole musíme celé projít, pokud bude mít n prvků, musíme n krát porovnat uložené minimum s daným prvkem, n krát zvýšit proměnnou cyklu a několikrát za nové minimum dosadit. Dejme tomu, že v průměrném případě to uděláme 3n krát. Časová složitost bude O(3n). Složitost O(3n) budeme chápat jako O(n). Zajímá nás kvalita algoritmu tedy řád proměnné n. Budeme předpokládat, že 1 operace trvá 1ms: Prvků O(n) O(n log n) O(n 2 ) O(n!) 10 0,01 sekundy 0,03 sekundy 0,1 sekundy 1 hodina 15 0,015 sekundy 0,06 sekundy 0,2 sekundy 41 let 100 0,1 sekundy 0,7 sekundy 10 sekund nevyčíslitené sekundu 10 sekund půl hodiny nevyčíslitelné Dejme tomu, že počítač měl frekvenci procesoru 3GHz. Co když ho zrychlíme? Ty špatné algoritmy přece půjdou potom rychleji, ne? Přestože algoritmus se složitostí O(n) po 10ti násobném zrychlení počítače zvládne opravdu 10x více prvků, ty s vyšší časovou složitostí ale zvládnou méně nebo podstatně méně prvků. Symbol velké O se nazývá "velké O notace" nebo "Omikron notace". Znamená, že složitost daného algoritmu je asymptoticky menší nebo rovna výrazu v závorce. Udává tedy, jakou "třídu časové složitosti" algoritmus nikdy nepřekročí. Můžete se také setkat se symboly velké Omega (větší nebo rovno) a také s malými ekvivalenty omegy a omikronu, které symbolizují ostrou nerovnost. Konečně velké Fí je asymptoticky rovno. Další vlastnosti algoritmů Stabilita O stabilním algoritmu hovoříme v případě, kdy zachovává předchozí pořadí takových prvků, které si jsou podle porovnávacího kritéria rovny. Pokud třídíme pole čísel, nemá to pro nás žádný užitek. Ale ve chvíli, kdy třídíme např. objekty nebo nějaké další kontejnerové struktury, může se nám to velmi hodit. Dejme tomu, že máme studenty seřazené v poli podle abecedy. A my je chceme seřadit podle věku. Pokud řadící algoritmus není stabilní, může se
192 nám stát, že dva stejně staří zaměstnanci (Adam a Zdeněk) budou v pořadí: Zdeněk, Adam. Stabilní algoritmus naopak v případě rovnosti zachová původní pořadí prvků, tedy Adam, Zdeněk. Zaměstnanci se stejným věkem tedy budou ještě seřazeni podle abecedy. Na místě Říkáme např., že třídíme na místě, pokud potřebujeme kromě vstupního pole žádnou dodatečnou paměť. Pokud ano, vzniká nebezpečí, že nemusíme mít paměti vždy dostatek Selection sort Selection sort je jedním z nejjednodušších řadících algoritmů. Jeho myšlenka spočívá v nalezení minima, které přesuneme na začátek pole (nebo můžeme hledat i maximum a to dávat na konec). V prvním kroku tedy nalezneme nejmenší prvek v poli a ten přesuneme na začátek. V druhém kroku již nebudeme při hledání minima brát v potaz dříve nalezené minimum. Po dostatečném počtu kroků dostaneme pole seřazené. Algoritmus má nepříliš výhodnou časovou složitost a není stabilní. Je však velice jednoduchý na pochopení i implementaci. Vlastnosti algoritmu Časová složitost O(n 2 ) Stabilita Rychlost Ne Pomalý Pozn. je myšlena časová složitost průměrného případu a rychlost vzhledem ke všem třídícím algoritmům Průběh Máme pole následujících prvků: Nyní cyklem projedeme pole od prvního do posledního prvku (rozsah cyklu je zvýrazněn barevně) a vybereme to nejmenší číslo (zde 1) Toto číslo prohodíme s prvním číslem (zde s 3): Tímto krokem jsme dostali první prvek v pole zatříděný. Nyní cyklem opět proedeme pole a hledáme nejmenší prvek, ale ne od počátku kde jiý máme nejmenší prvek, ale projdeme pole od druhého do posledního prvku a opět vybereme nejmenší prvek (2):
193 Prvek si opět zatřídíme, protože již máme 1 prvek zatříděný z minula, prohodíme ho s druhým prvkem (5) Teď máme již 2 první prvky ve správném pořadí. Takto budeme pokračovat dále, budeme vybírat minimum ze zbylých prvků a zatřizovat ho tak dlouho, dokud nebudeme mít pole celé setříděné. Další kroky algoritmu by vypadaly následovně: Poslední prvek je již logicky zatříděný, proto si můžeme tento krok ušetřit. A je hotovo. public static void selectionsort(int[] list) int temp, min; for (int i = 0; i < (list.length - 1); i++) min = list.length - 1; // hledani minima for (int j = i; j < (list.length - 1); j++) if (list[min] > list[j]) min = j; // prohozeni prvku temp = list[min]; list[min] = list[i]; list[i] = temp;
194 Bubble sort Bubble sort je poměrně hloupý algoritmus. Nemá žádné dobré vlastnosti a je zajímavý pouze svým průběhem. Algoritmus probíhá ve vlnách, přičemž při každé vlně propadne "nějtěžší" prvek na konec (nebo nejlehčí vybublá nahoru, záleží na implementaci). Vlna porovnává dvojice sousedních prvků a v případě, že je levý prvek větší než pravý, prvky prohodí. Algoritmus je stabilní. Možná zlepšení Algoritmus lze napsat nejjednodušeji dvěma vnořenými cykly s pevně daným počtem opakování (oba by proběhly tolikrát, kolik je v poli prvků). Pokud se ale trochu zamyslíme, zjistíme, že je zbytečné porovnávat nejtěžší prvky na "dně" pole, protože jsou již na správném místě. Vlny si tedy můžeme dovolit postupně zkracovat o 1 prvek a tu poslední tedy dokonce úplně vypustit. Dalším zlepšením může být kontrola, zda pole není již setříděné, a protio zbytečné v třídění pokračovat. Můžeme jednoduše nastavit přízak, zda při průchofu došlo k prohození či ne. Vrcholnou variantou "bublacího" algoritmu je tzv. Bubble sort s přetřásáním (Shaker sort neboli Cocktail sort), kde v každém běhu vnitřního cyklu proběhnou 2 vlny - jedna zleva doprava, tlačící těžší prvky dolů, druhá zprava, vynášející lehčí prvky nahoru. Odstraňuje to problém tzv. zajíců a želv, kde zajíci jsou těžké prvky, které se rychle propadají dolů. V původní implementaci jdou však lehké prvky nahoru jen velmi pomalu. Vlastnosti algoritmu Časová složitost O (n 2 ) Stabilita Rychlost Ano Velmi špatná Pozn. je myšlena časová složitost průměrného případu a rychlost vzhledem ke všem třídícím algoritmům Průběh Protože Bubble sort je poněkud neoptimalizovaný algoritmus, bude průběh o něco delší, než bylo doposud zvykem. Mějme pole následujících prvků: První vlna projede celým polem a největší prvek "probublá" na konec.
195 Začneme tedy s vlnou a porovnáme první 2 prvky (3 a 2): je jistě větší než 2, proto prvky prohodíme Porovnáme další 2 prvky (3 a 5) Ty jsou ve správném pořadí, takže je prohazovat nebudeme. Vlna pokračuje dále Po první vlně se maximum (9) opravdu probublalo na konec. Poslední prvek je tedy zatříděný a my si ho už nebudeme všímat. Další vlna bude o 1 prvek kratší, než předchozí, a vynese na konec maximum z nesetříděné části pole Po druhé vlně máme na konci již 2 setříděné prvky. Uděláme další vlnu.
196 Vidíme, že pole je nyní setříděné. Program by udělal ještě jednu vlnu a když by zjistil, že se nic neprohodilo, vrátil by výsledek. Insertion sort je králem mezi jednoduchými třídícími algoritmy. Je stabilní, jednoduchý na implementaci, chová se inteligentně na již předtříděných polích a na malých polích (řádově desítky prvků) díky své jednoduchosti předběhne i QuickSort. Insertion sort vidí pole rozdělené na 2 části - setříděnou a nesetříděnou. Postupně vybírá prvky z nesetříděné části avkládá je mezi prvky v setříděné části tak, aby zůstala setříděná. Od toho jeho jméno - vkládá prvek přesně tam, kam patří a nedělá tedy žádné zbytečné kroky, jako například Bubble sort. Na větších polích se samozřejmě začně projevovat handicap algoritmu, který spočívá v jeho časové složitosti O(n 2 ) a začíná zaostávat za algoritmy chytřejšími. public static void bubblesort(int[] list) int j = list.length - 2, temp; // kontrola prohozeni bool swapped = true; while (swapped) swapped = false; for (int i = 0; i <= j; i++) // prohozeni if (list[i] > list[i + 1]) temp = list[i]; list[i] = list[i + 1]; list[i + 1] = temp; swapped = true; j--;
197 Heapsort Heapsort patří mezi chytré algoritmy, které jsou řádově rychlejší než ty doposud zmíněné. Staví na myšlenkách algoritmu Selection sort a je tedy založený na odtrhávání extrému (v tomto případě maxima), které vždy přesouvá na konec pole. Po začlenění všech maxim na konec máme jistě pole setříděné. Problém Selection sortu však byl právě ve hledání maxima nebo minima. V každém vnějším cyklu se celá nesetříděná část pole musela prohledat a každý prvek zkontrolovat, zda není náhodou hledaným maximem. V poli maximum rychleji asi nenalezneme. Ale co kdyby existovala datová struktura, ve které bychom mohli prvky reprezentovat stejně, jako v poli, a zároveň jsme maximum nalezli v konstantním čase, bez jediného průchodu? Nazývá se Halda (anglicky Heap, proto Heap sort neboli třídění haldou). Halda je binárním stromem s následujícími vlastnostmi: Všechna "patra" haldy až na poslední jsou plně obsazeny prvky (tedy každý vnitřní vrchol má právě 2 syny, strom je velmi vyvážený) Poslední patro haldy je zaplněno zleva (může být i zaplněno celé) Pro prvky v haldě platí tzv. speciální vlastnost haldy: oba synové jsou vždy menší nebo rovny otci. Ze speciální vlastnosti haldy nutně vyplývá, že v kořeni bude vždy uloženo maximum, což se nám velmi hodí. Halda může vypadat například takto:
198 Samozřejmě si můžeme definovat speciální vlastnost haldy obráceně a mít v kořeni minimum. Protože obvykle chceme třídit pole a ne haldu, musíme si z tohoto pole haldu nejprve postavit. Teď jistě očekáváte, že si vytvoříme stromovou strukturu pomocí ukazatelů nebo referencí a do ní budeme prvky přidávat. Haldu lze však malým trikem (díky jejímu vyvážení) reprezentovat v obyčejném poli, nebudeme tedy potřebovat žádnou pomocnou strukturu ani paměť. Budeme pracovat přímo v našem poli, na které se budeme dívat jako na haldu a můžeme tedy třídit rovnou na místě. Halda v obrázku výše by v poli vypadala takto (nahoře indexy prvků, dole prvky): Všimněte si, že zhaldované pole nemá moc společného s polem setříděným. Teď si ukážeme, jak s tímto polem pracovat jako s haldou. Jistě jste si všimli, že prvky v poli jsou prvky tak, jak jsou v haldě, seřazené shora dolů a zleva doprava. Když budeme chtít přistoupit ke kořeni, jednoduše sáhneme na index 0, poslední prvek haldy je také na indexu posledního prvku pole. Budeme však potřebovat přístup z libovolného otce do jeho synů a také ze syna k jeho otci. Pokud bude mít syn index i, index jeho otce vypočítáme jako (i - 1) / 2 (dělení je celočíselné). Pokud bude mít otec index i, jeho levý syn bude mít index (2 * i + 1) a pravý syn (2 * i) + 2.
199 Můžeme si rychle ověřit, že to funguje. Zkusíme najít syna prvku 7, ten má index 1 (pole je indexováno od 0 a je druhý). (1-1) / 2 je 0, jeho otec je tedy prvek 9, což sedí. Jeho synové budou (2 * 1 + 1) = 3 a na třetím indexu je pětka - opravdu jeho levý syn. Pravý syn má index o jednu větší a opravdu je na něm prvek 4. Máme tedy potřebné nástroje a můžeme se pustit do prvního kroku algoritmu - zhaldování pole. Průběh Zhaldování pole (postavení haldy) Máme pole následujících prvků: Pole nám reprezentuje následující "haldu": Uvozovky jsem použil záměrně, protože tato halda je rozbitá - neplatí nám speciální vlastnost haldy. Musíme ji tedy opravit a pole zhaldovat. Použijeme k tomu funkci up (nahoru), kterou budeme postupně volat na prvky od kořene dolů. Funkce zjistí, jestli daný prvek neporušuje speciální vlastnost haldy (tedy pokud není větší než jeho otec) a pokud je, prvek s otcem prohodí. Tím se však může stát, že se stejný problém přenese o úroveň výše, proto se funkce opakuje, dokud speciální vlastnost haldy pro daný prvek neplatí nebo dokud nedojedeme do kořene. Funkci voláme pro každý prvek dokud nedojedeme nakonec, pak si můžeme být jisti, že jsme haldu opravili. Na první prvek (kořen, 3) funkci up pouštět nebudeme, protože ten žádného otce nemá. Začneme tedy s prvkem 2, ten je menší než otec, necháme ho na místě. Další je prvek 5, ten je větší než otec (3), proto ho s otcem prohodíme a jsme již v kořeni, takže končíme. Prvek 1 je menší než 2, to je v pořádku. 9 je však větší než 2, proto je prohodíme. 9 je však stále větší než 3, prohodíme tedy znovu a 9 (maximum) se dostává do kořene. 4 je větší než 3, proto
200 prohodíme. 4 je potom menší než 9, to je již v pořádku. Zhaldované pole a halda, kterou reprezentuje, vypadají následovně: Můžeme tedy přistoupit k samotnému třídění Třídění Jak jsem se již zmínil, budeme provádět vlastně Selection sort v haldě, budeme tedy prohazovat maximum s posledním prvkem, potom další druhé maximum s předposledním atd. Maximum leží vždy na indexu 0, takže ho nalezneme v konstantním čase. Prohození také žádný čas nezabere. Menší problém tu však bude - prohozením prvků si totiž zcela jistě haldu rozbijeme. Původního maxima vzadu si již sice všímat nebudeme (je to již setříděná část pole, které si stejně jako v Selection sortu nevšímáme), ale prvek, který je nyní novým kořenem, dost pravděpodobně nebude maximum. Haldu opravíme podobnou funkci jako up, tentokrátfunkcí down (dolů). Tu pustíme na kořen a pokud je některý se synů větší než otec, tak ho s otcem prohodíme. Pokud jsou větší oba, prohodíme otce s větším synem. Podobně jako u funkce up, i zde se nám problém může posunou dolů, proto se znovu podíváme na syna (nyní již otce) a zjistíme, zda je větší než jeho synové. Proces opakujeme, dokud platí speciální vlastnost haldy nebo dokud dojedeme do poslední úrovně. Tuto funkci musíme zavolat po každém prohození kořene s posledním prvkem. Když se zamyslíme, zjistíme, že odtržení maxima nás nestojí vůbec nic, prohození také ne. Jediný časový problém je s funkcí down, která se bude provádět n krát (pro každé maximum) a v každém svém zavolání se bude opakovat maximálně log n krát (protože halda je vyvážený binární strom, má výšku log n, kde základ logaritmu je 2, protože každý vrchol má 2 syny. Funkce prověřuje jeden prvek v každém patře, v nejhorším případě tedy projede všechna patra, kterých je log n). U funkce up je časová složitost je, jako u funkce down, tedy opět O(n log n). Celkově zavoláme jednou funkci up a funkci down poté n krát, tedy výsledná časová složitost heapsortu je O(n log n)
201 Oproti dříve zmíněným algoritmům jsme si výrazně polepšili a zrychlení na větších polích je obrovské. Algoritmus však není stabilní, takže náročné uspokojí až Merge sort nebo Quicksort. Vlastnosti algoritmu Časová složitost O (n log n) Stabilita Rychlost Ne Velmi dobrá Pozn. je myšlena časová složitost průměrného případu a rychlost vzhledem ke všem třídícím algoritmům Merge sort Merge sort je algoritmus, založený na tzv. principu rozděl a panuj (latinsky divide et impera, anglicky divide and conquer). To znamená, že pokud nějaký problém neumíme vyřešit v celku, rozložíme si ho na více menších a jednodušších problémů. Ten samý postup aplikujeme i na tyto problémy (opět je rozdělíme na ještě menší, mimochodem velmi se zde nabízí rekurzivní řešení) až se dostaneme na takovou úroveň, kterou jsme schopni bez problému řešit. V problému třídění se často chceme dostat až k poli velikosti 1, které považujeme automaticky za setříděné. Merge sort operuje s myšlenkou, že dokážeme velmi rychle a v lineárním čase slít (spojit, anglicky merge) dvě již setříděná pole do jednoho tak, aby výsledné pole bylo opět setříděné. Na začátku samozřejmě máme jen jedno pole a to setříděné není. My si ho však můžeme rekurzivně rozdělit na 2 poloviny, každou polovinu opět rozdělit na další poloviny a tak dále. V nejhlubší hladině rekurze se nutně dostaneme do fáze, kdy nám zbydou už jen samá jednoprvková pole. Jednoprvkové pole můžeme považovat za setříděné, říká se o něm, že je setříděné triviálně. Jak se postupně vynořujeme z rekurze, sléváme tato jednoprvková pole na dvouprvková, ta na čtyřprvková a tak pokračujeme, dokud nám nezbydou dvě velká pole, která když slijeme, dostaneme naše původní pole setříděné. Protože sléváme vždy po rozdělení na poloviny, budeme to jistě dělat log n krát (kde základem logaritmu je 2, protože dělíme na 2 poloviny). Samotné slévání zvládneme v čase O(n), výsledná časová složitost algoritmu tedy bude O(n log n). Nejprve si ukážeme, jak se pole slévají. Slévání Mějme 2 pole, která jsou již setříděná: 2 3 5
202 Všimněte si, že pole nemusí mít stejnou velikost. Když budeme v Merge sortu dělit na polovinu pole o velikosti 7, dostaneme přesně takováto pole. Úskalím Merge sortu je fakt, že budeme potřebovat pomocnou paměť, ta bude zatím prázdná Pomocná paměť: Začneme tedy slévat. V každém poli budeme potřebovat 2 indexy, které budou zpočátku na prvním indexu. Zde budou zobrazeny tučně Pomocná paměť: Porovnáme prvky na indexech a ten menší prvek vložíme do pomocného pole na pozici, která je součtem indexů. Oba indexy máme nyní na pozici 0 (první pozice), pozice v pomocném poli tedy bude = 0. Porovnáme a zjistíme, že 1 < 2, vložíme tedy jedničku na pozici 0 do pomocného pole a index u jedničky zvětšíme o Pomocná paměť: 1
203 2 < 4, vložíme tedy dvojku na = 1 a posuneme se s indexem Pomocná paměť: < 4, vložíme 3 na pozici = 2, posuneme se s indexem Pomocná paměť: Pokračujeme dále Pomocná paměť: Pomocná paměť:
204 Nyní jsme se dostali do fáze, kdy jsme alespoň s jedním indexů již vyjeli z pole. Nyní se podíváme, zda druhý index není ještě v poli (zde je) a dolijeme zbylé prvky Pomocná paměť: Máme hotovo, výsledné pole je setříděné. V praxi samozřejmě nedostaneme pole už takto připravené na 2 setříděné poloviny, ale pořádně rozházené. Musíme se tedy rekurzí dostat až na jednotková pole a ty poté začít slévat. Průběh Průběh označuje následující diagram:
205 Možná vylepšení Kamenem úrazu algoritmu je potřebná pomocná paměť. Když se podíváte na diagram výše, vidíte, co všechno si musí funkce držet v paměti. Mohli bychom ovšem třídit naopak, tedy brát původní pole jako již rozdělené na pole jednotková a ta rovnou slévat na pole velikosti 2, ty pak na pole velikosti 4 a podobně. Pomocné paměti bychom se nezbavili, ale při troše práce by stačilo jen jedno pomocné pole, které by bylo stejně velké, jako pole původní. Do něj bychom si uložili výsledek po slití a potom slévali do pole původního a zas naopak. Přelévali bychom tedy data střídavě mezi těmito poli. Vlastnosti algoritmu Časová složitost O(n log n) Stabilita Rychlost Ano Pozn. je myšlena časová složitost průměrného případu a rychlost vzhledem ke všem třídícím algoritmům
206 // sliti dvou setridenych poli do jednoho public static void merge(int[] list, int[] left, int[] right) int i = 0; int j = 0; // dokud nevyjedeme z jednoho z poli while ((i < left.length) && (j < right.length)) // dosazeni toho mensiho prvku z obou poli a posunuti indexu if (left[i] < right[j]) list[i + j] = left[i]; i++; else list[i + j] = right[j]; j++; // doliti zbytku z nevyprazdneneho pole if (i < left.length) while (i < left.length) list[i + j] = left[i]; i++; else while (j < right.length) list[i + j] = right[j]; j++; // samotne trideni public static void merge_sort(int[] list) if (list.length <= 1) //podmínka rekurze return ; int center = list.length / 2; //stred pole int[] left = new int[center]; //vytvoreni a naplneni leveho pole for (int i = 0; i < center; i++) left[i] = list[i]; int[] right = new int[list.length - center]; //vytvoreni a naplneni praveho pole for (int i = center; i < list.length; i++) //vytvoreni a naplneni praveho pole right[i - center] = list[i]; merge_sort(left); // rekurzivni zavolani na obe nova pole merge_sort(right); merge(list, left, right); //sliti poli
207 Otázky k probranému učivu Co je to algoritmus. Popište funkci algoritmu Selection sort Popište funkci algoritmu Bubble sort Popište funkci algoritmu Headsort Shrnutí pojmů kapitoly (podkapitoly) Jednoduše řečeno, algoritmus je návod k řešení nějakého problému. Selection sort je jedním z nejjednodušších řadících algoritmů. Jeho myšlenka spočívá v nalezení minima, které přesuneme na začátek pole (nebo můžeme hledat i maximum a to dávat na konec). V prvním kroku tedy nalezneme nejmenší prvek v poli a ten přesuneme na začátek. V druhém kroku již nebudeme při hledání minima brát v potaz dříve nalezené minimum. Po dostatečném počtu kroků dostaneme pole seřazené Bubble sort je poměrně hloupý algoritmus. Nemá žádné dobré vlastnosti a je zajímavý pouze svým průběhem. Algoritmus probíhá ve vlnách, přičemž při každé vlně propadne "nějtěžší" prvek na konec (nebo nejlehčí vybublá nahoru, záleží na implementaci). Vlna porovnává dvojice sousedních prvků a v případě, že je levý prvek větší než pravý, prvky prohodí. Heapsort patří mezi chytré algoritmy, které jsou řádově rychlejší než ty doposud zmíněné. Staví na myšlenkách algoritmu Selection sort a je tedy založený na odtrhávání extrému (v tomto případě maxima), které vždy přesouvá na konec pole. Po začlenění všech maxim na konec máme jistě pole setříděné. Problém Selection sortu však byl právě ve hledání maxima nebo minima. V každém vnějším cyklu se celá nesetříděná část pole musela prohledat a každý prvek zkontrolovat, zda není náhodou hledaným maximem. Cíl Po prostudování tohoto odstavce budete umět popsat Quicksort a variace Quicksortu hledání extrému v poli binární vyhledávání Výklad Quicksort Jak již název napovídá, Quicksort je rychlý. Chová se dobře jak na malých, tak na velkých polích a je paměťově nenáročný. Algoritmus je založen na principu rozděl a panuj, který jsme si již vysvětlili v algoritmu Merge sort.
208 Quicksort si označí jeden prvek v poli jako tzv. pivot. Výběrem pivotu se prozatím nebudeme zabývat a budeme jako pivot brát vždy první prvek v poli. Nyní zavoláme funkci divide (rozděl), která přeuspořádá pole tak, aby byly zleva prvky menší než pivot, poté následovat pivot sám a za pivotem byly prvky větší, než je on sám. Prvky jsou tedy mezi sebou stále rozházené a jediné setřídění spočívá v jejich rozdělení pivotem. Tuto operaci jsme schopni zvládnout v lineárním čase a také velmi rychle. Nyní algoritmus rekurzivně zavoláme na levou polovinu pole před pivotem a na pravou polovinu za pivotem (pivota už necháme tam, kde je). S těmi provedeme to samé, jako s původním polem a takto budeme pokračovat až do chvíle, kdy na vstupu dostaneme jednotkové pole (pole velikosti 1). Po vynoření z rekurze nemusíme dělat už vůbec nic, pole je setříděné. Nyní si algoritmus předvedeme v praxi a řekneme si více o funkci rozděl. Průběh Protože Quicksort je velmi rychlý, předvedeme si ho tentokrát na poli o trochu větším, než obvykle, abychom z něj něco vůbec viděli Máme tedy pole následujících prvků: Jako pivot si vybereme první prvek (5) Nyní na pole zavoláme funkci divide. Funkce si jako první uloží pivot na konec pole, aby nepřekážel a také abychom se na něj mohli snadno odkazovat. Udělá to jednoduchým prohozením pivota s posledním prvkem Nyní si budeme držet pozici, od které začínají prvky větší, než je pivot (tedy pozice prvního většího prvku, než je pivot, dále už jen pozice). Protože jsme na začátku, bude mít hodnotu 0. Pole nyní projedeme cyklem od začátku do předposledního prvku (protože poslední je pivot). Pokud je prvek v poli menší, než je pivot, prohodí se s prvkem na pozici. Protože v tu chvíli se počet prvků menších než pivot zvětší, musíme i pozici zvýšit o 1. Až dojedeme do cíle, prohodíme samotný pivot s pozicí (jeden z větších prvků se tedy dostane na konec a pivot sám bude vymezovat obě části pole). Protože se pozice pivotu jistě posune (těžko bude zase na začátku, když jsme před něj navkládali menší prvky), musí tuto novou pozici funkce divide vrátit, aby s ním Quicksort mohl dále pracovat. V následující vizualizaci algoritmu budu pozici znázorňovat jako tečku
209 V našem poli tedy začneme prvním prvkem (8), pozici máme také na začátku. Protože prvek 8 je jistě větší než pivot (5), necháme ho na místě, s pozicí nebudeme hýbat a přistoupíme k dalšímu prvku (2) je jistě menší než 5, proto prvek prohodíme s prvkem na pozici (tedy prvek 2 se prohodí s prvkem 8 ) a pozici posuneme o 1 doprava. Přistoupíme k dalšímu prvku (9) necháme na místě (9 > 5) a přistoupíme k prvku < 5, prohodíme, zvýšíme pozici, jdeme na prvek není menší než 5, necháme být a jdeme ne poslední prvek < 5, prohodíme, zvýšíme Nakonec pivot prohodíme s prvkem na pozici Takhle tedy vypadá pole po zavolání funkce divide, nesmíme zapomenout vrátit pozici, aby Quicksort věděl, kde je nyní pivot. Všimněte si, že výsledek nemá se setříděným polem
210 moc společného, pole se pouze rozdělilo na dvě části pomocí pivotu. Takto nám v podstatě vznikla 2 další pole a to [2, 3, 1] a [5, 9, 8]. Nyní se "rozdvojíme" a pustíme Quicksort na obě vzniklá pole (tedy přesněji na 2 části původního pole, nová pole v podstatě nevznikla, jen se na situaci tak můžeme dívat). Nejprve zpracujeme to první, druhá větev bude zatím chvíli čekat. Původní pole nám samozřejmě nezmizí, jen si budeme všímat jen jeho části a proto bude zbytek znázorněn šedě. Vybereme pivota (2) Zavoláme na část pole funkci divide, výsledek bude vypadat takto: Když nyní pole opět rozdělíme, dostaneme 2 jednoprvková pole a to [1] a [3]. To je přesně to, čeho jsme chtěli dosáhnout, protože jednoprvkové pole považujeme za triviálně setříděné. Quicksort již na jednoprvkové pole volat funkci divide nebude a levá polovina pole je hotová. Vynoříme se tedy z rekurze a přejdeme k pravé polovině, která na nás zatím čekala. Vybereme pivota, tedy prvek Po zavolání funkce divide se jistě nic nezmění, pivot zůstane na začátku a oba prvky jsou větší než on. Moc si tedy nepomůžeme a pole se nám zkrátí jen o pivot. V nové části pole vybereme pivot (9) Funkce divide vrátí následující výsledek: Jednoprvkové pole ([8]) považujeme za setříděné. Vynoříme se z rekurze a pole je setříděné. Časová složitost Ohledně stability musím dodat, že zde uvedená funkce divide pro jednoduchost není stabilní. Lze ji však napsat tak, aby stabilní byla. Jak je to však s časovou složitostí? Již jsem se zmínil, že funkce rozděl probíhá v lineárním čase, její časová složitost je tedy O(n). Kolikrát se však provádí? Na to není jednoznačná odpověď a pokud očekáváte háček, očekáváte správně. V ideálním případě nám funkce divide pole rozdělí na 2 stejné poloviny.
211 Když něco dělíme na poloviny, složitost bude jistě dvojkový logaritmus, tedy O(log n). A protože samotné dělení trvá O(n), složitost celého algoritmu by v tomto případě byla naše oblíbené O(n log n). Je dokázáno, že Quicksort má i v průměrném případě opravdu tuto složitost, i když poloviny nejsou vždy přesně dodrženy. Jak již to bývá, extrémní rychlost Quicksortu je vykoupena špatným chováním algoritmu na již předtříděných polích. Zatím co Bubblesort si na předtříděných polích liboval, Quicksort vybere jako pivot první prvek (tedy nejmenší nebo největší číslo) a funkce rozděl pak logicky před pivota už žádný prvek nadá. Nové pole je tedy vždy menší jen o jeden jediný prvek a druhé pole se vůbec nevytvoří. Složitost nám spadne na O(n 2 ). S tímto problémem souvisí i hackerské útoky na informační systémy. Představte si, že víte, že nějaká firma třídí data v databázi pomocí Quicksortu, který vybírá jako pivota vždy první prvek. Stačí jim poslat tisíce setříděných polí, jejich algoritmus se náhle propadne na časovou složitost O(n 2 ) a protože server s takovou zátěží nepočítá, tak spadne. Tento problém lze však poměrně snadno vyřešit, nicméně je velmi důležité vědět, že existuje Variace Quicksortu Vybírat první prvek tedy asi není úplně nejlepší nápad. Když vybereme poslední, problém bude úplně stejný. Nabízí se nejjednodušší řešení: vybereme prvek prostřední nebo třeba vždy prvek ve 2/3 pole. Útočníka sice zmateme, ale když bude znát naše zdrojové kódy, dokáže opět vyrobit taková pole, aby složitost algoritmu spadla na O(n 2 ). Dalším řešením by mohlo být vybrat vždy medián a z něj udělat pivot. Pole bychom tak dělili vždy přesně na 2 poloviny. Dokonce i existuje algoritmus, který je schopný najít medián v lineárním čase. Takový Quicksort by měl potom zaručenou asymptotickou časovou složitost opravdu O(n log n). V praxi však bohužel hledání mediánu algoritmus natolik zpomalí, že se pak tuto variantu nevyplatí použít. Co kdybychom pivot prostě vybírali náhodně? Trefa, této verzi se říká Randomizovaný Quicksort. V praxi výběr náhodného čísla algoritmus časově příliš nezatíží. Prakticky však náhodné číslo neexistuje. Čísla, která generuje počítač, se nazývají pseudonáhodná čísla. Softwarové náhodné generátory totiž většinou pracují se systémovým časem a číselnými řadami. Např. unixové systémy jsou známé tím, že jejich generátory využívají šum ze zvukové karty nebo teploty procesoru. Generátory používané například pro armádní kriptografii mohou využívat štěpení izotopů a podobných jevů. Ale zpět k randomizovanému quicksortu - v 99% případů se jedná o naprosto spolehlivý a rychlý algoritmus. Prakticky je nenapadnutelný, i když teoreticky náhodné číslo neexistuje. Kdyby to však náhodou někomu nestačilo, existuje ještě další varianta quicksortu: Introsort. Introsort je Quicksort, který nemusí být ošetřený proti napadení a může tedy vybírat jako pivot hned první prvek. Navíc si však bude počítat, jak hluboké je zanoření rekurze. Pokud přesáhne log n, přepne se Quicksort na Heapsort a zbytek pole je setříděný Heapsortem, který má zaručenou složitost O(n log n) na jakémkoli poli. Introsort se v praxi vyskytuje velmi často a je to tedy již pravý algoritmus, kterým většina programů třídí svá data. Časová složitost O (n log n) pro průměrný případ, O (n 2 ) v nejhorším případě
212 Stabilita Rychlost Může být implementován stabilně Velmi vysoká Pozn. je myšlena časová složitost průměrného případu a rychlost vzhledem ke všem třídícím algoritmům // preusporada pole na prvky mensi nez pivot, pivot a prvky vetsi nez pivot public static int divide(int[] list, int left, int right, int pivot) int temp = list[pivot]; // prohozeni pivotu s poslednim prvkem list[pivot] = list[right]; list[right] = temp; int i = left; for (int j = left; j < right; j++) if (list[j] < list[right]) // prvek je mensi, nez pivot temp = list[i]; // prohozeni pivotu s prvkem na pozici list[i] = list[j]; list[j] = temp; i++; // posun pozice temp = list[i]; // prohozeni pivotu zpet list[i] = list[right]; list[right] = temp; return i; // vrati novy index pivotu public static void limited_quicksort(int[] list, int left, int right) if (right >= left) // podminka rekurze int pivot = left; // vyber pivotu int new_pivot = divide(list, left, right, pivot); // rekurzivni zavolani na obe casti pole limited_quicksort(list, left, new_pivot - 1); limited_quicksort(list, new_pivot + 1, right); // zavola omezeny quicksort na cele pole public static void quicksort(int[] list) limited_quicksort(list, 0, list.length - 1);
213 Hledání extrému (minima a maxima) v poli Algoritmus pro nalezení největšího nebo nejmenšího prvku v poli je sice zřejmý, ale pro jistotu i úplnost jsem se ho rozhodl uvést. Budu zde popisovat vyhledání maxima, vyhledání minima bude potom analogické. Algoritmus bude mít zpočátku v proměnné uložené výchozí maximum. Potom projede pole prvek po prvku a pokud je nějaký prvěk větší, než toto maximum, bude novým maximem tento prvek. Po projetí pole bude v proměnné jistě maximum z daných hodnot v poli. Teď jak je to s tím výchozím maximem. Některé z vás jistě napadlo dát mu hodnotu 0. Co když ale budeme mít pole plné záporných čísel? Naše maximum bude maximem i po průchodu polem (protože nic nebude větší než 0) a jeho hodnota je naprosto chybná, protože se v poli ani nevyskytuje. Dáme mu tedy nějakou hodnotu z pole, vůbec nezáleží jakou. Nabízí se např. první prvek, protože k němu máme nejsnažší přístup. V praxi se také do proměnné neukládá maximální hodnota, ale index s maximálním prvkem (v případě výchozího prvního prvku by byl tedy 0). Mnohdy nás totiž zajímá index, pod kterým se maximální hodnota skrývá, více, než kolik maximální hodnota číselně je. Když např. budeme mít v poli zaměstnance (objekty) a budeme chtít vypsat toho s nejvyšším platem, nebude nás zajímat jeho plat, ale jeho jméno. Když budeme mít index toho zaměstnance, můžeme si potom vypsat jeho libovolný údaj včetně jeho platu nebo jeho jména. Na závěr lze dodat, že časová složitost algoritmu je O(n) a neměli bychom tedy operaci provádět příliš často. Pokud tomu tak bude, měli bychom se zamyslet nad vhodnější datovou strukturou, než je pole. Nabízí se například setříděné pole nebo halda. Zdrojové kódy Ve zdrojových kódech program v jednom průchodu spočítá minimum i maximum z daného pole. // vrátí pozici minima ze zadaného pole public static int minimum (int[] list) int min = 0; for (int i = 0; i < list.length; i++) if (list[i] < list[min]) min = i; return min; // vrátí pozici maxima ze zadaného pole public static int maximum (int[] list)
214 int max = 0; for (int i = 0; i < list.length; i++) if (list[i] > list[max]) max = i; return max; Binární vyhledávání Základním problémem vyhledávání je struktura, ve které máme data uložená. Pokud budeme mít uloženo milion zaměstnanců v obyčejném poli a budeme mezi nimi chtít najít zaměstnance s platem kč, nezbude nám nic jiného, než celé pole projet cyklem od začátku do konce, kdy můžeme jen doufat, že cestou někdy narazíme na hledaný prvek. Nejlepší případ jistě bude, když hledaný zaměstnanec bude hned na prvním indexu v poli, nejhorší bude ten, kdy tam zaměstnanec vůbec nebude a my musíme projet celé milion prvků dlouhé pole, abychom na to přišli. Průměrná časová složitost takového naivního algoritmu by byla O(n), kde n je délka pole. Vyhledávání lze však výrazně urychlit tím, když si budeme udržovat pole setříděné. Algoritmus, který se používá pro vyhledávání prvků v setříděném poli, se nazývá binární vyhledávání. Binární proto, že dělí pole na dvě poloviny. Má následující průběh: Podíváme se na hodnotu uprostřed pole (prostřední index není problém získat, když známe délku pole, jen ji vydělíme dvěma) Pokud je hledaný prvek menší, než ten, který se nachází v prostředku pole, omezíme se pouze na levou polovinu pole a pravé si nebudeme všímat. Pokud je větší, omezíme se na pravou polovinu pole a nebudeme si všímat té levé. Můžeme to udělat díky tomu, že si jsme jisti, že v této polovině se prvek nemůže nacházet, protože je na to moc velký. Na zbylou polovinu aplikujeme to samé pravidlo, opět se podíváme doprostřed a zaměříme se jen na jednu polovinu této poloviny pole (už nás tedy zajímá jen 1/4 původního pole). Postup opakujeme, čímž se stále přesněji a přesněji přibližujeme k hledanému prvku. Obvykle stačí jen pár kroků k nalezení prvku, který hledáme. Končíme v tu chvíli, kdy je v prostředku prvek, který hledáme nebo jakmile už nemáme co dělit - tehdy tam hledaný prvek není. Když se nad algoritmem zamyslíme, zjistíme, že v každém kroku zahodíme celou polovinu pole, která nás vlastně nezajímá, protože víme, že se v ní hledaný prvek nemůže nalézat. Složitost bude tedy O(log n), kde základem logaritmu bude 2 (protože dělíme na 2 poloviny). Toto zrychlení je obrovské a jedná se o nejlepší možnou složitost problému vyhledávání. Existují sice algoritmy, které mohou hledat rychleji, ale bohužel je to vykoupeno špatnými nejhoršími případy.
215 Protože nic není dokonalé, naskýtá se i zde problém. Tímto problémem je vlastnost struktury pole, která sice umožňuje rychle přistupovat k prvkům pomocí indexu, ale pokud chceme do pole prvky přidávat nebo mazat, musíme pole zvětšit a prvky po jednom poposouvat, abychom vytvořili místo na nový prvek, který chceme vložit někam dovnitř. Tato operace je neskutečně časově nákladná. Nesmíme zapomenout na to, že pole si musíme udržovat setříděné. Pokud nějaký prvek přidáme, musíme ho vložit na místo, kam patří. Představte si pole velké milion, kde nově vkládaný prvek patří někam doprostřed - musíme provést půl milionu posunů doprava a vytvořit uprostřed volné místo. Někdo by mohl namítat, že bychom mohli použít spojový seznam, tam se sice vkládá v konstantním čase, ale zase nemůžeme rychle přistupovat k indexům. Řešení bude tedy někde uprostřed, existují totiž speciální struktury zvané vyhledávací stromy, které oba problémy řeší. Pokud chcete v poli jen vyhledávat, nic lepšího než binární vyhledávání není (pokud neznáte o datech nějakou další vlastnost). Pokud chcete hodně vyhledávat a občas něco přidat nebo umazat, pořád se algoritmus vyplatí. Pokud však potřebujete vyhledávat a i pole měnit, je to problém a čtěte další algoritmy v této sekci. Algoritmus binárního vyhledávání je možné zapsat s rekurzí i bez rekurze. Tradičně začneme řešením bez rekurze. Zdrojový kód - bez rekurze public static int binary_search(int[] array, int item) int left = 0; int right = array.length - 1; int center; if ((item < array[left]) (item > array[right])) // prvek mimo rozsah return -1; while (left <= right) // pokud mame co delit center = (left + right) / 2; if (item == array[center]) return center; // nalezeno else if (item < array[center]) right = center - 1; // vyhodime pravou polovinu else left = center + 1; // vyhodime pravou polovinu return -1; Rekurzivní řešení je zde o něco elegantnější, ale zase je vykoupeno větší paměťovou náročností. Zdrojový kód - s rekurzí public static int binary_search(int[] array, int item, int left, int right) int center;
216 if (left > right) return -1; center = (left + right) / 2; if (item < array[center]) return binary_search(array, item, left, center - 1); // vyhodime pravou polovinu if (item > array[center]) return binary_search(array, item, center + 1, right); // vyhodime levou polovinu return center; // nalezeno Otázky k probranému učivu Popište algoritmus Quicsort Popište princip hledání extrému v poli Shrnutí pojmů kapitoly (podkapitoly) Jak již název napovídá, Quicksort je rychlý. Chová se dobře jak na malých, tak na velkých polích a je paměťově nenáročný. Algoritmus je založen na principu rozděl a panuj Visual C# Developer Center -> C# 3.0 Language Specification -> download.microsoft.com/download/3/8/ 8/388e7205-bci b2a8-7535ic66Qb0Q/csharp%20language%20specificatÍ0n.d0c. Blogs from the C# Team -> C# Samples for Visual Studio > Visual C# Technical Articles -> us/vcsharp/bb aspx. How Do I Videos - Visual C# -> Visual Studio 2008 and.net Framework 3.5 Training Kit -> MSDN Forums - Visual C# -> Webcasty v českom a slovenskom jazyku ->
217 Praktické cvičenia v slovenskom jazyku -> MSDN Magazine -> C# Corner -> Akhter, S., Roberts, J.: Multi-core Programming. Hillsboro: Intel Press, Hanák, J.: Inovácie v jazyku Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic 2005 pro pokročilé. Brno: Zoner Press, Hanák, J.: Programujeme v jazycích C++ s Managed Extensions a C++/CLI. Praha: Microsoft, Hanák, J.: C# - praktické příklady. Praha: Grada Publishing, Hanák, J.: VB 6.0 -> VB 2005: Přecházíme z jazyka Visual Basic 6.0 na jazyk Visual Basic Praha: Microsoft, Hanák, J.: Visual Basic.NET Začínáme programovat. Praha: Grada Publishing, Kraval, I.: Základy objektově orientovaného programování za pomoci jazyka Microsoft Visual Basic 5.0. Brno: Computer Press, Kraval, I., Ivachiv, P.: Základy komponentní technologie COM. Brno: Computer Press, Vaníček, J., Papík, M., Pergl, R., Vaníček, T.: Teoretické základy informatiky. Praha: Kernberg Publishing, 2007.
218 13. PROGRAMOVACÍ JAZYK A VÝVOJOVÉ PROSTŘEDÍ Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět Nainstalovat vývojové prostředí visual c# Po prostudování této kapitoly a vypracování úkolů získáte: Znalosti základních funkcí vývojového prostředí. Po prostudování této kapitoly a vypracování úkolů budete schopni: Vytvořit funkční program Výklad Programovací jazyk a vývojové prostředí V této učebnici budeme používat programovací jazyk C# vytvořený firmou Microsoft v rámci projektu. NET. Vývojové prostředí je specializovaný program pro vývoj programů. Toto vývojové prostředí shrnuje několik, dříve samostatných programů, jako je editor, grafický editor, překladač, nástroje ladění programu a pod V následujících kapitolách se budeme seznamovat s obecnými principy psaní programu, využijeme volně dostupné vývojové prostředí Microsoft Visual C# Express Edition. Jazyk C# a vývojové prostředí Visual C# jsou svázané s operačním systémem Windows. Ke studiu budeme potřebovat počítač s operačním systémem Windows a nainstalované prostředí Microsoft Visual C# Express Edition. Vzhledem k tomu, že firma Microsoft specifikaci jazyka C# zveřejnila, je možno v jazyce C# programovat i na platformách dalších operačních systémů. Pro začátky však doporučuji Windows a Visual C#. Pak se budete moci držet příkladů v textu Instalace vývojového prostředí Instalační soubory vývojového prostředí Microsoft Visual C# Express Edition lze zdarma stáhnout z webových stránek firmy Microsoft. Je k dispozici ve více verzích, my použijeme verzi Tato je dostupná z adresy Před zahájením stahování programu se objeví dialogové okno. Nejjednodušší postup je zvolit volbu Spustit kliknutím na příslušné
219 tlačítko. Po spuštění stáhnutého programu se objeví dialogové okno s průběhem stahování jednotlivých komponent vývojového prostředí. Obr. Instalace vývojového prostředí Proces instalace vývojového prostředí lze rozdělit do čtyř následujících kroků: Kontrola systémových požadavků - pro verzi 2010 potřebujete alespoň Windows XP. Vlastní instalace. Volitelně instalace opravného balíčku (Service Pack) vývojového prostředí. Registrace programu je zdarma a je ji nutno provést do třiceti dnů. V průběhu instalace je důležité v obrazovce Licence terms" zaškrtnout souhlas s licenční smlouvou (políčko I have ). Obr. Průběh instalace
220 Jednotlivé části produktu se budou postupně stahovat a instalovat na váš počítač Obr. Ukončení instalace Po prvním spuštění programu v nabídce (menu) Help zvolíte Help > Register Product. Objeví se dialogové okno, kde zvolíte Obtain a registration key online. Vytvoříte si dle pokynů účet u firmy Microsoft, zadáte požadované informace včetně platného ového účtu a nakonec vám na obrazovce prohlížeče vyskočí okno, ze kterého zkopírujete registrační kód a potvrdíte registraci produktu. Obr. Registrační klíč
221 Předpokládám nyní, že jste instalaci úspěšně zvládli, a můžeme se pustit do prvních programů. Nedaří-li se vám nějakou náhodou program nainstalovat, požádejte o pomoc vyučujícího, zkušenějšího kolegu či kamaráda. Instalace vývojového prostředí na vašem počítači je krok nezbytně nutný k dalšímu pokračování! Spuštění vývojového prostředí Během instalace se vytvoří zástupce v nabídce Start. Odtud můžete vývojové prostředí spustit. Při prvním spuštění proběhne konfigurace programu a pak se objeví úvodní obrazovka podobná té z obrázku. Obr. Okno nového projektu
222 13.4. První program Ukážeme si, že vytvořit funkční program není vůbec obtížné. Z nabídky File /zvolíme New Project. V novém dialogovém okně, viz obrázek 5.7. V jeho hlavní části nazvané Templates" je třeba vybrat vhodnou šablonu podle toho, jaký druh programu chcete vytvářet. My budeme vytvářet především aplikace s grafickým uživatelským rozhraním, proto myší klepněte na Windows Application", resp. Windows Forms Obr. Dialogové okno pro vytvoření projektu Vybereme volbu New Project, v 5.7 je červeně zarámována. Objeví se nové dialogové okno, které můžeme vidět nav5.8. Postupně, podle číslovaných rámečků vybereme v poli Installed Templates" volbu Visual C# (krok 1). Pak ve druhém kroku vybereme ve středním poli Windows Form Application". Dále je potřeba nový projekt1 v poli Name" nějak pojmenovat. Můžete zvolit název např. MujPrvniProgram" (3) a vše odsouhlasit stiskem tlačítka OK" (4). Úvodní obrazovka, jak můžeme vidět na obrázku 5.8, bude nahrazena prostředím Návrháře Design v zatím jediné záložce vývojového prostředí. Návrhář umožňuje v grafické formě návrh uživatelského rozhraní programu. Prvotní návrh programu obsahuje čtvercové okno nového programu. Program má v titulkové liště nápis Form1" a ikonky pro minimalizaci, maximalizaci a zavření okna programu. Jde vlastně o zcela funkční kostru nového programu, který obsahuje mnohé z toho, na cop jsem u programů zvyklí.
223 Obr. Vzhled vývojového prostředí po vytvoření projektu Nyní si můžeme náš nový program spustit. A to i přesto, že jsme zatím vůbec neprogramovali. Vývojové prostředí vytvořilo náš první projekt z plně funkční šablony. Moc toho sice neumí, ale poběží. Program v této formě spustíme tak, že z nabídky Debug vyberte položku Start Debugging. Ve spodní stavové liště vývojového prostředí se postupně objeví nápisy Build started", Build progress" a Build succeeded". Dochází k tzv. sestavování programu. Program je během chvilky připraven a vývojové prostředí ho spustí. Vzhled spuštěného programu odpovídá tomu čtvercovému oknu, které bylo zobrazeno v záložce Návrháře. Běžící program má svou vlastní ikonu, zástupce dole na hlavním panelu Windows. Program lze minimalizovat, maximalizovat, okno programu lze zvětšit tažením za okraj nebo za roh. V tuto chvíli jsme úspěšně vytvořili a spustili náš prvního programu. Než pokročíme dále, nezapomeňme spuštěný program zavřít ikonkou s křížkem Uložení projektu Než budeme pokračovat v práci, nejprve náš projekt uzavřeme. Ve vývojovém prostředí můžeme pracovat vždy jen s jedním projektem. Vývojové prostředí tedy musí zavřít aktuální projekt., tzn. v našem případě Můj první program", zeptá se vás, jestli stávající projekt chcete uložit. Obdržíme tři možnosti: Uložit náš projekt (1). Neuložit či neuložit změny (2). Zrušit akci (3). Zvolíme možnost (1), Save, uložit.
224 1 2 3 Obr. V novém okně volíme, jakým způsobem a kde chceme projekt uložit. Máme k dispozici pole: Obr. Dialog pro uložení projektu Jméno projektu (1), zde můžeme změnit jeho jméno. Umístění projektu (2), vybíráme složku, do které se má projekt uložit. Pokud už jsme někdy toto umístění zadávali, pak šipkou (5) můžeme vybírat ze seznamu složek, pro výběr složky lze také využít tlačítko Browse (6). Název řešení (3), může být shodný se jménem projektu, můžeme ukládat ještě další nastavení našeho řešení. Vytvořit složku pro řešení (4), pokud je políčko zatrženo, vytvoří se složka řešení se jménem určeným v poli (3). V našem příkladu tedy zvolíme složku, ponecháme jméno projektu, zrušíme zaškrtnutí (4) a projekt uložíme tlačítkem Save (7). Zvolenou složku (2), do které vývojové prostředí umístilo náš projekt, si dobře zapamatujeme. Pokud projekt budeme chtít přenést jinam, někam odeslat, musíme odeslat obsah této složky Druhý program Zatím se opět ke psaní programu moc nedostaneme. Mnoho věcí, zejména vzhled rozličných prvků se určuje přímo grafickým návrhem. Jiné vlastnosti, jako jsou například popisky, velikost, typ a barva text se volí nastavením příslušných vlastností prvku. Postup a princip si vysvětlíme na prvku, který jsme použili již ve svém prvém programu. Je to okno programu. Říkali jsme, že okno má v titulkové liště jméno programu. Toto jméno lze změnit. Obdobně lze nastavit počáteční velikost okna, kterou má program při svém spuštění.
225 Změna velikosti okna je velmi jednoduchá. V Návrháři změníme velikost okna tažením za některý z jeho úchytných bodů, což jsou malé čtverečky na okraji okna. Vyzkoušejme si změnu velikosti a pak spusťme program. Program se spustí s novou velikostí okna. Než budeme pokračovat dále, program ukončete. Název okna a jeho další vlastnosti lze změnit pomocí Editoru vlastností a událostí, který se nachází v panelu Properties. Panel zobrazíme z nabídky View volbou Properties Window. Můžeme použít i klávesovou zkratku Ctrl+W, P (nejprve zmáčkneme klávesu Ctrl a pak klávesu W, následně samostatně klávesu P). Obr. Jak zobrazit panel Vlastnosti V tomto panelu se zobrazují všechny vlastnosti právě vybraného objektu. Pokud potřebujeme například změnit titulek v záhlaví okna, musíme v grafickém editoru toto okno nejprve vybrat klepnutím ukazatele myši někam do tohoto okna. V panelu vlastností - Properties - se zobrazí vlastnosti vybraného prvku, v našem případě vybraného okna. Vzhled vývojového prostředí může vypadat podobně tomu, které je zobrazeno na obrázku. Zeleně (1) je na obrázku zvýrazněno vybrané okno v grafickém editoru Design a červeně (2) panel vlastností Properties. Okno se jmenuje Form1, automaticky jsou zobrazeny i jeho vlastnosti.
226 1 2 Obr. Objekt okna programu a jeho vlastnosti v panelu Vlastností V panelu vlastností (2) jsou vypsány všechny vlastnosti příslušného objektu. Věnujme tedy několik okamžiků a seznamme se s tím, co tento panel obsahuje. nahoře můžeme vidět seznam všech objektů (1), Níže je umístěna lišta s ikonami, kterými můžeme určovat, co a jakým způsobem bude zobrazeno v seznamu vlastností vybraného objektu (2). Úplně na spodu je pole, ve kterém se vypisuje stručná charakteristika, co která vlastnost určuje. Seznam vlastností, jak jej vidíme na obrázku je minimalizovaný, Jsou zde zobrazeny jen skupiny vlastností. Každá skupina se dá rozvinout kliknutím na prázdnou šipku vlevo.
227 1 2 3 Obr. Okno vývojového prostředí po zobrazení Editoru vlastností a událostí Vraťme se nejprve k ikonám. Jejich účel shrnuje tabulka. Pro naši práci se ujistěme, že je vybrána ikona vlastností, má tvar prosvětleného tlačítka. Pak je pro tento případ klikněte na první ikonu, řazení do skupin podle společné charakteristiky skupiny vlastností. Jedná se například o vlastnosti, které určují vzhled prvku. Mnohdy je výhodnější řazení vlastností podle abecedy. Pokud rozklikneme všechny skupiny, nevejdou se nám do okna a na boku se zobrazí posuvník. Nemusíme si pamatovat význam všech vlastností, většinou si zapamatujeme jen ty nejpoužívanější. Ostatní bývají nastaveny tak, aby vlastnosti prvku vyhovovaly v co možná nejvíce případech. Pokud nám nestačí krátký popis té které vlastnosti v poli naspodu okna (3), pak jej můžeme dohledat v dokumentaci vývojového prostředí. O hledání v nápovědě se zmíníme později. Tab. Rozlišení způsobu uspořádání vlastností Kategorie - řazení dle kategorií Abecedně - řazení dle vlastností Vlastnosti - zobrazit vlastnosti
228 Události - zobrazit události Stránky vlastností Zvolíme-li abecední řazení, pak je na každém řádku Editoru je zobrazena jedna vlastnost. V levém sloupci je její název, v pravém její hodnota. Důležité vlastnosti okna programu shrnuje Tab.. Zkuste některé z nich změnit. Pak program spusťte (již víme jak) a podívejme, jak se změnil vzhled a chování našeho programu (okna). Hodnotu konkrétní vlastnosti změníte tak, že na ni klepnete. Pak buď zapíšete novou hodnotu určující vybranou vlastnost, nebo ji vybereme z nabízeného seznamu. Tab. Důležité vlastnosti okna programu Vlastnost okna Popis Text Text v titulkové liště okna BackColor Barva pozadí okna; lze vybírat ze systémových barev (záložka System"), nebo z doporučených barev pro internet (záložka Web"), nebo uživatelem definovaných (záložka Custom ). FormBoderStyle Styl rámečku okna (např. zda jde okno zvětšovat atp.) MinimizeBox Určuje, zda okno má ikonku pro minimalizaci. MaximizeBox Určuje, zda okno má ikonku pro maximalizaci. HelpButton Určuje, zda okno má ikonku pro nápovědu. Na Obr. je zobrazen vzhled našeho druhého programu. Vidíte, že text v titulku okna je změněn na Druhý Program a místo vlevo je umístěn vpravo, někam zmizely ikonky pro minimalizaci a maximalizaci okna a přibyla ikonka s otazníčkem, ikona nápovědy. Barva pozadí okna je na modrá. Všechny změny byly provedeny změnou příslušných vlastností: text titulkové lišty okna na Druhý program", do pole vlastnosti RightToLeft byla vepsána (lépe vybrána z nabídnutého seznamu) hodnota Yes - nadpis v okně je zarovnán zprava doleva, vlastnost HelpButton byla nastavena na hodnotu true - zobrazila se ikonka s otazníkem, vlastnosti MinimizeBox a MaximizeBox byly nastaveny na hodnotu False, ikonky pro minimalizaci a maximalizaci okna se nezobrazily, barva pozadí BackColor byla nastavena na hodnotu GradientActiveCaption.
229 Pokud jste pozorně přečetli předchozí text, všimli jste si, že různé vlastnosti se definovaly různým způsobem. Všimněme se zatím následujících možností: Tam, kde byl výběr jen ze dvou možností, například ikona se zobrazí / ikona se nezobrazí, jsme také volili jen ze dvou možností: true a false, pravda, platí, 1 a nepravda, neplatí, 0. Jedná se o logické hodnoty. Logické výrazy mohou nabývat pouze dvou hodnot. Vlastnost, která buď je, nebo není, také nabývá pouze dvou hodnot, je logického typu. Některé vlastnosti, například velikost okna, nabývají číselných hodnot. Tyto hodnoty mohou být celočíselní nebo desetinné. Titulek okna je libovolný text, tato vlastnost nabývá textových hodnot Soubory projektu Již jsme několikrát hovořili o pojmu projekt. Podívejme se, co si pod tímto slovem máme představit. Projekt tvoří soubor složek a souborů na disku počítače. V Průzkumníku Windows si zobrazíme složku, do níž jsme uložili např. Druhý program. Obsah složky bude vypadat obdobně tomu, co lze vidět na obrázku Obr. Složka s projektem v Průzkumníkovi Ve složce projektu vidíme složky v modrém rámečku (1), definiční soubory projektu ve fialovém rámečku (2) s příponami csproj. user a csproj, soubory zdrojových kódů programu, s příponou resx. V této chvíli jsou pro nás nejdůležitější soubory s příponou.cs, tedy ty v červených rámečcích (3). Těmto souborům říkáme zdrojové texty programu (někdy se také říká zdrojové kódy). Tyto soubory obsahují vlastní zápis (kód) programu zapsaný v programovacím jazyce C#. Jde vždy o soubory prostého, neformátovaného textu. Takovéto soubory si můžete prohlédnout v editorech jako je např. Poznámkový blok, PSPad apod. Také je lze v těchto editorech vytvořit případně upravovat.
230 Obr. Zdrojový text Form1.cs v Poznámkovém bloku Příklad zdrojového kódu Form1. cs v Poznámkovém bloku vidíme na obrázku Text je pěkně čitelný, zatím mu v počátku studia programování pravděpodobně nerozumíte. Zdrojové texty jsou základní formou uložení programu. Přesto, že uživatelské rozhraní programu navrhujeme graficky pomocí grafického editoru, ten je ukládá v podobě zdrojového kódu. Obrázek návrhu okna s titulkovou lištou a ikonkami v okně grafického editoru nebo Editor vlastností a událostí, to jsou nástroje, které vývojové prostředí nabízí programátorovi. Vlastní obrázek okna programu nikde na disku nenajdeme. Otevřeme-li dříve uložený projekt, vývojové prostředí Visual C# vytvoří obrázek návrhu okna i nastaví hodnoty jeho vlastností podle zdrojových kódů programu! Kdykoli ve vývojovém prostředí změníme podobu okna nebo jeho vlastnosti, prostředí tyto změny uloží ve zdrojovém kódu programu. Kromě zdrojových textů se ve složce projektu nalezneme soubor s příponou. csproj, což je definiční soubor projektu. Tento soubor určitým způsobem zastřešuje celý projekt, říká, co všechno do projektu patří, které zdrojové texty, odkazy na knihovny atd. Dovnitř souboru nebudeme nikdy přímo zasahovat. Soubor nikdy nemažeme.
231 Obr. Složka projektu DruhyProgram Vývojové prostředí umožňuje rovněž vytváření větších programových systémů tak, že několik příbuzných projektů sdružit do jednoho řešení (solution). Pak v kořenové (základní) složce projektu nalezneme soubor s příponou sln, jde o tzv. definiční soubor řešení. Soubor obsahuje seznam obsahující, které všechny projekty patří do určitého řešení. Tento soubor také nemažeme ani sami neupravujeme. Chcete-li přenést rozpracovaný program např. ze školy domů, je nejjednodušší přenést celou složku projektu se všemi soubory a podsložkami. Některé soubory není nutné přenášet (vytvářejí se vždy znovu), ale pro jednoduchost raději zkopírujte vždy celou složku projektu. Po přenesení složky na nové místo, poklepejte buď na soubor. csproj, nebo na soubor. sln. Projekt se vám otevře ve vývojovém prostředí, které ale na daném počítači musí být instalované Spustitelná podoba programu Zdrojové texty, soubory projektu jsou pouze prostředky k vytvoření programu v tzv. spustitelné podobě. Takový soubor poznáme podle přípony. exe. Uživatelé potřebují mít program ve spustitelné podobě, pouze poklepat na příslušný.exe soubor, případně na jeho zástupce, a začít s programem pracovat.
232 Obr. Spustitelný program ve strojových instrukcích Spustitelný program tvoří sled instrukcí určených pro procesor počítače. Instrukce jsou zapsány ve strojovém jazyce. Tento kód je pro člověka obtížně srozumitelný (obr. 5-18). Spustitelná podoba vzniká během sestavování programu. Toto sestavování proběhlo v okamžiku, kdy jsme chtěli program spustit. V průběhu sestavování spustitelného kódu dochází: K překladu programu z programovacího jazyka do jazyka strojového, který ještě není spustitelný. Tomuto kódu říkáme objektový kód a příslušný soubor má příponu obj. Náš program totiž potřebuje využívat různých funkcí, které jsou společné pro většinu programů a které jsou umístěny v tzv. knihovnách.
233 Další fází je spojování různých částí programu a k vytváření vazeb na knihovny, případně jiné součásti operačního systému. Jedná se o takzvané sestavování (linkování) programu, jejím výsledkem je spustitelný soubor. V našem případě se po úspěšném sestavení DruhyProgram. exe zapíše do podsložky bin hlavní složky projektu. Pokud se do této složky podíváme, zjistíme, že složka bin obsahuje dvě další, pojmenované Debug a Release. Během práce na programu, během tzv. ladění, kdy hledáme rozličné chyby v programu, využíváme podložku Debug a ve finální verzi vznikne konečný tvar programu, který je umístěn ve složce Release. Do složky bin\debug se program sestavuje při volbě Debug > Start Debugging. Do složky bin\release se program vytváří, pokud volíme příkaz Debug > Start Without Debugging, případně Build > Build solution (sestavení bez spuštění) Průzkumník řešení Každý projekt je tvořen celou řadou souborů, odkazů atd. Proto je potřeba, aby vývojové prostředí obsahovalo nástroj, který se s jednotlivými součástmi projektu dokáže pracovat. Tímto nástrojem je Průzkumník řešení Jeho panel zobrazíme z nabídky View volbou Solution Explorer. Průzkumník řešení se standardně zobrazí na pravé straně okna vývojového prostředí, stejně jako Editor vlastností a událostí. Pokud jsou oba nástroje zobrazeny současně, pak si příslušnou část okna rozdělí napůl. Panely lze také střídat s využitím záložek. Panely mohou být také automaticky uzavírány. Pokud chceme, aby panel byl vždy zobrazen, připíchneme jej špendlíkem do okna (klepneme na něj), pokud jej chceme skrývat, pak špendlík vytáhneme (opět klepnutím kurzorem myši). Automatické ukrývání okna lze také zvolit příkazem AutoHide a to tak, že na lištu panelu klepneme pravým tlačítkem myši. a) b) c) Obr. Vývojové prostředí se spuštěným průzkumníkem Vývojového prostředí se spuštěným Průzkumníkem řešení ukazuje Obr V případě a) je zobrazen Průzkumník a panel Vlastnosti, v případě b) je zobrazen pouze Průzkumník a panel Vlastnosti je skrytý za svou záložku, v případě c) jsou oba panely ukryty a jsou zobrazeny pouze dvě záložky.
234 Jednotlivé části řešení (solution) jsou v Průzkumníku organizovány hierarchicky. Na vrcholu u horního okraje panelu Průzkumník, je ikona řešení - DruhyProgram. Jeho součástí je jediný projekt DruhýProgram. Projektu obsahuje především zdrojové texty: Form1. cs, Program, cs a Form1. Designer. cs. Tento soubor se objeví po klepnutí na ikonku s prázdnou šipkou, vedle ikonky Form1. cs. Obr. Zobrazení panelu Návrháře z kontextové nabídky Průzkumníka řešení budeme používat zejména k akcím: Zobrazení návrhu Návrhářem - pokud si Návrháře omylem zavřeme; v takovém případě klepneme v Průzkumníku pravým tlačítkem na Form1.cs a z kontextové nabídky vyberte View Designer (klávesová zkratka Shift+F7, stiskneme klávesu Shift a pak současně funkční klávesu F7). Zobrazení zdrojového kódu. Činnosti, které budou vytvářené programy provádět, zapisujeme do zdrojového kódu Form1.cs. Pro jeho zobrazení klepneme pravým tlačítkem na Form1. cs a z kontextové nabídky vyberte View Code (klávesová zkratka F7). Přejmenování řešení, projektu, nebo zdrojových textů. Provedeme klepnutím pravým tlačítkem na příslušnou položku a z kontextové nabídky vyberte Rename.
235 Jistě jste si všimli, že zdrojových textů je několik. V našem příkladu máme takové soubory tři, rozsáhlé projekty jich obsahují třeba tisíce. Tím, že rozsáhlý program rozčleníme na jednotlivé soubory, se zvýší přehlednost celého programového kódu. Členěn není nahodilé, nýbrž je smysluplně. Každý soubor obvykle osahuje nějakou logicky ucelenou část programu. Šablona Windows Application", která nám posloužila k vytvoření našich programů, obsahuje tři zdrojové soubory. Oba soubory Form1 se týkají hlavního okna programu. Soubor Program, cs je jakási koordinátor našeho programu. Anglické slovo Form", v češtině jej označujeme formulář", označuje okno programu. Těch může být více. Jejich jména vývojové prostředí pojmenovává Form1", Form2 atd., formuláře postupně čísluje. Jedná o první formulář", první okno", Druhý formulář Jména těchto objektů je možné změnit tak, aby byl zřejmý jejich funkce. Soubory začínající Form1 jsou dva Form1. cs a Form1.Designer.cs. Soubor Form1. cs upravuje programátor, říká, co má náš program dělat. Soubor Form1.Designer.cs vytváří Návrhář a zapisuje do něj, jaká okna a ovládací prvky programátor navrhl a jaké jsou jejich vlastnosti. Z funkčního hlediska na názvech zdrojových textů programu nezáleží. Pro přehlednost je správné je volit smysluplně. Název zdrojového souboru by měl naznačovat, funkci programu tento zdrojový kód řeší. Pojmy k zapamatování K programování se používá vývojové prostředí např. vývojové prostředí Microsoft Visual C# Express Edition. Nové programy se vytváří jako projekty z nabídky File > New Project. Programy s grafickým uživatelským rozhraním se vytvářejí ze šablony Windows Application", resp. Windows Forms Application" K vizuálnímu návrhu uživatelského rozhraní programu slouží Návrhář. Jeho součástí je editor Vlastnosti umožňující nastavování různých vlastností okna programu (barva, text titulkové lišty atd.). Editor se vyvolá nabídkou View > Properties Window. Připravené programy se spouští pomocí nabídky Debug > Start Debugging. Projekty se na disku ukládají vždy do samostatné složky. Jejím přenesením lze projekt přenést na jiný počítač. Složka projektu obsahuje několik zdrojových textů a dalších, převážně pomocných souborů. K manipulaci s projektem slouží Průzkumník řešení. Jeho zobrazení je možné přes nabídku View > Solution Explorer. Programy se uživatelům distribuují ve spustitelné podobě jako soubory s příponou. exe. Otázky k probranému učivu K čemu slouží vývojové prostředí
236 Co je to projekt a jaké soubory jej tvoří Jak vypadá spustitelná podoba programu. Co musíte zkopírovat, pokud chcete přenést program z jednoho počítače na jiný Shrnutí pojmů kapitoly (podkapitoly) Pro vývoj programů používáme vývojová prostředí, Projekt tvoří různé soubory, které jsou umístěny ve složce projektu, která se jmenuje tak, jak jsme ji pojmenovali v začátku tvorby projektu. Výchozí formou programu je jeho zdrojový kód Po sestavení programu vývojovým prostředím vznikne spustitelný program. Ten má příponu exe. Cíl Po prostudování tohoto odstavce budete umět Vytvořit na ploše okna programu ovládací prvky. Přiřadit vlastnosti ovládacímu prvku. Po prostudování této kapitoly a vypracování úkolů získáte: Základní potřebné dovednosti s panelem Vývojář (Designer). Informace potřebné pro práci s panelem Vlastnostmi (Properties) Po prostudování této kapitoly a vypracování úkolů budete schopni: Vypracovat uživatelské rozhraní programu a přiřadit jeho prvkům základní vlastnosti. Výklad Textové pole Nejdůležitějším ovládacím prvkem pro nás bude Textové pole. Je to obdélník, který uživateli programu umožňuje zadat v textové formě vstupní údaj. Textové pole lze také s výhodou využít v i pro zobrazování výstupních údajů programu. Okážeme si i ovládací prvek popisek, textové pole má k dispozici více nástrojů, zejména zalamování řádků a posuvníky. Ukážeme si, jak vytvořit program s textovým polem, který bude mít ve svém okně jedno textové pole, ve kterém bude blikající kurzor čekat na zadání textu od uživatele. Nejprve vytvoříme nový projekt Textové pole". Zvolíme vhodnou velikost okna. K práci nyní budeme potřebovat sadu nástrojů anglicky nazývané nazvané Toolbox". Najdeme ji u levého okraje okna vývojového prostředí. Toolbox zastupuje ikonka s obrázkem kladívka a
237 paličky. Pokud se nějakým nedopatřením ztratí, můžete Nástroje zobrazit z nabídky View volbou Toolbox. Obr. Záložka panelu nástrojů Klepnutím myší na Toolbox jej rozbalíme. V Nástrojích je umístěno mnoho užitečných nástrojů. Ty jsou pro přehlednost sdruženy do skupin. Názvy skupin mají vedle sebe buď prázdnou, nebo plnou šipku. Prázdná šipka znamená, že skupina je sbalená. Klepnutím na šipku ji můžeme rozbalit. Plná šikmá šipka znamená, že skupina je rozbalená a klepnutím na šipku ji můžeme sbalit.
238 Obr. Nástroje - volba skupiny společné prvky Pro naši další práci bude důležitá zejména skupina Common Controls", v té jsou umístěny všechny základní ovládací prvky, se kterými budeme pracovat. V rozbalené skupině vyhledáme ovládací prvek nazvaný TextBox". Kurzorem myši nad něj najedeme, stiskneme levé tlačítko a ovládací prvek přetáhneme nad okno programu na povrchu Návrháře. Tam jej umístíme, uvolníme stisknuté tlačítko myši. Další možností, jak prvek umístit do okna programu, je pouze klepnout v Nástrojích na nástroj a ovládací prvek v okně programu myší umístit tak, že na nějakém místě stiskneme levé tlačítko myši a naznačený obdélník roztáhneme na žádanou velikost.
239 Obr. Menu pro vycentrování objektu v okně Pokud jsme textové pole přenesli do okna programu, máme náš třetí program hotový. Známým způsobem jej spustíme (Debug a Start Debugging) a vyzkoušíme, jak funguje. Do textového pole můžeme, zapisovat, zapsaný text je možno editovat, mazat, jsou i funkční klávesové zkratky Ctrl+C a Ctrl+V pro zkopírování nebo vložení označeného textu do / ze schránky Windows. Pokud chceme mít textové pole v okně programu vycentrované přesně, jako je tomu na obrázku, klepněme na něj, tím jej vybereme (zobrazí se úchytné body) a v menu Format vybereme příkaz Center in Form a pak Horizontally a Vertically. Totéž můžeme provést pomocí příslušných ikon na panelu nástrojů Layout". Vybraný prvek, a pouze ten, má své vlastnosti zobrazeny v panelu Vlastnosti. Klepneme-li na okno programu, bude vybráno ono, klepneme-li na textové pole, bude vybráno ono. Nejdůležitější vlastnosti pole jsou jeho jméno (Name) a Text. Vlastnost (Name) najdeme při abecedním řazení úplně nahoře. Tuto vlastnost má každý ovládací prvek. Přesněji se jedná o jeho identifikátor, pro jeho tvorbu platí určitá pravidla. Vždy, když vložíte do programu ovládací prvek, ho také ihned pojmenujte. Dodržováním tohoto pravidla si ušetříme řadu problémů v budoucnu! Vývojové prostředí přiřadí každému ovládacímu prvku nějaké jméno. Pro textová pole to jsou jména textbox1, textbox2, textbox3 atd. Pro pozdější snazší orientaci každému prvku dáme srozumitelné jméno. Výstižná pojmenování jsou v programování nesmírně důležitá. Pro čitelnost kódu. Náš další program bude pracovat se třemi textovými poli pro zadání jména, příjmení a telefonního čísla (obrázek ). Nabízí se pojmenování textových polí textjméno, textpříjmení a
240 texttelefon. Tím vyjádříme, že jde o textová pole, a navíc naznačíme, co se v nich bude zadávat. Stávající program z obrázku můžete mít vstupní jméno Telefon. Obr. Program Telefon, počáteční návrh Pravidla pro tvorbu identifikátoru (jména) jsou následující: Nesmí obsahovat mezery. I bez mezer jména zůstanou čitelná, pokud dodržíme konvenci:první slovo píšeme celé malými písmeny a v každém dalším slově napíšeme počáteční písmeno velké. Můžou obsahovat písmena, číslice a případně podtržítko a nesmí začínat číslicí. Ostatní znaky jsou nepřípustné. Je rozdíl mezi malými a velkými písmeny. Například textadresa a textadresa by byly dvě různé věci. Všimněme si také, že ve jménech můžete používat česká písmena i s diakritikou, s háčky a čárkami. Vlastnost text Nejdůležitější vlastností textového pole je Text. Hodnotou je text, který je v textovém poli zobrazen. Jde obvykle o text, který do něj zapsal uživatel. Počáteční hodnotu můžeme nastavit v nástroji Vlastnosti. Například do pole vlastnosti Text zapíšeme např. Zadejte text" (zapisujeme bez uvozovek). Po spuštění bude program mít hned od začátku v textovém poli nápis Zadejte text". Hodnota vlastnosti Text se během programu neustále podle toho, jak uživatel v textovém poli zadává nebo maže znaky. Program může přečíst, co obsahuje a s touto hodnotou pracovat. Další vlastnosti textového pole Mimo nejdůležitějších vlastností Name a Text najdeme další často využívané vlastnosti v Tab. 6. Vlastnost BackColor, ForeColor a Font nevyžadují nějaké další vysvětlování. Tyto tři
241 vlastnosti existují u všech ovládacích prvků. Velikost textového pole lze jednoduše měnit roztažením za okraj, stejně jako tomu bylo u okna programu. Obdobně se velikost dá měnit i u ostatních ovládacích prvků. Tab. Některé vlastnosti textového pole Název vlastnosti BackColor ForeColor Font Popis Barva pozadí textového pole (možno zvolit způsobem podobným jako u okna programu) Barva textu v textovém poli (možno zvolit způsobem podobným jako u BackColor) Druh a velikost písma použitého k zobrazení textu v textovém poli (rozbalte ikonku s plusem) Chování textového pole určují vlastnosti z tabulky 6. Vyzkoušejte, co se stane, když budeme měnit jejich hodnotu. Tab. 6 Vlastnosti ovlivňující chování textového pole Tab. Další vlastnosti textového pole Název vlastnosti Popis MaxLength Multiline ReadOnly ScrollBars TextAlign Maximální počet znaků, které uživatel může do textového pole zadat. Při nastavení na True umožňuje zadání víceřádkového textu. Při nastavení na True nedovolí editaci textu. Vhodné, když nějaký text chcete jen zobrazit. Umožňuje okolo textového pole zobrazit posuvníky (hodnoty Vodorovný, Svislý, Oba, Žádný). Zarovnání textu v textovém poli (Doleva, Doprava, Na střed). UseSystemPass wordchar Při nastavení na True místo znaků zobrazuje puntíky. Vhodné pro zadávání hesla. WordWrap Při nastavení na True automaticky na konci řádku zalamuje text (u víceřádkového pole) Popisek Popisek je textový nápis, který můžeme umístit kamkoli do okna programu. Většinou se umisťuje blízko jiných ovládacích prvků a svou přítomností naznačuje jejich význam. Práci s
242 popisky si ukážeme na programu pro zadávání telefonních čísel, jehož vzhled je zachycen na Obr. 17. Ve Visual C# si vytvořte nový projekt nazvaný Telefon". Program bude mít šest ovládacích prvků - tři textová pole a tři popisky. Nejprve zadáme textová pole. Ze skupiny Common Controls" v Nástrojích přetáhnete na okno programu jeden objekt TextBox, pak druhý a nakonec třetí. Všechna tři textová pole umístíme pod sebe. Zarovnáni ve vodorovném směru je snadné, vývojové prostředí po hranách prvku ukazuje modré čáry, na které se další ovládací prvky samy umisťují. Ve svislém směru požadujeme, aby všechna tři textová pole byla ve stejných odstupech. Vybereme všechna tři pole tak, že přidržte klávesu Ctrl a postupně klepneme na každé pole. Všechna by měla být vybrána, označena malými čtverečky. Z nabídky Format zvolíme Vertical Spacing a pak Make Equal. Obr. Vyrovnávání popisků s textovými poli Do okna programu postupně přetáhneme tři popisky a umístíme je k textovým polím. Objekty popisků se nacházení v Nástrojích ve skupině Common Controls" pod názvem Label". Po přetažení je ve vodorovném směru zarovnáme pomocí modrých čar navzájem vůči sobě, ve svislém směru pomocí fialových čar vždy k pomyslným linkám, na kterých se v textových polích zobrazuje text. Popisky po řadě pojmenujeme popisekjméno, popisekpříjmení, popisektelefon. Textová pole pojmenujeme: textjméno, textpříjmění, texttelefon. Nakonec vlastnost (Name) samotného okna programu nastavíme na oknoseznamu. Po pojmenování všech objektů nastavte jejich vlastnosti Text podle Tab. 6. Všimněme si, že ačkoli vlastnost Text mají všechny druhy objektů, má u každého nich trochu jiný význam. Tab. 6 Hodnoty vlastnosti Text pro jednotlivé objekty
243 Tab. Hodnoty vlastnosti Text u jednotlivých objektů Objekt Hodnota vlastnosti Význam vlastnosti Text Text oknoseznamu Telefon text titulkové lišty popisekjméno Jméno: text popisku popisekpříjmení popisektelefon Příjmení: Telefon: textjméno (prázdné) text zobrazený v textovém poli textpříjmění texttelefon (prázdné) (prázdné) Klávesové zkratky a pořadí tabulátoru Program dále doplníme o klávesové zkratky. Ty jsou v textu zdůrazněný podtržením. V textu Jméno:" bychom např. mohli chtít podtrhnout písmeno J. Při stisku klávesy Alt+J požadujeme, aby se aktivovalo textové pole pro zadání jména. Podtržení konkrétního písmene v textu popisku se docílí tím, že ve vlastnost Text daného popisku změníme tak, že před písmeno, které má být podtrženo, napíšeme znak &. Tímto způsobem podtrhneme vždy první písmeno. Nové hodnoty jejich vlastností Text potom budou takové, jak je ukazuje Tab. 7. Tab. 7 Vlastnost Text změněná tak, aby vždy první písmeno bylo podtržené Tab. Změněná vlastnost Text popisku, první písmeno je podtrženo Objekt popisekjméno Text &Jméno: popisekpříjmění &Příjmení : popisekadresa &Telefon:
244 Pokud při spuštění programu nebudou podtržená první písmena všech popisků, je to zaviněno nastavením Windows, kdy až do stisknutí klávesy Alt se podtržení znaků skrývají. Ve Windows 7 nastavíme Ovládací panely->centrum usnadnění přístupu->usnadnit používání klávesnice->zjednodušit používání klávesových zkratek->zaškrtnout "Podtrhávat klávesové zkratky a přístupové klávesy". Ve Windows XP toto nastavení najdeme tak, že pravým tlačítkem klepneme na plochu, z kontextové nabídky vybereme Vlastnosti, v okně klepneme na kartu Vzhled" a v ní na tlačítko Efekty..." Vyzkoušíme-li klávesové zkratky Alt+J, Alt+P, Alt+A, zjistíme, že nefungují nebo že fungují jen zčásti. Funkčnost zkratek souvisí s otázkou pořadí tabulátoru, které nás zajímá pokaždé, když program má více ovládacích prvků a bude mít ovládání z klávesnice. Tabulátor je klávesa, která slouží k pohybu mezi jednotlivými ovládacími prvky sloužícími jako vstup z klávesnice. Pokud uživatel stiskne tabulátor, přesune se zaměření klávesnice (někdy se říká fokus) na další ovládací prvek. Pořadí, ve kterém jsou jednotlivé ovládací prvky aktivovány, se nazývá pořadím tabulátoru. Pořadí určuje vlastnost TabIndex každého ovládacího prvku. Prostředí Visual C# přiřazuje ovládacím prvkům hodnoty TabIndex podle pořadí, ve kterém jsme je vytvořili. Prvky, které jsme vytvořili dříve, budou mít hodnoty vlastností TabIndex, jak je ukazuje prostřední sloupec v Tab. 8. Číslování začíná nulou, což je v jazyce C# běžné. Čísluje se v něm všechno od nuly. Tab. Změna hodnot pořadí tabulátoru Objekt původní hodnota Tablndex nová Tablndex polejméno 0 1 polepříjmění 1 3 poleadresa 2 5 popisekjméno 3 0 popisekpřijmění 4 2 popisekadresa 5 4 hodnota
245 Obr. Původní pořadí tabulátoru Abychom vyřešili náš původní problém s nefunkčními klávesovými zkratkami, je třeba hodnoty vlastnosti TabIndex všech ovládacích prvků změnit tak, jak ukazuje pravý sloupec v Tab. 8. Přečíslovat můžeme ručně pomocí Vlastnost nebo pomoci vývojového prostředí. Pokud nemá zobrazenu lištu nástrojů Layout, zviditelníme ji, když z nabídky Tools zvolíte Customize a v okně zaškrtneme políčko u nápisu Layout. Pořadí tabulátoru u všech ovládacích prvků zobrazíme klepnutím na ikonu Tab Order. Klepáním na prvé pole nastaví počáteční hodnotu nula a pak v požadovaném pořadí klepneme na zbylé prvky. Dalším kliknutím na ikonu Tab Order čísla z Návrháře skryjeme. Pořadí tabulátoru je důležité pro správnou a přívětivou funkci programu. Mnohdy potřebujeme procházet zadávací pole pomocí klávesy tabulátoru a nechceme, aby uživateli tabulátor po dialogovém okně skákal z jednoho konce na druhý. Vždy zkontrolujeme a případně změníme hodnoty TabIndex, aby procházení polí tabulátorem bylo logické. Tlačítko Tlačítko po stisknutí spouští určitou akci. Tlačítka známe z dialogového okna, kdy zadání potvrdíme tlačítkem OK", nebo zrušíme tlačítkem Storno".
246 Obr. Program Kalkulačka Vytvoříme návrh panelu kalkulačky, jak ji vidíme na obrázku 6.7. Hotový program při stisku tlačítka Sečti" má brát čísla ze dvou textových polí nalevo, sečíst je a výsledek zapsat do textového pole. Textová pole již umíme použít, obdobně do okna programu umístíme z Panelu nástrojů tlačítko. Nalezneme jej podnázvem Button". Tlačítko umístíme doprostřed pod textová pole. Ovládací prvky můžete uspořádat a vyrovnat pomocí nabídky Formát či panelu nástrojů Layout". Nejdůležitější vlastnosti tlačítka jsou (Name) a Text. Tlačítko tedy pojmenujte, např. jako tlačítkosečti, jeho vlastnost Text, který odpovídá nápisu na tlačítku, nastavíme na Sečti. Pokud program vyzkoušíme, zjistíme, že zejména nic nesečte. To vyřešíme příště. Čísla zarovnáváme k pravému okraji textových polí. Abychom tohoto dosáhli, ve vlastnostech každého textové ho pole nastavíme jejich vlastnost TextAlign na Right. Nejlépe to provede tak, že přidržíme klávesu Ctrl a postupně na jednotlivá pole kleneme myší. Když budou všechna označena, nastavím novou hodnotu vlastnosti TextAlign (ze seznamu). V posledním textovém poli, do kterého se má zapsat výsledek, nedovolíme uživateli měnit zobrazený text. Docílíme toho nastavením jeho vlastnosti ReadOnly na True. Nezapomene upravit pořadí tabulátoru. Bude-li uživatel opakovaně mačkat tabulátor, měl by se fokus přesouvat v pořadí X, Y, Z a tlačítko Sečti a zase zpět na první číslo X Zaškrtávací políčko Zaškrtávací políčko je ovládací prvek, který poskytuje informace typu ano/ne (víme, že se jim říká dvojkové nebo booleovské), v programu to budou True/False. Jde o čtvereček, ve kterém se zobrazí symbol zaškrtnutí ( fajka") doplněný popiskem. Použití zaškrtávacích políček si ukážeme na příkladu programu objednávky zákazníka(obr. ).
247 Obr. Program Objednávka Velkou část práce již umíme zvládnout. Ve vývojovém prostředí vytvoříme nový projekt a nazveme jej ObjednavkaZbozi". V návrháři postupně provedeme: Vytvoříme čtyři popisky s texty Zákazník", Jméno, Příjmení a Zboží". Umístíme tři prázdná textové pole. Objekty uživatelského rozhraní vhodně nazveme, např. popisekzákazník, popisekjméno, popisekpříjmení, textjméno, textpříjmení, popisekzboží, textzboží a oknoobjednávka. Nastavíme vlastnosti Text popisků a okna na Zákazník, Jméno, Příjmení, Zboží, Objednávka Přidáme zaškrtávací pole, z Nástrojů, ze skupiny Common Controls" přetáhneme dva objekty CheckBox". Pojmenujte je např. zašknutístudent a zaškrknutíosobníodběr. Zaškrtávací políčka mají mimo vlastnost (Name) také důležitou vlastnost Text, který obsahuje text popisku zobrazovaného vedle políčka. Tuto vlastnost nastavíme na Student", a druhé na Osobní odběr". Zbývá ještě nastavit, zda po startu programu má být políčko zaškrtnuto či ne. To udává jeho vlastnost Checked. Hodnota True znamená Políčko je zaškrtnuto", hodnota False Políčko není zaškrtnuto." Nastavení této vlastnosti v Editoru vlastností a událostí odráží stav zaškrtnutí při startu programu. Tuto vlastnost program umí zjistit a uživatel měnit jak potřebuje. Program dokončíme nastavením klávesových zkratek a správného pořadí tabulátoru. Po spuštění zkontrolujeme činnost zaškrtávacích políček Shrnutí kapitoly
248 Uživatelské rozhraní programu se obvykle vytváří pomocí ovládacích prvku, jako jsou textová pole, popisky, tlačítka či zaškrtávací políčka. Nástroje pro vkládání jednotlivých druhů ovládacích prvků najdete ve vývojovém prostředí v Nástrojích (nabídka View > Toolbox), konkrétně v jeho kategorii Common Controls". Názvy jednotlivých nástrojů ukazuje tabulka Tab. Názvy nástrojů pro ovládací prvky Ovládací prvek Textové pole Popisek Tlačítko Zaškrtávací políčko Nástroj v Nástrojích TextBox Label Button CheckBox Pojmy k zapamatování Editor vlastností a událostí pracuje vždy s ovládacím prvkem, který je v Návrháři aktivován (označen). Každý ovládací prvek je třeba ihned po vytvoření pojmenovat nastavením jeho vlastnosti (Name). Názvy ovládacích prvků by měly být smysluplné, naznačující druh a funkci ovládacího prvku. Z víceslovných názvů je nutno vypustit mezislovní mezery. Pro čitelnost je při tom vhodné každé pokračující slovo psát velkým písmenem. V názvech se mohou vyskytovat písmena včetně české diakritiky, číslice a podtržítka, přičemž záleží na rozdílech mezi velkými a malými písmeny. Všechny ovládací prvky mají důležitou vlastnost Text, každý z nich ji však interpretuje po svém. Klávesové zkratky Alt+klávesa pro aktivaci ovládacích prvků se zavádějí vložením znaku & před písmeno, které má v klávesové zkratce figurovat. Systém Windows toto písmeno podtrhne. Pro srozumitelné ovládání programu a pro fungování klávesových zkratek je důležité dodržet správné pořadí tabulátoru. Nastavit jej lze z nabídky View > Tab Order.
249 Otázky k probranému učivu K čemu slouží panel Vývojář (Designer) Kde najdeme nástroje pro vytváření ovládacích prvků programu? Co jsou to vlastnosti ovládacího prvku. K čemu slouží vlastnost Text. Co je to vlastnost (Name) ovládacího prvku? Shrnutí pojmů kapitoly (podkapitoly) Uživatelské rozhraní programu vytváříme pomocí vývojového prostředí. V návrhu používáme Toolbox (Nástroje), kde najdeme nástroj pro tvorbu každého ovládacího prvku. Každý typ prvku má svůj vlastní nástroj. Nástroje jsou rozděleny do skupin podle svých společných vlastností. K nastavení vlastností prvku slouží okno vývojového prostředí Vlastnosti (Properties). Jména objektů (Name)- identifikátory - smí tvořit pouze písmena a podržítko a číslice. Nesmí obsahovat mezery a nesmí začínat číslicí. Cíl Po prostudování tohoto odstavce budete umět Napsat jednoduchý kód programu. Odstranit chybu v programu Spojit tento kód s událostí, která určuje, co bude ovládací prvek dělat. Po prostudování této kapitoly a vypracování úkolů získáte: Přehled základních událostí. Přehled o knihovnách Po prostudování této kapitoly a vypracování úkolů budete schopni: Napsat jednoduchý příkaz. Zavolat ve svém programu podprogram. Vyvolat dialogové okno s hlášením. Výklad Události
250 Běh programů s grafickým uživatelským rozhraním je řízen událostmi. Něco vykonává jen tehdy, když vznikne nějaká událost. Událostí může být rozličná uživatelská akce, klepnutí či poklepání myší, přesunutí myši, stisknutí klávesy či její uvolnění Událost může vyvolat i jiný program, který chce s tím naším komunikovat. Může to být časovač, který v pravidelných časových intervalech generuje impulsy. Zdrojem událostí je ale také program sám o sobě. Například událost stisku určité klávesové zkratky vyvolá reakci příslušného tlačítka okna, které se stiskne. Na událost tohoto stisku může reagovat dialogové okno a zavře se Může vzniknout celá řada navazujících událostí. V předchozí kapitole jsme si ukázali ovládacím prvkem tlačítko a zdůraznili, že obvykle v programu spouští nějakou akci. Vytvoříme nový projekt Udalosti a tlacitka" s uživatelským rozhraním jak je ukazuje obr. Vypíšeme si potřebné kroky: Obr. Program Události a tlačítka Vlastnost (Name) okna programu nastavit na oknoprogramu a vlastnost Text na Události a tlačítka. Nástrojem Button" přetáhnout do okna programu dvě tlačítka. U tlačítek nastavit vlastnost (Name) na tlačítkovypsat, resp. tlačítkoukončitprogram. U tlačítek nastavit vlastnost Text na &Vypsat zprávu, resp. &Ukončit program. V panelu Události klepneme na ikonu s Bleskem, tím jej přepneme do režimu editace událostí. Klepnutím na tlačítko Ukončit program" jej vybereme. V panelu Události se zobrazí všechny události, které mohou být spojeny s tlačítkem. Je jich přehršel, my budeme využívat ty nejdůležitější. Dobré je znát ty základní, jiné časem odhadnete, zbytek dohledáte v dokumentaci Obsluha událostí Nedůležitější událostí tlačítka je událost Click. Ta je vyvolána pokaždé, když je tlačítko stisknuto. Chceme-li, aby program na vznik této události přiměřeně reagoval, musíte s ní
251 spojit obslužný podprogram. Obslužný podprogram je relativně samostatná část programu, ve které je popsána činnost, jako reakce na vznik události. Tab. Ikony pro volbu mezi editací vlastností a událostí Panel Vlastnosti - zobrazit události Panel Vlastnosti - zobrazit vlastností Ve Vlastnostech poklepeme na příslušnou událost a vývojové prostředí připojí obslužný podprogram k dané události a současně otevře zdrojový kód Form1.cs a vloží do něj prázdný obslužný podprogram. Pokud je vše v pořádku, měli bychom mít v nové záložce otevřený zdrojový kód tak, jak vidíme na obrázku Ve zdrojovém textu Form1. cs existovala většina řádků ještě před vložením obslužného podprogramu a byly převzaty ze šablony Windows Application" pomocí níž jsme program vytvořili. Nyní nově přibyla kostra obslužného podprogramu. Zde bliká kurzor Obr. Editor po vložení události Click Podívejme se spolu na to, jak nově vložený kód vypadá. První řádek podprogramu je jeho hlavička, která obsahuje jméno podprogramu, konkrétně tlačítkoukončit_click. Jméno vytvořilo vývojové prostředí automaticky tak, že pomocí znaku podtržítka připojilo jméno události ke jménu objektu, s níž se událost váže. Za hlavičkou podprogramu následuje vždy jeho tělo. do které se zapisují příkazy, které se mají vykonat, co má program udělá po vzniku této události. Tělo začíná řádkem levou
252 složenou závorkou a končí řádkem s pravou složenou závorkou. Na místě, kde se má začít se zápisem příkazů, bliká kurzor. Do tohoto místa se kdykoli později můžeme vrátit opětovným poklepáním na příslušnou událost, případně si to místo zapamatujeme. Přesně na toto místo zapišme: Close() Podprogram obsluhy události Click tedy nyní vypadá tak, jak ukazuje Výpis. private void tlačítkoukončit_click(object sender, EventArgs e) Close(); Obsluha události Click tlačítka Ukončit program" je nyní plně funkční a program můžeme spustit a odzkoušet. Ověřte, že horní tlačítko ještě nic nedělá, klepnutí na dolní tlačítko způsobí ukončení programu Chyba v programu Pokud se program nespustí a vývojové prostředí namísto toho něco vypíše, pak to znamená, že v programu je nějaká chyba. Bud' náš příkaz nezapsali přesně tak, jak bylo uvedeno, nebo jsme jej zapsali na špatné místo. Pokud se náš program spustil, úmyslně v něm uděláme chybu, například vymažeme středník za Close(). Při pokusu o spuštění tohoto programu s chybou zobrazí chybové hlášení obr. Program se nepodařilo sestavit. Protože nechceme spustit program ve formě, jak nám fungoval, odpovíme vždy tlačítkem No". Obr. Reakce vývojového prostředí na chybu v programu Pak vývojové prostředí bude mít vzhled jako to na Obr.. Ve spodní části okna se objeví panel Error list, ve kterém jsou hlášeny chyby vzniklé při překladu a sestavování programu. Na Obr. je v hlášení pouze jedna chyba, mnohdy jich je několik. Postupujeme vždy tak, že se vždy se snažíme odstranit první chybu, ta je vždy nejdůležitější chyba. První chyba v
253 programu vždycky reálně existuje, zatímco ty následující mohly vzniknout přičiněním té prve chyby. Tyto tzv. zavlečené chyby vznikají tehdy, když předchozí skutečná chyba zmate vývojové prostředí natolik, že zbytek programu chápe špatně. Po odstranění skutečných chyb zavlečené chyby zmizí. zvýraznění červená vlnovka Obr. Vzhled vývojového prostředí po nalezení chyby Další události Budeme chtít zobrazit událost, že se kurzor myši zastavil nad tlačítkem. Tato skutečnost aktivuje událost MouseHover. V našem programu budeme chtít na zastavení myši reagovat zobrazením zprávy Myš je nad tlačítkem. Známým způsobem s tlačítkem spojíme událost MouseHover (poklepáním na tuto událost). Do místa, kde po vložení obslužného podprogramu bliká kurzor přesně opíšeme : MessageBox.Show("Myš se zastavila nad tlačítkem"); Podprogram by měl nyní vypadat tak, jak ukazuje Výpis private void tlačítka_mousehover(object sender, EventArgs e) MessageBox.Show("Myš se zastavila nad tlačítkem");
254 Pokud je příkaz v pořádku, zobrazí se jeho část mezi uvozovkami červené. Vývojové prostředí takto barevně rozlišuje přímo uvedené textové řetězce neboli řetězcové literáty, tzn. kusy textu, které nejsou ani jménem, ani klíčovým slovem jazyka C#. Barevným rozlišením různých prvků zdrojového kódu zlepšuje Visual C# přehlednost programu a orientaci v něm. Světle modrým odstínem se obarvilo i slovo MessageBox. Modře jsou obvykle zobrazeny klíčová slova, tedy příkazy jazyka. Program spusťte a ověřte, že při zastavení myši ihned vyskočí okno se zprávou. Ukončení programu je trochu problém. Ukončíme jej příkazem Debug -> Stop Debugging (případně klávesovou zkratkou Shift F Knihovny podprogramů Příkaz Close zavře okno programu a ukončí program. Příkaz MessageBox. Show zobrazí uživateli okno se zprávou a čeká, až ho uživatel zavře. Do té doby nedovolí se zbytkem programu pracovat. Jedná se o knihovní předem připravené podprogramy. Každá vývojářská platformy obsahuje také knihovny podprogramů. Jsou to funkce, které se budou hodit mnoha lidem, a ty jsou poskytnuty ve formě podprogramů. Takovéto podprogramy se sdružují do větších celků, tzv. knihoven. Knihovny mají v dnešní době většinou formu souborů s příponou. Aby programátor mohl některou z připravených knihoven ve svém programu použít, musí se ze svého projektu na ni odkázat. Ve Visual C# k tomu slouží volba Add Reference z nabídky Project Volání podprogramů Pokud chceme nějakou předpřipravenou činnost v programu vykonat, musíme příslušný podprogram zavolat. Volání podprogramu se provede tak, že uvedete jeho jméno a za jménem v kulatých závorkách předáte parametry. Parametry jsou upřesňující informace, jejich prostřednictvím říkáte, jak přesně se má podprogram vykonat. V prvním příkladu se v příkazu Close(); podprogram jmenoval Close. Za jeho jménem jsou prázdné kulaté závorky, protože jsme podprogramu nepředávali žádné parametry. Závorky je při volání podprogramu nutno uvést vždy. Ve druhém příkladu se v příkazu MessageBox.Show("Myš je nad tlačítkem"); podprogram jmenoval MessageBox. Show a předával se mu jeden parametr - text zprávy, která se má v panelu zobrazit. Zatímco podprogram Close je jednoúčelový pro zavření okna programu, je podprogram pro zobrazení zprávy obecný. Může zobrazit jakoukoli zprávu,
255 kterou mu předáním parametru konkretizujeme - říkáte mu, zobraz tuto zprávu". V našem příkladě jde o text Myš je nad tlačítkem". Řetězec znaků Myš je nad tlačítkem je uzavřen do uvozovek. Podle pravidel jazyka C# jde o přímo uvedený textový řetězec textový literál. Uzavření řetězcového literálu do uvozovek je věc programovacího jazyka a je bezpodmínečně nutné. Bez něj by ani nešel sestavit. Každý příkaz je ukončen středníkem. Jedno ze syntaktických pravidel jazyka C# - každý příkaz zapsaný v programovacím jazyce C# musí být ukončen středníkem Bližší určování Zastavme se ještě na chvíli u jména MessageBox. Show. Je komplikovaný proto, že se jmenuje Show a to MessageBox je jakési příjmení", odborně se mu říká bližší určení nebo též kvalifikace. Bližší určení se používá ze stejného důvodu, jako se používají příjmení u lidí. Aniček může být mnoho, proto mnohdy musíme říci, že chceme mluvit např. Aničkou Kvítkovou. Podprogramů Show může být více a my nyní chceme použít právě MessageBox. Show. Jako Kvítková označuje rodinu, skupinu příbuzných lidí, označuje MessageBox třídu, skupinu příbuzných podprogramů metod třídy. Plně určené jméno podprogramu se sestaví uvedením jména třídy, za kterým je tečka a za ní vlastní jméno podprogramu. Zápis MessageBox.Show tedy počítač chápe tak, že chcete volat podprogram (metodu) Show třídy MessageBox. Je možný ještě jeden způsob bližšího určování, a to pomocí jména objektu. Pokud se nějaký podprogram vztahuje ke konkrétnímu objektu, voláme jej tak, že uvedeme jméno objektu, tečka a jméno podprogramu. Pokud je dotyčným objektem okno programu, nemusí se bližší určování provádět. To je případ volání metody Close, které se uvádí jak bez jména třídy, tak bez jména objektu Hlavní událost objektu Horní tlačítko s nápisem Zobrazit zprávu" by mělo po kliknutí zobrazit zprávu Vítá vás program s tlačítky". Stisknutí tlačítka vyvolává událost Click. Mohli bychom postupovat stejně jako u předchozího tlačítka, ve Vlastnostech zobrazit události horního tlačítka a poklepat na událost Click. Událost Click je ale tzv. hlavní událostí tlačítka, můžete obslužnou metodu2 do programu vložit i jinak. V Návrháři poklepáme na samotné tlačítko! Do zdrojového kódu se vloží prázdná obslužná metoda. Na místo, kde bliká kurzor, vložíme příkaz MessageBox.Show("Zdraví vás program s tlačítky") ; private void tlačítkovypsat_click(object sender, EventArgs e)
256 MessageBox.Show("Vítá vás program s tlačítky"); Každý objekt má nějakou hlavní událost. Hlavní události nám známých objektů shrnuje tabulka Tab. Hlavní události některých objektů Objekt Hlavní událost Kdy událost vzniká Okno programu Load Těsně před zobrazením okna programu. Textové pole TextChanged Změní se text zadaný uživatelem. Popisek Click Uživatel na popisek klepne nebo stiskne příslušnou klávesovou zkratku. Tlačítko Click Uživatel stiskne tlačítko. Zaškrtávací pole CheckedChanged Změní se stav zaškrtnutí. Pokud se stane, že na objekt v Designeru omylem poklepeme a vývojové prostředí vloží obslužnou metodu, aniž jsme to zamýšlel, pak je asi nejjednodušším řešením nedělat nic. Rozhodně nesmíme kód prázdné metody smazat! V programu by zůstávala vazba události na obslužnou metodu. Jestliže by metoda z programu zmizela, vazba by ukazovala do prázdna" a program by se nesestavil Společná obsluha více událostí Mnohdy se stane, že chceme reagovat stejným způsobem na několik událostí. Je to možné vyřešit tak, že tytéž příkazy zkopírujete do obslužných rutin všech událostí. Je to jednoduché, ale ne dobré. K pravidlům dobrého programování patří nic neprogramujeme dvakrát či dokonce vícekrát". Důvodem je snadnost a bezpečnost údržby programu. Pokud tutéž věc" naprogramujete opakovaně na několika různých místech programu a v budoucnosti vznikne požadavek zde něco změnit, museli bychom vyhledat všechna místa a jednotně je změnit. Dříve či později se stane, že buď nezměníme vše, nebo některé místo změníte jinak. Pokud bude každá činnost naprogramovaná jen na jednom místě, stačí změnu provést jedenkrát a automaticky dojde ke stejné změně na všech místech, kde se používá, navíc se také zlepšuje logická přehlednost programu.
257 Budeme chtít, aby zpráva Myš je nad tlačítkem" vyskakovala při zastavení nad oběma tlačítky. Vybereme horní tlačítko, v Událostech najdeme událost MouseHover, ale místo poklepání vedle jména události klepneme, stiskneme levé tlačítko myši, a v seznamu, který se objeví po dalším klepnutí, vyberte tlačítko Ukončit_MouseHover Přejmenování události Nyní se ale obslužná metoda jmenuje tlačítkoukončit_mousehover, přesto že obsluhuje událost MouseHover obou tlačítek. Zmiňovali jsme se o důležitosti vhodných jmen v programování. Měli bychom tedy obslužnou metodu přejmenovat na tlačítka_mousehover. Nelze to provést tak, že ve zdrojovém kódu najdeme hlavičku metody tlačítkoukončit_mousehover a změníme její jméno! Metoda by se sice přejmenovala, ale odkazy na ni by zůstaly beze změn. Opět bychom měli vazbu události směřující do prázdna. Jakákoli přejmenování v programu provádíme zásadně nástroji vývojového prostředí. Na jméno metody, kterou chceme přejmenovat, klepneme ve zdrojovém kódu pravým tlačítkem a z kontextové nabídky zvolíme Refactor a následně Rename. Teprve v novém okně zadáme v poli New name" nové jméno obslužné metody Obr. Vzhled vývojového pro přejmenování metody
258 Pojmy k zapamatování Běh programu s grafickým uživatelským rozhraním je řízen událostmi, jako je klepnutí myší, stisk klávesy či vnitřní události programu. Každý objekt uživatelského rozhraní definuje celou řadu událostí, z nichž jedna, tzv. hlavní událost objektu, svým významem výrazně převyšuje všechny ostatní. Pro ovládací prvek tlačítko je hlavní událostí událost Click, která vzniká, když uživatel tlačítko stiskne. Aby program mohl na událost reagovat, je třeba k ní připojit obslužnou metodu (obslužný podprogram). Vazba se vytváří pomocí Editoru vlastností a událostí, jenž lze ikonou s bleskem přepnout do režimu editace událostí. Obsluhu hlavní události objektu lze vytvořit také poklepáním na příslušný objekt v Designeru. Obslužná metoda je ve zdrojovém kódu tvořena hlavičkou a tělem. Hlavička obsahuje především jméno metody, tělo zase příkazy, které se v metodě mají provést. Jako příkazy se dají mj. zapsat volání předpřipravených podprogramů, jako jsou Close pro zavření okna programu nebo MessageBox.Show pro zobrazení okna se zprávou. Podprogramy se volají uvedením jejich jména, za kterým v závorkách následuje seznam parametrů. Při nedodržení správného zápisu jmen nebo syntaktických pravidel jazyka C# se program nesestaví a s pomocí seznamu chyb (View > Error List) je třeba jej opravit. Jakákoli přejmenování v programu, např. přejmenování metod, provádějte tak, že na jméno, které chcete změnit, klepnete pravým tlačítkem a vyberete Refactor a následně Rename. Otázky k probranému učivu Co řídí program s grafickým rozhraním. Co je to událost. Co je to obslužná metoda a jak ji vytváříme. Co obsahuje obslužná metoda. Co je to příkaz programu. Jak vzniká syntaktická chyba a jaký je postup její opravy. Shrnutí pojmů kapitoly (podkapitoly) Běh programu s grafickým uživatelským rozhraním je řízen událostmi. Každý objekt uživatelského rozhraní definuje celou řadu událostí, jedna z nich, nejdůležitější, je hlavní událost objektu. Aby program mohl na událost reagovat, je třeba k ní připojit obslužnou metodu (obslužný podprogram). Obslužné metody tvoří příkazy jazyka. Při nedodržení pravidel jazyka se program nesestaví, obsahuje pak tzv. syntaktické chyby. Přejmenování metod v programu je možno učinit jen nástroji vývojového prostředí.
259 Cíl Po prostudování tohoto odstavce budete umět Správně použít přiřazovací příkaz. Použít více příkazů v jedné metodě. Po prostudování této kapitoly a vypracování úkolů získáte: Poznatky o sekvenčním zpracování programu. Po prostudování této kapitoly a vypracování úkolů budete schopni: Napsat metody, které obsahují sekvenci příkazů. Provést několikanásobné přiřazení tak, aby se žádná informace nikam neztratila. Provést základní kontrolu logické (sémantické) správnosti programu- Výklad Kopírování textového pole Přiřazovacím příkaz si ukážeme na kopírování obsahu textových polí. Návrh rozhraní je zobrazeno na Obr. 27. Obsahuje dvě textová pole s popisky a tlačítko. Spodní pole nepůjdeš editovat, bude určeno jen ke čtení". Při stisku tlačítka se text zadaný v horním poli přiřadí (přepíše) do spodního pole Obr. Návrh programu Kopírování Vytvořte nový projekt Přepis" a uživatelské rozhraní navrhneme obdobně tomu z obrázku. Horní textové pole pojmenujte polevzor, spodní polekopie a tlačítko tlačítkopřepiš. Kopírování se má spustit po stisku tlačítka, musíme proto vložit obslužnou metodu události tlačítka Click. Připomeňme, že stačí na tlačítko poklepat v Návrháři. Na místě blikajícího kurzoru napíšeme polekopie.text = polevzor.text;
260 Celou obslužnou metodu pak zachycuje Výpis. Program spusťte a ověřte jeho funkčnost. private void tlačítkokopíruj_click(object sender, EventArgs e) polekopie.text = polevěta.text; Přiřazovací příkaz Příkaz, který jsme napsali, je. přiřazovací příkaz, který přiřadí hodnotu výrazu na pravé straně od rovnítka na místo proměnné na straně levé. Obecně jej zapíšeme takto kam = co; Náš příkaz přiřadí hodnotu vlastnosti Text objektu polevzor vlastnosti Text objektu polekopie. Vlastnost Text reprezentuje text zobrazený v textovém poli, po provedení příkazu bude polekopie.text obsahovat týž text jako polevzor. Pokud uživatel následně obsah polevzor zedituje pak se již nebudou zobrazované texty shodovat. Budou si rovny až při dalším stisku tlačítka Tab. 12 Porovnání obecného zápisu s použitým příkazem Obecně V zapsaném příkazu Jak to přečte počítač Kam polekopie.text Do vlastnosti Text objektu polekopie... = =... přiřaď... Co polevzor.text... hodnotu vlastnosti Text objektu polevzor. V příkazu se provádí bližší určováni jménem objektu. Píšeme jméno objektu, tečka, jméno metody (tedy činnosti daného objektu). Vlastnost Text mají všechny objekty uživatelského rozhraní, je tedy nutné vždy určit, kterého objektu vlastnost.text použijeme. Zápis polevzor.text znamená vlastnost Text objektu polevzor. Zápis polekopie.text odkazuje na vlastnost Text objektu polekopie.
261 Další přiřazování Nyní si ukážeme, že lze přiřadit nejen hodnotu z jiného objektu, ale i hodnotu, která je přímo uvedená.ve zdrojovém textu programu Následující program bude umět zapsat větu do textového pole, povolit a zakázat jeho editaci, změnit barvu textu v poli na zelenou. Obr. Návrh programu testujícího přiřazovací příkaz Program vytvoříme pole návrhu z obrázku 8.2. Textové pole umožní uživateli zadat slovo či větu a bude ovládáno jednotlivými tlačítky: Tlačítko Zdravím svět" vloží do textového pole tuto větu. Tlačítko Vymaž" smaže obsah textového pole. Tlačítko Text zeleně" obarví text v poli na zeleno. Tlačítko Zablokuj" zakáže editaci a přenos textu do schránky. Tlačítko Odblokuj" povolí editaci. Vytvoříme nový projekt podle obrázku Pojmenování objektů: oknoprogramu, popisekvěta, textvěta, tlačítkozdravím, tlačítkovymaž, tlačítkotextzeleně, tlačítkozablokuj a tlačítkoodblokuj. U všech tlačítek půjde o obsluhu hlavní události Click. Postupujeme následovně: poklepeme na tlačítko Zdravím svět" a do vložené obslužné metody tlačítkozdravím_click napíšeme
262 textvěta.text = "Zdravím svět"; Tím se přiřadí přímo uvedený řetězec do vlastnosti Text textového pole. Poklepejte na tlačítko Vymaž" a do vložené obslužné metody tlačítkovymaž_click vložíme textvěta.text = ""; nebo příkaz textvěta.text = null; První varianta pracuje s tzv. prázdným řetězcem zapsaným dvojicí uvozovek bezprostředně za sebou bez jakékoli mezery mezi. Ve druhé variantě představuje klíčové slovo null tzv. nedefinovanou hodnotu. Používá se i jinde než jen při práci s řetězci. Prázdný řetězec a nedefinovaná hodnota není obecně jedno a totéž. Poklepeme na tlačítko Text zeleně" a do vložené obslužné metody tlačítkotextzeleně_click vložíme příkaz textvěta.forecolor = Color.Green; Pracujeme teď s jinou vlastností objektu textvěta, s vlastností ForeColor udávající barvu zobrazeného textu. Konkrétní hodnota barvy určí slovem Color, za kterým následuje tečka a anglický název barvy. V okamžiku, kdy za slovem Color napíšeme tečku, vývojové prostředí nám nabídne všechny použitelné názvy barev. Jde o našeptávání ze strany vývojového prostředí, slovo za vás dokončí případně po klepnutí na variantu či stisku klávesy Enter, Ctrl+Enter, případně Tab. Poklepejme na tlačítko Zablokuj" a do vložené obslužné metody tlačítkozablokuj_click vložte příkaz textvěta.enabled = false; Tento příkaz pracuje s vlastností Enabled textového pole. Je-li nastavena na hodnotu true, pole normálně pracuje, je-li nastavená na hodnotu false, pole je zablokované a nereaguje. Poklepejme na tlačítko Odblokuj" a do vložené obslužné metody tlačítkoodblokuj_click vložme příkaz textvěta.enabled = true; Vysvětlení je obdobné jako u tlačítka Zablokuj". Výpis kódu přiřazení
263 public partial class oknoprogramu : Form public oknoprogramu() InitializeComponent(); private void tlačítkozdravím_click(object sender, EventArgs e) textvěta.text = "Zdravím svět"; private void tlačítkovymaž_click(object sender, EventArgs e) textvěta.text = null; private void tlačítkotextzeleně_click(object sender, EventArgs e) textvěta.forecolor = Color.Green; private void tlačítkozablokuj_click(object sender, EventArgs e) textvěta.enabled = false; private void tlačítkoodblokuj_click(object sender, EventArgs e) textvěta.enabled = true; private void oknoprogramu_load(object sender, EventArgs e) Více příkazů v metodě Doplníme některé funkce našeho programu. Budeme povolovat a blokovat jen ta pole, která má smysl používat. Pokud například textové pole lze editovat, nemá význam mačkat tlačítko Odblokuj". Podobně můžeme uvažovat o funkci tlačítka Zablokuj". V obsluze tlačítka Odblokuj: odblokujeme textové pole, vyřadíme (zablokujeme) tlačítko Odblokuj" povolíme tlačítko Zablokuj". Obsluha tlačítka Zablokuj" bude analogická: zablokujeme textové pole, vyřadíme tlačítko Zablokuj" a povolíme tlačítko Odblokuj". Obslužné metody obou tlačítek ukazuje Výpis. Upravte samostatně program a vyzkoušejte jej. Výpis kódu více příkazů v metodě
264 namespace Různá_přiřazení public partial class oknoprogramu : Form public oknoprogramu() InitializeComponent(); private void tlačítkovymaž_click(object sender, EventArgs e) textvěta.text = null; private void tlačítkotextzeleně_click(object sender, EventArgs e) textvěta.forecolor = Color.Green; private void tlačítkozablokuj_click(object sender, EventArgs e) textvěta.enabled = false; tlačítkozablokuj.enabled = false; tlačítkoodblokuj.enabled = true; private void tlačítkoodblokuj_click(object sender, EventArgs e) textvěta.enabled = true; tlačítkoodblokuj.enabled = false; tlačítkozablokuj.enabled = true; private void oknoprogramu_load(object sender, EventArgs e) tlačítkoodblokuj.enabled = false; private void oknoprogramu_load_1(object sender, EventArgs e) Při testování programu jsme si mohli všimnout, že při startu jsou aktivní obě tlačítka. Pomocí Vlastností nastavíme vlastnost Enabled tlačítka Odblokuj" na počáteční hodnotu False. Většinou se každý příkaz z důvodu přehlednosti zapisuje na samostatný řádek zdrojového kódu.. Rozhodujícím kritériem je vždy přehlednost programu a to, aby jeho formální podoba co nejlépe odrážela logiku řešení Sekvenční zpracování Ukážeme si program, který po stisku tlačítka zkopíruje zadaný text do pole kopie a pole kopie přepíše do pole záloha
265 Obr. Návrh programu testujícího přiřazovací příkaz Zamysleme se nad pořadím, v jakém se pole musí kopírovat, Pokud bychom nejprve zkopírovali zadaná text do pole kopie a to pak do pole záloha, pak by pole kopie i záloha obsahovaly tentýž text. Musíme postupovat z konce na začátek, Nejprve do pole záloha zkopírujeme obsah pole kopie a teprve pak editovaný text do pole kopie. Pole si pojmenujeme: poleoriginál,polekopie, polezáloha. V programu bude jediná obslužná metoda obsluha události Click tlačítka. Napíšeme do ní ve vhodném pořadí přiřazovací příkazy provádějící kopírování. Sami se zamyslete kolik příkazů bude a v jakém musí být pořadí. Pro kontrolu výsledek ukazuje Dvojité přepisování namespace Kopie_a_záloha public partial class oknoprogramu : Form public oknoprogramu() InitializeComponent(); private void tlačítkozálohuj_click(object sender, EventArgs e) polezáloha = polekopie.text; polekopie.text = poleoriginál.text; private void oknoprogramu_load(object sender, EventArgs e)
266 Program spusťte a zkontrolujte jeho činnost Sémantické chyby Chyby, kdy je program funkční, ale dělá něco jiného,než očekáváme, říkáme sémantické chyby ve smyslu chyby významové. Chybám jako je třeba zapomenutý středník, se oproti tomu říká chyby syntaktické. Pojmy k zapamatování Jedním z nejdůležitějších příkazů vůbec je přiřazovací příkaz. V jazyce C# se zapisuje v této podobě: kam = co; Příkaz funguje tak, že hodnotu uvedenou napravo od rovnítka přiřadí (zkopíruje) do místa zapsaného na straně levé. Při práci s vlastnostmi různých objektů se používá bližší určení jménem objektu, které lze. Zápis se provádí ve tvaru jméno objektu, tečka, jméno vlastnosti daného objektu". Například textvěta.text označuje vlastnost Text objektu textvěta. Chceme-li v metodě vykonat více akcí, jednoduše zadáme více příkazů. Příkazy se pak vykonávají sekvenčně, jeden za druhým, v pořadí, v jakém jsou ve zdrojovém kódu zapsány. Kromě syntaktických chyb, což jsou prohřešky proti pravidlům programovacího jazyka, existují ještě chyby sémantické. Sémantickými chybami rozumíme ty případy, kdy se program sestaví, ale po spuštění dělá něco jiného, než programátor zamýšlel. Zatímco syntaktické chyby odhalí vývojové prostředí, sémantické chyby musí odhalit člověk, programátor. Každý program musíme dostatečně důkladně otestovat, abychom odhalili nejlépe všechny sémantické (logické) chyby návrhu programu a také samotného programu. Otázky k probranému učivu Co je to sekvenční příkaz. Jak vznikají sémantické chyby.
267 Jak zabránit ztrátě dat při sekvenčním zpracování programu. Shrnutí pojmů kapitoly (podkapitoly) Cíl Po prostudování tohoto odstavce budete umět Deklarovat a definovat proměnnou. Navrhnou jednoduchou strukturu algoritmu. Po prostudování této kapitoly a vypracování úkolů získáte: Základní přehled o proměnných a způsobu jejich použití. Po prostudování této kapitoly a vypracování úkolů budete schopni: Vytvořit jednoduché programy, které využívají návratových hodnot některých metod. Ošetřit chyby, které mohou vzniknout nesprávným zadáním vstupních hodnot programu a kterým říkáme chyby za běhu programu Výklad Nefunkční kalkulačka V programu bude jediná obslužná metoda, a to obslužná metoda události Click tlačítka. Metodu vytvoříme a vložíme do ní příkaz pro sečtení obsahu obou textových polí: polevýsledek.text = polečíslox.text + polečísloy.text; V uvedeném výrazu jsou hodnoty vlastností Text obou textových polí zkombinovány pomocí operátoru +. Zdrojový kód obslužné metody ukazuje Výpis. Program spusťte a otestujte. Nefunkční kalkulačka namespace Kalulačka_text public partial class oknoprogramu : Form public oknoprogramu() InitializeComponent(); private void tlačítkorovnáse_click(object sender, EventArgs e) polevýsledek.text = polečíslox.text + polečíslox.text;
268 private void oknoprogramu_load(object sender, EventArgs e) Program namísto sčítání spojuje vstupní hodnoty, které ani nemusejí být čísla, jak ukazuje Datové typy Při práci s vlastnostmi jste si zřejmě všimli, že hodnoty, kterých vlastnosti mohou nabývat, jsou několika druhů. Do některých vlastností se mohl zadat textový řetězec, jinde se zadávala barva nebo číslo. Některé mohly nabývat jednu ze dvou logických hodnot, True nebo False. Důležité jsou názvy zapsané tučným písmem. Ty musíte bezpečně znát, v dalších programech je budete hojně používat. Zbytek tabulky je spíše informativní. Tab. Názvy některých datových typů Druh hodnoty Název typu Alternativní název1 Textový řetězec string String Barva Color Logická hodnota bool Boolean Číslo int Int 32 Tlačítko Výčet hodnot pro vlastnost ScrollBars Button ScrollBars Kalkulačka Nyní zpátky k otázce, proč úvodní program této kapitoly namísto sčítání spojuje. Důvodem je to, že vlastnost Text textových polí je typu string. A pro typ string není sčítání definováno. Sčítat se dají pouze čísla, nikoli textové řetězce. Shodou okolností" je operátor +, který se běžně používá pro normální sčítání čísel, definován v jazyce C# také pro řetězce, avšak s odlišným významem - spojováním. Proto jsme namísto sčítačky získali slučovačku textu. Prostředkem, jak získat kalkulačku, je typová konverze. Tímto pojmem se označuje změna typu hodnoty či vlastnosti. Konkrétně, řetězce jako "23" či "7" potřebujeme změnit (převést) na čísla 23 a 7. Ta už potom půjdou bez problémů sečíst. Výsledkem pak bude například číslo 30, které ovšem před zobrazením v textovém poli musíme zase převést na řetězec! Vlastnost polevýsledek.text je typu string, musíme tedy do ní přiřadit hodnotu typu string Vývojový diagram a pseudokód
269 Pokud program není natolik triviální, je třeba o něm kratší či delší dobu přemýšlet. Těmto činnostem předcházejícím samotnému zápisu zdrojového kódu se říká návrh programu. Někdy se můžeme setkat s výrazem analýza programu. Výstupem fáze návrhu může být například slovní popis. Mnohdy je však přehlednější výsledek návrhu zaznamenat nějak strukturovaně, graficky. Ukážeme si použití vývojových diagramů a zápis v pseudokódu. Vývojový diagram pro návrh obsluhy stisku rovnítka v programu Kalkulačka je na Obr.. Zachycuje operace, které se mají pro vyřešení určité úlohy provést, a zároveň jejich časovou posloupnost. Můžeme také říct, že daný vývojový diagram zachycuje postup řešení neboli algoritmus určité dílčí úlohy programu. Každá operace, jež se má provést, je ve vývojovém diagramu zakreslena obdélníčkem, časovou posloupnost zachycují šipky, no a kolečka s písmeny Za k sou tam pro úplnost, pro označení začátku a konce. Obr. Vývojový diagram činnosti kalkulačky Zapisuje-li se postup řešení textově, v pseudokódu, používá se namísto obrázkové formy forma textová, která se podobá zápisu zdrojového kódu v programovacím jazyce, není však natolik detailní. Pro záznam návrhu obsluhy naší události bychom mohli použít pseudokód Příklad pseudokódu private void tlačítkorovnáse_click(object sender, EventArgs e) Hodnoty Text obou vstupních polí převést na čísla Čísla sečíst Výsledek převést na řetězec a zapsat jako hodnotu do Textu výstupního pole Pro psaní pseudokódu nejsou žádná pevná pravidla, pseudokód není programovací jazyk. Měli byste ho psát tak, abyste bez nadměrných detailů vystihli podstatu řešení. Hlavní body jsme do zdrojového kódu zapsali formou tzv. komentářů či poznámek. To jsou kusy textu, které vývojové prostředí při sestavování programu ignoruje a které tak slouží pouze programátorovi. Komentáře se v jazyce C# uvozují dvojicí lomítek a vývojové prostředí je zobrazuje zelenou barvou. Touto formou zapsané hlavní body mají tu výhody, že je na závěr ani netřeba odmazávat. Zůstanou v programu jako vysvětlující poznámky.
270 Převod řetězce na číslo K převodu řetězce na číslo slouží metoda Tolnt32 třídy Convert. Metoda přebírá jeden parametr, kterým je převáděný řetězec. Obecně by se tedy převod mohl zapsat následovně: Convert.ToInt32(převáděný řetězec); My chceme konkrétně převést řetězec zobrazený v prvním textovém poli, příkaz zapíšeme takto: Convert.ToInt32(polečísloX.Text); Dejme tomu, že v prvním textovém poli je zobrazena dvojka a za ní trojka. Vlastnost Text objektu polečíslox má tedy hodnotu řetězce " 23 ". Tato hodnota se do výše uvedeného příkazu dosadí. Můžeme si představit, jako kdyby vypadal takto: Convert.ToInt32("23"); Příkaz se provede a jeho výsledkem je číslo Proměnné Aby program s číslem mohl dále pracovat, musí ho někam uložit. V počítači to znamená do operační paměti '. Jakákoli hodnota, která se neuloží do paměti či bezprostředně nepoužije, je okamžitě zapomenuta, zahozena. Programy si neukládají do paměti jen jedno číslo, ukládají si do ní třeba tisíce čísel, řetězců, obrázků atd. Z toho důvodu musí existovat nějaký prostředek pro orientaci ve všech těchto údajích. Právě proměnné nám umožní se orientovat, pojmenovat místa v operační paměti". Operační paměť tvoří integrované obvody osazené na základní desce počítače. Do ní se ukládají veškerá data, se kterými programy pracují, do ní se nahrávají veškeré programy, které na počítači v danou chvíli běží. Pro každé ukládané číslo, pro každý ukládaný řetězec, pro každý ukládaný obrázek si musí program vyhradit v paměti místo a toto místo musí vhodně pojmenovat. Vyhrazování míst v paměti a jejich pojmenovávání plní ve zdrojovém kódu deklarace proměnných. Deklarace konkrétní proměnné se v jazyce C# provádí formou příkazu, v němž se uvede nejprve datový typ této proměnné a poté za mezerou její jméno: typ_proměnné jméno_proměnné; Příkaz deklarace je, jako každý jiný příkaz, ukončen středníkem. V programu Kalkulačka chceme vyhradit místo pro uložení čísla vzniklého převodem. Toto místo, tuto proměnnou, nazvěme' třeba číslo. Pohledem do tabulky lze zjistit, že název typu reprezentujícího číslo je int, a proto příkaz deklarace naší proměnné bude vypadat takto: int číslox; Víme, jak převést řetězec na číslo, víme, jak v paměti pro toto číslo vyhradit prostor. Část programu pak vypadá následovně:
271 int číslox; číslox = Convert.ToInt32(polečíslo1.Text); Za deklaraci proměnné číslo jsme zařadili přiřazovací příkaz, který výsledek pře vodu do této proměnné přiřadí. Kompletní Kalkulačka Stejným způsobem se na číslo převede také hodnota druhého textového pole. Pro uložení výsledku převodu bude samozřejmě potřeba druhá proměnná: int čísloy; čísloy = Convert.ToInt32(poleČíslo2.Text); Součet se provede s pomocí operátoru + stejně, jako jsme tímto operátorem v programu Slučovačka spojovali řetězce. Pro uložení výsledku bude potřeba třetí proměnná: int výsledek; výsledek = číslox + čísloy; Pro převod výsledku na řetězec slouží jiná metoda třídy Convert, a to metoda ToString: polevýsledek.text = Convert.ToString(výsledek); Zdrojový kód Form1.cs ve finální podobě pro kontrolu ukazuje výpis. Kód programu Kalkulačka public partial class oknoprogramu : Form public oknoprogramu() InitializeComponent(); private void tlačítkorovnáse_click(object sender, EventArgs e) // Hodnoty Text obou vstupních polí převést na čísla int číslox; číslox = Convert.ToInt32(poleČíslo1.Text); int čísloy; čísloy = Convert.ToInt32(poleČíslo2.Text); // Čísla sečíst int výsledek; výsledek = číslox + čísloy; // Výsledek převést na řetězec a zapsat jako hodnotu // Text výstupního pole polevýsledek.text = Convert.ToString(výsledek); private void oknoprogramu_load(object sender, EventArgs e) Než půjdeme dál, ještě bych rád explicitně zdůraznil, jaký je rozdíl mezi číslox, polečíslox a polečíslox.text.
272 Takže: číslox je proměnná typu int, místo v operační paměti pro uložení jednoho celého čísla; polečíslox je objekt textového pole, celý se vším všudy, obsahující text, barvu, zarovnání textu atd.; polečíslox.text je jedna jediná vlastnost tohoto objektu, konkrétně vlastnost uchovávající text v zobrazený textovém poli. Naprosto analogický výklad se dá provést i pro číslox a výsledek Návratová hodnota V přiřazovacím příkazu se napravo od rovnítka zapisuje přiřazovaná hodnota. V našich programech to byla hodnota nějaké vlastnosti, přímo uvedená hodnota anebo hodnota vhodné proměnné. Eventuálně také hodnota vzniklá vyhodnocením výrazu. Podíváme-li se z této perspektivy na použitý příkaz číslox = Convert.ToInt32(poleČísloX.Text); vidíme, že volání metody Convert.ToInt32 se chová, jako by to byla nějaká hodnota. Je-li např. polečíslox.text rovno řetězci "23", chová se uvedené volání jako hodnota čísla 23. Tedy jako kdyby byl v programu příkaz číslox = 23; Tomuto říkáme volání metody, hodnotě, která se za zápisem volání dosadí, se říká návratová hodnota metody. V témže kontextu mluvíme také o typu návratové hodnoty. Například výsledkem volání metody ToInt3 2 je vždy nějaké číslo, říkáme tudíž, že ToInt32 je metoda s návratovou hodnotou typu int. Ne všechny metody předávají svou návratovou hodnotu. Poznali jsme již metodu Close, která jen zavře okno. O takových metodách říkáme, že to jsou metody bez návratové hodnoty. Zatímco volání metod s návratovou hodnotou jsou obvykle součástí většího celku, např. přiřazovacího příkazu, tvoří volání metod bez návratové hodnoty vždy samostatný příkaz - napsali jsme Close, závorky, středník Chyby za běhu programu Při práci s programem Kalkulačka jste možná zjistili, že pokud uživatel do některého ze vstupních polí nezadá číslo, ale napíše namísto něj nějaké slovo (nebo se překlepne), program po stisknutí tlačítka s rovnítkem přestane pracovat. V takové situaci říkáme, v došlo k chybě za běhu programu. Chyba běhu programu se projeví tak, že se na monitoru dostane do popředí okno vývojového prostředí vypadající podobně jako to na obrázku 9.2. Řádek zdrojového kódu, na kterém došlo k chybě, bude zobrazen žlutě a v jeho blízkosti vyskočí okénko s chybovým hlášením.
273 Obr. Chyba při běhu programu při ladění programu Je nutno ukončit běh programu (Debug> Stop Debugging) a pátrat po příčinách. Situace z obrázku 9.2 nastala tehdy, když jsme do prvního textového pole zadali text Nejsem číslo". K chybě došlo na žlutě vyznačeném řádku ČísloX = Convert.ToInt32(poleČisloX.Text); protože metoda ToInt32 nedokázala řetězec Nejsem číslo" převést na žádné rozumné číslo. Pokud je program spuštěn mimo vývojové prostředí, např. poklepáním na jeho spustitelnou podobu (. exe soubor), vývojové prostředí se při běhové chybě samozřejmě neotvírá. Namísto toho se chyba ohlásí okénkem podobným tomu, které je zobrazeno na obrázku 9.3. I v tomto případě je nejrozumnější reakcí ukončení programu stiskem tlačítka Quit". Obr. Chyba při běhu programu ve Windows Ošetření chyb během chodu programu Pokud děláte program jen sami pro sebe, nebývá výskyt běhové chyby až takovou tragédií, většinou se dovtípíte, v čem je problém. V programech určených jiným uživatelům by se však běhové chyby pokud možno objevovat neměly.
274 Ošetření těchto chyb v programu Sčítačka je možné dvěma způsoby. Zaprvé můžete zadání nekorektní hodnoty uživateli znemožnit. Existují například ovládací prvky, které dovolí pouze zadání číselného údaje. Zadruhé můžete na zadání nekorektní hodnoty přiměřeně reagovat, například zobrazením srozumitelné hlášky v češtině a pokračováním v běhu. Ukážeme si druhou metodu. Budeme ji využívat i k ošetření jiných chyb, než je zadání nekorektní hodnoty. Postupujeme takto: Přejděte do zdrojového kódu Form1. cs programu Sčítačka a myší (nebo pomocí Shift+šipky) označíme všechny řádky těla metody tlačítkorovnáse_click, mimo složených závorek vymezujících tělo metody (obrázek) Obr. Označení kódu metody Click Pravým tlačítkem klepněte někam do vyznačené oblasti a z kontextové nabídky vyberte Surround With.
275 Obr. Situace po klepnutí pravým tlačítkem V seznamu, který se objeví (obrázek.), najdeme slovo try a poklepejte na něj Obr. Název případové studie Zdrojový kód metody se změní do podoby podle Obr. 38. Ve spodní části metody odmažte (Exception) a také throw;
276 Obr Vzhled programu po vložení bloku try Do prázdného řádku mezi složenými závorkami za slovem catch vložte příkaz MessageBox.Show( "Byl zadán chybný údaj."); Výpis :Kalkulačka se zachytáváním výjimek. public partial class oknoprogramu : Form public oknoprogramu() InitializeComponent(); private void tlačítkorovnáse_click(object sender, EventArgs e) try // Hodnoty Text obou vstupních polí převést na čísla int číslox = Convert.ToInt32(poleČísloX.Text); int čísloy = Convert.ToInt32(poleČísloY.Text); catch // Čísla sečíst int výsledek = číslox + čísloy; // Výsledek převést na řetězec a zapsat jako hodnotu // Text výstupního pole polevýsledek.text = Convert.ToString(výsledek);
277 // Při nekorektním zadání informujeme uživatele MessageBox.Show("Byl zadán chybný údaj."); private void oknoprogramu_load(object sender, EventArgs e) Jak funguje konstrukce try-catch, kterou jsme do programu vkládali? Blok příkazů za slovem try je tzv. pokusný blok. Příkazy v pokusném bloku se vykonají jaksi na zkoušku". Když některý z nich selže, nemá to fatální následek. Namísto vzniku běhové chyby se provede to, že se přeskočí zbytek pokusného bloku a provedou se příkazy v bloku za slovem catch. Dojde k tzv. zachycení výjimky.' Co to znamená v konkrétním případě programu Kalkulačka? Jestliže například uživatel nevyplní první textové pole a stiskne rovnítko, dojde k výjimce během volání Convert.ToInt32 (polečíslox.text) - prázdný řetězec nelze převést na žádné číslo. Už se ani nedokončí přiřazení do proměnné číslox, přeskočí se další akce ve zbytku pokusného bloku a namísto toho všeho se vykoná jediný příkaz bloku za slovem catch, tzn. Příkaz MessageBox.Show("Byl zadán chybný údaj"); který způsobí zobrazení okénka se zprávou Byl zadán chybný údaj". Všimněte si, že do pokusného bloku se uzavírají nejen příkazy, které mohou způsobit běhovou chybu, ale také všechny další, které na ně navazují. V programu Sčítačka se při vzniku výjimky nemůže provést přiřazení do proměnné číslox, protože není co přiřazovat, nemůže se provést sčítání, protože není co sčítat, ani se nemůže zobrazit výsledek, protože žádný není Deklarace s inicializací Finální podobu obslužné metody stisku rovnítka ukazuje Výpis 13 Všimněte si, že v programu byla provedena ještě jedna úprava, a tou je sloučení deklarací proměnil seli s přiřazeními do nich. Namísto dvou příkazů int číslox; číslox = Convert.ToInt32(polečísloX.Text); lze zapsal jediný příkaz int číslox = Convert.ToInt32(polečíslo1.Text); Použili jsme tzv. deklaraci s inicializací, zápis, který umožňuje do proměnné ihned po jejím vzniku přiřadit počáteční hodnotu. Syntaxe tohoto zápisu vypadá následovně: typ_proměnné jméno_proměnné = počáteční_hodnota; Dbejte také na to, že pokud do proměnné budete někde dále v metodě přiřazovat novou hodnotu, zapíšete už samozřejmě jen přiřazení bez deklarace. Deklarovat proměnnou můžete jen jedenkrát, přiřadit do ní něco můžete, kolikrát chcete. Každá vlastnost či hodnota je vždy nějakého typu. Typ určuje druh hodnot, možnosti jejich použití a možné operace s nimi.
278 Každý typ má v jazyce C# nějaký název. Pro textové řetězce slouží typ string, pro čísla typ int, pro barvy typ Color, pro logické hodnoty typ bool. Na pravé straně přiřazovacího příkazu se mohou vyskytovat celé výrazy. Je možno například dvě hodnoty spojit pomocí operátoru +. Ten při použití na čísla sčítá, při použití na řetězce spojuje. Veškeré mezivýsledky si program ukládá v operační paměti do pojmenovaných oblastí, do proměnných. Pro každou ukládanou hodnotu je třeba vyhradit samostatnou proměnnou. Proměnná se vytváří pomocí příkazu deklarace proměnné, který má tvar typ_proměnné jméno_proměnné; resp. typ_proměnné jméno_proměnné = počáteční_hodnota; U hodnoty lze změnit typ pomocí typové konverze. Ke konverzím mezi čísly a řetězci slouží metody ToInt32 a ToString třídy Convert. Řada metod definuje návratovou hodnotu, která se jakoby dosadí za zápis volání metody poté, co se metoda provede. Některé operace, například pokus o převedení řetězce na číslo, mohou vyvolat chybu za běhu programu. K ošetření těchto tzv. výjimek slouží konstrukce try-catch. Jestliže některý z příkazů uzavřených do pokusného bloku za slovem try selže, zbytek pokusného bloku se přeskočí a namísto toho se vykonají příkazy bloku za slovem catch. Pojmy k zapamatování Rozeznáváme různé datové typy. Mezi základní typy patří Celočíselný typ je typ int. Řetězec znaků je typ string. Booleovský (logický, pravdivostní) je typ boolean. Různé typy mohou nabývat pouze hodnot, které souvisí s daným typem. Hodnota, kterou metoda předává, se nazývá návratová hodnota metody. Před použitím jakékoli proměnné ji musíme nejprve deklarovat, tedy ji pojmenovat, současně s deklarací ji můžeme i definovat, tedy ji i přiřadit hodnotu. 14. Grafika Čas ke studiu: xx hodin Cíl Po prostudování tohoto odstavce budete umět V programu použít příkazy, které vykreslí přímku, elipsu, obdélník. Změnit barvu obrysu tvaru. Vykreslit plný tvar. Po prostudování této kapitoly a vypracování úkolů získáte: Znalosti o grafice v programu.
279 Znalosti grafické orientace okna programu. Po prostudování této kapitoly a vypracování úkolů budete schopni: Programově vykreslit základní obrazce a pochopit metodu paint, která se pro vykreslování užívá. Použít pera s štětce pro kreslení. Výklad Kreslení čárových obrazců Napíšeme program, který okně nakreslí úsečky, obdélník a elipsu Obr. Grafický program Vytvoříme nový projekt s názvem Obrazce". Nebude obsahovat žádné ovládací prvky. Oknu programu nastavíme vlastnost (Name) na oknoprogramu a vlastnost Text na Obrazce". Přepneme Vlastnosti do režimu Události a pokepáme na událost Paint. Do otevření motody zapíšeme příkazy z výpisu. Najdeme zde komentáře, uvozené dvěma lomítky. Ty vysvětlují funkci kódu, ale z hlediskla činnosti programu nemají žádnou roli. Výpis Obsluha události Paintc okna programu
280 public partial class oknoprogramu : Form public oknoprogramu() InitializeComponent(); private void oknoprogramu_paint(object sender, PaintEventArgs e) // Nejprve získáme objekt kreslicí plochy Graphics kreslicíplocha; kreslicíplocha = e.graphics; // Nakreslíme černou čáru z bodu [0, 0] do bodu [150, 100] // Význam parametrů: (pero, xpoč, ypoč, xkonc, ykonc) kreslicíplocha.drawline(pens.black, 0, 0, 150, 100); // Ještě jednu čáru - vodorovnou z [0, 100] do [150, 100] kreslicíplocha.drawline(pens.black, 0, 100, 150, 100); // Nareslíme modrý obdélník v bodě [250, 150] // o šířce 55 pixelů a výšce 140 pixelů // Význam parametrů: // (pero, xlevýhorní, ylevýhorní, šířka, výška) kreslicíplocha.drawrectangle(pens.blue, 250, 150, 55, 140); // Nakreslíme čokoládovou elipsu umístěnou uvnitř // obdélníka, který začíná v bodě [250, 150] a // je 55 pixelů široký a 140 pixelů vysoký // Význam parametrů je stejný jako při kreslení obdélníka kreslicíplocha.drawellipse(pens.chocolate, 250, 150, 55, 140); private void oknoprogramu_load(object sender, EventArgs e) Událost paint Tuto událost obsluhujeme vždy, když chceme ovlivnit vzhled okna programu bez pouřití ovládacích prvků. Do obsluhy události Paint zapisujeme příkazy pro kreslení grafických obrazců, které v okně chcete mí. Programy s grafickým ozhraním fungují tak, že musí umět svou podobu vytvořit či obnovit kdykoli na požádání. Požadavky na vykreslení okna programu vytváří obvykle operační systém vždy předtím, než se okno poprvé zobrazí na monitoru; je-li okno minimalizováno a pak obnoveno do původní velikosti; jestliže se část okna odkryje. Na tyto události má okno vestavěnou automatickou obsluhu, která přiměje všechny ovládací prvky, aby se vykreslily (vyvolá jejich události Paint). Kromě toho může vzhled okna ovlivnit programátor, když k události Paint připojí vlastní obslužnou metodu. Tak, jak jsme to udělali v úvodním programu Obrazce Grafické příkazy a kreslicí plocha Jistě jste již zběžným pohledem pochopili, že pro nakreslení každého grafického elementu existuje nějaký příkaz, volání nějaké metody připravené v knihovnách prostředí.net. V
281 každém volání se předávají parametry přesně definující kreslený prvek - barvy, souřadnice atd. Jména metod jsou jednoduchá a jsou vypsány Tab Tab. 15 Metody pro kreslení grafických elementů Metoda Její funkce Její parametry DrawLine Kreslení úsečky Pero a souřadnice koncových bodů DrawRectangle Kreslení obdélníka Pero, souřadnice rohu, šířka a výška DrawEllipse Kreslení elipsy Pero a parametry opsaného obdélníka Metoda patří vždy buď do nějaké třídy nebo nějakému objektu. Grafických metody patří vždy i objektu kreslicí plochy okna. Kreslíme vždy na nějakou kreslicí plochu (též plátno). V obslužné metodě události Paint nás zajímá kreslicí plocha okna programu obsažená v proměnné (objektu) kreslicíplocha. Jménem tohoto objektu blíže určíme volání metody DrawLine. Úsečka tak musí vykreslit právě na ploše okna a ne na žádné jiné. Datový typ objektů kreslicích ploch se jmenuje Graphics, proto proměnnou deklarujeme příkazem Graphics kreslicíplocha; Následně je naplněna odkazem na kreslicí plochu okna příkazem kreslicíplocha = e.graphics; Pera Prvním parametrem příkazů pro kreslení úseček, obdélníků a elips je vždy tzv. pero. Pero je úplnou charakteristikou čáry, to znamená určuje barvu čáry, její tloušťku a její styl. Náš program kreslí plné tenké čáry Zatím vystačíme s pery z připravené sady per Pens. Napíšete Pens a za tečku připojíte jméno barvy Souřadný systém okna Všechny parametry grafických příkazů následující za perem určují geometrii kreslených obrazců - jejich polohu, rozměry... Vše vychází z pojmu souřadný systém okna. Jeho počátek, o souřadnicích [0, 0], leží v levém horním rohu okna,.x-ová souadnice roste směrem doprava a y-ová roste směrem dolů. V čem se udávají jednotky souřadnic lze určit programátorsky.
282 Obr. Souřadnicový systém Mohou to být například centimetry na monitoru, nebo metry reálného světa, který se programem modelujeme. Pokud neučíme jinak, jsou jednotkami souřadného systému pixely, neboli obrazové body monitoru Kreslení úseček, obdélníků a elips Úsečka: Kreslení úsečky. Po specifikaci pera se zadávají nejprve souřadnice x a y počátečního bodu a potom souřadnice x a y koncového bodu. Například úsečka by se nakreslila tímto příkazem: kreslicíplocha.drawline(pens.black, 50, 150, 270, 80); Obdélník: Kreslení obdélníku vyžaduje čtyři parametry nejprve souřadnice x a y jeho levého horního rohu a potom šířku a výšku obdélníku. Například kreslicíplocha.drawrectangle(pens.blue, 200, 100, 50, 100); nakreslil obdélník s levým horním rohem v bodě [200, 100], šířkou 50 a výškou 100.
283 Elipsa: Obr. Vykreslování elipsy Kreslení elipsy je obtížnější. Nejprve si okolo ní musíme představit pomyslný obdélník (na obrázku je zobrazen tučně čárkovaně) a poté zadat jeho parametry, souřadnice x a y levého horního rohu, šířku a výšku. Chcete-li kreslit elipsu, pak nejdřív zjistíte, že levý horní roh je například v bodě [70, 90], poté určíte jeho šířku jako 200 (=270-70) a jeho výšku jako 80 (=170-90). Příkaz pro kreslení takovéto elipsy potom bude vypadat takto: kreslicíplocha.drawellipse(pens. Black, 70, 90, 200, 80); V ukázkovém programu je volání DrawEllipse se shodnými parametry jako je volání DrawRectangle. Vidíme, že se elipsa se nakreslí přesně dovnitř dříve nakresleného obdélníka (obrázek ) Vyplněné obrazce Metody pro kreslení vyplněných obrazců -uchycuje Tab. 16. Pojmenování jsou podobná jako u metod pro kreslení obrazců čárových, jen namísto Draw se píše Fill. Shodné jsou i používané parametry jedinou s tím, že místo per se používají štětce. Tab. Kreslicí metody pro vyplněné obrazce Metoda Její funkce Její parametry FillRectangle Kreslení vyplněného obdélníka Štětec, souřadnice rohu, šířka a výška FillEllipse Kreslení vyplněné elipsy Štětec a parametry opsaného obdélníka Štětec představuje úplnou charakteristiku výplně, to znamená především její barvu a styl, tedy šrafování, gradient barvy ap.). Nejsnazší je použít již připravenou sadu štětců" Brushes. Jednoduše napíšete i Brushes a za tečku připojíte jméno barvy. Výpis Obsluha události Paint okna programu, plné obrazce private void oknoprogramu_paint(object sender, PaintEventArgs e) // Získáme kreslicí plochu kp Graphics kp = e.graphics; // Nakreslíme chrpově modrý VYPLNĚNÝ obdélník // v bodě [20, 100] o šířce i výšce 50 pixelů // (takže vlastně čtverec) // Význam parametrů stejný jako u DrawRectangle kp.fillrectangle(brushes.cornflowerblue, 20, 100, 50, 50); // Ještě jeden obdélník, tentokrát zelený kp.fillrectangle(brushes.green, 0, 200, 300, 10); // Pomocí obdélníků 1x1 můžeme kreslit tečky kp.fillrectangle(brushes.black, 10, 10, 1, 1);
284 // Nakreslíme růžovou VYPLNĚNOU elipsu umístěnou // uvnitř obdélníka, který začíná v bodě [120, 30] // a je 100 pixelů široký a 70 pixelů vysoký // Význam parametrů je stejný jako u DrawEllipse kp.fillellipse(brushes.pink, 120, 30, 100, 70); Program také ukazuje použití metody FillRectangle pro kreslení teček. Když chcete v okně nabarvit jej jediný obrazový bod, jediný pixel, zavolejte FillRectangle a zadejte šířku i výšku obdélníka jako 1 pixel. Pokud jste si toho bodíku při spuštění programu nevšimli, spusťte jej znovu a pečlivě se podívejte do blízkosti levého horního rohu klientské oblasti okna. Je namalován černě, takže by se neměl na světlém (předpokládám!) pozadí okna ztrácet Grafika v části okna Doposud jsme měli k dispozici celou klientskou oblast okna. Nyní se podíváme na případ, kdy je pro grafiku potřeba vymezit jen část okna. Program, ukazuje Obr. 45. Program nakreslí ohraničenou elipsu na pozici a velikosti zadané uživatelem. V případě, že část elipsy zasahuje mimo vyhrazenou oblast, bude elipsa oříznuta. Vytvoříme nový projekt nazvaný Částečná grafika". Navrhneme uživatelské rozhraní programu. Pojmenování ovládacích prvků zvolíme: polex_střed, poley_střed, polešířka, polevýška, tlačítkopřekresli Panel Obr. Grafika v panelu Použití, ovládacího prvku panel omezíme grafiku jen na část okna. Jedná se o kontejner jiných ovládacích prvků, s výhodou jej však lze využít také pro vymezení určité oblasti okna. Do pravé části okna přetáhneme nástroj Panel" opět ne ze skupiny Common Controls, ale ze skupiny nazvané Containers". Upravíme ho do čtvercové podoby. Panel pojmenujte panelokna a jeho vlastnost BorderStyle nastavíme na FixedSingle čímž ho ohraničíme.
285 Přepneme se do editace událostí a poklepáme, na událost panelu Paint. Ve zdrojovém kódu se otevře obslužná metoda panel_paint. Nejprve získáme kreslicí plochu: Graphics kp = e.graphics; Jelikož se nacházíme v obsluze události Paint panelu, získáme tak kreslicí plochu panelu. To má přinejmenším dva důsledky: Veškerá grafika bude na hranicích panelu oříznuta. Počátek souřadného systému bude v levém horním rohu panelu. Pokud byste chtěli, získal přesný čtverec, najděte pro panel vlastnost Size a obě čísla stejná Zpracování vstupu od uživatele Nyní chceme vykreslit elipsu v takové šířce, v jaké si ji uživatel přeje. Znamená to, že ve volání FillEllipse nebude čtvrtý parametr (šířka) zadán číslem uvedeným v programu, ale že do něj musíme přenést hodnotu zadanou uživatelem v textovém poli Šířka". Nabízí se jako čtvrtý parametr volání FillEllipse uvést hodnotu vlastnosti Text příslušného textového pole. To ale nemůžeme provést přímo. Šířka elipsy je číslo, neboli hodnota typu int, zatímco hodnota polešířka.text je textový řetězec, typ string. Musíme převést hodnotu polešířka.text na číslo, a to uložit v nějaké proměnné typu int. Hodnotu této proměnné následně použít ve volání FillEllipse. Správné řešení vypadá takto: int šířka = Convert.ToInt32(poleŠířka.Text); kp.fillellipse(...,...,..., Šířka,...); Asi je evidentní, že stejným způsobem se zpracuje výška zadaná uživatelem Geometrie elipsy Uživatel v našem programu zadává souřadnice středu, zatímco volání FillEllipse vyžaduje souřadnice levého horního rohu. Tyto souřadnice musíme nejprve vypočítat. V proměnných xstředu a ystředu jsou uloženy údaje z příslušných textových polí po převodu do číselné formy. Nyní potřebujeme zjistit hodnoty xroh a yroh. Po chvíli přemýšlení zjistíme, že od x-ové souřadnice středu je nutno odečíst polovinu šířky elipsy. Od y-ové souřadnice středu je potřeba odečíst polovinu výšky elipsy. Deklarace proměnných xstředu a ystředu bude tedy vypadat takto: int xroh = xstředu - šířka / 2; int yroh = ystředu - výška / 2; Všimněte si, že operace dělení se zapisuje pomocí lomítka. Pro správné vyhodnocení výpočtu je důležité, že dělení se provede dříve než odčítání. Říkáme, že dělení má před odčítáním přednost, má vyšší prioritu Kompletní obsluha události paint Celý kód obslužné metody události Paint panelu ukazuje výpis private void panel_paint(object sender, PaintEventArgs e) // Získáme kreslicí plochu PANELU Graphics kp = e.graphics; // Údaje z textových polí převedeme do číselné formy
286 int xstředu, ystředu, šířka, výška; try xstředu = Convert.ToInt32(poleXstředu.Text); ystředu = Convert.ToInt32(poleYstředu.Text); šířka = Convert.ToInt32(poleŠířka.Text); výška = Convert.ToInt32(poleVýška.Text); catch // V případě zadání špatné hodnoty ukončíme // vykonávání obsluhy události Paint, čímž // panel zůstane prázdný return; // Spočteme souřadnice levého horního rohu // pomyslného opsaného obdélníka int xroh = xstředu - šířka / 2; int yroh = ystředu - výška / 2; // A konečně vykreslíme elipsu (i s ohraničením) kp.fillellipse(brushes.cornflowerblue, xroh, yroh, šířka, výška); kp.drawellipse(pens.black, xlh, ylh, šířka, výška); Na vysvětlenou výpisu ještě několik komentářů: Všechna volání ToInt32 jsou uzavřena do pokusného bloku try. V případě, že uživatel některé textové pole nevyplní, příp. uvede nekorektní hodnotu, přenese se řízení do bloku za slovem catch, kde se vykoná příkaz return. Ten okamžitě ukončí vykonávání obslužné metody. Proměnné xstředu, ystředu, šířka a výška jsou deklarovány všechny jediným příkazem. Kdykoli potřebujeme deklarovat několik proměnných stejného typu, můžete to udělat, takže na začátku příkazu uvedete název typu a potom za mezerou seznam proměnných oddělených čárkami. Proměnné jsou deklarovány mimo pokusný blok, protože existence každé proměnné končí s koncem bloku, ve kterém je proměnná deklarována Vyvolání události paint Nyní zkusíme program spustit. Do textových polí zadáme nějaká rozumná čísla, např. 100, 30, 80, 50, a stiskneme tlačítko Překresli". Nic nestane. Okno se správně vykreslí, jen pokud změníme jeho velikost nebo pokud je minimalizujeme a pak obnovíme. Pokud si vzpomeneme, při jakých událostech dojde k překreslení okna, najdeme i řešení našeho problému. Tím vyvolání událost Paint v reakci na stisk tlačítka Překresli". Poklepáme na tlačítko a zdrojový kód obsluhy události Click upravíme: private void tlačítkopřekresli_click(object sender, EventArgs e) panel.refresh(); V těle metody je jediný příkaz, volání metody Refresh() objektu panel. Toto volání způsobí, že daný objekt, tzn. panel, bude svůj vizuální obsah" považovat za neaktuální a vyvolá událost Paint.
287 Pojmy k zapamatování S grafikou lze pracovat v obsluze události Paint okna programu, případně konkrétního ovládacího prvku. Událost Paint vzniká především před prvním vykreslením okna, při jeho odkrytí či obnově z minimalizovaného stavu. S objektem kreslicí plochy lze provádět nejrůznější grafické operace, jako např. kreslení úseček, vyplněných i nevyplněných obdélníku a elips atd. Volání příslušných metod dokumentují programy Čárové obrazce a Vyplněné obrazce. V obsluze události Paint se vždy nejprve získá kreslicí plocha a uloží do proměnné typu Graphics: Graphics kp = e.graphics; Veškeré geometrické parametry kreslených obrazců (poloha, rozměry) se uvádějí v pixelech, není-li specifikováno jinak. Souřadný systém má počátek v levém horním rohu klientské oblasti okna, souřadnice x roste směrem doprava, souřadnice y směrem dolů. Pokud chcete pro grafiku vyhradit jen část okna, použijte ovládací prvek panel. Najdete jej v Nástrojích v kategorii Containers". Pro vložení grafických příkazů použijte obsluhu události Paint panelu, nikoli celého okna. Příkaz return způsobí okamžité ukončení metody. Lze jej použít např. tehdy, když chcete zbývající příkazy přeskočit. Existence každé proměnné končí s koncem bloku, ve kterém je proměnná deklarována. Např. proměnné deklarované v pokusném bloku jsou mimo tento blok neviditelné. Událost Paint lze z programu vyvolat voláním metody Refresh blíže určeném jménem ovládacího prvku, např. panelu. Bez bližšího určení se vyvolá událost Paint celého okna programu, která mj. způsobí překreslení všech ovládacích prvků. Shrnutí pojmů kapitoly (podkapitoly) Otázky k probranému učivu K čemu slouží objekty Pen a Brush. Jaký je souřadný systém okna v grafickém programu. Jak zařídíme, aby okno programu vždy obsahovalo aktuální obsah. Jaké je použití prvku Panel při vykreslování grafiky v okně programu. Cíl Po prostudování tohoto odstavce budete umět Používat dokumentaci vývojového prostředí. Najít informace k objektům a jejich požití. Po prostudování této kapitoly a vypracování úkolů získáte: Základní znalosti o objektech.
288 Znalosti o vytváření objektů. Po prostudování této kapitoly a vypracování úkolů budete schopni: Využít statických prvků tříd. Vyžít výčtových typů. Výklad Objekty a třídy Pro porozumění objektům je důležité uvědomit si, co všechno ke každému objektu může patřit. V každém objektu mohou být definovány vlastnosti, události a metody. V programech se obvykle vyskytuje více objektů stejného druhu. Takto chápanému druhu objektů" se v programování říká třída. Třída textových polí se jmenuje TextBox, třída popisku Label a tak dále. U ovládacích prvků je to jednoduché - jména tříd jsou shodná se jmény příslušných nástrojů Toolboxu. Ve třídě TextBox je definováno, že každé textové pole bude mít vlastnosti Text, ReadOnly, BackColor, události TextChanged, MouseHover a metody Cut, Copy, Show Kdykoli se v programu má vytvořit nějaký objekt textového pole, sáhne se do třídyšablony TextBox a přesně podle ní se vytvoří nový objekt. Často se mluví o tom, že jednotlivé objekty jsou instancemi dané třídy. Zjednodušeně řečeno, třída můžeme chápat jako datový typ a objekt, instanci třídy jako proměnnou tohoto datového typu. Celou situaci okolo objektů jakožto instancí tříd na příkladu programu Grafika ukazuje obrázek Obr. Třídy a objekty Dokumentace tříd
289 K tomu, aby se ve všech potřebných třídách programátor zorientoval, slouží dokumentace knihoven. Dnes je mnohdy on-line přístupná pomocí internetu, mnohdy je lépe ji nainstalovat na svůj počítač. Potřebujeme-li nalézt, jak se dá pracovat s pery, musíme si uvědomit, že každé pero je objekt třídy Pen, budeme tedy hledat informace o této třídě. Spustíme vývojové prostředí a z nabídky vyberte Help > Index. Otevře se okno pro prohlížení dokumentace a v jeho levé části se zobrazí rejstřík. V poli Look for" zadejte hledané slovo, v našem případě slovo Pen". Ve sloupci pod polem se zobrazují jednotlivé položky rejstříku počínaje slovem, které jsme zadali. Nás bude zajímat položka Pen class", neboli třída Pen". U každé třídy se nejzajímavější informace dovíte v podpoložkách about" a all members". Nejdůležitější kategorie jsou: Konstruktory (Constructors); Vlastnosti (Properties); Metody (Methods); Události (Events). Co jsou vlastnosti, metody a události, už víte. Co však jsou konstruktory? Konstruktor je metoda, jejímž voláním se vytváří nový objekt Konstruktory třídy pen Když chceme s nějakým objektem pracovat, musíme ho nejdřív umět vytvořit. Příklad dokumentace vidíme na (Obr. 53). Ve sloupci Name" se u každé varianty zobrazuje seznam parametrů či, přesněji řečeno, pouze jejich typů (bohužel). Ve sloupci Description" najdete popis příslušné varianty metody Vlastnosti třídy pen Aby se nějak dalo, rozlišil mezi stejně pojmenovanými třídami, organizují se třídy do větších celků, tzv. jmenných prostorů. Ovládací prvky pro aplikace Windows například patří do jmenného prostoru System.Windows.Forms1 Třídy související s grafikou, jako např. Graphics, Color či Pen patří do jmenného prostoru System.Drawing. Řada základních tříd se nachází ve jmenném prostoru System. Do jakého jmenného prostoru daná třída patří, můžete zjistit např. v úvodu stránky about" dokumentace k dané třídě. Použití tříd z vybraných jmenných prostorů se ve zdrojovém kódu zapisuje úvodními řádky začínajícími slovem using. Šablona Windows Application", z níž všechny naše programy vytváříme, sama vloží do zdrojového kódu Form1.cs sedm odkazů na jmenné prostory Statické složky třídy
290 V této kapitole jsme rozebírali, že objekty při svém vzniku přebírají všechny složky (vlastnosti, události, metody), které jsou definovány v příslušné třídě. Když se potom chce programátor odvolat na některou složku vytvořeného objektu, použije konvenci jméno objektu, tečka, jméno složky", např. modrépero.width. Kromě běžných složek však může třída obsahovat i tzv. statické složky. To jsou složky, které nejsou spjaty s žádným objektem, složky, které instance třídy při svém vytvoření nepřebírají. Zatímco se běžné složky používají pouze s odkazem na nějaký existující objekt (bližší určování jménem objektu), patří složky statické pouze k dané třídě (bližší určování jménem třídy) Autogenerující složky Typem statické vlastnosti Color.Red je Color. Setkáváme se zde se zajímavým případem autogenerující vlastnosti, kdy hodnotou vlastnosti třídy je objekt této třídy! Výčtový typ Pojednání o objektech zakončíme výkladem pojmu výčtový typ. Pokud je typ nějaké vlastnosti typem výčtovým, znamená to, že vlastnost může nabývat pouze vybraných předepsaných hodnot. Každá z předepsaných hodnot je nějak pojmenována a stejně tak je pojmenován i celý výčet. Výčtovým typem je například typ vlastnosti DashStyle objektů třídy Pen. Tato vlastnost určuje styl čar kreslených příslušným perem. Pojmy k zapamatování Programátorské objekty se skládají z vlastností, událostí a metod. V programech se obvykle vyskytuje více objektů téhož druhu. Tomuto druhu objektů se říká třída. Třída funguje jako šablona pro vytváření objektů neboli instancí dané třídy. Objekt při svém vytvoření získá všechny složky (vlastnosti, události, metody), které jsou v příslušné třídě definovány. K jednotlivým třídám lze hledat potřebné informace v dokumentaci instalované současně s vývojovým prostředím. V dokumentaci se dá hledat například přes rejstřík. Z nabídky Help zvolíte položku Index a následně v poli Look for" zadáte například název hledané třídy (ve Visual C# 2008 ještě předtím v poli Filtered by" vyberete unfiltered"). Nejzajímavější informace najdete v podpoložkách about" a all members". Konstruktor je metoda, jejímž voláním se vytvářejí nové instance dané třídy. V jazyce C# se konstruktor jmenuje vždy stejně jako daná třída. Volání konstruktoru předchází klíčové slovo new. K zajímavým vlastnostem objektů třídy Pen patří Color (barva), Width (tloušťka) a DashStyle (styl čáry). Vlastnost DashStyle může nabývat pouze některé z hodnot
291 definovaných v tzv. výčtovém typu, výčtu DashStyle. Konkrétní hodnota výčtového typu se zapisuje tak, že za jméno výčtu se doplní tečka a jméno zvolené hodnoty. Třídy se sdružují do větších celků, které nazýváme jmenné prostory. Použité jmenné prostory musíme na začátku zdrojového kódu vyjmenovat. To provedeme tak, že na počátek řádku napíšeme klíčové slovo using. Ve třídách mohou být kromě běžných složek definovány také složky statické, které se neváží na konkrétní objekt a jejichž názvy se blíže určují jménem třídy. V dokumentaci se statické složky označují ikonkou ve tvaru písmene S. Autogenerující složky třídy, jsou vlastnosti či metody, jejichž hodnotou je objekt téže třídy. Příkladem může být statická vlastnost Color.Red definující červenou barvu, nebo statická metoda Color.FromArgb vracející barvu namíchanou z komponent RGBm které jsou jí předány jako parametry. Otázky k probranému učivu Vyložte pojem objekt, třída a jmenný prostor. Čemu náleží statický člen třídy. Znáte příklad autogenerující složky třídy. Uveďte příklad výčtového typu. Shrnutí pojmů kapitoly (podkapitoly) Moderní styl programování je objektové programování. Objekty sdružují datové složky, vlastnosti a metody. Složky třídy mohou být i statické, ty náleží vždy třídě, jsou společné pro všechny objekty dané třídy a určujeme je pomocí jména třídy. Výčtový typ určujeme sami tak, že vyjmenuje všechny jeho složka. Proměnná výčtu může nabývat jen hodnot, které jsme určili, když jsme výčtový typ definovali. Cíl Po prostudování tohoto odstavce budete umět Použít složené přiřazení a měnit obsah proměnných. Po prostudování této kapitolya vypracování úkolů se naučíte: Spojovat řetězce a využít metody NewLine Využívat třídu okna programu. Používat proměnné a porozumíte, kdy proměnná vzniká a kdy zanikne. Po prostudování této kapitoly a vypracování úkolů budete schopni: Sestavit složitější program, který bude využívat pokročilou práci s proměnnými. Využívat složeného přiřazení a operace zvětšení či zmenšeni proměnné o jedničku.
292 Výklad Seznam hlášení Vlastnost objektu existuje tak dlouho, dokud existuje objekt. Takové textové pole umístěné v okně programu může informace uchovávat po celou dobu běhu programu. Obr. Rozhraní programu Seznam Vytvoříme aplikaci, která bude uchovávat, ukládat text zadávaný uživatelem Při stisku tlačítka Ulož" se obsah horního textového pole Hláška" přidá jako nový řádek spodního textového pole Seznam". Současně se horní textové pole vyčistí pro zadání další věty. Vytvoříme nový projekt Seznam" a připravíme uživatelské rozhraní programu. Aktivní ovládací prvky nazveme polehláška, poleseznam a tlačítkoulož. Ve spodním textovém poli nastavíme ReadOnly na True; Multiline na True (až poté půjde pole roztáhnout ve svislém směru); ScrollBars na Both (budeme chtít oba posuvníky); WordWrap na False (zalamování řádků se v této aplikaci nehodí) Složené přiřazení Při každém stisku tlačítka se má vzít text z horního pole, přidat jej na konec textu pole spodního a horní pole vymazat. Obsluha události Click by tedy mohla vypadat tak, jak ji ukazuje Výpis. Obsluha události Click tlačítka programu Seznam
293 private void tlačítkozapiš_click(object sender, EventArgs e) poleseznam.text += polehláška.text; polehláška.text = null; Nejdůležitějším je první řádek těla obslužné metody, ve kterém se za pomocí operátoru += provádí tzv. složené přiřazení. Pokud by se ve zmíněném řádku použilo obyčejné přiřazení poleprotokol.text = polehláška.text; pak by text horního pole vždy kompletně nahradil text pole spodního. My však chceme, aby horní text byl přidán ke stávajícímu textu spodnímu. Přesně to provede složené přiřazení +=. Významu tohoto zápisu lépe porozumíte, když si ukážeme, jak by se obsluha tlačítka řešila bez znalosti operátoru +=. Úlohu lze řešit i pomocí běžného přiřazen: poleseznam.text = poleseznam.text + polehláška.text; Přestože jsou oba výpisy naprosto ekvivalentní, určitě je lepší používat složené přiřazení - hned je na první pohled zřejmé, že se k hodnotě poleseznam.text bude něco přidávat; čtenář programu nemusí luštit, že na levé a pravé straně od rovnítka se vyskytuje tatáž vlastnost Obsluha tlačítka bez složeného přiřazení private void tlačítkozapiš Click (object sender, EventArgs e) poleseznam.text = poleseznam.text + polehláška.text; polehláška.text = null; Řádkování Pokud jsme program s výše uvedenou obsluhou tlačítka otestovali, zjistili jsme, že funguje jen částečně. Zadávané věty správně přidává na konec seznamu, ale přidává je všechny za sebe na jeden řádek. Musíme doplnit řádkování. Za každou větu do pole Seznamu přidanou přidáme navíc ještě konec řádku. Pro symbol konce řádku se používá statická vlastnost NewLine třídy Environment. S ní bude finální podoba obslužné metody stisku tlačítka vypadat tak, jak ji ukazuje Výpis Obsluha události Paint okna programu
294 private void tlačítkozapiš_click(object sender, EventArgs e) poleseznam.text += polehláška.text + Environment.NewLine; polehláška.text = null; Složené přiřazení a čísla Operátor += připojuje jeden řetězec na konec druhého. Lze jej použít také na čísla a v takovém případě zvětší hodnotu jednoho čísla o hodnotu čísla druhého. Ukážeme si to na programu napodobujícím ovládací prvek Číselník (Obr. 64). Při stisku tlačítka +1" se číslo v textovém poli zvětší o jedničku, při stisku tlačítka -1" se o jedničku zmenší. Obr. Rozhraní programu Nahoru a dolů Jelikož je text v textovém poli typu string, bude se j muset provést konverze na typ int. Obsluha stisku tlačítka +1" tedy bude vypadat takto: Hodnotu polečíslo.text převést na typ int a uložit do proměnné číslo Hodnotu proměnné číslo zvětšil o jedničku. Novou hodnotu proměnné číslo převést na typ string a uložit do polečíslo.text. Obsluha stisku minus jedničky bude naprosto analogická. Připravíme nový projekt Číselník" a jeho uživatelské rozhraní, pojmenovanými polečíslo, tlačítkoplus a tlačítkominus. Textovému poli nastavte výchozí hodnotu vlastnosti Text na 0". Vložíme obslužné metody obou tlačítek a jejich zdrojový kód upravíme do podoby, kterou ukazuje Výpis programu Nahoru a dolů
295 public partial class oknoprogramu : Form public oknoprogramu() InitializeComponent(); private void tlačítkoplus_click(object sender, EventArgs e) int číslo = Convert.ToInt32(poleČíslo.Text); číslo += 1; polečíslo.text = Convert.ToString(číslo); private void tlačítkominus_click(object sender, EventArgs e) int číslo = Convert.ToInt32(poleČíslo.Text); číslo -= 1; polečíslo.text = Convert.ToString(číslo); private void oknoprogramu_load(object sender, EventArgs e) V obsluze stisku plus jedničky je zajímavý příkaz číslo += 1; který zvětší hodnotu proměnné číslo o jedničku. Podobně příkaz číslo -= 1; v obsluze minus jedničky hodnotu proměnné číslo o jedničku zmenší. Bez použití složeného přiřazení by se uvedené příkazy daly ve stejném významu napsat jako: číslo = číslo + 1; resp. Číslo = číslo - 1; Pro čísla lze složené přiřazení použít ve spojení s libovolným aritmetickým operátorem. Kromě += tedy existuje -=, *= atd. Pro jednoduchost program neuvažuje možnost zadání nekorektní hodnoty. Ošetření takovéto situace s použitím konstrukce try-catch ponecháváme na vás Operátory ++ a - -
296 V programu Nahoru a dolů se hodnota proměnné číslo zvětšuje, resp. zmenšuje je vždy o jedničku. Kdybyste chtěli zvětšovat např. o desítku, snadno byste příslušný příkaz upravili: číslo = číslo + 10; Zvětšování, resp. zmenšování konkrétně o jedničku je však v programování natolik časté, že pro ně v jazyce C# existuje speciální kompaktní zápis, kdy se pouze za jiné no proměnné uvede operátor inkrementace ++, resp. operátor dekrementace Okno programu JAKO TŘÍDA Ve složitějších úlohách programátoři nejen že používají již připravené třídy, ale také vytvářejí své vlastní. Od samých začátků totiž za nás Návrhář vytváří třídu oknoprogramu. Okno programu je samozřejmě objektem. To, čemu dáváme název oknoprogramu, je třídou. Samotné okno je nepojmenovanou instancí této třídy. Když se podíváme poblíž začátku zdrojového kódu Form1.cs najdeme tam řádek public partial class oknoprogramu : Form Slovo class v něm oznamuje začátek definice nové třídy. Zbytek řádku říká, že se třída jmenuje oknoprogramu a je odvozená ze třídy Form. Třída Form obsahuje všechno, co regulérní okno programu potřebuje. Třída oknoprogramu může obsahovat ještě položky navíc a těmi jsou metody obsluhy událostí a případně členské proměnné třídy Tyto proměnné budou dalšími složkami navíc" ve třídě oknoprogramu. Ano, ve třídě mohou být kromě vlastností, událostí a metod definovány také proměnné. Pojmy k zapamatování Složené přiřazení se používá, když na levé i pravé straně přiřazovacího příkazu je stejná proměnná. Složený příkaz přiřazení je složen z operátoru (+, -, /, *) a znaku =. Zapisuje jej ve tvaru Výsledek += Výsledek + 10; V uvedeném příkladu k proměnné Výsledek je přičtena hodnota deset a výsledek je opět uložen do stejné proměnné Výsledek. Pro zvětšení (inkrementaci) a zmenšení (dekrementaci) hodnoty proměnné použijeme operátor ++ a --. Mimo vlastností a metod třída může obsahovat i proměnné, datové prvky třídy. Otázky k probranému učivu Napište příklad složeného operátoru přiřazení (složeného přiřazení) a vysvětlete jeho význam.
297 Jak se nejjednodušeji zapíše příkaz, který zmenší proměnnou o jedničku? Existuje i příkaz, který ba proměnnou uměl zvětšit právě o jedničku? Shrnutí pojmů kapitoly (podkapitoly) Mimo jednoduchého příkazu přiřazení můžeme často využít jeho kombinaci s nějakou operací, nejčastěji se sčítáním, odečítáme apod. Pro inkrementaci a dekrementaci hodnoty proměnné použijeme operátory ++ a -- Cíl Po prostudování tohoto odstavce budete umět Pracovat s celými a desetinnými čísly. Používat čísla jako objekty. Převádět řetězce na čísla a naopak. Převádět mezi různými typy číselných hodnot. Po prostudování této kapitoly a vypracování úkolů získáte: Potřebné vědomosti pro sestavení složitějšího programu, který pracuje s čísly. Znalosti, jak používat běžné matematické funkce ve svém programu a jak tyto funkce zapisujeme jako příkazy programu. Po prostudování této kapitoly a vypracování úkolů budete schopni: Sestavit program využívající matematické funkce a převádět vstupy od uživatele do formy, ve které se dají využívat k výpočtům. Sestavit program, který výsledek výpočtů zobrazí ve formě, které porozumí běžný člověk. Výklad Desetinná čísla Zatím jsme se setkali s celými čísla, např. 2, 77 či -18. Matematika počítače dělá podstatný rozdíl mezi čísly celými a desetinnými Čísla desetinná se v počítači ukládají jiným způsobem než čísla celá a výpočty s nimi provádí dvě odlišné části procesoru. Výpočty s desetinnými čísly jsou náročnější a také při nich vzniká tzv. zaokrouhlovací chyba, což je nepřesnost výpočtu způsobená nemožností přesného uložení většiny desetinných čísel. Práci s desetinnými čísly si ukážeme na modifikaci programu Kalkulačka. Program upravíme tak, aby akceptoval také vstup desetinných čísel. Pokud jde o uživatelské rozhraní, to zůstane stejné. Co se změní, bude obsluha tlačítka Sečti".. Pro desetinná čísla existuje datový typ double. Všechny proměnné, tzn. číslo1, číslo2
298 a výsledek, budeme deklarovat, jako proměnnou typu double Tím umožníme, aby se do nich ukládala desetinná čísla. Operátor sčítání je možno použít pro desetinná čísla i pro čísla celá. Musíme se zamyslet nad voláním metody Convert.ToInt32. Ta totiž odmítá všechno kromě celých čísel. Její dvě volání je tedy potřeba nahradit voláními Convert.ToDouble. Vzhledem k tomu, že metodě Convert.ToString z posledního řádku bloku try je jedno, jestli má na řetězec převést číslo celé nebo desetinné, nebude potřeba dalších úprav. Finální verzi obslužné metody tlačítka ukazuje Výpis. Výpis Část zdrojového textu upravené Sčítačky Čísla jako objekty Desetinná čísla se chovají jako objekty třídy Double, celá čísla jako objekty třídy Int32, řetězce jako objekty třídy String Přehled výpočtů Základní výpočetní operace shrnuje Tab. 21. Operátory + a - jsou stejné jako v matematice, násobení se zapisuje hvězdičkou *, dělení lomítkem /. Hvězdičku je nutno psát vždy, není možno ji vynechávat podobně, jako se symbol násobení mnohdy vynechává v matematice. Násobení a dělení má vždy přednost před sčítáním a odečítáním, stejně jako v matematice. Pokud chcete výraz vyhodnotit v jiném pořadí, je nutné použít závorky! Tab. Základní výpočty V matematice V C# y = 2x + 5 y = 2*x + 5; (2x-11)/ ( 5x + 2) y = (2*x - 11) / (5*x + 2); v = 2x-17/(5x)+2 y = 2*x - 17/(5*x) + 2; Výpočty s mocninami a odmocninami shrnuje Tab. 22. Druhou mocninu nejsnáze vypočtete, když číslo znásobíte samo se sebou. Pro výpočet jakýchkoli jiných mocnin použijte volání metody Pow třídy Math. Metoda přijímá dva parametry, první je základ mocniny, druhým exponent. Druhou odmocninu získáte voláním metody Sqrt třídy Math. Jakoukoli jinou odmocninu převeďte na mocninu a použijte Math. Pow. Pro připomenutí, n-tá odmocnina je totéž co umocnění na 1/ In. Tab. Mocniny a odmocniny
299 V matematice V C# y = -3*x*x + 4*(x ); y = 7.3 * Math.Pow(10, x-3); y = Math.Sqrt(x / 2); y = Math.Pow(x + 1, 1.0/4.0); nebo y = Math.Pow(x + 1, 0.25); Další skupinou výpočtů jsou výpočty s goniometrickými funkcemi. Všechny goniometrické funkce jsou implementovány jako volání metod ze třídy Math, které přebírají vždy jeden parametr Úhel, jehož sinus, kosinus či tangentu chcete vypočítat, musí být vyjádřen v radiánech. Hodnotu Ludolfova čísla získáme jako Math. Pi): Tab. Goniometrické funkce V matematice V C# y = sin 2x + 0,5 y = Math.Sin (2*x) + 0.5; y = sin (2x + 0,5) y = Math.Sin (2 *x + 0.5); z = cos (x + y) tg x y = arc sin x + arc tg x z = Math.Cos (x + y) * Math.Tan (x); y = Math.Asin(x) + Math.Atan(x); Celočíselné dělení Celočíselné dělení nebo též dělení se zbytkem si ukážeme na příkladu. Pokud na kalkulačce vydělíte číslo 22 třeba pětkou, dostanete výsledek 4,4. Pokud však 22 vydělíte pětkou celočíselně, dostanete výsledek 4 se zbytkem 2 Z toho důvodu by výraz 1/4 počítač nevyhodnotil jako ale jako nulu! Pokud jsou dělitel i dělenec celá čísla nebo celočíselné výrazy, pak je i výsledek celé číslo. Zápis 1.0/4.0 ale říká, že čísla jsou desetinná a lomítko pak zastupuje normální neceločíselné dělení. Stačí, když aspoň jedno z čísel je desetinné. Pokud nechcete dělit celočíselně, musíte si být absolutně jisti, že aspoň jedno z čísel je desetinné.
300 Defenzivní programování je takové, kdy už dopředu uvažujeme, že v budoucnu se mohou změnit předpoklady, na kterých je program postaven, snažíte se jej napsat tak, aby těmito změnami nebyla dotčena jeho funkčnost přehled číselných typů Pro celá čísla používáme typ int, pro desetinná čísla typ double. Kompletní přehled číselných typů najdete v Tab. 26. Pokud to nebude vysloveně nutné, není třeba, abyste ve svých programech pracovali s jinými typy než int a double. Tab. Číselné typy Druh Název typu Alternativní název Rozsah hodnot Přesnost Počet hitů Celá čísla byte Byte 0 až Desetinná čísla Desetinná čísla s přesnou reprezentací sbyte SByte -128 až ushort UInt16 0 až short Int až uint UInt32 0 až 4 miliardy 32 int Int32 ± 2 miliardy 32 ulong UInt64 0 až 18 trilionů 64 long Int64 ± 9 trilionů 64 float Single ± míst 32 double Double ± míst 64 decimal Decimal ± míst Kombinace číselných typů Kombinací hodnot int a double můžeme vidět v příkazu double dělitel = 1 + sazba / 100; Proměnná sazba byla typu double, hodnoty 100 a 1 typu int. Celý výsledek se vyhodnotil v typu double a do proměnné tohoto typu uložil. Na otázku kombinace typů ve výrazech navazuje otázka kompatibility vzhledem k přiřazení. Výraz, který se vyhodnotí jako int, je kromě proměnné typu int možno přiřadit také
301 proměnné typu double Říkáme, že proběhla tichá nebo též implicitní typová konverze, případně říkáme, že typ int je kompatibilní vzhledem k přiřazení do typu double. double x = 4; // int do double lze int počet = 4.7; // double do int nelze! float y = x + 1; // double do float nelze! Opačným směrem to však tak snadno nejde. Výraz, který se vyhodnotí jako double, není možno bez dalšího přiřadit proměnné typu int. Jazyk C# neumožňuje double na int konvertovat implicitně. Pokud konverzi provést chcete, musíte si ji výslovně vyžádat. Této tzv. explicitní typové konverze docílíte tak, že před převáděnou hodnotu uvedete v závorkách její požadovaný typ. double x = 4; // int do double implicitně int počet = (int) 4.7; // double do int jen explicitně float y = (float) (x + 1); // double do float jen explicitně Pojmy k zapamatování V programování se důsledně rozlišuje mezi celými a desetinnými čísly. Typ int slouží pouze pro celá čísla. Pro čísla desetinná použijte typ double. K výpočtům slouží především aritmetické operátory +, -, * a /. Pro různé matematické funkce (mocniny, odmocniny, sinus atd.) existují metody třídy Math. Čísla a také řetězce se mohou chovat jako objekty, tzn., mohou mít např. své vlastní metody. Pro čísla je zajímavá metoda ToString, které lze jako parametr předat formátovací řetězec. Operátor / funguje v případě dělení dvou celých čísel jako operátor celočíselného dělení. Na to je třeba při výpočtech dávat obzvláštní pozor. Řada programů pracuje s náhodou. K dispozici je třeba mít generátor náhodných čísel, který je na platformě. NET představován instancí třídy Random. Kdykoli je potřeba náhodné číslo, zavolá se metoda Next této instance. Metodě se jako parametry předávají dolní a horní mez rozmezí, ve kterém se má číslo vygenerovat. Přesněji řečeno, druhým parametrem je horní mez zvětšená o jedničku. Kromě typů int a double existují další číselné typy, které mají použití spíše ve speciálních případech. Hodnoty výrazů lze převádět na jiný typ buď automaticky (implicitní typová konverze), nebo na požádání (explicitní typová konverze). Při explicitní konverzi se před převáděnou hodnotu uvede v závorkách požadovaný cílový typ. Otázky k probranému učivu Jak se v programu liší celé a desetinné číslo a jak je zapisujeme
302 Na co si musíme dát pozor při použití operátoru dělení. Je operátor dělení shodný pro celočíselné dělení i pro desetinné dělení. Co se stane, pokud sečteme v programu celé a desetinné číslo. Jaké znáte matematické funkce a jak je zapíšeme v programu. Jak zapisujeme operace s goniometrickými funkcemi. K čemu nám slouží třída Math. Jak lze využít metody Convert. Shrnutí pojmů kapitoly (podkapitoly) V programu se mimo běžných matematických operací dají zapsat i většina běžných matematických funkcí včetně funkcí goniometrických. Čísla v programu vystupují jako objekty a mají tudíž i definované příslušné metody. Jednou z nich je metoda Convert, která nám slouží pro převod mezi různými typy čísel a číselných proměnných. Mezi základní číselné typy patří typ int, double, a decimal. Cíl Po prostudování tohoto odstavce budete umět Vytvořit program, který bude alternativně provádět různou činnost na základě různých vstupních údajů nebo na základě průběhu výpočtu Po prostudování této kapitoly a vypracování úkolů ZÍSKÁTE: Potřebné znalosti, jakým způsobem se provádí větvení programu a jaký má smysl. Přehled relačních operátorů, operátorů, které slouží k porovnávání hodnot. Po prostudování této kapitoly a vypracování úkolů BUDETE SCHOPNI: Vytvořit program, který reaguje na měnící se podmínky během výpočtu nebo na změnu zadání od uživatele. Výklad Podmíněné vykonávání Podmíněné vykonávání částí kódu si ukážeme na programu zobrazeném na obrázku Program kreslí obrazec, jenž se může sestávat z kříže, čtverce a kružnice. To, jestli se příslušná část obrazce na monitoru objeví, řídí uživatel pomocí tří zaškrtávacích políček. Kříž, resp. čtverec či kružnice, se vykreslí jen za podmínky, že je příslušné políčko zaškrtnuté.
303 Obr. Podmíněné kreslení Přemýšlejme, jak program sestavit. Jádro bude evidentně v obsluze události Paint panelu. V ní budou všechny kreslicí příkazy, jejich vykonávání však bude podmíněno stavem zaškrtávacích políček, jak ukazuje vývojový diagram na Obr.. Vyhodnocování podmínek se ve vývojových diagramech vyznačuje pomocí kosočtverců. Obr. Větvení programu Obrazec v panelu musí reagovat na zaškrtávací políčka. Když uživatel fajfkuje" čtverec, musí se objevit čtverec. Když uživatel fajfku" zruší, musí čtverec zmizet. Z hlediska
Zápis programu v jazyce C#
Zápis programu v jazyce C# Základní syntaktická pravidla C# = case sensitive jazyk rozlišuje velikost písmen Tzv. bílé znaky (Enter, mezera, tab ) ve ZK překladač ignoruje každý příkaz končí ; oddělovač
Proměnná. Datový typ. IAJCE Cvičení č. 3. Pojmenované místo v paměti sloužící pro uložení hodnoty.
Proměnná Pojmenované místo v paměti sloužící pro uložení hodnoty. K pojmenování můžeme použít kombinace alfanumerických znaků, včetně diakritiky a podtržítka Rozlišují se velká malá písmena Název proměnné
5 Přehled operátorů, příkazy, přetypování
5 Přehled operátorů, příkazy, přetypování Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně budou uvedeny detaily týkající se operátorů. Doba nutná k nastudování
1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:
1. lekce 1. Minimální program do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme: #include #include int main() { printf("hello world!\n"); return 0; 2.
Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15
Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15 KAPITOLA 1 Úvod do programo vání v jazyce C++ 17 Základní pojmy 17 Proměnné a konstanty 18 Typy příkazů 18 IDE integrované vývojové
Algoritmizace a programování
Algoritmizace a programování Výrazy Operátory Výrazy Verze pro akademický rok 2012/2013 1 Operace, operátory Unární jeden operand, operátor se zapisuje ve většině případů před operand, v některých případech
Úvod do programovacích jazyků (Java)
Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2007/2008 c 2006 2008 Michal Krátký Úvod do programovacích
1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:
1. lekce 1. Minimální program do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme: #include #include int main() { printf("hello world!\n"); return 0; 2.
Programovací jazyk Pascal
Programovací jazyk Pascal Syntaktická pravidla (syntaxe jazyka) přesná pravidla pro zápis příkazů Sémantická pravidla (sémantika jazyka) pravidla, která každému příkazu přiřadí přesný význam Všechny konstrukce
PŘETĚŽOVÁNÍ OPERÁTORŮ
PŘETĚŽOVÁNÍ OPERÁTORŮ Jazyk C# podobně jako jazyk C++ umožňuje přetěžovat operátory, tj. rozšířit definice některých standardních operátorů na uživatelem definované typy (třídy a struktury). Stejně jako
7 Formátovaný výstup, třídy, objekty, pole, chyby v programech
7 Formátovaný výstup, třídy, objekty, pole, chyby v programech Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost formátovanému výstupu,
24-2-2 PROMĚNNÉ, KONSTANTY A DATOVÉ TYPY TEORIE DATUM VYTVOŘENÍ: 23.7.2013 KLÍČOVÁ AKTIVITA: 02 PROGRAMOVÁNÍ 2. ROČNÍK (PRG2) HODINOVÁ DOTACE: 1
24-2-2 PROMĚNNÉ, KONSTANTY A DATOVÉ TYPY TEORIE AUTOR DOKUMENTU: MGR. MARTINA SUKOVÁ DATUM VYTVOŘENÍ: 23.7.2013 KLÍČOVÁ AKTIVITA: 02 UČIVO: STUDIJNÍ OBOR: PROGRAMOVÁNÍ 2. ROČNÍK (PRG2) INFORMAČNÍ TECHNOLOGIE
Jazyk C# a platforma.net
Jazyk C# a platforma.net Katedra softwarového inženýrství Fakulta informačních technologií České vysoké učení technické v Praze Pavel Štěpán, 2011 Syntaxe jazyka C# - 1. část BI-DNP Evropský sociální fond
Algoritmizace a programování
Algoritmizace a programování Řídicí struktury jazyka Java Struktura programu Příkazy jazyka Blok příkazů Logické příkazy Ternární logický operátor Verze pro akademický rok 2012/2013 1 Struktura programu
Paměť počítače. alg2 1
Paměť počítače Výpočetní proces je posloupnost akcí nad daty uloženými v paměti počítače Data jsou v paměti reprezentována posloupnostmi bitů (bit = 0 nebo 1) Připomeňme: paměť je tvořena řadou 8-mi bitových
Operátory, výrazy. Tomáš Pitner, upravil Marek Šabo
Operátory, výrazy Tomáš Pitner, upravil Marek Šabo Operátor "Znaménko operace", pokyn pro vykonání operace při vyhodnocení výrazu. V Javě mají operátory napevno daný význam, nelze je přetěžovat jako v
Úvod do programování - Java. Cvičení č.4
Úvod do programování - Java Cvičení č.4 1 Sekvence (posloupnost) Sekvence je tvořena posloupností jednoho nebo více příkazů, které se provádějí v pevně daném pořadí. Příkaz se začne provádět až po ukončení
Algoritmizace a programování
Algoritmizace a programování Typy Základní (primitivní) datové typy Deklarace Verze pro akademický rok 2012/2013 1 Typy v jazyce Java Základní datové typy (primitivní datové typy) Celočíselné byte, short,
NPRG031 Programování II --- 2/2 Z, Zk
NPRG031 Programování II --- 2/2 Z, Zk paralelka Y St 14:00-15:30 v S3 Pavel Töpfer Kabinet software a výuky informatiky MFF UK MFF Malostranské nám., 4. patro, pracovna 404 pavel.topfer@mff.cuni.cz http://ksvi.mff.cuni.cz/~topfer
URČITÝM ZPŮSOBEM PODOBNÉ
Objekty Svět se skládá z objektů! konkrétní x abstraktní hmatatelné x nehmatatelné (letadlo) x (chyba v programu) Objekty mohou obsahovat jiné objekty (tělo obsahuje buňky, letadlo součásti). Objekty URČITÝM
Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007
Úvod do programovacích jazyků (Java) Michal Krátký 1 Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2006/2007 c 2006 Michal Krátký Úvod do programovacích jazyků
TEORIE ZPRACOVÁNÍ DAT
Vysoká škola báňská - Technická univerzita Ostrava Fakulta elektrotechniky a informatiky TEORIE ZPRACOVÁNÍ DAT pro kombinované a distanční studium Jana Šarmanová Ostrava 2003 Jana Šarmanová, 2003 Fakulta
Základy jazyka C# Obsah přednášky. Architektura.NET Historie Vlastnosti jazyka C# Datové typy Příkazy Prostory jmen Třídy, rozhraní
Základy jazyka C# doc. Ing. Miroslav Beneš, Ph.D. katedra informatiky FEI VŠB-TUO A-1007 / 597 324 213 http://www.cs.vsb.cz/benes Miroslav.Benes@vsb.cz Obsah přednášky Architektura.NET Historie Vlastnosti
for (i = 0, j = 5; i < 10; i++) { // tělo cyklu }
5. Operátor čárka, - slouží k jistému určení pořadí vykonání dvou příkazů - oddělím-li čárkou dva příkazy, je jisté, že ten první bude vykonán dříve než příkaz druhý. Např.: i = 5; j = 8; - po překladu
Úvod do programovacích jazyků (Java)
Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2007/2008 c 2006 2008 Michal Krátký Úvod do programovacích
1.1 Struktura programu v Pascalu Vstup a výstup Operátory a některé matematické funkce 5
Obsah Obsah 1 Programovací jazyk Pascal 1 1.1 Struktura programu v Pascalu.................... 1 2 Proměnné 2 2.1 Vstup a výstup............................ 3 3 Operátory a některé matematické funkce 5
NPRG031 Programování II 1 / :47:55
NPRG031 Programování II 1 / 43 23. 2. 2016 11:47:55 Objekty Svět se skládá z objektů! Objekt = data + funkce (metody) konkrétní x abstraktní hmatatelné x nehmatatelné (letadlo) x (chyba v programu) Objekty
Úvod do programování. Lekce 1
Úvod do programování Lekce 1 Základní pojmy vytvoření spustitelného kódu editor - psaní zdrojových souborů preprocesor - zpracování zdrojových souborů (vypuštění komentářů atd.) kompilátor (compiler) -
Datové typy strana 29
Datové typy strana 29 3. Datové typy Jak již bylo uvedeno, Java je přísně typový jazyk, proto je vždy nutno uvést datový typ datového atributu, formálního parametru metody, návratové hodnoty metody nebo
Základní pojmy. Úvod do programování. Základní pojmy. Zápis algoritmu. Výraz. Základní pojmy
Úvod do programování Michal Krátký 1,Jiří Dvorský 1 1 Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programování, 2004/2005 Procesor Procesorem je objekt, který vykonává algoritmem popisovanou
Logické operace. Datový typ bool. Relační operátory. Logické operátory. IAJCE Přednáška č. 3. může nabýt hodnot: o true o false
Logické operace Datový typ bool může nabýt hodnot: o true o false Relační operátory pravda, 1, nepravda, 0, hodnoty všech primitivních datových typů (int, double ) jsou uspořádané lze je porovnávat binární
6 Příkazy řízení toku
6 Příkazy řízení toku Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost příkazům pro řízení toku programu. Pro všechny tyto základní
7. Datové typy v Javě
7. Datové typy v Javě Primitivní vs. objektové typy Kategorie primitivních typů: integrální, boolean, čísla s pohyblivou řádovou čárkou Pole: deklarace, vytvoření, naplnění, přístup k prvkům, rozsah indexů
EVROPSKÝ SOCIÁLNÍ FOND. Úvod do PHP PRAHA & EU INVESTUJEME DO VAŠÍ BUDOUCNOSTI
EVROPSKÝ SOCIÁLNÍ FOND Úvod do PHP PRAHA & EU INVESTUJEME DO VAŠÍ BUDOUCNOSTI Úvod do PHP PHP Personal Home Page Hypertext Preprocessor jazyk na tvorbu dokumentů přípona: *.php skript je součást HTML stránky!
ADT/ADS = abstraktní datové typy / struktury
DT = datové typy obor hodnot, které může proměnná nabývat, s operacemi na tomto oboru určen: obor hodnot + výpočetní operace např. INT = { 2 147 483 648 až +2 147 483 647} + {+,,*,/,} ADT/ADS = abstraktní
Řídicí struktury. alg3 1
Řídicí struktury Řídicí struktura je programová konstrukce, která se skládá z dílčích příkazů a předepisuje pro ně způsob provedení Tři druhy řídicích struktur: posloupnost, předepisující postupné provedení
NMIN201 Objektově orientované programování 2016/17 1 / :03:29
NMIN201 Objektově orientované programování 2016/17 1 / 42 21. 11. 2016 11:03:29 Objekty Svět se skládá z objektů! konkrétní x abstraktní hmatatelné x nehmatatelné (letadlo) x (chyba v programu) Objekty
Úvod do jazyka C. Ing. Jan Fikejz (KST, FEI) Fakulta elektrotechniky a informatiky Katedra softwarových technologií
1 Fakulta elektrotechniky a informatiky Katedra softwarových technologií 12. října 2009 Organizace výuky Přednášky Teoretické základy dle normy jazyka C Cvičení Praktické úlohy odpřednášené látky Prostřední
9.3.2010 Program převod z desítkové na dvojkovou soustavu: /* Prevod desitkove na binarni */ #include <stdio.h>
9.3.2010 Program převod z desítkové na dvojkovou soustavu: /* Prevod desitkove na binarni */ #include int main(void) { int dcislo, kolikbcislic = 0, mezivysledek = 0, i; int vysledek[1000]; printf("zadejte
Čtvrtek 8. prosince. Pascal - opakování základů. Struktura programu:
Čtvrtek 8 prosince Pascal - opakování základů Struktura programu: 1 hlavička obsahuje název programu, použité programové jednotky (knihovny), definice konstant, deklarace proměnných, všechny použité procedury
Vysoká škola báňská Technická univerzita Ostrava TEORIE ÚDRŽBY. učební text. Jan Famfulík. Jana Míková. Radek Krzyžanek
Vysoká škola báňská Technická univerzita Ostrava TEORIE ÚDRŽBY učební text Jan Famfulík Jana Míková Radek Krzyžanek Ostrava 2007 Recenze: Prof. Ing. Milan Lánský, DrSc. Název: Teorie údržby Autor: Ing.
Algoritmizace prostorových úloh
INOVACE BAKALÁŘSKÝCH A MAGISTERSKÝCH STUDIJNÍCH OBORŮ NA HORNICKO-GEOLOGICKÉ FAKULTĚ VYSOKÉ ŠKOLY BÁŇSKÉ - TECHNICKÉ UNIVERZITY OSTRAVA Algoritmizace prostorových úloh Datové struktury Daniela Szturcová
Operátory. Základy programování 1 Martin Kauer (Tomáš Kühr)
Operátory Základy programování 1 Martin Kauer (Tomáš Kühr) Organizační poznámky Formátujte kód přehledně! Pomůžete sobě i mně. Spusťte si vaše programy a zkuste různé vstupy! Pokud program nedává správné
14.4.2010. Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.
Základy programování (IZAPR) Přednáška 7 Ing. Michael Bažant, Ph.D. Katedra softwarových technologií Kancelář č. 229, Náměstí Čs. legií Michael.Bazant@upce.cz Obsah přednášky 7 Parametry metod, předávání
Ahoj mami. Uložení dat v počítači. Příklady kódování dat. IAJCE Přednáška č. 4
Uložení dat v počítači Data = užitečné, zpracovávané informace Kódování (formát) dat = způsob uložení v počítači (nutno vše převést na čísla ve dvojkové soustavě) Příklady kódování dat Text každému znaku
Předmluva k aktuálnímu vydání Úvod k prvnímu vydání z roku Typografické a syntaktické konvence... 20
Obsah 5 Obsah Předmluva k aktuálnímu vydání 15 1 Úvod k prvnímu vydání z roku 2000 16 Typografické a syntaktické konvence................ 20 2 Základní pojmy 21 2.1 Trocha historie nikoho nezabije................
Tematický celek Proměnné. Proměnné slouží k dočasnému uchovávání hodnot během provádění aplikace Deklarace proměnných
Tematický celek 03 3.1 Proměnné Proměnné slouží k dočasnému uchovávání hodnot během provádění aplikace. 3.1.1 Deklarace proměnných Dim jméno_proměnné [As typ] - deklarace uvnitř procedury platí pouze pro
8. lekce Úvod do jazyka C 3. část Základní příkazy jazyka C Miroslav Jílek
8. lekce Úvod do jazyka C 3. část Základní příkazy jazyka C Miroslav Jílek 1/41 Základní příkazy Všechny příkazy se píšou malými písmeny! Za většinou příkazů musí být středník (;)! 2/41 Základní příkazy
Ukazka knihy z internetoveho knihkupectvi www.kosmas.cz
Ukazka knihy z internetoveho knihkupectvi www.kosmas.cz Upozornění pro čtenáře a uživatele této knihy Všechna práva vyhrazena. Žádná část této tištěné či elektronické knihy nesmí být reprodukována a šířena
Výrazy a operátory. Operátory Unární - unární a unární + Např.: a +b
Výrazy a operátory i = 2 i = 2; to je výraz to je příkaz 4. Operátory Unární - unární a unární + Např.: +5-5 -8.345 -a +b - unární ++ - inkrement - zvýší hodnotu proměnné o 1 - unární -- - dekrement -
Programování v jazyce JavaScript
Programování v jazyce JavaScript Katedra softwarového inženýrství Fakulta informačních technologií České vysoké učení technické v Praze Pavel Štěpán, 2011 Operátory a příkazy BI-JSC Evropský sociální fond
Algoritmizace prostorových úloh
INOVACE BAKALÁŘSKÝCH A MAGISTERSKÝCH STUDIJNÍCH OBORŮ NA HORNICKO-GEOLOGICKÉ FAKULTĚ VYSOKÉ ŠKOLY BÁŇSKÉ - TECHNICKÉ UNIVERZITY OSTRAVA Algoritmizace prostorových úloh Datové struktury Daniela Szturcová
6. Příkazy a řídící struktury v Javě
6. Příkazy a řídící struktury v Javě Příkazy v Javě Příkazy v Javě Řídicí příkazy (větvení, cykly) Přiřazovací příkaz = Řízení toku programu (větvení, cykly) Volání metody Návrat z metody - příkaz return
Programovací jazyk C++ Hodina 1
Programovací jazyk C++ Hodina 1 Používané překladače Bloodshed Dev C++ http://www.bloodshed.net/devcpp.html CodeBlocks http://www.codeblocks.org pokud nemáte již nainstalovaný překladač, stáhněte si instalátor
Jak v Javě primitivní datové typy a jejich reprezentace. BD6B36PJV 002 Fakulta elektrotechnická České vysoké učení technické
Jak v Javě primitivní datové typy a jejich reprezentace BD6B36PJV 002 Fakulta elektrotechnická České vysoké učení technické Obsah Celočíselný datový typ Reálný datový typ Logický datový typ, typ Boolean
Počítačové laboratoře bez tajemství aneb naučme se učit algoritmizaci a programování s využitím robotů CZ.1.07/1.3.12/04.0006
Počítačové laboratoře bez tajemství aneb naučme se učit algoritmizaci a programování s využitím robotů CZ.1.07/1.3.12/04.0006 Lekce 1 Jazyk Java Tento projekt je spolufinancován Evropským sociálním fondem
PHP - úvod. Kapitola seznamuje se základy jazyka PHP a jeho začleněním do HTML stránky.
PHP - úvod Kapitola seznamuje se základy jazyka PHP a jeho začleněním do HTML stránky. Klíčové pojmy: PHP, webový prohlížeč, HTTP, FTP Základní pojmy služba WWW = 1990 první prototyp serveru, od roku 1994
Výčtový typ strana 67
Výčtový typ strana 67 8. Výčtový typ V této kapitole si ukážeme, jak implementovat v Javě statické seznamy konstant (hodnot). Příkladem mohou být dny v týdnu, měsíce v roce, planety obíhající kolem slunce
8 Třídy, objekty, metody, předávání argumentů metod
8 Třídy, objekty, metody, předávání argumentů metod Studijní cíl Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost třídám a objektům, instančním
Maturitní otázky z předmětu PROGRAMOVÁNÍ
Wichterlovo gymnázium, Ostrava-Poruba, příspěvková organizace Maturitní otázky z předmětu PROGRAMOVÁNÍ 1. Algoritmus a jeho vlastnosti algoritmus a jeho vlastnosti, formy zápisu algoritmu ověřování správnosti
Racionální čísla, operátory, výrazy, knihovní funkce
Příprava studijního programu Informatika je podporována projektem financovaným z Evropského sociálního fondu a rozpočtu hlavního města Prahy. Praha & EU: Investujeme do vaší budoucnosti Racionální čísla,
- speciální symboly + - * / =., < > <> <= >= a další. Klíčová slova jsou chráněnými útvary, které nelze použít ve významu identifikátorů.
Základní symboly - písmena A B C Y Z a b c y z - číslice 0 1 2 9 - speciální symboly + - * / =., < > = a další - klíčová slova and array begin case const a další Klíčová slova jsou chráněnými útvary,
Objektově orientované programování
10. října 2011 Pragmatické informace Volitelný předmět, zápočet: zápočtový program(s dokumentací), aktivní účast na cvičení(body v CodExu), praktický test, zkouška: zkoušková písemka na objektový návrh
Pascal. Katedra aplikované kybernetiky. Ing. Miroslav Vavroušek. Verze 7
Pascal Katedra aplikované kybernetiky Ing. Miroslav Vavroušek Verze 7 Proměnné Proměnná uchovává nějakou informaci potřebnou pro práci programu. Má ve svém oboru platnosti unikátní jméno. (Připadne, musí
Operátory. Základy programování 1 Tomáš Kühr
Operátory Základy programování 1 Tomáš Kühr Operátory a jejich vlastnosti Základní konstrukce (skoro) každého jazyka Z daných operandů vytvoří výsledek, který je možné dále využívat Arita udává počet operandů
Formátové specifikace formátovací řetězce
27.2.2007 Formátové specifikace formátovací řetězce - je to posloupnost podle které překladač pozná jaký formát má výstup mít - posloupnosti začínají znakem % a určující formát vstupu/výstupu - pokud chcete
VÝUKOVÝ MATERIÁL. Bratislavská 2166, 407 47 Varnsdorf, IČO: 18383874 www.vosassvdf.cz, tel. +420412372632 Číslo projektu
VÝUKOVÝ MATERIÁL Identifikační údaje školy Vyšší odborná škola a Střední škola, Varnsdorf, příspěvková organizace Bratislavská 2166, 407 47 Varnsdorf, IČO: 18383874 www.vosassvdf.cz, tel. +420412372632
Seminář Java II p.1/43
Seminář Java II Seminář Java II p.1/43 Rekapitulace Java je case sensitive Zdrojový kód (soubor.java) obsahuje jednu veřejnou třídu Třídy jsou organizovány do balíků Hierarchie balíků odpovídá hierarchii
DUM 07 téma: Proměnné, konstanty a pohyb po buňkách ve VBA
DUM 07 téma: Proměnné, konstanty a pohyb po buňkách ve VBA ze sady: 03 tematický okruh sady: Tvorba skript a maker ze šablony: 10 Algoritmizace a programování určeno pro: 4. ročník vzdělávací obor: vzdělávací
Algoritmizace a programování
Algoritmizace a programování Struktura programu Vytvoření nové aplikace Struktura programu Základní syntaktické elementy První aplikace Verze pro akademický rok 2012/2013 1 Nová aplikace NetBeans Ve vývojovém
Obsah. Úvod 11 Základy programování 11 Objektový přístup 11 Procvičování 11 Zvláštní odstavce 12 Zpětná vazba od čtenářů 12 Errata 13
Úvod 11 Základy programování 11 Objektový přístup 11 Procvičování 11 Zvláštní odstavce 12 Zpětná vazba od čtenářů 12 Errata 13 KAPITOLA 1 Na úvod o Javě 15 Počítačový program 15 Vysokoúrovňový programovací
5a. Makra Visual Basic pro Microsoft Escel. Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina
5a. Makra Visual Basic pro Microsoft Escel Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina Cyklické odkazy a iterativní výpočty Zde bude stránka o cyklických odkazech a iteracích.
Programování v jazyce JavaScript
Programování v jazyce JavaScript Katedra softwarového inženýrství Fakulta informačních technologií České vysoké učení technické v Praze Pavel Štěpán, 2011 Syntaxe 1. část BI-JSC Evropský sociální fond
Příklad : String txt1 = new String( Ahoj vsichni! ); //vytvoří instanci třídy String a přiřadí ji vnitřní hodnotu Ahoj vsichni!
Java práce s řetězci Trochu povídání.. Řetězce jsou v Javě reprezentovány instancemi tříd StringBuffer a String. Tyto třídy jsou součástí balíčku java.lang, tudíž je možno s nimi pracovat ihned bez nutného
Algoritmizace a programování. Ak. rok 2012/2013 vbp 1. ze 44
Algoritmizace a programování Ak. rok 2012/2013 vbp 1. ze 44 Vladimír Beneš Petrovický K101 katedra matematiky, statistiky a informačních technologií vedoucí katedry E-mail: vbenes@bivs.cz Telefon: 251
Teoretické minimum z PJV
Teoretické minimum z PJV Pozn.: následující text popisuje vlastnosti jazyka Java zjednodušeně pouze pro potřeby výuky. Třída Zavádí se v programu deklarací třídy což je část programu od klíčových slov
Racionální čísla, operátory, výrazy, knihovní funkce
Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti Racionální čísla, operátory, výrazy, knihovní funkce BI-PA1 Programování a algoritmizace 1 Katedra teoretické informatiky Miroslav Balík
Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody
Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody Dynamická alokace paměti Jazyky C a C++ poskytují programu možnost vyžádat si část volné operační paměti pro
Vyučovací hodina. 1vyučovací hodina: 2vyučovací hodiny: Opakování z minulé hodiny. Procvičení nové látky
Vyučovací hodina 1vyučovací hodina: Opakování z minulé hodiny Nová látka Procvičení nové látky Shrnutí 5 min 20 min 15 min 5 min 2vyučovací hodiny: Opakování z minulé hodiny Nová látka Procvičení nové
2 Datové typy v jazyce C
1 Procedurální programování a strukturované programování Charakteristické pro procedurální programování je organizace programu, který řeší daný problém, do bloků (procedur, funkcí, subrutin). Původně jednolitý,
4a. Makra Visual Basic pro Microsoft Excel Cyklické odkazy a iterace Makra funkce a metody
4a. Makra Visual Basic pro Microsoft Excel Cyklické odkazy a iterace Makra funkce a metody Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina Cyklické odkazy a iterativní výpočty
ALGORITMIZACE A PROGRAMOVÁNÍ
Metodický list č. 1 Algoritmus a jeho implementace počítačovým programem Základním cílem tohoto tematického celku je vysvětlení pojmů algoritmus a programová implementace algoritmu. Dále je cílem seznámení
9. přednáška - třídy, objekty
třída String a její použití kolekce, typované kolekce 9. přednáška - třídy, objekty Algoritmizace (Y36ALG), Šumperk - 9. přednáška 1 Třída String Objekty knihovní třídy String jsou řetězy znaků Od ostatních
Programování v jazyce C pro chemiky (C2160) 3. Příkaz switch, příkaz cyklu for, operátory ++ a --, pole
Programování v jazyce C pro chemiky (C2160) 3. Příkaz switch, příkaz cyklu for, operátory ++ a --, pole Příkaz switch Příkaz switch provede příslušnou skupinu příkazů na základě hodnoty proměnné (celočíselné
Algoritmizace prostorových úloh
INOVACE BAKALÁŘSKÝCH A MAGISTERSKÝCH STUDIJNÍCH OBORŮ NA HORNICKO-GEOLOGICKÉ FAKULTĚ VYSOKÉ ŠKOLY BÁŇSKÉ - TECHNICKÉ UNIVERZITY OSTRAVA Algoritmizace prostorových úloh Algoritmus Daniela Szturcová Tento
for (int i = 0; i < sizeof(hodnoty) / sizeof(int); i++) { cout<<hodonoty[i]<< endl; } cin.get(); return 0; }
Pole Kdybychom v jazyce C++chtěli načíst větší počet čísel nebo znaků a všechny bylo by nutné všechny tyto hodnoty nadále uchovávat v paměti počítače, tak by bylo potřeba v paměti počítače alokovat stejný
ZPRO v "C" Ing. Vít Hanousek. verze 0.3
verze 0.3 Hello World Nejjednoduší program ukazující vypsání textu. #include using namespace std; int main(void) { cout
C++ přetěžování funkcí a operátorů. Jan Hnilica Počítačové modelování 19
C++ přetěžování funkcí a operátorů 1 Přetěžování funkcí jazyk C++ umožňuje napsat více funkcí se stejným názvem, těmto funkcím říkáme přetížené přetížené funkce se musí odlišovat typem nebo počtem parametrů,
2. Základy jazyka. Proměnné. Základy jazyka
2. Základy jazyka Napíšeme si první jednoduchý program v Javě, na kterém si vysvětlíme základy jazyka. Bude to program, který sečte dvě celá čísla a výsledek zobrazí na konzoli. public class PrvniProgram
Základy jazyka C. Základy programování 1 Martin Kauer (Tomáš Kühr)
Základy jazyka C Základy programování 1 Martin Kauer (Tomáš Kühr) Organizační záležitosti Konzultace Pracovna 5.076 Úterý 15:00 16:30 Emailem martin.kauer@upol.cz Web předmětu http://tux.inf.upol.cz/~kauer/index.php?content=var&class=zp1
Programování v Javě I. Leden 2008
Seminář Java Programování v Javě I Radek Kočí Fakulta informačních technologií VUT Leden 2008 Radek Kočí Seminář Java Programování v Javě (1) 1/ 45 Téma přednášky Datové typy Deklarace třídy Modifikátory
Struktura programu, proměnné, konstanty, výrazy v jazycích C a C#
Struktura programu, proměnné, konstanty, výrazy v jazycích C a C# Seznámit žáky se strukturou programu v jazycích C/C#, s klíčovými slovy jazyků C/C#, s psaním komentářů, se základními datovými typy, deklarací
PREPROCESOR POKRAČOVÁNÍ
PREPROCESOR POKRAČOVÁNÍ Chybová hlášení V C# podobně jako v C++ existuje direktiva #error, která způsobí vypsání chybového hlášení překladačem a zastavení překladu. jazyk C# navíc nabízí direktivu #warning,
Jazyk C Program v jazyku C má následující strukturu: konstanty nebo proměnné musí Jednoduché datové typy: Strukturované datové typy Výrazy operátory
Jazyk C Program v jazyku C má následující strukturu: Direktivy procesoru Globální definice (platné a známé v celém programu) Funkce Hlavička funkce Tělo funkce je uzavřeno mezi složené závorky { Lokální
Začínáme vážně programovat. Řídící struktury Přetypování Vstupně výstupní operace Vlastní tvorba programů
Začínáme vážně programovat Řídící struktury Přetypování Vstupně výstupní operace Vlastní tvorba programů Podmínky a cykly Dokončení stručného přehledu řídících struktur jazyka C. Složený příkaz, blok Pascalské
Funkce, podmíněný příkaz if-else, příkaz cyklu for
Funkce, podmíněný příkaz if-else, příkaz cyklu for Definice funkce Funkce je pojmenovaná část programu, kterou lze dále zavolat v jiné části programu. V Pythonu je definována klíčovým slovem def. Za tímto
Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007
Úvod do programovacích jazyků (Java) Michal Krátký Katedra informatiky VŠB Technická univerzita Ostrava Úvod do programovacích jazyků (Java), 2006/2007 c 2006 Michal Krátký Úvod do programovacích jazyků
Algoritmus. Přesné znění definice algoritmu zní: Algoritmus je procedura proveditelná Turingovým strojem.
Algoritmus Algoritmus je schematický postup pro řešení určitého druhu problémů, který je prováděn pomocí konečného množství přesně definovaných kroků. nebo Algoritmus lze definovat jako jednoznačně určenou
C++ Akademie SH. 2. Prom nné, podmínky, cykly, funkce, rekurze, operátory. Michal Kvasni ka. 20. b ezna Za áte níci C++
C++ Akademie SH 2. Prom nné, podmínky, cykly, funkce, rekurze, operátory Za áte níci C++ 20. b ezna 2011 Obsah 1 Prom nné - primitivní typy Celá ísla ƒísla s pohyblivou desetinnou árkou, typ bool 2 Podmínka