Počítače a programování 2 - UTEE
Transkript
FAKULTA ELEKTROTECHNIKY A KOMUNIKAČNÍCH TECHNOLOGIÍ VYSOKÉ UČENÍ TECHNICKÉ V BRNĚ Počítače a programování 2 Garant předmětu: Doc. Dr. Ing. Zbyněk Raida Autoři textu: Doc. Dr. Ing. Zbyněk Raida Ing. Pavel Fiala, Ph.D. Počítače a programování 2 1 Obsah 1 ÚVOD ................................................................................................................................3 1.1 1.2 1.3 1.4 1.5 1.6 1.7 2 PROGRAMOVÁNÍ ..........................................................................................................3 ZÁKLADNÍ POJMY A UJEDNÁNÍ.....................................................................................5 ZÁKLADNÍ PRINCIPY, FUNKCE POČÍTAČE .....................................................................6 ZÁKLADNÍ PRVKY ZÁPISU BLOKOVÉHO SCHÉMATU .....................................................8 ALGORITMY.................................................................................................................9 KONTROLNÍ PŘÍKLADY (ALGORITMIZACE).................................................................10 KONTROLNÍ OTÁZKY .................................................................................................11 BORLAND C++ BUILDER 5.0 ....................................................................................13 2.1 VÝVOJ APLIKACE .......................................................................................................14 2.1.1 Spuštění Builderu..............................................................................................14 2.1.2 Základní nastavení............................................................................................15 2.1.3 Sestavení okna ..................................................................................................15 2.1.4 Ošetření událostí ..............................................................................................16 2.1.5 Ladění ...............................................................................................................17 2.2 KONTROLNÍ PŘÍKLADY ..............................................................................................18 2.3 KONTROLNÍ OTÁZKY .................................................................................................19 3 JAZYK C.........................................................................................................................19 3.1 IDENTIFIKÁTORY .......................................................................................................19 3.2 TYPY DAT, PROMĚNNÉ ...............................................................................................20 3.2.1 Lokální a globální proměnné............................................................................20 3.2.2 Pravidla deklarování proměnných ...................................................................21 3.2.3 Základní typy proměnných ...............................................................................22 3.2.4 Ukazatele ..........................................................................................................22 3.2.5 Pole.......................................................................................................................23 3.2.6 Inicializace proměnných...................................................................................26 3.2.7 Kontrolní příklady ............................................................................................26 3.2.8 Kontrolní otázky ...............................................................................................26 3.3 LITERÁLY ..................................................................................................................27 3.3.1 Celočíselné konstanty .......................................................................................27 3.3.2 Racionální konstanty ........................................................................................27 3.3.3 Znakové konstanty ............................................................................................28 3.3.4 Řetězcové konstanty..........................................................................................28 3.4 VÝRAZY A OPERÁTORY .............................................................................................28 3.4.1 Aritmetické konverze ........................................................................................29 3.4.2 Priorita operací ................................................................................................30 3.4.3 Aritmetické operátory .......................................................................................31 3.4.4 Relační operátory .............................................................................................31 3.4.5 Logické operátory.............................................................................................31 3.4.6 Bitové operátory ...............................................................................................34 3.4.7 Operátory inkrementování a dekrementování ..................................................35 3.4.8 Přiřazovací operátory ......................................................................................36 3.4.9 Kontrolní příklady ............................................................................................36 3.4.10 Kontrolní otázky ...............................................................................................36 3.5 PŘÍKAZY ....................................................................................................................37 3.5.1 Příkazy pro větvení programu ..........................................................................39 2 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 3.5.2 Příkazy pro cykly.............................................................................................. 40 3.5.3 Příkazy pro přenos řízení................................................................................. 42 3.5.4 Kontrolní příklady............................................................................................ 45 3.5.5 Kontrolní otázky............................................................................................... 45 3.6 FUNKCE .................................................................................................................... 46 3.6.1 Rekurze funkcí .................................................................................................. 49 3.6.2 Funkce main..................................................................................................... 50 3.6.3 Kontrolní příklady............................................................................................ 51 3.6.4 Kontrolní otázky............................................................................................... 51 3.7 VÍCE O DATOVÝCH TYPECH ....................................................................................... 52 3.7.1 Struktury........................................................................................................... 52 3.7.2 Unie.................................................................................................................. 56 3.7.3 Výčtové typy ..................................................................................................... 56 3.7.4 Dynamické proměnné....................................................................................... 59 3.7.5 Kontrolní příklady............................................................................................ 69 3.7.6 Kontrolní otázky............................................................................................... 70 3.8 NÁMĚTY TÉMAT ZÁVĚREČNÝCH PROJEKTŮ ............................................................... 70 3.9 ZÁVĚR....................................................................................................................... 71 3.10 LITERATURA ............................................................................................................. 71 4 MATLAB ........................................................................................................................ 72 4.1 UŽIVATELSKÉ ROZHRANÍ MATLABU ...................................................................... 73 4.2 OPERACE S MATICEMI ............................................................................................... 75 4.2.1 Vytváření matic ................................................................................................ 76 4.2.2 Aritmetické operace ......................................................................................... 81 4.2.3 Logické operace ............................................................................................... 83 4.2.4 Relační operace................................................................................................ 84 4.2.5 Kontrolní příklady............................................................................................ 84 4.2.6 Kontrolní otázky............................................................................................... 85 4.3 M-SOUBORY.............................................................................................................. 86 4.3.1 Skripty .............................................................................................................. 86 4.3.2 Funkce.................................................................................................................. 87 4.3.3 Globální proměnné, lokální funkce.................................................................. 88 4.3.4 Řízení běhu programu...................................................................................... 90 4.3.5 Kontrolní příklady............................................................................................ 97 4.3.6 Kontrolní otázky............................................................................................... 98 4.4 PŘÍKLADY ELEKTROTECHNICKÝCH VÝPOČTŮ ........................................................... 98 4.4.1 Numerické integrování..................................................................................... 98 4.4.2 Výpočet spektra signálu ................................................................................. 101 4.4.3 Kontrolní příklady.......................................................................................... 104 4.4.4 Kontrolní otázky............................................................................................. 105 4.5 ZÁVĚR..................................................................................................................... 105 Počítače a programování 2 3 1 Úvod Předmět Počítače a programování 2 je vyučován v letním semestru 1. ročníku bakalářského studia. Předmět je společný všem oborům bakalářského studijního programu Elektrotechnika, elektronika, komunikační a řídicí technika. I když je předkládané skriptum věnováno předmětu letního semestru, jeho úvodní pasáže použijeme i v semestru zimním, v předmětu Počítače a programování 1. Zde se totiž poprvé seznámíme s jazykem C a s vývojovým nástrojem Borland C++ Builder 5.0, s jehož pomocí budeme společně do tajů jazyka C pronikat. Poté co se v našem kursu podrobně seznámíme s programováním v jazyce C, přejdeme k programu Matlab. Matlab je speciální matematický program pro inženýrské a vědecké výpočty. Většina matematických operací či metod pro zobrazování výsledků již byla tvůrci Matlabu vytvořena a je nám v současnosti k dispozici. Píšeme-li svůj algoritmus v Matlabu, nemusíme se matematikou zabývat a můžeme se soustředit na vlastní jádro našeho programu. Abychom získali popsané znalosti a dovednosti, musíme dobře rozumět tomu, proč počítač funguje a jak pracuje, co jsou základní části počítače a jaká je jejich funkce. Všechny tyto znalosti získáme v předmětu Počítače a programování 1. 1.1 Programování Pod pojmem programování rozumíme psaní textu, který procesoru počítače jednoznačně říká, co má dělat a jak. Psaní programu můžeme rozdělit do následujících kroků: 1. Sestavení algoritmu. Na základě zadaného úkolu navrhneme postup (sestavíme algoritmus), jakým lze danou úlohu vyřešit. Algoritmus obvykle vyjadřujeme blokovým schématem. start temp ← teplo.dat min_temp ← 900 m ←1 min_temp >temp(m) NE Na pevném disku počítače máme v souANO boru teplo.dat uloženy teploty, které byly min_temp ← teplo(m) během předchozího dne naměřeny v kažmin_time ← m dou celou hodinu. Našim úkolem je určit tu hodinu, kdy byla teplota nejnižší. Soubor tedy otevřeme (data uložíme do pole temp) a do pomocné proměnné min_temp uložíme nerealisticky vysokou NE m<25 konec teplotu (900°C). Obsah proměnné min_temp budeme postupně porovnávat ANO s jednotlivými zaznamenanými teplotami (na právě porovnávaný údaj ukazuje index m ← m + 1 m). Pokud je některá zaznamenaná teplota nižší nežli obsah min_temp (větev ANO) Obr. 1.1 Algoritmus hledání nejnižší teploty uložíme tuto hodnotu do min_temp, a 4 Fakulta elektrotechniky a komunikačních technologií VUT v Brně současně do proměnné min_time uložíme pořadí daného čísla (dané teploty) v souboru teplot; pořadí čísla v souboru totiž odpovídá hodině, kdy byla teplota naměřena. V opačném případě (testovaná teplota ze souboru je vyšší nežli obsah min_temp) se nic neděje. Jakmile projdeme všechna čísla v souboru (index m je větší než počet hodin dne), budeme mít v proměnné min_temp údaj o nejnižší naměřené teplotě a v proměnné min_time údaj o hodině, kdy byla tato teplota naměřena. Popsaný algoritmus lze vyjádřit blokovým schématem z obr. 1.1. 2. Zapsání algoritmu pomocí programovacího jazyka. Na základě přesně daných pravidel napíšeme text (zdrojový kód), který překladač programovacího jazyka umí přeložit do kódu strojového – do kódu, kterému „rozumí“ procesor našeho počítače. Pokud se rozhodneme algoritmus pro vyhledávání nejnižší teploty (obr. 1.1) vyjádřit v jazyku C (s využitím Borland C++ Builder), bude zdrojový kód algoritmu vypadat následovně: void __fastcall TForm1::find_min(TObject *Sender) { // pole teplot; první údaj naměřen v 1:00, poslední údaj // naměřen ve 24:00 double temp[24] = {-8.1, -9.0, +1.1, -5.2, -8.3, -8.5, +2.3, -6.3, -8.6, -7.9, +1.2, -7.7, -9.2, -6.7, -0.8, -8.5, -9.4, -5.0, -2.6, -9.1, -9.2, -2.3, -4.1, -9.3}; double min_temp; int min_time; int m; // nejnižší teplota // hodina, kdy naměřena min.teplota // index pro vyhledávání min_temp = 900; // počáteční nastavení for( m=1; m<25; m++) // cyklus přes 24 hodiny if( temp[m-1]<min_temp) // pokud v m-té hodině teplota nižší { // nežli min_temp min_temp = temp[m-1]; // změň obsah min_temp min_time = m; // ulož údaj o hodině s min.teplotou } } result->Caption = FloatToStr( min_temp); hour->Caption = IntToStr( min_time); // zobraz min.teplotu // zobraz hodinu První řádek obsahuje hlavičku, automaticky generovanou Builderem. Složené závorky označují kód, který společně tvoří jeden blok. Za dvojité lomítko můžeme psát svůj komentář (znaky komentáře jsou překladačem ignorovány). Slovo double uvozuje reálnou proměnnou, slovo int celočíselnou proměnnou. Proměnná temp sestává z 24 reálných čísel. Pomocí znaménka = vložíme do proměnné konkrétní číselnou hodnotu. Řádkem for říkáme, že následný kód budeme vykonávat od m=1 do m=24 (poté přestane platit m<25), přičemž po každém vykonání následného kódu bude hodnota indexu m zvýšena o jedničku (m++). Pokud je splněna nerovnost v kulaté závorce za if, vykoná se následný blok ve složené závorce; v opačném případě nebude vykonáno nic. Poslední dva řádky vypíší nejnižší teplotu a odpovídající hodinu do okna programu. 3. Ladění programu. Člověk je omylný, a proto se při psaní zdrojového kódu dopouští omylů. Naše možné chyby přitom můžeme rozdělit na omyly syntaktické a omyly logické. Počítače a programování 2 5 Syntaktickým omylem rozumíme omyl v zápisu (záměna malého a velkého písmene, odkaz na neexistující proměnnou, atd.). Na syntaktický omyl nás upozorní překladač, který v důsledku našeho omylu není schopen převést náš zdrojový kód na kód strojový. Pokud bychom cyklus v našem příkladu zahájili slovem For, dopustili bychom se syntaktického omylu (v syntaxi jazyka C je pro cyklus rezervováno slovo for). Logickým omylem je omyl, který překladač neodhalí; po spuštění programu se však naše aplikace chová jinak, než jsme očekávali. Napíšeme-li v našem příkladu místo přiřazení min_time=m nesprávně min_time=m+1, bude údaj o času nejnižší teploty posunut o jednu hodinu. Běh programu bude bezproblémový, avšak produkovaný výsledek bude chybný. Proces odstraňování omylů je nazýván laděním (debugging). Ladění je posledním krokem při vývoji programu. Soubory výše popsaného programu jsou uloženy v adresáři min_temp. 1.2 Základní pojmy a ujednání Programátoři mezi sebou komunikují specifickým jazykem. Abychom tomuto jazyku porozuměli, seznámíme se nyní společně s pojmy, s nimiž se budeme setkávat. Program, aplikace (program, application) v podstatě říká procesoru počítače, co má dělat, aby byl splněn zadaný úkol. Program sestává z dat (číselné údaje, které v programu zpracováváme) a z kódu (posloupnost instrukcí, jak mají být data zpracovávána). Proměnná (variable) reprezentuje v programu data. Proměnná představuje náš vlastní název části paměti počítače, do níž můžeme uložit údaje a z níž můžeme dříve uložené údaje číst. Funkce (function) je část kódu, která definuje odezvu programu na určitou událost. Funkce je posloupnost příkazů, které jsou při jejím volání postupně vykonávány. Objekt, třída (object, class) je jakýmsi rámem, svazujícím dohromady data (proměnné) a kód (funkce). Původní jazyk C objekty (objektově orientované programování) nepodporuje. Objektová verze jazyka C se jmenuje C++. Komponent (component) je objekt, který můžeme vizuálně programovat. V Borland C++ Builder jsou komponenty umístěny na paletě, z níž si je programátor myší vybírá a umisťuje do okna svého programu. Událost (event) popisuje přesně definovanou situaci, na níž má program reagovat. Událostí je kliknutí na tlačítko v okně (OnClick), událostí je stisknutí klávesy na klávesnici (onKeyDown) či otočení kolečkem mezi tlačítky myši (OnMouseWheel). Pracujeme-li v Borland C++ Builder, píšeme tzv. událostmi řízený program. Pro události, na které má náš program reagovat, sestavujeme obslužné funkce. Vznik události odpovídající funkci automaticky vyvolá. Programová jednotka (unit). Aby bylo dosaženo větší přehlednosti zdrojového kódu rozsáhlých programů, může být tento kód rozdělen do mnoha diskových souborů. Tyto diskové soubory se nazývají jednotky. Pracujeme-li v Borland C++ Builder, každé okno programu je uloženo ve zvláštní jednotce. Jednotka má příponu *.cpp. 6 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Projekt (project) je jakýmsi rámem, svazujícím dohromady všechny jednotky, z nichž sestává program. V Borland C++ Builder je projekt sestavován samočinně; soubor projektu má příponu *.bpr. Co se týká textu naší učebnice, budeme v ní střídat různá písma: Times New Roman – zvýraznění částí textů (zejména různé názvy) Courier – části zdrojových textů Courier – klíčová slova ve zdrojovém textu Courier – komentář zdrojových textů Arial Arial Arial – název diskového souboru nebo adresáře – nově zaváděný termín – anglický překlad nově zaváděného termínu Nyní, když máme definovány základní pojmy a stanovena základní pravidla, budeme se věnovat procvičení algoritmického myšlení. Sestavené algoritmy budeme v dalším převádět do zdrojového kódu v jazyce C a v jazyce Matlabu. Dříve, než tak učiníme, zopakujeme si základní vědomosti o počítačích, s nimiž jsme se seznámili v předchozím kursu. 1.3 Základní principy, funkce počítače V současné době se můžeme setkat s mnoha typy počítačů. Rozlišujeme mezi programovatelnou kalkulačkou a počítačem. Jsou používány kapesní počítače (pocket computers), osobní počítače (personal computers) stolní (tower, desktop), přenosné (notebook) a stanice (stations). Vyšší systémy a třídy počítačů zatím nebudou uváděny. Jednotlivé třídy počítačů mají jak vlastní způsob řešení technického vybavení (hardware), tak vybavení programového (software). Kapesní počítač SW- operační systém Překladač textu uživatelského programu c Procesor d Sběrnice Kapesní počítače bývají omezeny ve RAM- text uživatelského svých funkcích, zobrazovacím zařízení programu (display), výstupních zařízeních – tisk (printer), kresba (plotter). Jsou omezeny v maximální použité operační (vnitřní) paměti RAM (Random Access Memory), Zobrazovací jednotka e použitém procesoru. Těmito skutečnostmi je display ovlivněna možnost použití programu, který Vstupní jednotkaf ovládá základní funkce procesoru, vstupklávesnice ního zařízení (klávesnice), výstupního zaříg zení, zobrazovacího zařízení a dalších, a naSmyčka zýváme jej operačním systém (OS). V třídě kapesních počítačů zatím pracují operační systémy jak instrukcemi řízené, tak událost- Obr. 1.4 Instrukcemi řízený OS Počítače a programování 2 mi řízené. Jejich principiální funkce je na obr. 1.4 a obr. 1.5. 7 Kapesní počítač V instrukcemi řízeném systému je poSW Operační systém užita pouze jedna smyčka, kde zařazené Systémová fronta instrukce jsou „okamžitě“ provedeny. K této smyčce není programově snadný přístup. Překladač textu Většinou do ní musíme vstupovat v jazyce uživatelského programu procesoru (jazyk symbolických adres – Aplikace I ASEMBLER). Text programu je uložen Aplikace II v paměti RAM. Nejsou zde příliš rozšířené externí paměti v podobě diskových jednotek. Jádro operačního systému prověřuje jednoProcesor tlivé části počítače v pravidelných intervalech, které nelze jednoduše měnit. Pokud Sběrnice toto zařízení předává nějakou instrukci, ta je v nejkratší možné době provedena. StanRAM dardním vybavením takového kapesního počítače je překladač jazyka. Většinou se jedná o jazyk BASIC, který vyniká jednoduchostí a snadností programování. Zobrazovací jednotka Program napsaný uživatelem je uložen do display paměti RAM a při spuštění programu dojde Vstupní jednotkak procházení paměti a k překladu textu proklávesnice gramu slovo za slovem. Mluví se o tak zvaSmyčka ných interpretrech. Nedojde k překladu programu a potom jeho spuštění, k ale postup- Obr. 1.5 Událostmi řízený OS nému překládání a podle získaných instrukcí je ovlivňován chod programu. Programy, spouštěné pomocí interpreterů jsou velmi pomalé, ale jsou snadno a rychle měnitelné. V událostmi řízeném systému (obr. 1.5), se několik skutečností změnilo. Bylo to způsobeno rozšiřováním grafických operačních systémů, zejména v třídě osobních počítačů. Jako komerčně úspěšné firmy lze uvést například výrobce počítačů Apple Macintosh nebo v naší zemi rozšířené produkty firmy Microsoft. Tedy u kapesních počítačů se objevují grafické operační systémy, které pracují jako událostmi řízené programy. Smyčka je programově přístupná, její doba a pořadí přístupu k aplikacím nebo částem počítače je řízena programem, to znamená i uživatelem. Překladač je jako samostatný program. Text programu překladač přeloží a uloží do paměti. Program je uložen ve formě spustitelného souboru. Osobní počítače (PC) jsou velmi rozšířenou skupinou počítačů. Jejich velké rozšíření bylo způsobenou několika faktory: pořizovací cena počítače je nízká vzhledem ke stanicím a k sálovým počítačům, jsou snadno modifikovatelné, mají přijatelný výkon za svou cenu, je k dispozici velké množství programů spustitelných na PC. Osobní počítače mají malé rozměry, jsou tvořeny do jisté míry samostatnými komponenty (klávesnice, monitor, tiskárna, myš, tablet, scanner, skříň počítače). Vzhledem ke kapesním počítačům jsou vybaveny o další prvky. Na obr. 1.6 je zachycen princip funkce programu v událostmi řízeném operačním systému. Důležitou částí je vnější paměť, disk. Přenosná a archivační paměťová média jsou diskety, ZIP disky, JAZ disky, kompaktní disky CD, digitální videodisky DVD a samozřejmě záznamové jednotky. Klávesnice je vzhledem ke kapesním počítačům rozšířená a má svůj standard. Vstupní zařízení je pro grafické operační systémy rozšířeno o takzvanou myš. Jedná se o zpětnovazební prvek, se kterým se lze v grafickém prostředí pohybovat, zadávat jednoduché povely. Operační systémy jsou sestavovány tak, aby případný výpadek funkce myši byl 8 Fakulta elektrotechniky a komunikačních technologií VUT v Brně nahrazen klávesnicí. Dalším vstupním zařízením je tablet. Slouží pro specializované aplikace k zadávání dat. Standardním výstupním zařízením osobního počítače je zobrazovací jednotka – monitor. Jsou na něj kladeny vyšší požadavky než je u kapesních počítačů. Má vyšší rozlišovací schopnost, barevnou věrnost, kvalitní zobrazení po celé ploše, minimální je fyziologická zátěž zraku. Operační systém Smyčka SW „server“ Fronta aplikací Systémová fronta Překladač textu uživatelského programu Aplikace I Aplikace II Tiskárny u osobních počítačů HW Procesor mají standard rozhraní, jsou ovládány vlastními programy, které se spouštějí v operačním systému počítače. Vlastní Sběrnice část počítače je umístěn ve skříni. RAM Skládá se ze základní desky (mainboard, matherboard) a ze zdroje. Na základní desce je umístěn procesor, paměť RAM a komponenty přizpůZobrazovací jednotka display sobení k dalším zařízením (karty). Karty jsou prostředky technického Vstupní jednotkavybavení, které mají standardizovaný klávesnice rozměr a připojení k základní desce. Vnější paměť (disk) je umístěna mimo Obr. 1.6 Událostmi řízený OS – osobní počítač desku. Bývá připojeno minimálně jedno zapisovací a čtecí zařízení pro přenosná a archivační paměťová média – disketa, CD, ZIP. Stanice jsou méně cenově dostupné počítače. Jsou výkonnější než osobní počítače a jejich vývoj je díky menšímu nasazení drahý. Konstrukce je blízká osobním počítačům. Je zde kladen velký důraz na vysoký výkon vlastní části počítače, značně velkou a rychlou paměť RAM. Vnější paměť bývá realizovaná ve zvláštních částech – disková pole. Počítač mívá více procesorů. Díky malému rozšíření počítačů je programové vybavení drahé. Používá se zde událostmi řízených operačních systémů s vysokým stupněm zabezpečení. Například systémy jako UNIX, LINUX a další jsou psány v jazyce C, C++. Rozšiřují se grafické nástavby pro tyto operační systémy. V následujícím odstavci se seznámíme podrobněji se základními značkami, které slouží k vyjádření algoritmu pomocí blokového schématu. 1.4 Základní prvky zápisu blokového schématu Abychom mohli srozumitelně zapisovat algoritmy programů, seznámíme se se základními značkami, které se objevují v blokových schématech. Značka pro předepsaný postup: A=B, A=A+1, A=A*B Počítače a programování 2 9 Značka pro načtení, výstup Vstup A, Výstup A Značka pro rozhodnutí A<B, A>B, A=B, A≠B, A≥B, A≤B a, nebo ∧ , ∨ negace A A Značka pro začátek, konec začátek konec Značka pro funkci, podprogram Značka pro cyklus cyklus I = 1, .., N Značka pro skok skok na 1.5 Algoritmy Má-li se řešit nějaká programátorská úloha, naskytne se více přístupů a postupů řešení. Z nich je třeba vybrat jeden postup a ten formulovat pomocí vývojového diagramu. Pro výběr se musí určit nějaká kritéria, podle kterých se zvolí postup řešení programu. Musí se tedy rozhodnout, který postup je „lepší“ a který je „horší“. Co to je „lepší“ a „horší“? Kritéria pro posuzování kvality algoritmu jsou jak subjektivní tak objektivní. Dále se budeme zabývat více těmi objektivními kritérii. Základní objektivní kritéria hodnocení algoritmu tedy jsou: 1. Rychlost algoritmu (výpočtu) 2. Paměťová náročnost – operační i externí paměť 3. Stabilita algoritmu Problematika těchto kritérií je velmi rozsáhlá, ale pro účely tohoto textu se budeme držet nutných pojmů a praktických důsledků bez uvedení odvození a důkazů. Prvně dvě kritéria jsou významná hlediska při hodnocení algoritmů. Jsou odlišná a velmi často stojí proti sobě. Bývají algoritmy takové, že jeden z nich je pomalejší, ale používá malou operační paměť, zatímco druhý je rychlejší, ale má vysoké nároky na operační paměť. To je často způsobeno tím, že pro zrychlení algoritmu je použita další pomocná proměnná nebo množina proměnných, které budou uchovány v operační paměti. Tím se zvětší 10 Fakulta elektrotechniky a komunikačních technologií VUT v Brně nároky na paměť za cenu zrychlení výpočtu. Pro volbu paměťové náročnosti a rychlosti neexistuje obecné řešení, záleží vždy na požadavcích, které jsou kladeny na program. Samozřejmě první dvě kritéria jsou závislá také na použitém technickém vybavení (hardware). Jako příklad lze uvést úlohu vyhledání telefonního čísla v seznamu. Jedním z postupů je vyhledání čísla tak, že začneme procházet seznam od počátku, až nalezneme to hledané. Tento postup se nazývá sekvenčním vyhledáváním. Ve skutečnosti jsou jména v seznamu seřazena podle abecedy. Postup při vyhledávání je podle srovnání rostoucích n-tic písmen jména. Pokud hledané jméno je Novák, první n-tice je N. Dále je hledána druhá n-tice No, atd. Stabilita algoritmu závisí jak na vstupních údajích programu, tak na zvolené metodě algoritmu. Doba výpočtu i paměťové nároky bývají také závislé na vstupních údajích. Program většinou řeší celou třídu podobných problémů, které se liší právě zadávanými vstupními údaji. Ale trvání výpočtu i paměťové nároky často neovlivňují konkrétní zadané hodnoty, jen velikost vstupních dat. Prakticky, je jedno zda vyhledávám jedno telefonní číslo v telefonním seznamu s 3000 položkami nebo vyhledávám naměřenou teplotu s 3000 údaji. Ale je rozdíl, pokud vyhledávám telefonní číslo v seznamu Znojma a Brna. Potom bude algoritmus mít rozdílnou dobu trvání. Paměťová náročnost u algoritmů se uvádí v jednotkách jakými jsou bit nebo bajt. Bit je jednotka, která může být nuď ve stavu logické jednotky nebo ve stavu logické nuly. Bajt je jednotka (slovo), která je složena z několika bitů. Počet bitů v bajtu je dán mnoha kritérii. Jsou obvykle používány 8, 16, (24), 32, 64, 128, … bitová slova. Časová náročnost se udává v souvislosti s použitým typem procesoru, velikostí RAM a její rychlosti, konfiguraci základní desky, typem a výrobcem operačního systému. Udává se buď jako doba pro nejhorší případ složitosti algoritmu se vstupními daty velikosti N nebo jako průměrný případ výpočtu z N vstupních dat. Algoritmy se porovnávají v jejich časové náročnosti. Ta je porovnána a je rozhodnuto, který z nich je rychlejší. Stává se, že vstupní data a jejich rozsah nejsou specifikovány. Potom nelze stanovit časovou náročnost. Postupuje se tak, že se zvolí pro rozsah dat velké číslo N a provede se srovnání algoritmů s uvedením podmínek. Jedná se tedy o asymptotickou časovou náročnost. Tedy časová náročnost lepšího algoritmu roste pomaleji s rostoucími hodnotami N. Časovou náročnost lze popsat typem funkce a jejím řádem. Rozeznáváme tedy základní polynomiální a exponenciální funkce. 1.6 Kontrolní příklady (algoritmizace) Příklad 1. Sestavte a nakreslete diagram algoritmu pro nalezení minimálního čísla z řady N zadaných. Nechť je výsledek zobrazen na výstupním zařízení. Příklad 2. Sestavte a nakreslete diagram algoritmu pro nalezení maximálního čísla z řady N zadaných. Nechť je výsledek zobrazen na výstupním zařízení. Příklad 3. Sestavte a nakreslete diagram algoritmu pro nalezení součtu čísel z množiny A a množiny B . Prvky množiny A i B jsou celá čísla, počet prvků množiny A je N a množiny B je M. Nechť je výsledek zobrazen na výstupním zařízení. Příklad 4. Sestavte a nakreslete diagram algoritmu pro nalezení součinu čísel z množiny A a množiny B . Prvky množiny A i B jsou reálná čísla, počet prvků množiny A je N a množiny B je M. Platí, že M=N. Nechť je výsledek zobrazen na výstupním zařízení. Počítače a programování 2 11 Příklad 5. Sestavte a nakreslete diagram algoritmu pro porovnání obsahu množiny A a množiny B . Prvky množiny A i B jsou celá čísla, počet prvků množiny A je N a množiny B je M. Pokud množiny mají společný prvek (číslo) zobrazte je na výstupním zařízení. Příklad 6. Sestavte a nakreslete diagram algoritmu pro průnik C obsahu množiny A a množiny B . Prvky množiny A i B jsou reálná čísla, počet prvků množiny A je N a množiny B je M. Množinu C zobrazte na výstupním zařízení. Příklad 7. Sestavte a nakreslete diagram algoritmu pro sjednocení C obsahu množiny A a množiny B. Prvky množiny A i B jsou reálná čísla, počet prvků množiny A je N a množiny B je M. Množinu C zobrazte na výstupním zařízení. Příklad 8. Sestavte a nakreslete diagram algoritmu pro zjištění, zda zadané číslo N je prvočíslo. Příklad 9.Sestavte a nakreslete diagram algoritmu pro výpočet funkce 5555. Příklad 10. Sestavte a nakreslete diagram algoritmu pro součet matic A a B. Prvky množiny A i B jsou reálná čísla, počet prvků množiny A je N×N a množiny B je M×M. Množinu C zobrazte na výstupním zařízení. Příklad 11. Sestavte a nakreslete diagram algoritmu pro součin matice A a vektoru B. Prvky množiny A i B jsou reálná čísla, počet prvků množiny A je N×N a množiny B je M. Množinu C zobrazte na výstupním zařízení. Příklad 12. Sestavte a nakreslete diagram algoritmu pro součin matic A a B. Prvky množiny A i B jsou reálná čísla, počet prvků množiny A je N×N a množiny B je M×M. Množinu C zobrazte na výstupním zařízení. 1.7 Kontrolní otázky 1. Určete maximální časovou náročnost třech vnořených cyklů s maximálním N smyčkami. 2. Určete průměrnou časovou náročnost třech vnořených cyklů s maximálním N smyčkami. 3. Určete časovou náročnost algoritmu, který zjišťuje, zda zadané číslo je prvočíslo. 4. Na čem je závislá časová náročnost algoritmu? 5. Existuje optimální nastavení paměťové a časové náročnosti algoritmu? 6. Jak ovlivní počet vnořených cyklů časovou náročnost algoritmu? 7. Jaký je rozdíl v časové náročnosti třech vnořených a třech postupně zařazených cyklů? 8. Mohou hodnoty vstupních dat ovlivnit časovou náročnost programu? 9. Může rozsah vstupních dat ovlivnit časovou náročnost programu? 10. Může rozsah vstupních dat ovlivnit stabilitu programu? 11. Mohou hodnoty vstupních dat ovlivnit stabilitu programu? 12. Vysvětlete rozdíl mezi operačním systémem řízeným událostmi a instrukcemi. 13. Je výhodné používat pro spuštění programu v osobním počítači interpreter? 14. Co je to algoritmus? 15. Co je to funkce, proměnná, objekt, třída? 16. K čemu je dobrý programovací jazyk? speedbar paleta komponentů inspektor objektů editor formulář Obr. 2.1 Borland C++ Builder, verze 5: integrované uživatelské rozhraní Počítače a programování 2 13 2 Borland C++ Builder 5.0 Borland C++ Builder je nástrojem pro vývoj programů, určených pro operační systémy Microsoft Windows. Builder pracuje se zdrojovými kódy, sestavenými v programovacím jazyce C++. Builder využívá principů tzv. vizuálního programování (drag &drop design). Psaní aplikace probíhá tak, že programátor sestavuje myší uživatelské rozhraní svého programu, a Builder mu generuje odpovídající zdrojový kód, napsaný v jazyce C++. Pokud programátor zasáhne do zdrojového kódu, změna se samočinně promítne do vizuálně sestaveného prvku a naopak (tzv. two-way tool). Integrované prostředí nástroje Builderu sestává ze sedmi základních částí (obr. 2.1): Paleta komponentů (component palette) je tvořena sadou záložek, na kterých jsou umístěny komponenty, z nichž lze vizuálně sestavovat okno programu (formulář, form). Volná místa na záložkách jsou připravena pro umístění originálních komponentů, vytvořených programátorem. Formulář (form) je základní okno operačního systému Windows s tečkovaným rastrem, do něhož myší umisťujeme jednotlivé komponenty. Jednotlivé parametry (proměnné) formuláře a vkládaných komponentů můžeme zadávat buď pomocí myši (umístění, rozměr), a jednak je můžeme určovat prostřednictvím inspektoru objektů (viz dále). Všechny zadané hodnoty proměnných se samočinně promítnou do zdrojového kódu vyvíjené aplikace. Inspektor objektů (Object Inspector) sestává ze dvou záložek – ze záložky s názvem Proměnné (Properties) a ze záložky s názvem Události (Events). Záložka Properties obsahuje seznam všech parametrů toho komponentu, který je zaostřen (focused, programátor na něm kliknul myší). Nastavíme-li např. šířku a výšku komponentu myší, numerické vyjádření nastavených rozměrů komponentu se automaticky objeví v inspektoru objektů vedle proměnných Height a Width. Postupovat lze samozřejmě i obráceně. Záložka Events obsahuje seznam událostí objektu, který je právě zaostřen ve formuláři. Událostí rozumíme vše, co může nastat při zaostření daného komponentu. Např. při kliknutí na tlačítko je generována událost OnClick. Má-li kliknutí na tlačítko spustit vykonávání určitého kódu (má být volána určitá funkce), vepíšeme do editačního řádku vedle události jméno funkce a stiskneme klávesu Enter. Builder automaticky vygeneruje hlavičku funkce, a nám stačí její tělo (prostor mezi složenými závorkami) vyplnit zdrojovým kódem. Editor kódu (Code Editor) slouží k vytváření zdrojového kódu, napsaného v jazyce C++. Část kódu je generována samočinně jako reakce na vizuální sestavování formuláře, zbytek musí programátor dopsat sám. Každý formulář aplikace je uložen v samostatné programové jednotce (unit). Zdrojový text jednotky má příponu *.cpp. Urychlující panel (Speedbar) je sestaven z tlačítek, soužících k vyvolání nejčastějších akcí. Detail rychlého panelu a popis funkce jednotlivých tlačítek je zobrazen na obr. 2.2. Anglický popis tlačítka je zobrazován v „bublinové“ nápovědě. Tlačítka Trace Into a Step Over slouží ke krokování programu (programátor manuálně vykonává instrukci za instrukcí). Při Trace Into se vnoříme se do funkce a řádek po řádku vy- 14 Fakulta elektrotechniky a komunikačních technologií VUT v Brně konáváme jednotlivé její příkazy. Při Step Over se do funkce nevnoříme a vykonáme ji jako jeden jediný příkaz. uložení všech souborů otevření projektu přidání jednotky do projektu uložení souboru vyjmutí jednotky z projektu otevření souboru nápověda nový objekt "step over" seznam jednotek seznam formulářů jednotka formulář nový formulář Obr. 2.2 "trace into" přerušení běhu programu spuštění programu Význam tlačítek na rychlém panelu Menu. Vzhledem k obrovskému množství položek, jež jsou do menu zahrnuty, nemá asi smysl brát jednu položku po druhé a vysvětlovat jejich význam. Situaci tedy vyřešíme tak, že se o důležitých položkách zmíníme v kapitole o psaní aplikací. Ladič (Debugger) není na obrázku vidět, protože je do Builderu integrován. Kliknutím na levý okraj řádku v editoru kódu vložíme na tento řádek tzv. přerušovací bod (Breakpoint). Spuštěný program se na daném řádku zastaví, takže programátor zde může kontrolovat a měnit obsah proměnných, může program krokovat nebo jej může znovu spustit, aby pokračoval ve svém chodu. Opětovným kliknutím na totéž místo přerušovací bod odstraníme. Nyní, když jsme se seznámili s integrovaným prostředím Builderu, ukážeme si na příkladu postup vývoje aplikace. 2.1 Vývoj aplikace Vývoj aplikace v Borland C++ Builderu lze rozdělit do několika základních kroků. Nyní si tyto kroky podrobně probereme, abychom mohli vytvořit svůj vlastní program. Základní kroky budeme vysvětlovat na programu, který po stisku tlačítka Počítej načte sčítance, jež uživatel vepíše do editačních řádků formuláře, zadané sčítance sečte a výsledek zobrazí. Formulář programu je zobrazen na obr. 2.3. 2.1.1 Spuštění Builderu Po spuštění obsahuje Builder Obr. 2.3 Okno programu pro sčítání prázdný formulář a prázdné editační okno, které odpovídá programové jednotce tohoto formuláře. Před zahájením práce je vhodné tuto „prázdnou“ aplikaci uložit. Stisknutím čtvrté horní ikony rychlého panelu (Save All) otevřeme standardní dialog pro ukládání do souboru. Builder nám v řádku Název souboru nabídne standardní jméno jednotky formuláře (unit1.cpp). My toto jméno změníme na add_form.cpp (formulář pro sčítání čísel), aby se nám v souborech lépe orientovalo. Po tisku tlačítka Uložit nám Builder nabídne standardní jméno projektu (project1.bpr), přičemž my toto jméno změníme na addition.bpr. Počítače a programování 2 15 Pokud prázdný, právě uložený projekt spustíme (pátá dolní ikona, Run), objeví se prázdné okno se standardní ikonou a s názvem Form1. Ve stavové liště Windows je běžící aplikace reprezentována tlačítkem se stejnou ikonou a se jménem Addition (jméno odpovídá zvolenému pojmenování projektu). 2.1.2 Základní nastavení Každému programu bývá většinou přiřazena vlastní ikona. Ikonu vytvoříme pomocí editoru, který v Builderu spustíme prostřednictvím položky menu Tools → Image editor (editor obrázků). Vybereme-li z menu editoru obrázků položku File → New… → Icon file (a potvrdíme-li standardní parametry ikony 32×32 bodů, 16 barev), spustíme jednoduchý grafický editor, v němž můžeme bod po bodu ikonu sestavit. Výběrem položky menu File → Save uložíme ikonu do adresáře k ostatním souborům našeho programu (soubor add_icon.ico). Vytvořenou ikonu přiřadíme naší aplikaci prostřednictvím položky menu Builderu Project → Options. Otevřeme tím dialog s třemi řadami záložek. Vybereme záložku Application a stiskem tlačítka Load Icon načteme námi vytvořenou ikonu. Do řádku Title vepíšeme řetězec Sčítání. Stiskem tlačítka OK dialog uzavřeme. Spustíme-li naši aplikaci znovu, jak okno tak tlačítko ve stavové liště Windows budou mít naši ikonu, a navíc, tlačítko v liště bude obsahovat český název Sčítání místo původního Addition. Po základním nastavení aplikace se zaměříme na nastavení parametrů formuláře: 1. Pevné rozměry okna. Myší nastavíme rozměr formuláře (v inspektoru se automaticky mění obsah proměnných Height a Width). V inspektoru nastavíme BorderStyle na bsSingle (okno nepůjde roztáhnout myší taháním za okraje) a v BorderIcons nastavíme biMaximize na false (zablokujeme ikonu pro roztažení okna přes celou obrazovku). 2. Popis okna. V inspektorovi naplníme parametr Caption = Sčítání (dosud parametr obsahoval řetězec Form1). Tím jsou základní nastavení dokončena. 2.1.3 Sestavení okna V dalším kroku postupně umístíme dovnitř formuláře komponenty z palety. Jak je vidět z obr. 2.3, pracujeme se třemi komponenty (všechny tři se v paletě nacházejí na záložce Standard). Návěští (Label). Jedná se o text, kterým ve formuláři popisujeme další objekty (v našem případě editační řádky). Návěští můžeme rovněž použít jako textový výstup (v našem případě pro vypsání součtu). V inspektorovi vyplňujeme u návěští proměnné Caption (řetězec, který se objeví ve formuláři), Font (otevře se standardní dialog pro výběr parametrů písma) a Name (jméno návěští). Pro snadnější orientaci ve zdrojovém kódu je vhodné přepisovat standardní jména generovaná Builderem jmény vlastními. Editační řádek (Edit). Jedná se o jednoduchý jednořádkový editor, který můžeme využít jako textový vstup programu (v našem případě pro načítání sčítanců). V inspektoru vyplňujeme u editačního řádku Text (obsah editačního řádku; v našem případě prázdný řetězec – tedy nic), Font a v případě potřeby jméno řádku Name. 16 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Pokud chceme editační řádek doplnit „bublinkovou“ nápovědou, nastavíme v inspektoru ShowHint = true a do proměnné Hint vepíšeme obsah „bublinky“ (v našem případě vepíšeme upozornění, že lze vepsat pouze celé číslo). Obdobný mechanismus funguje i u ostatních komponentů. Tlačítko (Button). Základním úkolem tlačítka je dát pokyn k provedení nějaké akce. Pokud uživatel na tlačítko klikne nebo pokud ho stiskne prostřednictvím klávesnice, vždy je generována událost tlačítka OnClick. Tuto událost nalezneme v inspektoru na záložce Events. Vepíšeme-li do editačního řádku vedle události jméno funkce, Builder tuto funkci deklaruje v editoru kódu (jako prázdnou) a její volání pevně sváže s danou událostí. V našem případě nazveme obslužnou funkci addition a její tělo si popíšeme v další podkapitole. Na záložce Properties inspektora zadáme text uvnitř tlačítka Caption = &Počítej (znak & způsobí podtržení následujícího písmene; při stisku klávesové kombinace Alt+P dojde ke stlačení tlačítka). Opět můžeme změnit jméno objektu (Name) a parametry písma uvnitř objektu (Font). Na závěr uveďme ještě jednu poznámku. Je zvykem, že při postupném mačkání tabelační klávesy postupně zaostřujeme jednotlivé komponenty v okně. Pořadí zaostřování komponentů přitom odpovídá pořadí, v němž byly komponenty do okna vkládány. Pokud chceme pořadí zaostřování změnit, učiníme tak v inspektoru prostřednictvím parametru TabOrder. Pokud nechceme, aby se tabelátor na určitém komponentu zastavil, nastavíme pro něj v inspektoru TabStop = false. 2.1.4 Ošetření událostí V našem programu pro sčítání dvou čísel budeme pracovat s jedinou událostí, a to se stiskem tlačítka Počítej. Jakmile uživatel programu toto tlačítko stiskne (objeví se událost tlačítka OnClick), zavoláme funkci addition. Abychom funkci svázali s uvedenou událostí tlačítka, tlačítko zaostříme (klikneme na něj myší) a v inspektoru vepíšeme na záložce Events řetězec addition vedle události OnClick. Potvrdíme-li svou volbu stiskem klávesy Enter, Builder vygeneruje deklaraci této funkce: void __fastcall TForm1::addition( TObject *Sender) { } Funkce je samozřejmě prázdná (neobsahuje žádnou instrukci, nic nedělá). Reakci na stisk tlačítka musíme mezi složené závorky napsat sami jako posloupnost vhodných instrukcí. Než začneme sestavovat program, vysvětleme si stručně význam jednotlivých slov v hlavičce funkce (první řádek). Slovo void označuje funkci, která nevrací žádnou hodnotu. Zatímco např. sin(0.5) vrací sinus 0.5 radiánu, od funkce pro ošetření stisku tlačítka žádnou hodnotu neočekáváme. Slovo __fastcall udává způsob, jakým má být funkce volána. Všechny funkce pro ošetření událostí musejí být volány s tímto slovem. Konstrukce TForm1::addition říká, že funkce addition je pevně svázána s hlavním formulářem našeho programu (na ploše tohoto formuláře naše tlačítko leží). Hlavnímu formuláři jsme ponechali standardní název Form1 (položka Name na záložce inspektora Properties), a proto má naše funkce tzv. předponu TForm1. Počítače a programování 2 17 V závorce za jménem funkce je uveden seznam vstupních parametrů (seznam proměnných, jejichž hodnoty funkci předáváme). V našem případě se jedná o adresu proměnné Sender (hvězdička říká, že nepředáváme funkci číselný obsah proměnné, ale její adresu). Proměnná Sender je typu TObject. O tom, co je to datový typ TObject, se dozvíme později. Nyní již hlavičce vygenerované prázdné funkce rozumíme, a proto se můžeme začít věnovat psaní jejího těla: void __fastcall TForm1::addition( TObject *Sender) { int first, second; // -1- první a druhý sčítanec first = StrToInt( Add1Edit->Text); second = StrToInt( Add2Edit->Text); // -2- první edit.řádek na číslo // -3- druhý edit.řádek na číslo Result->Caption = IntToStr( first + second); } // -4- sečtení a zobrazení Na řádku č.1 zavádíme dvě pomocné proměnné, které existují jen uvnitř naší funkce. Proměnné first a second jsou typu int (celé číslo). Druhý a třetí řádek musíme začít číst zprava. Konstrukce Add1Edit->Text říká, že budeme pracovat s textem, který uživatel vepíše do prvého editačního řádku (v inspektoru jsme ho pojmenovali – prostřednictvím položky Name – Add1Edit). Vepsaný text je uložen ve formě řetězce (posloupnost znaků) v proměnné editačního řádku Text (viz inspektor, záložka Properties). Abychom mohli provést operaci sčítání, musíme převést řetězec na číslo. O tuto konverzi se stará standardní funkce StrToInt (String To Integer, převod řetězce na celé číslo). Vstupním parametrem je řetězec Add1Edit->Text, výstupním parametrem je celé číslo. Získané celé číslo uložíme do celočíselné pomocné proměnné first. S obsahem druhého editačního řádku a s jeho převodem na druhý sčítanec je to obdobné. Výsledkem je druhý řetězec převedený na celé číslo second. Na posledním řádku obě čísla sečteme (first+second) a součet převedeme z celočíselné formy na řetězec (IntToStr, Integer To String). K zobrazení získaného řetězce využijeme modrého návěští vedle tlačítka Počítej (pojmenovali jsme ho Result – inspektor, položka Name). Text návěští je uložen v jeho proměnné Caption. Konstrukce Result->Caption = s tedy říká, že řetězec s ukládáme do proměnné Caption, která patří návěští Result. Dále si můžeme povšimnout, že pro lepší srozumitelnost je naše funkce doplněna komentáři. Komentářem rozumíme libovolný řetězec, umístěný za dvojité lomítko. Komentář může obsahovat libovolné znaky včetně mezer a českých písmen. Text za dvojitým lomítkem je překladačem ignorován. Naši jedinou událost tedy máme ošetřenu. Nyní je třeba zkontrolovat, zda program funguje a zda pracuje správně. 2.1.5 Ladění Při ladění zastavíme program na začátku bloku, v němž předpokládáme chybu (na odpovídající řádek programu vložíme přerušovací bod, breakpoint. Poté kritický blok krokujeme pomocí Trace Into nebo Step Over. V jednotlivých krocích prohlížíme obsah proměnných a 18 Fakulta elektrotechniky a komunikačních technologií VUT v Brně ověřujeme správnost jejich obsahu. Pokud zjistíme nesprávnou hodnotu, můžeme ji pro další ladění nahradit hodnotou korektní. Ke kontrole obsahu proměnných a jejich změně slouží položka menu Run → Evaluate/ Modify. Výběrem této položky otevřeme okno z obr. 2.4a. Do řádku Expression vepíšeme název proměnné, stiskneme Evaluate a v editačním poli Result objeví její obsah. Chceme-li obsah proměnné změnit, vepíšeme do řádku New value novou hodnotu proměnné a stiskneme Modify. Do řádku Expression lze psát i celé výrazy (např. i+j). Obr. 2.4 Okno pro vyčíslení výrazů a změnu obsahu proměnných (vlevo). Obsah vybraných proměnných (vpravo). K prostému prohlížení obsahu proměnných slouží jednak „bublinová“ nápověda editačního okna (zastavíme-li kurzor myši na jménu proměnné, objeví se barevný obdélník s textovým vyjádřením obsahu proměnné) a jednak tzv. Watch List (obr. 2.4b). Watch List je okno (otevřeme ho prostřednictvím položky menu Run → Add watch), zobrazující seznam vložených proměnných a jejich obsah. Proměnné vkládáme stisknutím klávesy Insert a mažeme klávesou Delete. Proměnnou (nebo výraz) do okna Watch List je možné rovněž přenést z řádku Expression okna Evaluate/Modify stiskem Watch. Ladicí nástroje Builderu jsou velmi efektivní a velmi pohodlné. Přesto je lepší dobře si promyslet a také nakreslit algoritmus sestavovaného programu, abychom se nedopouštěli zbytečných logických omylů. Nutné je dobře se naučit syntaxi programovacího jazyka, abychom se nedopouštěli zbytečných omylů syntaktických. Takže – hurá na studium jazyka C. 2.2 Kontrolní příklady Příklad 1. Navrhněte a sestavte program, který bude pomocí tří editačních řádků provádět operace součet a rozdíl. Výsledek bude zobrazen do třetího editačního řádku. Operaci součtu nebo rozdílu proveďte po kliknutí na odpovídající tlačítko. Výsledek se okamžitě zobrazí v příslušném editačním řádku. Příklad 2. Navrhněte a sestavte program, který bude pomocí jednoho editačního řádku provádět operace součet a rozdíl. Výsledek bude zobrazen v editačním řádku. Operaci součtu nebo rozdílu proveďte po kliknutí na odpovídající klávesu pro zobrazení výsledku. Příklad 3. Navrhněte a sestavte program, který bude pomocí jednoho editačního řádku provádět operace součet, rozdíl, součin a podíl. Výsledek bude zobrazen v editačním řádku. Operace proveďte a zobrazte po kliknutí na odpovídající tlačítko pro zobrazení výsledku. Příklad 4. Navrhněte a sestavte program, který bude pomocí jednoho editačního řádku a tlačítek pro zadání čísel provádět operace součet, rozdíl, součin a podíl. Výsledek bude zo- Počítače a programování 2 19 brazen v editačním řádku. Operace proveďte a zobrazte po kliknutí na odpovídající klávesu pro zobrazení výsledku. 2.3 Kontrolní otázky 1. Co je to ikona? 2. Co je to komponent a k čemu slouží? 3. Co je to formulář, k čemu slouží a co je v něm zobrazeno? 4. Co obsahuje inspektor objektů? 5. K čemu slouží editor kódů? 6. Co obsahuje rychlý panel v aplikaci Borland++ Builder? 7. K čemu slouží ladič (debugger)? 8. Kde se v Borland C++Builderu může ošetřit nastavená událost v sestavované aplikaci? 9. Jak se může reagovat v aplikaci na určitou událost? 10. Co označuje klíčové slovo void? 11. Co je to breakpoint? 12. Lze při ladění aplikace provádět změny obsahu proměnné? 3 Jazyk C Programovací jazyk C vyvinul na přelomu šedesátých a sedmdesátých let D.M. Ritchie u firmy AT&T. Jazyk se stal postupem doby natolik oblíbený, že byl kodifikován Americkým národním úřadem pro normalizaci (ANSI). Na základě této normy vznikla řada implementací jazyka C pro různé typy počítačů a pro různé druhy operačních systémů. Počátkem osmdesátých let byla navržena objektová verze1 jazyka, pro níž se vžilo označení C++. Jazyk C++ se postupem doby stal základem moderních vývojových nástrojů jakými jsou Borland C++ Builder nebo Microsoft Visual C++. S nástrojem Borland C++ Builder jsme se již seznámili a zůstaneme mu dále věrni. Nicméně, naši pozornost soustředíme na původní, neobjektovou verzi jazyka, na ANSI C. Důležité pro nás bude, abychom si zvykli na syntaxi jazyka C a abychom se naučili v jazyce C myslet. Přechod k moderní, objektové verzi C++ by pak už měl být pro nás relativně snadný. 3.1 Identifikátory Identifikátorem rozumíme libovolnou posloupnost písmen anglické abecedy a číslic. Identifikátor hraje roli jména naší vlastní proměnné nebo naší vlastní funkce. 1 Program obvykle sestává z nezávislého kódu (posloupnost instrukcí v tělech funkcí) a z nezávislých dat (proměnné, v nichž jsou uloženy programem zpracovávané údaje). Objektové programování skládá funkce (kód) a proměnné (data) do jediné struktury, kterou nazýváme objekt. 20 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Identifikátor musí začínat písmenem (nesmí mít na první pozici číslo). V jazyce C je třeba rozlišovat malá a velká písmena. 3.2 Typy dat, proměnné Termínem proměnná rozumíme místo v paměti, do něhož ukládáme data určitého typu. Jméno proměnné (sestavujeme jej podle výše uvedených pravidel pro identifikátory) zastupuje adresu paměťového místa. Typ proměnné jednoznačně určuje, jak velký paměťový prostor má být vyhrazen pro uložení obsahu proměnné. Proměnné musíme před jejich použitím deklarovat. Deklarace počítači oznamuje, jak velké paměťové místo má být pro naši proměnnou rezervováno a jakým jménem se budeme na toto paměťové místo odkazovat. 3.2.1 Lokální a globální proměnné Podle umístění deklarace můžeme proměnné rozdělit na lokální a globální. Lokální proměnná je deklarována v těle funkce. Tato proměnná je pak dostupná jen v rámci této funkce a existuje jen po dobu jejího provádění. Jakmile tělo funkce opustíme (je vykonána poslední instrukce funkce), je paměťové místo, v němž byla proměnná uložena, uvolněno (vymaže se jeho adresa, a tím je ztracen i jeho obsah). Globální proměnná je většinou deklarována mimo těla funkce. Tato proměnná existuje po celou dobu provádění programu a mohou k ní přistupovat všechny funkce, které jsou definovány za deklarací této proměnné. Práci s lokálními a globálními proměnnými si vysvětlíme na jednoduchém programu. Formulář programu je nakreslen na obr. 3.1. Na jeho ploše jsou umístěna tlačítka Přičti (Name=PlusBtn) a Odečti (Name= MinusBtn). Pokud uživatel klikne na tlačítko Přičti, zjistíme hodnotu čísla, která odpovídá řetězci v editačním řádku nalevo od tlačítka (Name=PlusEdit). O tuto hodnotu zvýšíme hodnotu zobrazenou červeným návěštím (Name=Result) v dolní části formuláře. Funkce tlačítka Odečti se liší pouze tím, že hodnota odpovídající červenému návěští je zmenšena o obsah druhého editačního řádku (Name=MinusEdit). Obr. 3.1 Lokální a globální proměnné Náš program bude sestávat ze dvou funkcí – odezev na událost stisku tlačítka (OnClick) Přičti a tlačítka Odečti. Každá funkce nejprve zjistí obsah odpovídajícího editačního řádku, převede ho na celé číslo a uloží toto číslo do pomocné proměnné. V dalším kroku je celé číslo z pomocné proměnné buď přičteno (tlačítko Přičti, funkce add) nebo odečteno (tlačítko Odečti, funkce subtract) od čísla (obsahu celočíselné proměnné total), které odpovídá červenému návěští. Z uvedeného popisu by mělo být zřejmé, že proměnná total musí být proměnnou globální (musí být přístupná jak funkci add tak pro funkci subtract). Naproti tomu pomocné proměnné pro uložení kladného sčítance plus a sčítance záporného minus mohou být deklarovány jako proměnné lokální. Zatímco obsah proměnné total musí být uchován po celou dobu běhu programu, proměnné plus a minus plníme čerstvým údajem z editačního Počítače a programování 2 21 řádku při každém stisku tlačítka a po aktualizaci proměnné total můžeme jejich obsah „zapomenout“. Zdrojový text popsaného programu je uložen v adresáři plus_minus a jeho zdrojový text vypadá následovně: int total = 0; // globální proměnná pro celkový součet // odezva na stisk tl. "Přičti" void __fastcall TForm1::add(TObject *Sender) { int plus; // lokál.prom. - kladný sčítanec } plus = StrToInt( PlusEdit->Text); total = total + plus; // editační řádek -> číslo // změna obsahu globál.proměnné Result->Caption = IntToStr( total); // zobrazení výsledku // odezva na stisk tl. "Odečti" void __fastcall TForm1::subtract(TObject *Sender) { int minus; // lokál.prom. - záporný sčítanec minus = StrToInt( MinusEdit->Text); // editační řádek -> číslo total = total - minus; // změna obsahu globál.proměnné } Result->Caption = IntToStr( total); // zobrazení výsledku Ve výše uvedeném výpisu si můžeme všimnout, že se v deklaraci globální proměnné total objevuje za jménem proměnné rovnítko následované celočíselnou hodnotou. Pomocí této konstrukce můžeme přímo v deklarační části programu nově vytvořenou proměnnou inicializovat (na paměťové místo označené identifikátorem total ukládáme hodnotu 0). Typ Bitů Rozsah unsigned char 8 X ∈ <0, +255> char 8 X ∈ <-128, +127> short int 16 X ∈ <-32.768; +32.767> unsigned int 32 X ∈ <0; +4.294.967.295> int 32 X ∈ <-2.147.483.648; +2.147.483.647> float 32 1,18 ⋅ 10-38 < |X| < 3,40 ⋅ 10+38 double 64 2,23 ⋅ 10-308 < |X| < 1,79 ⋅ 10+308 long double 80 3,37 ⋅ 10-4932 < |X| < 1,18 ⋅ 10+4932 Tab. 3.1 Vybrané základní typy proměnných Borland C++ Builderu 3.2.2 Pravidla deklarování proměnných Jak je zřejmé z uvedeného výpisu, deklarování proměnné se řídí následujícími pravidly: 1. Na volném řádku uvedeme typ proměnné (v našem případě int). 22 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 2. Typ proměnné oddělíme mezerou od jména proměnné daného typu. 3. Pokud potřebujeme deklarovat více proměnných daného typu, jejich jména oddělujeme čárkami (např. int first, second, third;). 4. Řádek s deklarací je ukončen středníkem. Dosud jsme se v našich příkladech setkali pouze s celočíselným typem proměnné int. O dalších základních typech se dozvíme v následujícím odstavci. 3.2.3 Základní typy proměnných Vybrané základní typy proměnných jsou uvedeny v tab. 3.1. Proměnné z tabulky můžeme rozdělit do tří skupin: Znakové char (znak ANSI znakové sady), unsigned char (unsigned omezuje proměnnou pouze na kladné hodnoty). Celočíselné int (32-bitové celé číslo se znaménkem), short int (short sníží možnou velikost celého čísla; ušetříme 16 bitů), unsigned int (unsigned omezuje proměnnou pouze na kladné hodnoty). Racionální 2 float (32-bitové číslo s plovoucí desetinnou čárkou a znaménkem) , 2 double (64-bitové číslo s plovoucí desetinnou čárkou a znaménkem) , long double (long zvýší přesnost reprezentace racionálního čísla double). Při výběru vhodného typu proměnné se rozhodujeme nejdříve mezi základními skupinami. Pro uložení znaku volíme skupinu char, pro uložení celočíselného indexu skupinu int, pro uložení racionálního čísla skupinu float-double. Podrobnější specifikaci typu uvnitř zvolené skupiny pak formulujeme pomocí tzv. modifikátorů (modifiers) unsigned, short, long … s ohledem na požadovaný číselný rozsah proměnné na jedné straně a na dostupnou velikost paměti na straně druhé. 3.2.4 Ukazatele Termínem ukazatel (pointer) rozumíme proměnnou, která je určena pro uložení adresy určitého paměťového místa. Každý ukazatel musí být přitom spjatý s datovým typem, který je na dané adrese uložen. Deklarujeme-li v programu ukazatel, stačí nám do standardní deklarace přidat před jeho jméno symbol *. Tzn., int *b je ukazatel na celočíselnou proměnnou. Pokud chceme do ukazatele uložit adresu proměnné int a, použijeme zápisu b=&a. Pokud si chceme prohlédnout obsah paměťového místa, jehož adresa je uložena v b, použijeme konstrukce c=*b (c je deklarováno jako celé číslo, obsah adresy uložené v b – tzn. hodnotu proměnné a – kopírujeme do proměnné c). 2 Obr. 3.2 Práce s ukazateli Deklarujeme-li v programu proměnnou tohoto typu, Builder automaticky připojí k našemu programu jednotku standardních matematických operací v plovoucí desetinné čárce math. Jednotka obsahuje goniometrické funkce (sin, cos, tan, asin, acos, atan, …), logaritmy (přirozený log, dekadický log10), exponenciální funkci (exp), mocniny a odmocniny (pow, sqrt), atd. Počítače a programování 2 23 Práci s ukazateli si vyzkoušíme na jednoduché aplikaci. Formulář aplikace obsahuje tlačítko Inkrementuj (Name=Button1) a žluté návěští (Name=Result). Když uživatel stiskne tlačítko, zvýší se hodnota zobrazená návěštím o jedničku. V programu deklarujeme globální celočíselnou proměnnou a (v rámci deklarace ji vynulujeme) a globální ukazatel na celočíselnou hodnotu b. Při vytváření formuláře před jeho vykreslením na obrazovce monitoru (událost OnCreate formuláře) přiřadíme ukazateli b adresu proměnné a (v těle funkce init, která je volána jako odezva na událost OnCreate). V odezvě na stištění tlačítka Inkrementuj (událost OnClick tlačítka, funkce increment) zvýšíme obsah proměnné a o jedničku (k tomu slouží zápis a++). Text návěští ovšem aktualizujeme prostřednictvím ukazatele b, jak ukazuje následující výpis: int a=0; int *b; // inicializovaná celočíselná proměnná // ukazatel na celočíselnou proměnnou // reakce na událost formuláře OnCreate void __fastcall TForm1::init(TObject *Sender) { b = &a; // do ukazatele adresa proměnné a } // reakce na událost tlačítka OnClick void __fastcall TForm1::increment(TObject *Sender) { a++; // zvýšení hodnoty a o jedničku Result->Caption = IntToStr( *b); } // obsah a zobrazován prostřednictvím b Za upozornění stojí skutečnost, že při chodu programu vykonáváme již jen dvě instrukce v těle funkce increment. V těle této funkce měníme pouze obsah proměnné a, obsah proměnné b se nemění. Při zobrazování se však odvoláváme na proměnnou b a nikoli na proměnnou a. Protože b obsahuje adresu proměnné a, údaj zobrazovaný návěštím se při každém stisku tlačítka o jedničku zvýší. Popsaný program je uložený v adresáři pointer. 3.2.5 Pole Pole je datová struktura tvořená několika složkami stejného typu. Počet složek pole udáváme v jeho deklaraci v lomené závorce za jménem pole. Počet složek musí být kladné celé číslo. Např. deklarace int d[3] zavádí pole d tvořené třemi složkami typu int. Jelikož složky pole jsou indexovány od nuly, sestává naše pole ze tří celých čísel d[0], d[1] a d[2]. TPaintBox TBevel Má-li pole více indexů, objeví se v deklaraci za jménem pole více Obr. 3.3 Práce s poli, se znaky a s grafikou lomených závorek. Např. pole double d[2][2] sestává ze čtyř racionálních čísel d[0][0], d[0][1], d[1][0] a d[1][1]. 24 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Dosud jsme ve svých příkladech pracovali pouze s celočíselnými proměnnými. Nyní si tedy na programu pro práci s poli ukážeme rovněž práci se znaky a s proměnnými racionálními. Navíc se pokusíme o jednoduchou grafickou reprezentaci výsledků našich výpočtů. Úkolem našeho programu bude počítat a zobrazovat průběh funkce sinus nebo funkce kosinus pro hodnoty argumentu od 0 do 2π radiánu. Program bude reprezentován formulářem, který je znázorněný na obr. 3.3. Do editačního řádku Edit1 uživatel může zadat buď S (počítá se sinus) nebo C (počítá se kosinus). Nastavením parametru MaxLength=1 editačního řádku v inspektoru zajistíme, že lze do řádku zapsat jediný znak. Zadání jiného znaku než S a C (např. s, c) není v programu ošetřeno (program spadne – je nekorektně ukončen). Text nad editačním řádkem (S = sinus, C = kosinus) je realizován návěštím Label1. Stištění tlačítka Funkce (Button1) vyvolá událost OnClick, na níž reaguje funkce compute. Úkolem compute je přečíst znak z řádku Edit1, vypočítat průběh zvolené funkce a průběh vykreslit. Editační řádek, návěští a tlačítko jsou opticky odděleny od zbytku formuláře rámečkem (Bevel, záložka palety komponentů Additional). K vykreslení průběhu používáme komponent kreslicí plocha, PaintBox (záložka palety komponentů System). Kreslicí plocha je prostorově omezená plocha, která má v našem programu šířku Width=409 a výšku Height=201 bodů (viz inspektor objektů). Levý horní roh plochy má souřadnice (0,0), pravý dolní roh (Width, Height). Můžeme si představit, že kreslicí plocha je potažena malířským plátnem (Canvas). Plátno obsahuje kreslicí nástroje – pero (pen) pro kreslení čar, štětec (brush) pro natírání ploch a písmo (font) pro vytváření textových popisů. Jedním z parametrů pera je barva čáry (Color). Chceme-li tedy nastavit barvu čáry, musíme říci kreslicí ploše PaintBox1, že má říci malířskému plátnu ->Canvas, že má říci peru ->Pen, že měníme jeho barvu ->Color. Novou barvu zadáváme pomocí konstant clXXX (bílá je clWhite, žlutá clYellow, atd.). Dále plátno obsahuje kreslicí funkce. Funkce MoveTo(x,y) nastaví pero na souřadnici ( x, y), funkce LineTo(x1,y1) vykreslí čáru z bodu, na který bylo pero nastaveno, do bodu ( x1, y1). Další kreslicí funkce nalezneme v nápovědě Builderu. Jakmile máme formulář sestavený, můžeme začít programovat: 1. Jednotku s naším programem spojíme s knihovou standardních matematických funkcí, abychom mohli vyčíslovat funkční hodnoty sinu a kosinu #include "math.h" // jednotka matematických operací 2. Deklarujeme globální proměnné. Mezi globální proměnné zařadíme Ludolfovo číslo pi a pole úhlů angle, pro něž budeme vyčíslovat goniometrické funkce. Tyto parametry se v programu nemění – mají globální platnost. double pi = 3.14159265358; // Ludolfovo číslo // pole od 0 do 1.9 radiánů double angle[20] = { 0.0*pi, 0.1*pi, 0.2*pi, 0.3*pi, 0.4*pi, 0.5*pi, 0.6*pi, 0.7*pi, 0.8*pi, 0.9*pi, 1.0*pi, 1.1*pi, 1.2*pi, 1.3*pi, 1.4*pi, 1.5*pi, 1.6*pi, 1.7*pi, 1.8*pi, 1.9*pi}; Počítače a programování 2 25 Z výpisu je vidět, jak můžeme pole inicializovat přímo v deklaraci a že inicializačními hodnotami mohou být i matematické výrazy, které je třeba před přiřazením vyčíslit. 3. Definujeme funkci compute jako odezvu na událost tlačítka OnClick. // odezva na stisk tlačítka "Funkce" void __fastcall TForm1::compute(TObject *Sender) { double value[20]; // pole funkčních hodnot char funct; // znak S (sinus), C (cosinus) int n; // index pro cykly a pole funct = Edit1->Text[1]; // znak funkce z edit.řádku if (funct=='S') // je-li znakem "S" for (n=0;n<20;n++) // pro všechny úhly počítáme value[n] = sin( angle[n]); // sinus if (funct=='C') // je-li znakem "C" for (n=0;n<20;n++) // pro všechny úhly počítáme value[n] = cos( angle[n]); // cosinus } // nastavení barvy pera PaintBox1->Canvas->Pen->Color = clWhite; // nastavení výchozí pozice pera PaintBox1->Canvas->MoveTo( 5, 100-100*value[0]); for (n=1;n<20;n++) // kreslení průběhu PaintBox1->Canvas->LineTo( 5+20*n, 100-100*value[n]); Mezi lokálními proměnnými funkce je pole funkčních hodnot value, znak určující typ goniometrické funkce funct a pomocný index n. Všechny tři proměnné plníme aktuálními daty po každém stisknutí tlačítka Funkce, a proto jsou deklarovány lokálně. Text v editačním řádku Edit1 lze chápat jako pole znaků. Text[0] obsahuje řídicí znak, v Text[1] je potom první znak zapsaný do editačního řádku. Tento znak kopírujeme do pomocné proměnné funct typu char. V dalším kroku obsah znakové proměnné funct testujeme. Znak, s nímž obsah funct srovnáváme, musí být uveden v jednoduchých uvozovkách. Operátor rovnosti == vrací hodnotu pravda (true), pokud jsou jeho levá a pravá strana identické (obsahem funct je znak S, obsahem funct je znak C). Vrátí-li operátor rovnosti == hodnotu pravda, příkaz if předá řízení cyklu for. V opačném případě je cyklus for přeskočen. Co se týká cyklu for, konstrukce (n=0;n<20;n++) říká, že cyklus provádíme od n=0 do n=19 (dokud je n menší než 20) a že v každém cyklu zvýšíme velikost n o jedničku (n++). Při vykreslování průběhu pomocí MoveTo nastavíme pero do počátečního bodu. Vodorovná pozice pera je posunuta o 5 bodů od levého okraje kreslicí plochy (první parametr funkce MoveTo). Svislou pozici určuje výraz 100-100*value (pro value=0 jsme uprostřed plochy, pro value=+1 jsme u horní hrany plochy a pro value=-1 jsme u dolní hrany plochy). Pomocí cyklu, který obsahuje LineTo, spojujeme počáteční bod value[0] s prvním bodem value[1], první bod value[1] s druhým bodem value[2] atd. Popsaný program je uložený v adresáři goniometric. 26 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 3.2.6 Inicializace proměnných Inicializací rozumíme přiřazení počátečních hodnot deklarovaným proměnným. Jak jsme si ukázali v předchozím příkladě, u skalární proměnné doplníme deklaraci o rovnítko následované počáteční hodnotou (double pi = 3.14;), u pole je deklarace doplněna složenou závorkou, která obsahuje počáteční hodnoty jeho složek (double angle[5] = {0.0, 0.1, 0.2, 0.3, 0.4}). Jak jsme si ukázali v předchozích příkladech, v inicializační části deklarace se mohou vyskytovat i výrazy. Např. při double phase[3] = {0.1*a, 0.2*b, 0.3*c} jsou nejdříve vyčísleny součiny, a poté jsou výsledky těchto součinů přiřazeny jednotlivým složkám pole. Deklarace a inicializace proměnných a, b, c musí samozřejmě předcházet výše uvedenou deklaraci a inicializaci pole phase. Vyčíslování výrazů při inicializaci pole phase se liší v případě, kdy je phase deklarováno jako globální pole (na začátku jednotky, mimo funkce) a kdy jako pole lokální (uvnitř některé z funkcí). V prvém případě jsou výrazy v inicializační části vyčíslovány jen jednou, a to na začátku programu. V druhém případě jsou výrazy počítány při každém volání funkce znovu. Pokud je obsah proměnných a, b, c neměnný během vykonávání celého programu, je lepší inicializovat phase jako globální proměnnou. Pokud se však obsah a, b, c během programu mění, nezbývá než phase inicializovat lokálně. 3.2.7 Kontrolní příklady Příklad 1. Navrhněte a sestavte program, který bude pomocí editačních řádků a dalších prvků provádět operace součet a rozdíl matice A a matice B, s rozměrem (3,3). Výsledek bude zobrazen do editačních řádků nebo přímo na plochu okna. Operaci součtu nebo rozdílu proveďte po kliknutí na odpovídající tlačítko. Příklad 2. Navrhněte a sestavte program, který bude pomocí editačních řádků a dalších prvků provádět operace součet, rozdíl matice A a matice B, součiny matice A.B, B.A, s rozměrem (3,3). Výsledek bude zobrazen do editačních řádků nebo přímo na plochu okna. Operace proveďte po kliknutí na odpovídající tlačítko. Příklad 3. Navrhněte a sestavte program, který bude pomocí editačních řádků a dalších prvků provádět operace součet, rozdíl matice A a matice B, součiny matice A.B, B.A, determinant matice A, matice B, s rozměrem (3,3). Výsledek bude zobrazen do editačních řádků nebo přímo na plochu okna. Operace proveďte po kliknutí na odpovídající tlačítko. Příklad 4. Navrhněte a sestavte program, který bude pomocí editačních řádků a dalších prvků provádět operace součet, rozdíl matice A a matice B, součiny matice A.B, B.A, determinant matice A, matice B, determinanty A, B při vynechání n-tého řádku a m-tého sloupce, s rozměrem (3,3). Výsledek bude zobrazen do editačních řádků nebo přímo na plochu okna. Operace proveďte po kliknutí na odpovídající tlačítko. 3.2.8 Kontrolní otázky 1. Vysvětlete co je to globální a lokální proměnná. 2. Vysvětlete jaký je rozdíl v použití globální a lokální proměnné v programu. 3. K čemu slouží v programu deklarace proměnných? 4. Jaké znáte typy proměnných, proč se používají různé typy proměnných? 5. Co je to ukazatel v kontextu jazyka C/C++? Počítače a programování 2 6. Proč se používá inicializace proměnných? 7. Co znamená zápis v programu C/C++ ve tvaru b = &a? 8. Co znamená zápis v programu C/C++ ve tvaru b ++ ? 27 3.3 Literály Termínem literál označujeme tzv. přímé konstanty (konkrétní číslo, konkrétní znak, konkrétní řetězec). Jak lze tušit, literály tedy můžeme dělit na celočíselné, racionální, znakové a řetězcové konstanty. 3.3.1 Celočíselné konstanty Celočíselné konstanty zapisujeme jako posloupnost číslic desítkové soustavy 0, 1, …, 9, přičemž tuto posloupnost může předcházet znak + nebo -. Jako příklady celočíselných konstant můžeme uvést 0, +1, -987. Posloupnost číslic nesmí obsahovat mezeru. Např. 98 765 je chápáno jako zápis dvou konstant 98 a 765. Hodnota celočíselné konstanty musí ležet v intervalu, odpovídajícím typu proměnné, které tuto konstantu přiřazujeme. Toto konstatování ilustruje následující příklad: short int a = 32767, b = 32770; int c, d; c=a+1; d=b+2; Jak je uvedeno v tab. 3.1, proměnná short int musí být z intervalu <-32768; +32767>. Proměnná a tuto podmínku splňuje, takže výsledek operace a+1 (uložený v proměnné c) je korektní (c=32768). V případě proměnné b tato podmínka splněna není. To se projeví tak, že namísto 32770 obsahuje b konstantu -32766 (při překročení horní hranice 32767 začínáme jakoby „nanovo“ od nejnižšího čísla intervalu, tj. -32768, -32767, -32766). Výsledkem operace b+2 (obsah proměnné d) tedy bude číslo -32764. 3.3.2 Racionální konstanty Racionální konstanty můžeme zapisovat buď v přímém tvaru nebo ve tvaru semilogaritmickém. Přímý tvar odpovídá klasickému zápisu desetinného čísla s tou výjimkou, že desetinná čárka je nahrazena tečkou. Je-li celočíselná část konstanty (část před desetinnou tečkou) nulová, můžeme nulu vynechat (tj. 0.3 a .3 jsou ekvivalentní zápisy). Je-li desetinná část konstanty (část za desetinnou tečkou) nulová, můžeme nulu opět vynechat (tj. 3.0 a 3. jsou ekvivalentní zápisy. Obě výše uvedená pravidla nelze samozřejmě aplikovat současně. Semilogaritmický tvar sestává z čísla v přímém tvaru (mantisa), které je násobeno mocninou desítky (mocnitel nazýváme exponentem). Číslo -7,68⋅10-2 (-7,68 je mantisa, -2 je exponent) v jazyce C vyjádříme jako -7.68E-2 nebo -7.68e-2. Posloupnost znaků, reprezentujících racionální konstantu, opět nesmí obsahovat mezeru. 28 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 3.3.3 Znakové konstanty Chceme-li zapsat konstantu, jejíž hodnota odpovídá zobrazitelnému znaku, vepíšeme tento znak mezi dva apostrofy (např. '@', '$', 'a', 'A', '5'). Tento způsob zápisu lze využít pro všechny znaky vyjma obráceného lomítka \, apostrofu ' a uvozovek ". Uvedené tři znaky musíme zapsat jako '\\', '\'' a '\"'. To znamená, že znaku, který odpovídá uvedeným třem výjimkám, musíme předřadit zpětné lomítko. 3.3.4 Řetězcové konstanty Řetězcovou konstantou rozumíme posloupnost zobrazitelných znaků (vyjma tří výše uvedených výjimek), uzavřenou mezi uvozovky. Pokud požadujeme, aby řetězec obsahoval výjimečný znak, musíme tomuto znaku opět předřadit zpětné lomítko. Práci s řetězci si vysvětlíme na jednoduchém programu. Jeho formulář sestává z modrého návěští Label1, jehož prostřednictvím zobrazujeme řetězec, a ze tří tlačítek. Tlačítko s nápisem Manželka kopíruje do globální proměnné char *c (ukazatel na řetězec) text Miluju Tě "lásko" jako reakci na jeho stisknutí (OnClick). Tlačítko s nápisem Obr. 3.4 Práce s řetězci Tchýně kopíruje do c text Vy ještě žijete, "maminko"?, přičemž znaky následující interpunkční čárku mají být zobrazeny na novém řádku. Text se zobrazí po stisku tlačítka s nápisem Ukaž. Popsaným funkcím odpovídá následující zdrojový kód: char *c; // globální ukazatel na řetězec // stisk tlačítka "Manželka" void __fastcall TForm1::wife(TObject *Sender) { c = "Miluju Tě \"lásko\""; } // stisk tlačítka "Tchýně" void __fastcall TForm1::MIL(TObject *Sender) { c = "Vy ještě žijete, \n\"maminko\"?"; } // stisk tlačítka "Ukaž" void __fastcall TForm1::show(TObject *Sender) { Label1->Caption = c; } Z výpisu je vidět, že uvozovky v řetězci předchází zpětné lomítko a že přechod na nový řádek uskutečňuje řídicí znak \n. Celý program je uložen v adresáři strings. 3.4 Výrazy a operátory Výrazem rozumíme konstrukci, která slouží k výpočtu hodnot. Výraz sestavujeme z operandů a operátorů. Roli operandu přitom může hrát proměnná, konstanta nebo volání Počítače a programování 2 29 funkce, která vrací hodnotu. Operátorů obsahuje jazyk C velké množství, takže se seznámíme jen s těmi nejdůležitějšími. Z hlediska počtu operandů můžeme operátory rozdělit na unární (jeden operand) a binární (dva operandy). Mezi unární operátory patří např. změna znaménka -a; - je unární operátor a a je operand. Mezi binární operátory patří např. součet a+b; + je binární operátor a a, b jsou operandy. Operátory můžeme dělit podle účelu použití na aritmetické, logické, relační, přiřazovací a další. V našem textu se přidržíme tohoto druhého členění. 3.4.1 Aritmetické konverze Aritmetické binární operátory mohou mít operandy různých typů. Dříve, než je provedena operace přikazovaná operátorem, musí dojít ke sjednocení typu operandů. Výsledek operace je pak stejného typu jako operandy po konverzi. Konverze typu operandů se řídí následujícími pravidly: 1 Všechny operandy typu char a short jsou převedy na int. Všechny operandy typu float jsou převedeny na double. 2.1 Je-li jeden z operandů typu double, je i druhý operand převeden na typ double. 2.2 Je-li jeden z operandů typu long, je i druhý operand převeden na typ long. 2.3 Je-li jeden z operandů typu unsigned, je i druhý operand převeden na typ unsigned. 3 Nenastane-li žádný z případů (2), oba operandy musejí být typu int. Aritmetické konverze si prakticky vyzkoušíme na jednoduchém programu: void __fastcall TForm1::aritmetic(TObject *Sender) { int a, b; double c, d; a = 4/5; b = 4/5.0; // - 1 // - 2 - c = 4/5; d = 4/5.0; } // - 3 // - 4 - • Na pravé straně řádku –1– jsou oba operandy celá čísla, tedy typ int. Proto rovněž výsledek musí být celé číslo. Protože celá část podílu 0.8 je nulová, bude a=0. Pokud bychom pravou stranu změnili např. na 8/5, byla by celá část podílu 1.6 jednotková a a=1. • Na pravé straně řádku –2– je první operand typu double a druhý typu int. Podle pravidla (2.1) jsou tedy oba operandy převedeny na typ double a výsledek je téhož typu. Nicméně výsledek typu double přiřazujeme proměnné typu int. Do b je tedy zkopírována pouze celá část výsledku (b=0) a desetinná část je ignorována. • Pravá strana řádku –3– je celočíselná (oba operandy jsou typu int). I když je tedy proměnná c deklarována jako racionální, bude c=0. • Pravá strana řádku –4– je racionální (jelikož jeden operand je typu double, je i druhý operand konvertován na tento typ). A jelikož proměnná d je rovněž deklarována jako typ double, bude platit d=0.8. 30 Fakulta elektrotechniky a komunikačních technologií VUT v Brně I když tedy z matematického hlediska jsou všechny čtyři řádky našeho programu identické, z pohledu jazyka C se liší. Na automatické konverze datových typů, které za nás dělá překladač jazyka C, proto musíme dávat pečlivý pozor. 3.4.2 Priorita operací Sestává-li výraz z více operátorů, jsou odpovídající operace prováděny v pořadí, daném prioritou těchto operátorů. Např. ve výrazu a+b*c bude nejdříve vyčíslen součin b*c (násobení má vyšší prioritu než sčítání), a až poté bude výsledek přičten k operandu a. Pokud se jedná o operace stejné priority (např. a+b+c), u naprosté většiny operací dochází k vyhodnocování zleva doprava (napřed je vyčíslen součet a+b, a poté je k výsledku přičten obsah proměnné c). U operací, kde je tomu naopak (vyhodnocování se děje zprava doleva) na to jmenovitě upozorníme. operátor popis prior. poznámka + unární plus 2 +a; provede se aritmetická konverze a, operátor vrátí získanou hodnotu - unární minus 2 -a; provede se aritmetická konverze a, operátor vrátí získanou hodnotu s opačným znaménkem * násobení 4 a*b; / dělení 4 a/b; dělení celočíselné (a i b typu int nebo char, b≠0) nebo racionální (alespoň 1 operand racionální) % zbytek po celočís.dělení 4 a%b; oba operandy int, b≠0, výsledkem 4%5 je 4 + binární plus 5 a+b; - binár. minus 5 a-b; Tab. 3.2 Aritmetické operátory Pořadí operací lze měnit pomocí kulatých závorek, jak ukazuje následující příklad: int i; double a, b; b= ( i= ( a= 2.718281828)); Jako první se vyhodnocuje obsah nejvnitřnější závorky, takže v prvém kroku je do racionální proměnné a uloženo Eulerovo číslo e = 2.718281828. V druhém kroku je vyhodnocován obsah nadřazené závorky, v níž do celočíselné proměnné i ukládáme celou část obsahu nejvnitřnější závorky – tedy hodnotu 2. V posledním kroku je celočíselný obsah nadřazené závorky konvertován na racionální číslo a uložen do racionální proměnné b; tato proměnná tedy obsahuje hodnotu 2.0. Jelikož operátor přiřazení = patří mezi operátory, které se vyhodnocují zprava doleva, lze řádek se závorkami přepsat do tvaru b=i=a=2.718281828, aniž by se jakkoli změnilo fungování programu. V dalších odstavcích této kapitoly se seznámíme s vybranými operátory. U každého operátoru vyjádříme celým číslem jeho prioritu. Čím menší bude hodnota čísla, tím větší bude priorita operace. Počítače a programování 2 31 3.4.3 Aritmetické operátory Aritmetické operátory jsou určeny k provádění operací s číselnými operandy. Přehled aritmetických operátorů je uveden v tab. 3.2. Jelikož se jedná o všeobecně známé operátory, nebudeme se jimi dále zabývat. 3.4.4 Relační operátory Relační operátory nám umožňují porovnávat obsah číselných proměnných, obsah proměnných znakových (char) nebo ukazatele na stejný datový typ. Dále nám relační operátory umožňují vzájemné porovnání znakové hodnoty char s číselnými hodnotami. Přehled relačních operátorů je uveden v tab. 3.3. operátor popis prior. == rovná se 8 != nerovná se 8 < menší než 7 > větší než 7 <= menší než nebo rovno 7 >= větší než nebo rovno 7 Tab. 3.3 Relační operátory Mají-li porovnávané proměnné číselný charakter, provede se nejdříve aritmetická konverze. Pak operátor vrátí hodnotu 1, pokud je relace splněna, a hodnotu 0, pokud splněna není. V případě ukazatelů je považována za vyšší hodnotu ta, která ukazuje na vyšší adresu. 3.4.5 Logické operátory Logické operátory realizují základní logické operace – negaci, konjunkci (logický součin) a disjunkci (logický součet). Operandy musejí být buď číselné nebo ukazatele. Přehled logických operátorů je uveden v tab. 3.4. operátor ! popis prior. negace 2 && konjunkce 12 || disjunkce 13 Tab. 3.4 Logické operátory Výsledek logické operace je vždy celočíselný (int). Výsledek pravda je reprezentován hodnotou 1, výsledek nepravda hodnotou 0. Operátor negace vrací hodnotu 1, je-li operand nulový, a vrací hodnotu 0, je-li operand nenulový. Operátor konjunkce vrací hodnotu 1, jsou-li oba operandy nenulové. V ostatních případech vrací operátor nulu. Operátor disjunkce vrací hodnotu 1, je-li alespoň jeden z operandů nenulový. 32 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Práci s dosud probranými operátory si vyzkoušíme na jednoduchém programu, jehož hlavní formulář je nakreslen na obr. 3.5. Do obrázku jsou rovněž vepsána jména jednotlivých komponentů, z nichž je formulář sestaven. Jména editačních řádků jsou napsána červeně (komponent Edit, záložka palety komponentů Standard), jména návěští modře (komponent Label, záložka Standard), jména tlačítek fialově (komponent Button, záložka Standard) jména rámů šedě (komponent Bevel, záložka Additional) a jména rádiových tlačítek černě (komponent RadioGroup, záložka Standard). CharEdit2 CharEdit1 CharBtn IntLab1 IntBtn IntLab2 Bevel1 TypeGroup RelGroup LogLab LogEdit Bevel2 CompBtn ConLabel ConResult DisLabel DisResult Obr. 3.5 Relační a logické operace Úkolem programu je vyčíslit výrazy (operand1 porovnání operand2) && operand3 (operand1 porovnání operand2) || operand3 a výsledek zobrazit pomocí návěští ConResult a DisResult. Ve výrazech můžeme volit typ porovnání (relační operátory v soustavě rádiových tlačítek RelGroup), hodnotu operand3 (editační řádek LogEdit), typ proměnných operand1 a operand2 (soustava rádiových tlačítek TypeGroup). Zvolíme-li položku Číslo-Číslo, je celočíselná hodnota zobrazená návěštím IntLab1 dosazena za operand1 a hodnota zobrazená IntLab2 za operand2. Vybere-li uživatel položku Číslo-Znak, je do operand1 dosazeno číslo zobrazené pomocí IntLab1 a do operand2 obsah editačního řádku CharEdit1. V případě výběru třetí položky, je do operand1 dosazen obsah CharEdit1 a do operand2 obsah CharEdit2. Začněme načítáním. Editační řádky CharEdit1 a CharEdit2 jsou nastaveny pro zadání jednoho znaku (proměnná editačního řádku MaxLength je v inspektorovi nastavena na 1). Pokud uživatel stiskne tlačítko Načti, znak zapsaný do řádku CharEdit1 je kopírován do globální znakové proměnné c1 a znak z CharEdit2 do globální c2 (viz odezva na událost OnClick tlačítka Načti – funkce read_char). Počítače a programování 2 33 Celočíselné operandy jsou generovány pomocí generátoru náhodných čísel a jsou ukládány do celočíselných globálních proměnných i1 a i2. Generátor náhodných čísel inicializujeme voláním funkce randomize v těle funkce init (odezva na událost OnCreate formuláře; randomize totiž musí proběhnout před prvním generováním náhodné hodnoty). Náhodné celé číslo z intervalu od nuly do RAND_MAX (standardní konstanta) je generováno voláním funkce rand. Pokud požadujeme, aby vygenerované číslo bylo z intervalu od –100 do +100, hodnotu vrácenou voláním funkce rand vynásobíme racionální konstantou 200.0/RAND_MAX a od součinu odečteme hodnotu 100. V dalším kroku převedeme vygenerovanou celočíselnou hodnotu na řetězec (standardní funkce IntToStr) a zobrazíme pomocí návěští IntLab1 a IntLab2. Popsané operace provádí funkce gen_int, která je samočinně volána jako reakce na událost OnClick tlačítka Generuj. Dosazení požadovaného operátoru a požadovaných typů a hodnot operandů do výše uvedených vztahů, jejich vyčíslení a zobrazení výsledků je úkolem funkce compute, která je volána jako odezva na událost OnClick tlačítka Počítej. Pomocí příkazů if (pokud je splněna podmínka v kulaté závorce, vykoná se následující příkaz – obsah složené závorky; jinak se tento příkaz ignoruje) postupně testujeme, které rádiové tlačítko je zaostřeno. Je-li černá kulička v nejhornějším políčku, je proměnná ItemIndex příslušné soustavy rádiových tlačítek nastavena na hodnotu 0. Pro kuličku v prostředním políčku obsahuje ItemIndex hodnotu 1, atd. Samotné vyčíslování výrazů a zobrazení výsledků by mělo být jasné z výpisu: char int c1 ='A', c2 ='b'; i1 = 12, i2 = -4; // znaky z editačních řádků // celá čísla z návěští // inicializace programu void __fastcall TForm1::init(TObject *Sender) { randomize(); } // načtení znaků z editačních řádků void __fastcall TForm1::read_char(TObject *Sender) { c1 = CharEdit1->Text[1]; c2 = CharEdit2->Text[1]; } // generování náhodných celých čísel void __fastcall TForm1::gen_int(TObject *Sender) { i1 = (200./RAND_MAX) * rand() - 100; // čísla od -100 do +100 i2 = (200./RAND_MAX) * rand() - 100; } IntLab1->Caption = IntToStr( i1); IntLab2->Caption = IntToStr( i2); // zobrazení // dosazení do obec.výrazů, zobraz.výsledku void __fastcall TForm1::compute(TObject *Sender) { int result; // result = operand1 porovnání operand2 if (TypeGroup->ItemIndex==0) // volba číslo-číslo { if (RelGroup->ItemIndex==0) {result = i1==i2;}; // rovnost if (RelGroup->ItemIndex==1) {result = i1<i2;}; // menší if (RelGroup->ItemIndex==2) {result = i1>i2;}; // větší } 34 Fakulta elektrotechniky a komunikačních technologií VUT v Brně if (TypeGroup->ItemIndex==1) // volba číslo-znak { if (RelGroup->ItemIndex==0) {result = i1==c1;}; // rovnost if (RelGroup->ItemIndex==1) {result = i1<c1;}; // menší if (RelGroup->ItemIndex==2) {result = i1>c1;}; // větší } if (TypeGroup->ItemIndex==2) // volba znak-znak { if (RelGroup->ItemIndex==0) {result = c1==c2;}; // rovnost if (RelGroup->ItemIndex==1) {result = c1<c2;}; // menší if (RelGroup->ItemIndex==2) {result = c1>c2;}; // větší } } ConResult->Caption = IntToStr( result && StrToInt( LogEdit->Text)); DisResult->Caption = IntToStr( result || StrToInt( LogEdit->Text)); Závěrem ještě několik poznámek: 1. Porovnání dvou číselných hodnot je pro nás přirozenou záležitostí. Jak však máme rozumět relačnímu výrazu, v němž vystupují znakové operátory či dokonce znakové a číselné operátory dohromady? Zde si stačí uvědomit, že každý znak je reprezentován v počítači kladným celým číslem (tzv. ASCI kódem znaku). V uvedených případech pak porovnáváme hodnotu kódu dvou znaků nebo hodnotu kódu znaku s číselnou hodnotou. 2. Jak se vkládají jednotlivá rádiová tlačítka do komponentu RadioGroup? V inspektoru objektů klikneme na proměnnou komponentu Items. Otevře se nám jednoduché editační okno, do jehož řádků vepisujeme řetězce, popisující jednotlivá tlačítka. Kolik řádků do editoru vepíšeme, tolik bude v RadioGroup tlačítek. 3. Poslední poznámka se vztahuje ke dvojici komponentů LogLab (návěští) a LogEdit (editační řádek). Po návěští požadujeme, aby se při jeho zaostření (kliknutí na návěští, stištění klávesové kombinace Alt+O) samočinně zaostřil editační řádek LogEdit (začne v něm blikat kurzor). Toho docílíme vepsáním jména editačního řádku do proměnné návěští FocusControl v inspektorovi objektů. Celý program je uložen v adresáři relations. 3.4.6 Bitové operátory Bitové operátory pracují s jednotlivými bity operandů. Operand je operátorem chápán jako pole bitů; operátor bere složku po složce (bit po bitu) a s každou postupně provede požadovanou operaci. Přehled bitových operátorů je uveden v tab. 3.5. Bitové operátory můžeme rozdělit do dvou skupin, a to na operátory posuvu a na bitové logické operátory. Mezi operátory posuvu patří posuv vlevo o1<<o2 a posuv vpravo o1>>o2. Operandy o1 a o2 musejí být buď celočíselné nebo znakové. Při posuvu vlevo posouváme jednotlivé bity o1 o o2 pozic doleva. o2 uvolněných míst vpravo je zaplněno nulami. o2 prvních bitů operandu o1, které jsme jakoby vysunuli mimo, je ztraceno. Při posuvu vpravo je situace podobná. Mělo by být zřejmé, že o2 (o kolik pozic posouváme jednotlivé bity) musí obsahovat nezáporné číslo, které je menší než počet bitů operandu o1; v opačném případě není výsledek definován. Počítače a programování 2 35 operátor popis prior. << bitový posuv vlevo 6 >> bitový posuv vpravo 6 & konjunkce po bitech 9 | disjunkce po bitech 11 ^ nerovnost po bitech 10 ~ bitový komplement 2 Tab. 3.5 Bitové operátory Bitové logické operátory jsou obdobou výše vysvětlených operátorů logických (nebitových) s tím rozdílem, že jsou na hodnoty celočíselných nebo znakových proměnných aplikovány bit po bitu. Výsledky bitové operace pro všechny možné kombinace hodnot bitů A0 a A1 jsou uvedeny v tab. 3.6. bit A0 bit A1 bit A0&A1 bit A0|A1 bit A0^A1 bit ~A0 0 0 0 0 0 1 0 1 0 1 1 1 1 0 0 1 1 0 1 1 1 1 0 0 Tab. 3.6 Příklady bitových operací 3.4.7 Operátory inkrementování a dekrementování Operátor inkrementování (++) zvýší obsah proměnné o jedničku, operátor dekrementování (--) obsah proměnné o jedničku sníží. Je-li operátor ++ (--) umístěn za proměnnou, je nejprve vyčíslen výraz, v němž se proměnná vyskytuje, a až poté je zvýšen (snížen) obsah této proměnné. Pokud operátor ++ (--) umístíme před proměnnou, nejprve je zvýšen (snížen) obsah této proměnné, a až poté dochází k vyčíslení výrazu, v němž se tato proměnná nachází. To dokumentuje následující příklad: double r1, r2, a1=5.1, a2=5.1, b=4.2; r1 = a1++ + b; r2 = ++a2 + b; Jak proměnná a1 tak proměnná a2 obsahují hodnotu 6,1 = 5,1 + 1 (výsledek inkrementování). Zatímco však v proměnné r1 je uloženo číslo 9,3 (nejprve byl vyčíslen součet 5,1 + 4,2, a až poté byla zvýšena hodnota a1 na 6,1), proměnná r2 obsahuje hodnotu 10,3 (nejprve bylo a2 inkrementováno na 6,1, a až poté byl vypočten celkový součet. Operátory ++ a -- mají prioritu 2. 36 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 3.4.8 Přiřazovací operátory Základní přiřazovací operátor je symbolizován znakem =. Máme-li výraz A1 = A2, je nejprve vyčíslena pravá strana operátoru, je konvertována na týž typ, jakého je A1, a výsledek je poté do A1 uložen. A1 a A2 musejí být buď aritmetické typy nebo ukazatele na stejné typy. Přiřazovacímu operátoru může být předřazen symbol aritmetické operace (součet +, rozdíl -, násobení *, dělení /, celočíselné dělení %), logické operace (logický součin &, logický součet |, logická nerovnost ^) nebo bitového posuvu (posuv vlevo <<, posuv vpravo >>). Potom např. zápis a<<=b je ekvivalentní zápisu a = a<<b. Přiřazovací operátory mají prioritu 15. 3.4.9 Kontrolní příklady Příklad 1. Navrhněte a sestavte program, který bude pomocí editačních řádků provádět operace s množinou čísel nebo znaků. Výsledek bude zobrazen pomocí kulatých tlačítek – „radio button“. Proveďte seřazení množiny A vzestupně nebo sestupně podle velikosti. Podobně s množinou B. Příklad 2. Navrhněte a sestavte program, který bude pomocí editačních řádků provádět operace s množinou čísel nebo znaků. Výsledek bude zobrazen pomocí kulatých tlačítek – „radio button“. Proveďte seřazení množiny A vzestupně nebo sestupně podle velikosti. Podobně s množinou B. Porovnejte obě množiny jak v původním tak upraveném tvaru. Vypište do editačního řádku společné prvky. Příklad 3. Navrhněte a sestavte program, který bude pomocí editačních řádků provádět operace s množinou čísel nebo znaků. Výsledek bude zobrazen pomocí kulatých tlačítek – „radio button“. Proveďte seřazení množiny A vzestupně nebo sestupně podle velikosti. Podobně s množinou B. Porovnejte obě množiny jak v původním tak upraveném tvaru. Vypište do editačního řádku společné prvky, vypište sjednocení obou množin. Novou množinu opět seřaďte podle velikosti, jak vzestupně tak sestupně. Příklad 4. Navrhněte a sestavte program, který bude pomocí editačních řádků provádět operace s množinou čísel nebo znaků. Výsledek bude zobrazen pomocí kulatých tlačítek – „radio button“. Proveďte seřazení množiny A vzestupně nebo sestupně podle velikosti. Podobně s množinou B. Porovnejte obě množiny jak v původním tak upraveném tvaru. Vypište do editačního řádku společné prvky, vypište sjednocení obou množin. Novou množinu opět seřaďte podle velikosti, jak vzestupně tak sestupně, nalezněte minimum množinu, maximum. Mezi prvky zakomponujte typy char, int, float, double. 3.4.10 Kontrolní otázky 1. 2. Co je to literál? Jaký je rozdíl mezi celočíselnou konstantou a racionální konstantou? 3. Jmenujte některé funkce, které převádějí znakovou konstantu na celočíselnou a zpět. 4. Co se rozumí pod pojmem výraz? Jaká jsou pravidla v aritmetických konverzích? 5. Jak lze upravit prioritu aritmetických operací? Jak se pracuje s logickými operandy? 6. Jmenujte nějaké aritmetické, logické a relační operátory. 7. Jaké znáte bitové operátory, k čemu by jste je použili? Počítače a programování 2 37 3.5 Příkazy Program je v podstatě posloupnost příkazů, které procesor postupně „provádí“. Pokud speciální příkaz nezpůsobí přenesení řízení do jiné části programu nebo pokud speciální příkaz nezpůsobí přerušení programu, jsou příkazy plněny sekvenčně. Nejjednodušším příkazem v jazyce C je prázdný příkaz. Řádek s prázdným příkazem obsahuje pouze středník, který tento příkaz ukončuje. Prázdný příkaz využijeme s výhodou tehdy, když potřebujeme přenést řízení do jiné části programu: if( err) goto end; c++; end: ; // pokud došlo k chybě (err!=0), skoč na end // jinak inkrementuj // prázdný příkaz, označený návěštím Konstrukce goto end přenáší řízení programu na řádek, který začíná konstrukcí end: (tzv. návěští). Pokud se objeví chyba (obsah proměnné err je nenulový), neinkrementujeme obsah proměnné c, protože skočíme na prázdný příkaz, umístěný na posledním řádku. Stejný význam jako prázdný příkaz má rovněž prázdný blok {}. O něco málo složitějším příkazem je příkaz výrazový. Mezi výrazové příkazy řadíme přiřazení, volání funkcí a podobné konstrukce. Jako příklad si uveďme: C++; A = cos( b) + c; S výrazovými příkazy se setkáváme od našeho prvního programu. Třetím elementárním příkazem je blok (složený příkaz). Složený příkaz použijeme v případě, kdy je jazykem C striktně vyžadováno volání jediného příkazu, avšak pro požadované fungování programu je nutno použít příkazů více. Složený příkaz vytvoříme z posloupnosti příkazů tak, že tuto posloupnost uzavřeme do složených závorek. Složený příkaz může obsahovat další složený příkaz (další blok). Potom mluvíme o bloku vnořeném a bloku nadřízeném. Kromě posloupnosti příkazů může složený příkaz obsahovat rovněž deklarace nových proměnných. Proměnné, které deklarujeme uvnitř bloku, existují pouze po dobu, kdy jsme v tomto bloku (neopustíme jeho složené závorky). Pokud uvnitř vnořeného bloku deklarujeme proměnnou stejného jména, jaké nese proměnná v bloku nadřízeném, lokální proměnná zastíní proměnnou globální. Práci s bloky si ukážeme na jednoduchém příkladu. Jeho formulář (obr. 3.6) bude sestávat z jednoduchého geometrického tvaru (komponent Shape, záložka palety Additional), ze zarážek Tvar a Barva (komponent CheckBox, záložka Standard), z tlačítka Změň (komponent Button, záložka Standard) a z návěští (Label, záložka Standard). Návěští zobrazuje počet stisknutí tlačítka Změň. Pokud uživatel stiskne tlačítko Změň, vygeneruje se pro zatrženou zarážku (její proměnná Checked má nenulový obsah) náhodné celé číslo od nuly do dvou. Obr. 3.6 Práce s bloky V případě zarážky Tvar změníme podle hodnoty tohoto čísla tvar objektu na kruh (n==0), elipsu (n==1) nebo zakulacený obdélník (n==2); tvar komponentu Shape určuje obsah jeho stejnojmenné proměnné Shape. 38 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Obrys objektu kreslíme perem. Barva obrysové čáry Color je proměnnou pera Pen a pero je proměnnou komponentu Shape. Je-li zatržena zarážka Barva, změníme barvu podle hodnoty náhodného čísla na modrou (n==0), zelenou (n==1) nebo červenou (n==2). Celý program sestává z globální celočíselné proměnné n, která slouží jako počítadlo stisknutí tlačítka Změň, a ze dvou funkcí. Funkce init je volána na začátku programu jako odezva na událost OnCreate formuláře. V těle této funkce inicializujeme generátor náhodných čísel a nastavujeme parametry pera pro obtažení obvodu geometrického tvaru. Tloušťka čáry je nastavena na tři body a barva čáry na žlutou. Všechny tři příkazy jsou sevřeny složenými závorkami do jediného bloku – tvoří tedy dohromady jediný složený příkaz. Funkce jump je volána jako odezva na stisknutí tlačítka Změň. V jejím těle nejprve testujeme, zda je zatržena zarážka povolující změnu tvaru ShapeBox. Je-li obsah její proměnné Checked nenulový, umožní příkaz if vykonat jedinou následující instrukci. V opačném případě je tato instrukce přeskočena. My však potřebujeme v případě zatržení zarážky vykonat více – musíme vygenerovat náhodné číslo od nuly do dvou a podle jeho hodnoty nastavit tvar geometrického objektu. Proto odpovídající instrukce sevřeme do bloku. Tento blok je vnořeným blokem bloku nadřízeného – samotného těla funkce jump. Všimněme si, že na prvním řádku bloku deklarujeme lokální n, které zastiňuje globální počítadlo stisků tlačítka Změň. Že je vše v pořádku, potvrzuje návěští, které korektně zobrazuje počet stisků. Celé číslo od nuly do dvou pro lokální n vytvoříme z racionálního čísla od nuly do tří (pravá strana operátoru přiřazení) odříznutím desetinné části (samočinná konverze na celočíselný formát int). Ošetření změny barvy je analogické k ošetření změny tvaru, a proto se jím nebudeme zabývat. Celý výpis programu následuje: int n = 0; // počítadlo změn // událost OnCreate formuláře void __fastcall TForm1::init(TObject *Sender) { randomize(); // inicializace generátoru náhod.čísel // parametry obrysu tvaru Shape->Pen->Width = 3; // tloušťka čáry 3 body Shape->Pen->Color = clYellow; // barva čáry žlutá } // událost OnClick tlačítka "Změň" void __fastcall TForm1::jump(TObject *Sender) { if( ShapeBox->Checked) // povolena změna tvaru { int n = (3./RAND_MAX) * rand(); // náhodné číslo - zastíní globál. n if( n==0) Shape->Shape = stCircle; if( n==1) Shape->Shape = stEllipse; if( n==2) Shape->Shape = stRoundRect; } if( ColorBox->Checked) // povolena změna barvy { int n = (3./RAND_MAX) * rand(); // náhodné číslo - zastíní globál. n if( n==0) Shape->Pen->Color = clBlue; if( n==1) Shape->Pen->Color = clGreen; Počítače a programování 2 39 if( n==2) Shape->Pen->Color = clRed; } } Label->Caption = IntToStr( ++n); // inkrementace počítadla Celý program je uložen v adresáři shapes. 3.5.1 Příkazy pro větvení programu Příkazy pro větvení programu bývají označovány jako příkazy podmíněné nebo výběrové. Podle toho, zda je či není splněna určitá podmínka, je totiž vybrána určitá alternativa dalšího pokračování programu. Mezi příkazy pro větvení patří náš dobrý známý if. Doposud jsme ho používali ve formě if( cond) make; (je-li splněna podmínka cond, vykonej příkaz make). Příkaz však lze rozšířit přidáním alternativy: if( cond) make_this; else make_that; (při splnění podmínky cond vykonej příkaz make_this, při nesplnění podmínky cond vykonej příkaz make_that). Dalším příkazem pro větvení je switch (přepínač). Přepínač používáme v případech, kdy chceme nabídnout více potenciálních možností pokračování programu nežli dvě. Toho lze samozřejmě dosáhnout kombinací několika příkazů if, avšak takové konstrukce bývají většinou dosti nepřehledné. Použití přepínače si předvedeme na předchozím programu, věnovaném geometrickým tvarům. První větvení (výběr mezi kruhem, elipsou a zaobleným obdélníkem) vytvoříme pomocí přepínače switch, druhé větvení (výběr mezi modrou, zelenou a červenou) necháme pro kontrast v původní podobě: if( ShapeBox->Checked) // povolena změna tvaru { switch( 3*rand()/RAND_MAX) { case 0: Shape->Shape = stCircle; break; case 1: Shape->Shape = stEllipse; break; case 2: Shape->Shape = stRoundRect; } } if( ColorBox->Checked) // povolena změna barvy { int n = (3./RAND_MAX) * rand(); if( n==0) Shape->Pen->Color = clBlue; if( n==1) Shape->Pen->Color = clGreen; if( n==2) Shape->Pen->Color = clRed; } V závorce za klíčovým slovem switch musí být výraz, který vrací celočíselnou hodnotu (v našem případě jsou všechny tři operandy celá čísla, a proto i operátor dělení pracuje jako dělení celočíselné). Program pak přeskočí na ten řádek uvedený slovem case, na němž je toto slovo následováno stejnou hodnotou. Žádné dvě hodnoty následující slovo case nesmějí být přirozeně stejné. Pokud závorka za slovem switch obsahuje hodnotu, která není k dispozici za žádným case, program skočí na řádek uvedený slovem default (alternativa default může být umístěna na kterémkoli řádku mezi návěštími case). Pokud možnost default chybí, nevykoná se nic. V našem programu by využití default mohlo vypadat následovně: 40 Fakulta elektrotechniky a komunikačních technologií VUT v Brně switch( 4*rand()/RAND_MAX) { case 0: Shape->Shape = stCircle; break; case 1: Shape->Shape = stEllipse; break; case 2: Shape->Shape = stRoundRect; break; default: Shape->Shape = stSquare; } Pro náhodná čísla od nuly do dvou se nic nemění. Pokud je vygenerováno vyšší číslo (koeficient ve výrazu za switch jsme zvýšili na 4), vykreslí se čtverec. Ačkoli se z hlediska uživatele jeví funkce konstrukce s přepínačem switch stejně jako funkce konstrukce sestavená z několika příkazů if, z hlediska vykonávání programu tomu tak není. V případě konstrukce z if na každém řádku testujeme, zda je splněna předepsaná rovnost pro n. Pokud je rovnost splněna, vykonáme následný příkaz a přecházíme na další řádek. Pokud rovnost splněna není, na další řádek přecházíme bez plnění příkazu. Na zmíněném dalším řádku pak testujeme další rovnost bez ohledu na to, zda předchozí rovnost splněna byla (a tudíž další rovnosti nemohou z principu nastat) či nikoli. V případě přepínače okamžitě skočíme na řádek s odpovídající hodnotou a vykonáme příkaz za dvojtečkou. Klíčové slovo break, které řádek ukončuje, pak způsobí okamžité vystoupení z těla konstrukce switch (proto na poslední řádek není třeba break dávat). 3.5.2 Příkazy pro cykly Příkazy pro cykly (pro vytváření smyček) používáme v situacích, kdy potřebujeme cyklicky vykonávat stejnou posloupnost instrukcí. V našich příkladech jsme se již setkali s jedním zástupcem skupiny příkazů pro cykly, a to s příkazem for. Obecně můžeme tento příkaz zapsat jako for(init,cond,update) make. Pomocí výrazu init provedeme počáteční nastavení pro spuštění cyklu. Výraz cond představuje podmínku, která musí být splněna (výraz musí mít nenulovou hodnotu); v opačném případě je cyklus ukončen. Pomocí výrazu update aktualizujeme obsah proměnných po ukončení každého cyklu. Slovo make reprezentuje příkaz, který vykonáváme tak dlouho, dokud platí podmínka cond. Pomocí cyklu for můžeme napsat jednoduchý program, který najde největší číslo ze zadané pětice racionálních čísel: double number[5] = {1.27E-2, 7.93E+1, 3.27E+0, 9.91E+1, 6.61E+1}; void __fastcall TForm1::sort( TObject *Sender) { double max = 1e-5; // nastavíme na co nejnižší hodnotu int n; // pro řízení cyklu for( n=0; n<5; n++) // od n=0 dokud n<5, na konci cyklu inkrementuj n if( number[n]>max) max=number[n]; } // pokud n-té číslo větší než max, ulož ho do max V našem případě vykonáváme příkaz if pětkrát. Od počáteční hodnoty n=0 pokračujeme sekvencí n=1,2,3,4; pak přestává platit podmínka n<5. Inkrementaci n po vykonání každého cyklu nám předepisuje aktualizace n++. Počítače a programování 2 41 Upozorněme, že ani jeden výraz v kulaté závorce za for není povinný. To znamená, že i zápis for(;;;) je v pořádku. Tento zápis představuje nekonečnou smyčku. Rovněž je zajímavé, že obsah proměnných vystupujících v kulaté závorce za for můžeme měnit z těla cyklu (v žádném případě to však nedoporučujeme). Dalším příkazem cyklu, který nám jazyk C dává k dispozici, je while(cond) make. Je-li podmínka cond vyhodnocena jako pravdivá, je vykonán příkaz make. Poté je zahájen druhý cyklus novým vyhodnocením podmínky cond, novým podmíněným vykonáním příkazu make. Cyklus končí v okamžiku, kdy je cond vyhodnocena jako nepravdivá. Použití cyklu while si můžeme ukázat na jednoduchém vyhledávání kořene rovnice y = x - 1. Na začátku uložíme do proměnné x hodnotu 0.05. Postupně budeme tuto hodnotu zvyšovat o přírůstek 0.1 tak dlouho, dokud nedosáhneme nejmenší funkční hodnoty (v absolutní hodnotě): 2 void __fastcall TForm1::while_zero(TObject *Sender) { double x = 0.05, // startovní hodnota proměnné y, // aktuální funkční hodnota min = 100.0; // vysoká "předchozí" funkč. hodnota } while( (y=fabs( x*x-1.0))<min) // pokud aktuál.hodnota < předchozí { min = y; // archivuj aktuální funkční hodnotu x += 0.1; // udělej další krok } x -= 0.1; // musíme o krok zpět Nutnost posunutí x o krok zpátky je dána tím, že cyklus končí v situaci, kdy aktuální funkční hodnota je větší než funkční hodnota předchozí. Musíme se tedy vrátit k předchozí hodnotě, která byla blíže nule. V uvedeném programu jsme poprvé použili funkci fabs. Funkce fabs je definována ve standardní matematické knihovně math, takže na začátek jednotky musíme umístit direktivu # include "math.h". Funkce fabs počítá absolutní hodnotu z racionálního čísla. Dále si všimněme obsahu kulaté závorky za klíčovým slovem while. V této závorce nejdříve (díky závorkám) vypočteme aktuální funkční hodnotu fabs( x*x-1.0), tuto hodnotu uložíme do proměnné y, a konečně porovnáme předchozí a aktuální funkční hodnotu pro podmínečné vykonání cyklu. Možnostmi efektivního zápisu zdrojového kódu je jazyk C proslulý. Posledním příkazem pro cykly je do. Základní odlišnost příkazu do od příkazu while spočívá v tom, že tělo cyklu nejdříve vykonáme a až poté testujeme splnění podmínky. Zatímco u příkazu while se může stát, že tělo cyklu není vykonáno ani jednou (protože ani napoprvé není splněna podmínka), u příkazu do je tělo nejméně jednou vykonáno vždy (nejdřív vykonávám, potom testuji). Stejný program pro jednoduché vyhledání minima naší funkce bychom mohli s použitím do vytvořit například takto: void __fastcall TForm1::do_zero(TObject *Sender) { double x = 0.05, // startovní hodnota proměnné y, // aktuální funkční hodnota min = 100.0; // vysoká "předchozí" funkč. hodnota 42 } Fakulta elektrotechniky a komunikačních technologií VUT v Brně y=fabs( x*x-1.0); // počítej aktuál.funkč.hodnotu do // BEZPODMÍNEČNĚ dělej: { min = y; // archivuj aktuální funkční hodnotu x += 0.1; // udělej další krok } while( (y=fabs( x*x-1.0))<min); // dokud není splněno x -= 0.1; // musíme o krok zpět Program, z něhož byly uvedené dvě funkce vyňaty, je uložen v adresáři minima. 3.5.3 Příkazy pro přenos řízení Jednotlivé příkazy, z nichž program v jazyce C sestává, jsou vykonávány postupně jeden za druhým. Pokud potřebujeme tuto přirozenou posloupnost příkazů přerušit a řízení programu přenést do jiného místa (tj. na jinou instrukci než tu, která následuje za instrukcí aktuální), využijeme k tomu příkazy pro přenos řízení. První dva příkazy pro přenos řízení – break a continue – můžeme použít pouze v tělech cyklů (for, while, do) nebo v těle přepínače (switch). Použití zmíněných příkazů kdekoli jinde znamená chybu. Použití příkazu break způsobí přerušení cyklu, z jehož těla je příkaz volán. Narazíme-li tedy uprostřed 5. iterace cyklu od nuly do devíti na break, druhá polovina 5. iterace a šestý až devátý cyklus nebudou vykonány. Příkaz continue ukončí právě probíhající iteraci cyklu a přejde k prvnímu příkazu další iterace. Narazíme-li tedy uprostřed 5. iterace cyklu od nuly do devíti na continue, druhá polovina 5. iterace nebude vykonána a cyklus bude pokračovat od počátku šesté iterace dále. Použití příkazů continue a break si ukážeme v programu, jehož formulář je nakreslen na obr. 3.7. Hlavním komponentem programu tabulka pro psaní a zobrazování řetězců (StringGrid, záložka Additional). Při stisku tlačítka Generuj je tato tabulka naplněna řetězci, které odpovídají náhodným číslům od hodnoty 0,0 do hodnoty 1,0. Při stisku tlačítka Menší než přečteme obsah sousedního editačního řádku. Racionální číslo, které odpovídá zadanému řetězci, postupně porovnáváme s hodnotami v jednotlivých sloupcích tabulky. Pokud narazíme ve sloupci na buňku, jejíž hodnota je menší než zadané racionální číslo, přepíšeme obsah této buňky symbolem A. Tím naše práce se sloupcem končí; o následující buňky sloupce se nestaráme a přecházíme k sloupci následujícímu. Při stisku tlačítka Větší než nahradíme obsah všech buněk tabulky, jejichž obsah je větší než obsah odpovídajícího editačního řádku, symbolem B. Obr. 3.7 Příkazy pro přenos řízení Počítače a programování 2 43 Při programování komponentu StringGrid musíme nastavit v inspektoru objektů počet sloupců tabulky ColCount=5, a počet řádků tabulky RowCount=8. Pokud si přejeme, aby mohl uživatel hodnoty do tabulky zapisovat i ručně (místo použití tlačítka Generuj), rozbalíme v inspektorovi proměnnou tabulky Options a goEditing nastavíme na true. Obsah jednotlivých buněk tabulky zapisujeme do složek pole Cells[i][j]. Index i označuje sloupce tabulky, index j její řádky. Šedá políčka odpovídají indexům 0. O převod racionálního čísla na řetězec a naopak se starají funkce FloatToStr a StrToFloat. Rolovací lišty se u tabulky objeví samy v případě, kdy zadáme rozměr komponentu menší než je rozměr tabulky. V našem případě plocha komponentu nestačí pro plné zobrazení 5 sloupců a 8 řádků tabulky, a tudíž se objevily lišty. Co se týká kódu, celý program sestává ze čtyř funkcí. První funkce, funkce init, je automaticky volána při vytváření formuláře. V jejím těle inicializujeme generátor náhodných čísel a plníme bílá políčka tabulky (cykly probíhají od 1 do ColCount a od 1 do RowCount): // při vytváření formuláře void __fastcall TForm1::init(TObject *Sender) { int i, j; // indexy pro cykly randomize(); } // inicializuj generátor náhod.čísel // naplň tabulku for( i=1; i<StringGrid->ColCount; i++) for( j=1; j<StringGrid->RowCount; j++) StringGrid->Cells[i][j] = FloatToStr( 0.1*random( 11)); Voláme-li nám dobře známou funkci random s celočíselným parametrem N, vrátí nám náhodnou celočíselnou hodnotu z intervalu od 0 do (N-1). Druhou funkcí je generate. Tělo funkce generate, která je volána jako odezva na stisk tlačítka Generuj, je shodné s tělem funkce init; jen volání randomize() chybí. Třetí funkcí je find_small. Volání této funkce je odezvou na stisk tlačítka Menší než. Přímo v deklarační části převedeme obsah sousedního editačního řádku na číslo, a poté se postaráme o to, aby toto číslo bylo z intervalu od 0 do 1. Ve dvojitém cyklu (vnější cyklus jde přes sloupce, vnitřní přes řádky) potom testujeme, zda je obsah buňky menší nežli obsah editačního řádku vedle tlačítka. Pokud tomu tak je, vepíšeme do odpovídající buňky symbol A a pomocí příkazu break ukončíme cyklus přes všechny řádky j sloupce i. Vnější cyklus nás přenese do sloupce i+1 a vnitřní cyklus začíná postupně zkoumat jeho řádky. Symbol A tedy ve sloupci nahrazuje pouze první hodnotu, splňující podmínku „menší než“. Odpovídající zdrojový kód následuje: // reakce na stisk tlačítka "Menší než" void __fastcall TForm1::find_small(TObject *Sender) { int i, j; // indexy pro cykly double value = StrToFloat( SmallEdit->Text); if( value<0.0) value = fabs( value); if( value>1.0) value = 1.0; SmallEdit->Text = FloatToStr( value); for( i=1; i<StringGrid->ColCount; i++) // je-li číslo záporné... // je-li číslo větší než 1... // zobraz opravenou hodnotu 44 } Fakulta elektrotechniky a komunikačních technologií VUT v Brně for( j=1; j<StringGrid->RowCount; j++) { // pokud tab. obsahuje znaky A, B if( (StringGrid->Cells[i][j]=='A') || (StringGrid->Cells[i][j]=='B')) goto fin; // přeruš testování // je-li hodnota menší if( StrToFloat( StringGrid->Cells[i][j])<value) { StringGrid->Cells[i][j] = 'A'; // nahraď ji znakem A break; // přeskoč zbytek řádků ve sloupci } } fin: ; // skok na prázdný příkaz Z výpisu je vidět, že funkce testuje přítomnost znaků A a B v naší tabulce (uživatel nestiskl tlačítko Generuj nebo písmena nepřepsal). V případě, kdy v tabulce narazíme na A nebo B, voláme goto fin (goto je příkaz, fin je tzv. návěští). Funkce goto je třetím příkazem k přenosu řízení. Řízení je přitom přeneseno na řádek, který je označen stejným návěštím, jaké následuje za klíčovým slovem goto (v našem případě fin). Návěští píšeme na začátek řádku, na který chceme pomocí goto přenést řízení, a doplníme ho dvojtečkou, za níž následuje příkaz (v našem případě je to příkaz prázdný). Návěští musí být jednoznačné (dva příkazy v téže funkci nelze označit stejným návěštím). Pomocí uvedené konstrukce můžeme skákat v jen rámci jedné funkce – skok z těla jedné funkce do těla funkce druhé je nepřípustný. V naší funkci skáčeme při prvním výskytu písmena na návěští fin, čímž vykonávání této funkce ukončíme. Tělo funkce find_high, která je odezvou na stisk tlačítka Větší než, je podobné: // reakce na stisk tlačítka "Větší než" void __fastcall TForm1::find_high(TObject *Sender) { int i, j; // indexy pro cykly double value = StrToFloat( HighEdit->Text); if( value<0.0) value = fabs( value); if( value>1.0) value = 1.0; HighEdit->Text = FloatToStr( value); } // je-li číslo záporné... // je-li číslo větší než 1... // zobraz opravenou hodnotu for( i=1; i<StringGrid->ColCount; i++) for( j=1; j<StringGrid->RowCount; j++) { // pokud tab. obsahuje znaky A, B if( (StringGrid->Cells[i][j]=='A') || (StringGrid->Cells[i][j]=='B')) goto fin; // přeruš testování // je-li hodnota menší, rovna if( StrToFloat( StringGrid->Cells[i][j])<=value) continue; StringGrid->Cells[i][j] = 'B'; // NENAHRAZUJ číslo písmenem } fin: ; // skok na prázdný příkaz Ve výpisu vidíme, že namísto situace „větší než“ vyhledáváme situaci komplementární (menší, rovno). Pokud není splněna podmínka „větší než“ (je splněno menší, rovno), voláme příkaz continue, který přeruší vykonávání aktuálního cyklu (tzn. že na uložení znaku B do tabulky nedojde). Index cyklu, v němž je příkaz continue volán (tzn. j) je zvýšen o jedničku a je Počítače a programování 2 45 testován další řádek buňky. Pokud komplementární podmínka splněna není (platí podmínka „větší než“), continue není vykonán a obsah odpovídající buňky je nahrazen znakem B. Popsaný program je uložen v adresáři grid. Pokud uživatel program spustí a najede myší na jedno ze tří tlačítek (Generuj, Menší než, Větší než), objeví se bublina se stručnou nápovědou. Bublinovou nápovědu aktivujeme nastavením proměnné ShowHint komponentu (v našem případě tlačítek) na hodnotu true a vepsáním řetězce, který chceme v bublině zobrazit, do proměnné Hint komponentu. 3.5.4 Kontrolní příklady Příklad 1. Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování a dalších prostředků vykreslovat na kreslicí ploše okna průběh základních goniometrických funkcí, například sin, cos, tan. Příklad 2. Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování a dalších prostředků vykreslovat na kreslicí ploše okna průběh základních goniometrických funkcí, například sin, cos, tan. Tento program doplňte o vykreslování dalších funkcí jako např. exp(x), ln(x), log10(x), sin(x)/x, 10x, xn, n!, sin(ωt+ϕ). Příklad 3. Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování a dalších prostředků vykreslovat na kreslicí ploše okna průběh základních goniometrických funkcí, například sin, cos, tan. Tento program doplňte o vykreslování dalších funkcí jako např. exp(x), ln(x), log10(x), sin(x)/x, 10x, xn, n!, sin(ωt+ϕ). Přidejte možnost zadání mezí nezávislé proměnné, přidejte volbu pro automatické vyhledání maximálního měřítka závislé veličiny. Příklad 4. Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování a dalších prostředků vykreslovat na kreslicí ploše okna průběh základních goniometrických funkcí, například sin, cos, tan. Tento program doplňte o vykreslování dalších funkcí jako např. exp(x), ln(x), log10(x), sin(x)/x, 10x, xn, n!, sin(ωt+ϕ). Přidejte možnost zadání mezí nezávislé proměnné, přidejte volbu pro automatické vyhledání maximálního měřítka závislé veličiny. Doplňte prostředky k nalezení extrémů funkce, nalezení a zobrazení derivace funkce. 3.5.5 Kontrolní otázky 1. Jakou funkci ve zdrojových textech C/C++ má středník? 2. Jaké prostředky můžete použít k přenesení řízení v těle programu? 3. Co jsou to výrazový příkaz, blok? K čemu se v programování v jazyce C/C++ používají? 4. Co je to deklarace, proč se používá , kde je možné deklaraci zařadit ve zdrojovém textu? 5. Jak je v programu C/C++ možné kombinovat událostmi řízení toku s dalšími prostředky přenesení řízení? 6. Vyjmenujte základní příkazy pro podmíněné i výběrové větvení programu. 7. Jaké znáte příkazy cyklu, vyjmenujte odlišnosti v jejich použitích. 8. Kde se může použít příkaz continue? 46 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 3.6 Funkce Při pohledu na zdrojový kód předchozího programu si nejde nevšimnout, že se některé jeho části ve výpisu vícekrát opakují. Kvůli tomu je zdrojový kód zbytečně dlouhý a málo přehledný. Uvedený problém můžeme vyřešit pomocí funkcí. Pokud procesor při vykonávání programu narazí na volání funkce, je řízení přeneseno na první instrukci v těle funkce. Postupně je vykonávána posloupnost instrukcí, které tvoří funkci. Jakmile procesor vykoná instrukci poslední, vrátí se řízení programu na řádek, který následuje po řádku s voláním funkce. Opakuje-li se ve zdrojovém kódu programu vícekrát stejná sekvence instrukcí, je vhodné utvořit z této sekvence tělo funkce a namísto opakování stejné sekvence příkazů volat funkci. Z hlediska vykonávání programu se nic nemění. Jeho zdrojový kód je však kratší a přehlednější. Dosud jsme se setkali pouze s funkcemi, jejichž rámec (hlavičku a následný prázdný blok) za nás deklaroval Builder. Tyto funkce navíc byly svázány s určitou událostí, která vyprovokovala jejich volání, a proto jsme se nemuseli o volání starat. Vraťme se k předchozímu příkladu a pokusme se zamezit tomu, aby se ve zdrojovém kódu dvakrát vyskytoval dvojitý cyklus pro plnění tabulky náhodnými čísly. Cyklus s plněním tedy umístíme do těla své vlastní funkce: void to_grid( void) { int i, j; } // indexy pro cykly // naplň tabulku for( i=1; i<Form1->StringGrid->ColCount; i++) for( j=1; j<Form1->StringGrid->RowCount; j++) Form1->StringGrid->Cells[i][j] = FloatToStr( 0.1*random( 11)); Identifikátor to_grid je jméno naší funkce, vytvořené podle pravidel pro tvorbu identifikátorů. Jméno funkce musí být jedinečné. V závorce za jménem funkce následuje seznam vstupních parametrů (jedná se o data, která přicházejí do funkce zvnějšku a která uvnitř funkce zpracováváme). Klíčové slovo void říká, že naše funkce žádné vstupní parametry nemá. Hodnoty, jimiž tabulku plníme, totiž náhodně generujeme uvnitř funkce. Slovo void před jménem funkce říká, že funkce nevrací žádnou hodnotu (na rozdíl např. od sin(x), která vrací sinus argumentu x). Náhodně vygenerované hodnoty totiž přímo ukládáme do buněk Cells[i][j] tabulky StringGrid. Při podrobnějším pohledu na tělo naší vlastní funkce to_grid si všimneme, že před identifikátor StringGrid jsme navíc přidali Form1->. Důvod je prostý. Dříve byla skutečnost, že tabulka patří formuláři našeho programu, vyjádřena řetězcem TForm1:: před identifikátorem funkce v její hlavičce (tj. v hlavičce, generované samočinně Builderem). Tato předpona u naší vlastní funkce chybí, a proto musíme sounáležitost formuláře Form1 a tabulky StringGrid vyjádřit v těle funkce. Těla funkcí init a generate, v nichž je tabulka daty plněna, pak budou vypadat následovně: Počítače a programování 2 47 void __fastcall TForm1::init(TObject *Sender) { randomize(); // inicializuj generátor náhod.čísel to_grid(); // vyplň tabulku náhodnými čísly } void __fastcall TForm1::generate(TObject *Sender) { to_grid(); // vyplň tabulku náhodnými čísly } Při volání funkce to_grid je obsah závorek za jménem prázdný (funkce nemá žádné vstupní parametry). Navíc řádek s voláním funkce to_grid neobsahuje žádný přiřazovací příkaz (funkce nevrací žádnou hodnotu). Podívejme se pozorněji na nový zápis funkce generate. V jejím těle pouze voláme naši novou funkci to_grid. Je tedy zřejmé, že těla funkcí generate a to_grid jsou shodná. Rozumnější tedy bude nevytvářet to_grid a místo ní volat v těle funkce init funkci generate: void __fastcall TForm1::generate(TObject *Sender) { int i, j; // indexy pro cykly // naplň tabulku for( i=1; i<StringGrid->ColCount; i++) for( j=1; j<StringGrid->RowCount; j++) StringGrid->Cells[i][j] = FloatToStr( 0.1*random( 11)); } void __fastcall TForm1::init(TObject *Sender) { randomize(); // inicializuj generátor náhodných čísel generate( Sender); } Všimněme si, že volaná funkce generate má jeden vstupní parametr. Tento parametr je typu ukazatel na TObject a je reprezentován identifikátorem Sender. Stejný vstupní parametr má ovšem i funkce init, v jejímž těle funkci generate voláme. Proto vstupní parametr funkce init prostě předáme funkci generate. Rozdíl mezi deklarací funkce a mezi voláním funkce spočívá v tom, že při deklarování musíme specifikovat typ vstupních parametrů a typ vracené hodnoty, zatímco při volání dosazujeme za vstupní a výstupní parametry skutečně existující proměnné. Při volání funkce init tedy můžeme do závorky za identifikátorem dosadit A (ukazatel na TObject). A stejné A je potom předáno funkci generate (substitucí za Sender). Nyní, když umíme funkci předat parametr, pokusíme se zjednodušit zápis funkcí find_high a find_small. Opět, velká část jejich těl je shodná. Společný kód tedy přeneseme do těla funkce test, kterou pak budeme ve funkcích find_high a find_small volat namísto původního kódu. Prvním vstupním parametrem této funkce bude obsah editačního řádku, který se nachází buď vedle tlačítka Menší než (SmallEdit) nebo vedle tlačítka Větší než (HighEdit). Tento vstupní parametr (text jednoho z editačních řádků) označíme identifikátorem edit. Typ vstupního parametru edit musí být stejný jako typ proměnné Text editačního řádku. V nápovědě Builderu zjistíme, že se jedná o řetězec AnsiString. 48 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Druhým vstupním parametrem funkce test bude ukazatel na TObject – proměnná Sender. Důvody k zavedení tohoto druhého vstupního parametru poznáme za chvíli. Funkce test bude vracet racionální číslo, odpovídající řetězci v daném editačním řádku (řetězec reprezentuje hodnotu od nuly do jedné). V těle funkce navíc zajistíme, aby toto číslo z požadovaného intervalu skutečně bylo. Dále prohledáváme obsah tabulky StringGrid a zjišťujeme, zda se v ní náhodou nenacházejí písmena A nebo B. Pokud zjistíme výskyt písmene, malým okénkem (obr. 3.8) uživatele informujeme o tom, že obsah tabulky bude přepsán nově vygenerovanými náhodnými hodnotami. Jakmile uživatel okénko s informací uzavře, zavoláme funkci generate( Sender), která nové hodnoty vygeneruje a obsah tabulky jimi přepíše (nyní vidíme, že druhý parametr funkce test musel být zaveden proto, aby jeho hodnota mohla být předána funkci generate). Pomocí prvého příkazu break ukončíme cyklus přes všechny řádky aktuálního sloupce for( j…). Pomocí druhého příkazu break pak vyskočíme z cyklu přes všechny sloupce tabulky for( i…). Konečně musíme říci, jakou hodnotu (typu double) má funkce test vrátit. K tomuto účelu slouží klíčové slovo return, které musí být následováno identifikátorem proměnné, jejíž obsah funkce vrátí. Konstrukcí return value tedy říkáme, že racionální hodnota vrácená funkcí je uložena v proměnné value. Celá funkce test tedy vypadá následovně: double test( AnsiString edit, TObject *Sender) { int i, j; // indexy pro cykly double value = StrToFloat( edit); // číslo z editačního řádku if( value<0.0) value = fabs( value); if( value>1.0) value = 1.0; // je-li číslo záporné… // je-li číslo větší než 1… for( i=1; i<Form1->StringGrid->ColCount; i++) // přes sloupce for( j=1; j<Form1->StringGrid->RowCount; j++) // přes řádky { // test na písmena v tabulce if( (Form1->StringGrid->Cells[i][j]=='A') || (Form1->StringGrid->Cells[i][j]=='B') ) { // v tabulce je písmeno !!! MessageDlg("Tabulka obsahuje písmena.\n Bude vygenerován nový obsah", mtInformation, TMsgDlgButtons() << mbOK, 0); Form1->generate( Sender); // ↑ okénko se zprávou break; // ↑ generování nového obsahu tabulky } break; } return value; // funkce vrátí obsah proměnné value } Podívejme se podrobněji na vytvoření okénka se zprávou. Jak je vidět z výpisu, používáme k tomuto účelu standardní funkci Builderu MessageDlg. Prvním parametrem této funkce je řetězec, který se má objevit uvnitř okénka. Pomocí řídicího znaku \n (konec řádku) jsme řetězec rozdělili na dva řádky. Obr. 3.8 Message dialog: Okénko se zprávou. Počítače a programování 2 49 Pomocí druhého parametru určíme typ okénka. Různé typy okének odpovídají různým konstantám mtXxx (mt je zkratkou Message Box Type). Okénko pro zobrazení obecné informace (v záhlaví okna text Information, vlevo nahoře bublina s modrým i uvnitř) odpovídá konstantě mtInformation a je nakresleno na obr. 3.8. Další konstanty odpovídající jiným typům okének jsou uvedeny v nápovědě Builderu. Prostřednictvím třetího parametru funkce MessageDlg můžeme do okénka vkládat různá standardní tlačítka (OK, Yes, No, Cancel…). Různá tlačítka odpovídají různým konstantám mbXxx (mb je zkratkou Message Box Button, Xxx odpovídá textu v tlačítku). Konstrukce TMsgDlgButtons()<<mbOK slouží k převodu typu proměnné mbOK na typ, vyžadovaný funkcí MessageDlg. Za poslední parametr můžeme dosadit celočíselný index, který spojí okénko s tématem nápovědy Windows. Pokus pro svůj program nápovědu nevytváříme, za tento parametr obvykle dosazujeme nulu. Celý popsaný program je uložen v adresáři grid_func. 3.6.1 Rekurze funkcí Rekurzním voláním funkce označujeme situaci, kdy ve svém těle funkce volá sebe samu. To znamená, že k novému volání funkce dochází ještě před dokončením jejího běhu. V jazyku C je rekurzní volání funkcí povoleno. Hloubka rekurze (počet současně platných volání) není přitom teoreticky omezena. Praktické omezení je dáno velikostí zásobníku3. Rekurzi funkcí si ukážeme na programu pro výpočet faktoriálu. Jak dobře víme, faktoriál čísla N spočítáme jako součin f(N) = N ⋅ ⋅ (N-1) ⋅ (N-2) ⋅ … ⋅ 3 ⋅ 2 ⋅ 1. S využitím rekurze můžeme tento součin přepsat do tvaru f(N) = N ⋅ f(N-1), takže odpovídající funkce bude v jazyku C vypadat následovně: Obr. 3.9 Formulář // faktoriál unsigned long faktorial( unsigned int n) { if( n) // pro nenulové n return n*faktorial( n-1); // rekurze else return 1; // pro nulové n } programu pro výpočet faktoriálu Formulář našeho programu je nakreslen na obr. 3.9. Kromě známých komponentů se nám ve formuláři programu objevil jeden komponent nový, komponent nahoru-dolů (UpDown, záložka Win32). Vizuálně tento komponent vypadá jako dvojice tlačítek s šipkou nahoru a šipkou dolů. Číselná hodnota, která je uložena v proměnné komponentu Position, je při stisku šipky nahoru (dolů) zvýšena (snížena) o číselnou hodnotu, uloženou v proměnné Increment 3 Zásobník (stack) je speciální typ paměti, z níž vybíráme jako první tu hodnotu, kterou jsme do paměti uložili jako poslední. Do zásobníku se samočinně ukládají návratové adresy při volání funkcí. Pokud tedy rekurzivně voláme nějakou funkci, při každém novém volání se návratová adresa uloží do zásobníku. Pokud funkci v rekurzi voláme mnohokrát, ukládáme návratovou adresu do zásobníku mnohokrát, a zásobník může přetéci (vyčerpáme jeho kapacitu). 50 Fakulta elektrotechniky a komunikačních technologií VUT v Brně (obvykle je tato proměnná nastavena na 1). Interval, v jehož mezích se může Position měnit, je dán hodnotami Min a Max. Komponent nahoru-dolů používáme ve svém programu pro jednoduché zvyšování nebo snižování číselné hodnoty, zobrazené v editačním řádku. Pokud tedy uživatel klikne na komponent nahoru-dolů, změní se hodnota v proměnné Position, a proto se musí současně změnit rovněž text v editačním řádku. Z tohoto důvodu vytvoříme funkci update_edit, která bude automaticky volána jako reakce na událost OnClick komponentu nahoru-dolů: // kliknutí na šipky... void __fastcall TForm1::update_edit(TObject *Sender, TUDBtnType Button) { // synchronizace šipek a editačního řádku Edit1->Text = IntToStr( UpDown1->Position); } Samotný výpočet faktoriálu zahájíme stisknutím tlačítka Počítej. Toto tlačítko definujeme jako přednastavené (proměnnou default tlačítka nastavíme v inspektoru na true). Přednastavené tlačítko je aktivováno vždy, když stiskneme v otevřeném formuláři klávesu Enter (tlačítko je automaticky pořád zaostřeno). V metodě ošetřující stisk tlačítka (událost OnClick) nejprve synchronizujeme editační řádek (obsah proměnné Text) a komponent nahoru-dolů (obsah proměnné Position). Tím ošetřujeme případ, kdy uživatel vepíše do editačního řádku nové číslo a nepoužije přitom šipek. Při stlačení šipek by pak byla nová hodnota odvíjena od obsahu proměnné Position komponentu nahoru-dolů a nikoli od obsahu editačního řádku (řetězec v proměnné Text). Jakmile jsou komponenty synchronizovány, voláme funkci faktorial, která vypočte požadovanou funkční hodnotu a výsledek zobrazí pomocí návěští: // stisk tlačítka "Počítej" = výpočet faktoriálu void __fastcall TForm1::compute(TObject *Sender) { // synchronizace editačního řádku a šipek UpDown1->Position = StrToInt( Edit1->Text); Label1->Caption = IntToStr( faktorial( UpDown1->Position)); } // faktoriál zobrazen návěštím Celý zdrojový text programu je uložen v adresáři faktorial. 3.6.2 Funkce main Každý program napsaný v jazyce C musí obsahovat právě jednu funkci s identifikátorem main. Tato funkce hraje roli hlavního modulu programu, z něhož se volají ostatní funkce. Pokud píšeme v Builderu program pro operační systém Windows, funkce main je před námi skryta. Pokud si přejeme její volání poodhalit, musíme otevřít soubor se jménem našeho projektu a příponou cpp. V případě našeho programu pro výpočet faktoriálu se jedná o faktorial.cpp: #include <vcl.h> #pragma hdrstop // vazba na knihovnu komponentů Visual Component Library USERES("faktorial.res"); USEFORM("form.cpp", Form1); // tzv. resources programu // vazba na hlavní formulář aplikace WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int) { // speciální funkce main pro Windows try { // ↓ inicializuj program (připrav jeho běh) Počítače a programování 2 51 Application->Initialize(); // ↓ vytvoř hlavní formulář programu Application->CreateForm(__classid(TForm1), &Form1); Application->Run(); // smyčka programu – zpracování událostí } catch (Exception &exception) // zpracování výjimek (chyb při běhu) { // ↓ zobraz okénko s hlášením chyby Application->ShowException(&exception); } return 0; } Výše uvedený zdrojový kód místo nás generuje Builder a v naprosté většině případů není třeba do něj zasahovat. Jedná se v podstatě o obecný rámec programu, v němž se program připraví ke spuštění, vytvoří se hlavní formulář programu a rozeběhne se nekonečná smyčka. V této smyčce vyčkáváme, až se objeví nějaká událost (stisknutí tlačítka, aktivace okna, …). Události ošetřujeme vlastními funkcemi, jejichž jména definujeme prostřednictvím inspektoru a jejichž tělo vepisujeme do vygenerovaného rámu (hlavička plus prázdný blok). Smyčka skončí v okamžiku, kdy se objeví událost WM_QUIT (zpráva operačního systému – Windows Message – o ukončení programu). 3.6.3 Kontrolní příklady Příklad 1. Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování a dalších prostředků zobrazovat na kreslicí ploše analogový ručkový přístroj. Pomocí komponentů CheckBox a RadioButton zobrazte pomocí ručky přístroje okamžitou hodnotu, například funkcí sin, cos se zadaným počtem period. Příklad 2. Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování a dalších prostředků zobrazovat na kreslicí ploše analogový ručkový přístroj. Pomocí komponentů CheckBox a RadioButton zobrazte pomocí ručky přístroje okamžitou hodnotu, například funkcí sin(ωt+ϕ) , cos(ωt+ϕ) se zadaným počtem period, amplitudy. Příklad 3 Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování a dalších prostředků zobrazovat na kreslicí ploše analogový ručkový přístroj. Pomocí komponentů CheckBox a RadioButton zobrazte pomocí ručky přístroje okamžitou hodnotu, např. Asin(ω1t+ϕ1), Bcos(ω2t+ϕ2), Csin(ω1t+ϕ1).cos(ω2t+ϕ2), Dsin(ω1t+ϕ1)sin(ω2t+ϕ2), Ecos(ω1t+ϕ1)cos(ω2t+ϕ2) se zadaným počtem period, amplitudy. Příklad 4 Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování a dalších prostředků zobrazovat na kreslicí ploše analogový ručkový přístroj. Pomocí komponentů CheckBox a RadioButton zobrazte pomocí ručky přístroje okamžitou hodnotu, např. Asin(ω1t+ϕ1), Bcos(ω2t+ϕ2), Csin(ω1t+ϕ1).cos(ω2t+ϕ2), Dsin(ω1t+ϕ1)sin(ω2t+ϕ2), Ecos(ω1t+ϕ1)cos(ω2t+ϕ2) se zadaným počtem period, amplitudy, doplňte přepínač rozsahů, jak ručním tak automatickým. 3.6.4 Kontrolní otázky 1. Kdy a za jakých okolností je vhodné používat volání funkcí? 2. Jak by se mohly rozčlenit typy funkcí v jazykové konvenci C/C++? 3. Jaký je rozdíl mezi deklaracví a voláním funkce v C/C++? 52 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 4. Co myslíme tím, když mluvíme o rekurzivním volání funkce, jaké toto volání má výhody? 5. Proč se musí používat v jazyce C/C++ funkce Main a kde se nalezne v aplikaci pro MS Windows, prostředí Bilderu? 3.7 Více o datových typech Se základními typy dat jsme se seznámili již v kapitole 3.2. Zavedli zde jsme číselné datové typy int (celé číslo), float a double (racionální čísla) a znakový typ char. Dále jsme si uvedli, že k těmto datovým typům můžeme přiřadit tzv. modifikátory (přívlastky) short, long, signed a unsigned. Dále jsme se při našem seznamování s jazykem C setkali s prázdným datovým typem void. Proměnnou prázdného typu je přitom zakázáno deklarovat. Typu void jsme využívali k deklaraci funkcí, které nevracely hodnotu, a umísťovali jsme ji do seznamu formálních parametrů (do kulatých závorek za identifikátor funkce), když daná funkce žádné formální parametry neměla. Rovněž jsme se již seznámili s ukazatelem na proměnnou daného typu a s polem (složky pole jsou tvořeny proměnnými jednoho konkrétního typu). V následujících odstavcích se seznámíme se složitějšími datovými konstrukcemi. 3.7.1 Struktury Strukturu si můžeme představit jako pole, které sestává z položek různých typů (což u pole bylo nemyslitelné). Struktura tak může sdružovat proměnné různých typů, které společně popisují jediný objekt (např. osobu, zařízení, poštovní adresu) a které proto k sobě logicky patří. Představme si, že chceme naprogramovat jednoduchý telefonní seznam dívek, s nimiž se známe4. Každou dívku popíšeme jejím jménem (řetězec), jejím věkem (celé číslo) a jejím telefonním číslem (opět řetězec). Odpovídající struktura tedy může vypadat následovně: typedef struct { AnsiString unsigned int AnsiString } girl; tgirl // záznam o dívce name; age; phone; // jméno // věk // telefonní číslo // proměnná typu tgirl Klíčové slovo typedef říká, že deklarujeme svůj vlastní datový typ. Na vlastní datový typ se potom můžeme v programu odkazovat stejným způsobem, jako se odkazujeme na datové typy základní (int, float, double, char). Našim vlastním datovým typem je v uvedeném příkladě struktura (klíčové slovo struct). Na tento náš vlastní datový typ se můžeme odvolávat prostřednictvím identifikátoru tgirl (písmenem t na počátku slova říkáme, že se jedná o identifikátor typu – jedná se o zvykovou záležitost, která není v žádném případě vyžadována). Ve složené závorce za 4 Do seznamu můžeme samozřejmě také vkládat telefonní čísla svých kamarádů, protože program netestuje pohlaví vkládané osoby. Něco takového snad softwarově ani nelze realizovat… Počítače a programování 2 53 identifikátorem typu pak následují deklarace jednotlivých složek struktury (řetězce jméno a telefonní číslo, kladné celé číslo věk). Identifikátorem girl za složenou závorkou deklarujeme proměnnou girl našeho vlastního typu tgirl. Řetězce v naší struktuře jsou typu AnsiString. Jedná se o typ implementovaný firmou Borland, s nímž se můžeme setkat nejen v Builderu, ale například i v Delphi (jedná se o stejný vývojový nástroj jako je Builder, avšak zdrojový kód je sestavován v jazyce Object Pascal). V našem telefonním seznamu budeme mít větší počet dívek (jinak by nemělo smysl program psát). Proto struktury typu tgirl uspořádáme do pole: tgirl int int my_girls[10]; count; index; // řetězec našich dívek // počet dívek v databázi // index zobrazené dívky Jak je vidět z výše uvedené deklarace, pole my_girls může sestávat až z desíti složek typu tgirl. Složky pole jsou tedy homogenní (všechny jsou typu tgirl), avšak samotný typ tgirl je nehomogenní strukturou (sestává z řetězců a celého čísla). Pole my_girls deklarujeme v našem programu jako globální proměnnou, a to společně s celočíselnými proměnnými count (počet dívek v seznamu) a index (index té složky pole, jejíž hodnoty jsou zobrazeny ve formuláři programu. Formulář programu je nakreslen na obr. 3.10. Sestává ze známých komponentů, jejichž jména (proměnná Name v inspektoru) jsou do obrázku vepsána. Návěští jsou propojena s odpovídajícími editačními řádky prostřednictvím své proměnné FocusControl; editační řádek lze tedy jednoduše zaostřit stiskem klávesové kombinace Alt + podtržené písmeno návěští. Do formuláře jsme umístili pět tlačítek. Při stisku tlačítka (událost OnClick) je volána funkce, jejíž jméno odpovídá jménu příslušného tlačítka (funkce forward je volána při stisku tlačítka ForwardBtn, funkce backward při stisku BackwardBtn, funkce add při stisku AddBtn, funkce erase při stisku EraseBtn a funkce finish při stisku FinishBtn). Tlačítko Přidej je deklarováno jako přednastavené (při stištění klávesy Enter je toto tlačítko samočinně vybráno; proměnnou Default tohoto tlačítka jsme museli v inspektoru nastavit na true). Kromě uvedených pěti funkcí obsahuje program funkci init (reakce na událost formuláře OnActivate) a show (není přímo svázána s žádnou událostí). NameLabel NameEdit AgeLabel AgeEdit PhoneLabel PhoneEdit AddBtn Bevel2 ForwardBtn BackwardBtn FinishBtn EraseBtn Bevel1 Obr. 3.10 Formulář programu pro práci se strukturami 54 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Úkolem funkce init je naplnit napevno danými daty nulovou položku pole my_girls, nastavit počítadlo položek v databázi na jedničku (count=1, v databázi jsou údaje o jedné osobě), zaostřit tuto položku (index=0, pole je indexováno od nuly) a obsah položky zobrazit v editačních řádcích formuláře: // +++ inicializace hlavního formuláře void __fastcall TForm1::init(TObject *Sender) { my_girls[0].name = "Erika"; // nultou položku pole... my_girls[0].age = 19; // ...naplníme údaji... my_girls[0].phone = "+420-776-121324"; // ...o zvolené dívce } index = (count=1) - 1; show(); // v poli 1 dívka, index dívky je 0 // zobrazení údajů ve formuláři Jak je zřejmé z výpisu, k jednotlivým složkám struktury přistupujeme prostřednictvím jejich identifikátorů. Identifikátor složky struktury předchází identifikátor struktury jako celku, který je oddělený od jména složky tečkou. Jelikož struktura je složkou pole my_girls, pracujeme s identifikátorem struktury stejně, jako jsme zvyklí pracovat s identifikátorem pole. Dále upozorněme na inicializaci celočíselných proměnných count a index na předposledním řádku těla funkce. Výraz count=1 je umístěn v závorce, a proto má toto přiřazení na daném řádku nejvyšší prioritu (vykoná se jako první). Jakmile dojde na operaci odčítání, je proměnná count již inicializována, a do proměnné index je uložena hodnota count-1. V posledním kroku jsou údaje o první dívce v databázi zobrazeny v editačních řádcích formuláře: void show( void) // vepíše údaje z pole do editačních řádků... { if( count>0) // ...pokud je počet dívek nenulový { Form1->NameEdit->Text = my_girls[ index].name; Form1->AgeEdit->Text = IntToStr( my_girls[ index].age); Form1->PhoneEdit->Text= my_girls[ index].phone; } } Postupně zde bereme jednotlivé složky „zaostřené dívky“ (její index je uložen v globální proměnné index) a přenášíme je do odpovídajících editačních řádků. Jelikož funkce show není svázána pomocí své předpony s našim formulářem, musíme jméno formuláře vždy předřadit jménu editačního řádku. Díky tomu, že jsme svůj telefonní seznam koncipovali jako pole struktur, můžeme velmi snadno naprogramovat listování seznamem dopředu (tlačítko >>, funkce forward) a dozadu (tlačítko <<, funkce backward); stačí nám jednoduše se posouvat po složkách pole. Po jednoduchém testu, zda jsme nedosáhli poslední (první) složky pole, zvýšíme (snížíme) index o jedničku a nově zaostřenou položku zobrazíme v editačních řádcích formuláře: // +++ stisknutí tlačítka ">>" void __fastcall TForm1::forward(TObject *Sender) { if( (index+1)<count) // pokud nejsme na poslední dívce v poli... { index++; // ...posuneme se o jednu dívku dopředu a... show(); // ... zobrazíme údaje o ní ve formuláři } Počítače a programování 2 55 } // +++ stisknutí tlačítka "<<" void __fastcall TForm1::backward(TObject *Sender) { if( (index-1)>=0) // pokud nejsme na první dívce v poli... { index--; // ...posuneme se o jednu dívku dozadu a... show(); // ... zobrazíme údaje o ní ve formuláři } } V opačném případě se nic neděje. Dále se podívejme na přidávání nových údajů do databáze. V těle funkce add přečteme obsah editačních řádků a naplníme jimi složky struktury v nejnižší volné složce pole my_girls (jednotlivé struktury jsou do pole vkládány za sebou, aby mezi dvěma obsazenými složkami pole nikdy nebylo volné místo; první volná pozice se tedy nachází za poslední obsazenou složkou pole). Nejnižší volná složka pole má index count (počet dívek v databázi; index nejvyšší obsazené složky pole je o jedničku nižší, jelikož pole je indexováno od nuly). Dále proměnnou count inkrementujeme (přibyl nám další údaj) a novou dívku zaostříme (index = count-1). Voláním funkce show údaje o nové dívce vepíšeme do editačních řádků. Jelikož tyto údaje jsou v řádcích vepsány uživatelem, jen si tímto způsobem kontrolujeme korektnost uložení nových dat. Funkci add tedy odpovídá následující zdrojový kód: // +++ přidání dívky do databáze void __fastcall TForm1::add(TObject *Sender) { // ↓ obsahem edit.řádků naplníme neobsazenou položku pole my_girls[count ].name = NameEdit->Text; my_girls[count ].age = StrToInt( AgeEdit->Text); my_girls[count++].phone = PhoneEdit->Text; // ↑ počet položek v databázi zvýšíme o 1 index = count - 1; // ↑ zaostříme novou dívku show(); // údaje o ní zobrazíme ve formuláři } Mazání dívky z databáze provedeme tak, že všechny následné složky pole posuneme o jednu pozici níže. Mažeme-li třetí dívku, přepíšeme třetí složku pole složkou čtvrtou, čtvrtou složku pole pátou složkou, atd. Nevýhoda tohoto přístupu spočívá v tom, že jím nelze smazat poslední dívku z telefonního seznamu. Obr. 3.11 Hlášení při pokusu Mazání rovněž neumožňuje smazat všechny dívky smazat jedinou položku seznamu (databáze nemůže zůstat prázdná). Pokud je v seznamu jediná položka, při pokusu o její smazání se objeví okénko se zprávou (obr. 3.11) a žádost o smazání položky je ignorována. Mazání dívek z databáze je realizováno následujícím zdrojovým kódem: // +++ vymazání dívky z databáze void __fastcall TForm1::erase(TObject *Sender) { int n; if( count>1) // jsou-li v databázi alespoň dvě dívky... { for( n=index; n<count-1; n++) // mazanou dívku přepíšeme... 56 Fakulta elektrotechniky a komunikačních technologií VUT v Brně my_girls[ n] = my_girls[ n+1]; count--; show(); } // ...následujícími dívkami // snížíme počet dívek o 1 // aktualizujeme obsah formuláře } else // jinak zobrazíme okénko s upozorněním MessageDlg("Alespoň jednu dívku\nbys mohl v programu nechat", mtWarning, TMsgDlgButtons() << mbYes, 0); Konečně poslední funkcí je ošetření stlačení tlačítka Konec. Jelikož s tímto úkolem jsme se již setkali, uveďme bez komentáře pouze odpovídající zdrojový kód: // +++ stisknutí tlačítka "Konec" void __fastcall TForm1::finish(TObject *Sender) { Close(); // ukončení aplikace } Kompletní zdrojový text našeho telefonního seznamu je uložen v adresáři girls. 3.7.2 Unie Unie představují možnost, jak do jedné proměnné ukládat hodnoty různých typů. Jako příklad si uveďme union u1 {int i; double d; char c;} U = {'u'}; Deklarace začíná klíčovým slovem union. Identifikátor za klíčovým slovem (v našem případě u1) nazýváme jmenovkou unie. Ve složených závorkách za jmenovkou následuje seznam složek unie. Výše uvedená deklarace zavádí unii typu u1 a proměnnou U typu u1. Do proměnné U můžeme přitom ukládat celočíselné hodnoty (int), racionální hodnoty (double) a hodnoty znakové (char). Jednotlivé složky unie se překrývají (v naší unii může být uloženo buď i nebo d nebo c). To znamená, že počáteční adresa všech tří našich složek je stejná (adresa unie je adresou všech jejích složek). Co se týká rozsahu unie, ten je dán rozsahem její největší složky. 3.7.3 Výčtové typy Výčtové (enumerativní) typy připomínají svou deklarací struktury a unie. Tak např. enum karty {sedm, osm, devet, deset, kluk, dama, kral, eso} sada_1, sada_2; zavádí výčtový typ karty a dvě proměnné sada_1, sada_2 tohoto typu. Výčtový typ karty sestává z osmi konstant sedm, osm, …, eso. Konstantě sedm přitom odpovídá celočíselná hodnota nula, konstantě osm hodnota jedna a konstantě eso hodnota sedm. Složkám výčtového typu ovšem můžeme přiřadit i jiné celočíselné hodnoty: enum karty {sedm=7, osm, devet, deset, kluk=20, dama=30, kral=40, eso=50} sada_1, sada_2; Složka sedm je rovna sedmi, složka osm je rovna osmi (neuvedeme-li vedle identifikátoru přiřazovací příkaz, je hodnota konstanty vypočtena inkrementováním hodnoty konstanty předchozí), kluk dvacíti, atd. Počítače a programování 2 57 Práci s výčtovými typy si vysvětlíme na jednoduchém programu, jehož formulář je nakreslen na obr. 3.12. Program slouží k bodování jednotlivých dní v týdnu. Dny jsou popsány enumerativní globální proměnnou the_day výčtového typu days enum days { mo, tu, we, th, fr, sa, su} the_day; Sedm konstant mo až su (pondělí až neděle) odpovídá číselným hodnotám nula až šest. Jednotlivé dny v týdnu bodujeme podobným způsobem, jaký známe ze školy. Zvoleným bodovým intervalům od nuly do sta přiřazujeme přitom jednak známku (písmena A až D) a jednak obrázek. Přehled bodových intervalů, odpovídajících známek a obrázků je uveden v tab. 3.7. Obr. 3.12 Program pro práci s výčtovými typy V programu jsou hranice bodových intervalů popsány enumerativní globální proměnnou the_mark výčtového typu marks enum marks {C = 50, B = 70, A = 90} the_mark; Aktuální den zadáváme pomocí komponentu UpDown (dvě šipky napravo od editačního řádku Den). Editační řádek Den slouží pouze ke zobrazení anglické zkratky dne. Název dne do něj uživatel nemůže zapsat, pokud proměnnou Enabled našeho editačního řádku Edit1 nastavíme v inspektoru na false. Obsah proměnné Position komponentu UpDown, která je inkrementována při stisku šipky nahoru a dekrementována při stisku šipky dolů, přímo odpovídá kódu dne v týdnu (pro pondělí mo je Position=0, pro neděli su je Position=6). O svázání komponentu UpDown s editačním řádkem Edit1 (vedle návěští Den) se stará funkce new_day, která je samočinně volána jako odezva na událost OnClick komponentu UpDown // změna dne v řádku při kliknutí na UpDown void __fastcall TForm1::new_day(TObject *Sender, TUDBtnType Button) { // za nedělí nic není if( UpDown1->Position>su) UpDown1->Position=su; set_color( the_day=UpDown1->Position); // nastavení barvy } switch( the_day) { case mo: Edit1->Text case tu: Edit1->Text case we: Edit1->Text case th: Edit1->Text case fr: Edit1->Text case sa: Edit1->Text case su: Edit1->Text } = = = = = = = "mo"; "tu"; "we"; "th"; "fr"; "sa"; "su"; break; break; break; break; break; break; break; // změna dne // v editačním řádku Na prvním řádku těla funkce (příkaz if) testujeme, zda není hodnota proměnné Position komponentu UpDown větší než neděle (su=6). Pokud tomu tak je, do Position uložíme poslední den týdne (su=6). V dalším kroku se postaráme o správnou barvu návěští se známkou (viz níže) a podle hodnoty proměnné the_day vybereme řetězec pro editační řádek Edit1. Ještě si všimněme volání funkce pro nastavení barvy návěští set_color – v kulaté závorce nejprve uložíme do proměnné the_day obsah proměnné Position komponentu UpDown, a poté předáme tuto hodnotu funkci set_color jako vstupní parametr. 58 Fakulta elektrotechniky a komunikačních technologií VUT v Brně interval bodů obrázek známka 0 - 49 bubble.bmp D 50 - 69 pufftext.bmp C 70 - 89 saturn.bmp B 90 - 100 whirl.bmp A Tab. 3.7 Známkování Jak jsme již naznačili, s dny v týdnu souvisí barva návěští Label3. Toto návěští je umístěno pod tlačítkem Známka a zobrazuje známku. Známka odpovídá počtu bodů, který uživatel vepíše do editačního řádku Edit2. Tento editační řádek se nachází vedle návěští Body (návěští je s editačním řádkem propojeno prostřednictvím proměnné návěští FocusControl). Pokud je bodovaný den dnem pracovním (mo až fr), má známka modrou barvu. Bodujeme-li víkendový den (sa, su), má známka barvu červenou. O správnou barvu návěští Label3 se stará funkce // přepínání barvy návěští podle dnů void set_color( days a_day) { if( (a_day==sa) || (a_day==su)) // pro sobotu a neděli Form1->Label3->Font->Color = clRed; // je návěští červené else // pro pracovní dny Form1->Label3->Font->Color = clBlue; // je návěští modré } Vstupním parametrem funkce je proměnná a_day výčtového typu days. Je-li dnem sobota (a_day==sa) nebo neděle (a_day==su), je barva (Color) písma (Font) návěští se známkou (Label3) v našem formuláři (Form1) nastavena na červenou (clRed). V opačném případě (pracovní dny) je barva návěští nastavena na modrou (clBlue). Nyní se podívejme na způsob, jakým je počet bodů pro konkrétní den převeden na známku. Nejprve převedeme řetězec, který uživatel vepsal do editačního řádku Edit2, na celočíselnou hodnotu. Podle toho, do jakého intervalu počet bodů padne (viz tab. 3.7), vybereme odpovídající známku a odpovídající obrázek. Výběr obrázku má na starosti následující funkce: // přepínání obrázku podle známky void set_picture( marks a_mark) { if( a_mark<C) Form1->Image1->Picture->LoadFromFile( "bubble.bmp"); else if( a_mark<B) Form1->Image1->Picture->LoadFromFile( "pufftext.bmp"); else if( a_mark<A) Form1->Image1->Picture->LoadFromFile( "saturn.bmp"); else Form1->Image1->Picture->LoadFromFile( "whirl.bmp"); } S obrázkem pracujeme prostřednictvím komponentu Image1 (záložka Additional palety komponentů). Komponent Image1 umístíme ve formuláři doprostřed rámečku Bevel1 (záložka Additional) a vhodně upravíme jeho rozměry (Width=145, Height=129). Obsah proměnné Center komponentu Image1 nastavíme v inspektoru na true (obrázek je potom na výše specifikované ploše komponentu centrován). Počítače a programování 2 59 Klíčovou proměnnou komponentu Image1 je proměnná Picture. Do této proměnné lze totiž uložit grafický objekt všech běžných formátů (bitová mapa, meta-soubor, ikona) Grafický objekt můžeme do proměnné Picture načíst s diskového souboru voláním funkce LoadFromFile. V případě našeho programu jsme nakopírovali do adresáře s jeho soubory čtyři bitové mapy (viz tab. 3.7). Je-li hodnota známky menší nežli C (méně než 50 bodů), načítáme bitovou mapu bubble.bmp pomocí funkce LoadFromFile do vnitřní proměnné Picture komponentu Image1, který je umístěn v našem okně Form1. Pokud hodnota známky není menší nežli C, testujeme, zda není menší než B nebo než A. Funkce set_picture je volána v rámci funkce new_mark. Funkce new_mark je deklarována jako odezva na událost OnClick tlačítka Známka. // změna známky při stisku tlačítka "Známka" void __fastcall TForm1::new_mark(TObject *Sender) { the_mark = StrToInt( Edit2->Text); set_picture( the_mark); // vykreslení obrázku // vypsání známky if( the_mark<C) Label3->Caption = "D"; else if( the_mark<B) Label3->Caption = "C"; else if( the_mark<A) Label3->Caption = "B"; else Label3->Caption = "A"; } V prvém kroku převedeme řetězec, který uživatel vepsal do editačního řádku Edit2 (a vyjádřil tak bodové hodnocení daného dne) na celočíselnou hodnotu. Tuto hodnotu uložíme do globální proměnné the_mark enumerativního typu marks. Celočíselné hodnocení the_mark je poté předáno výše popsané funkci set_picture, která vybere bitovou mapu, odpovídající bodovému hodnocení dne. V následující soustavě tří příkazů if potom podle počtu bodů vybíráme znak vyjadřující známku, která byla přiřazena dni uživatelem. Závěrem upozorněme, že proměnná default tlačítka Známka byla v inspektoru nastavena na true, aby při každém stisknutí klávesy Enter byla automaticky generována událost tlačítka OnClick. Celý popsaný program je uložen v adresáři marks. 3.7.4 Dynamické proměnné Dosud jsme při své práci využívali tzv. statických proměnných. Pokud byly tyto proměnné deklarovány jako globální (mimo těla funkcí), byla jim při spuštění programu vyhrazena odpovídající část paměti, a tento paměťový prostor nebylo po celý běh programu možno využít k jiným účelům. Pokud se jednalo o lokální proměnnou, rezervace paměťového místa se vztahovala k období, kdy byly vykonávány instrukce z těla dané funkce. Pokud program pracuje s velkými objemy dat, které se využívají jen po určitou krátkou dobu, je využití statických proměnných neefektivní (velký paměťový prostor je pro proměnné rezervován i v době, kdy s proměnnou nepracujeme). Popsaný problém můžeme vyřešit využitím tzv. dynamických proměnných. V okamžiku, kdy potřebujeme s proměnnou pracovat, přidělíme („alokujeme“) pro ni v paměti počítače odpovídající paměťový prostor (pomocí funkce malloc), přidělený prostor naplníme daty a ty zpracováváme. Jakmile jsou data zpracována, uvolníme (release) paměťový prostor voláním funkce free. 60 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Práci s dynamickými proměnnými si vysvětlíme na příkladě, který bude soužit jako databáze našich oblíbených CD. Hlavní formulář programu je nakreslen na obr. 3.13. MainMenu1 SingerLabel SingerEdit TitleLabel TitleEdit BackwardBtn AddBtn EraseBtn ForwardBtn Obr. 3.13 Práce s dynamickými proměnnými Každý disk bude popsán jménem interpreta a názvem alba. Jelikož se oba uvedené řetězce vztahují k jednomu objektu, sdružíme je do struktury: typedef struct tcd // záznam o disku { ShortString singer; // jméno zpěváka ShortString title; // název alba } cd; Ve výše uvedené struktuře vystupují řetězce typu ShortString, které se liší od nám známých AnsiString jen svou délkou (maximálním možným počtem znaků). Zatímco 31 AnsiString může sestávat až z 2 znaků, délka ShortString je omezena na 256 znaků. To ovšem pro naše účely bohatě stačí. Údaje o jednotlivých discích budeme ukládat do dynamických proměnných a adresy těchto proměnných budeme ukládat do pole. Zatímco v našem programu, který sloužil jako telefonní seznam přátel, byly do pole ukládány celé struktury, zde ukládáme pouze adresy. Tomu odpovídá deklarace globálních proměnných programu: tcd int int *my_cds[10]; count; index; // pole ukazatelů na disky // počet disků v databázi // index zobrazeného disku Pole my_cds může mít maximálně deset složek; to nám říká řetězec [10]. Jednotlivé složky pole budou ukazatele (to vyjadřujeme hvězdičkou) na struktury typu tcd. Při spuštění programu (událost OnActivate jeho hlavního formuláře) vytvoříme dynamickou strukturu a naplníme ji daty jednoho konkrétní disku. Ukazatel na tuto strukturu uložíme do pole my_cds. Tím zajistíme, že pole nebude nikdy prázdné (a ušetříme si tak práci s testováním, zda máme k dispozici potřebná data). Výše uvedené činnosti jsou realizovány následující funkcí: // inicializace programu void __fastcall TForm1::init(TObject *Sender) { tcd *a_cd; // ukazatel na první disk a_cd = (tcd *) malloc( sizeof( tcd)); // alokace místa pro první disk SingerEdit->Text= ((*a_cd).singer = "Kamil Střihavka"); // zpěvák TitleEdit->Text = ((*a_cd).title = "VooDoo"); // album count = 1; index = 0; // v databázi zatím jeden údaj Počítače a programování 2 } my_cds[ index] = a_cd; 61 // adresa prvého disku do databáze Na prvním řádku těla funkce deklarujeme pomocnou dynamickou proměnnou, do níž bude možno uložit informace o disku (tj. strukturu tcd). V prvém kroku musíme v paměti počítače vyhradit pro ukládaná data místo. O toto vyhrazení paměťového prostoru se postará funkce void *malloc(size_t size) Parametrem této funkce je údaj o velikosti paměťového místa, které si přejeme v paměti vyhradit pro naše CD. Velikost místa v bajtech zjistíme voláním funkce sizeof, jejímž parametrem je identifikátor typu proměnné, pro kterou chceme paměťové místo vyčlenit (v našem případě struktura popisující disk tcd). Funkce malloc vrací ukazatele (hvězdička před jejím identifikátorem) na prázdný typ void. Jelikož my však pracujeme s ukazateli na proměnné typu tcd, musíme provést tzv. přetypování. Pokud před identifikátor funkce malloc předřadíme (tcd *), dáváme najevo, že adresa, kterou funkce malloc vrací, neukazuje na prázdný typ ale na naši strukturu typu tdc. Po vykonání druhého řádku funkce init máme v pomocné proměnné a_cd uloženu adresu paměťového místa, které sestává z dostatečného počtu bajtů pro uložení naší struktury. Vyhrazené paměťové místo je v tuto chvíli prázdné. V dalším kroku ho tedy musíme naplnit daty. Pokud chceme do dynamické proměnné a (tj. do paměťového bloku, jehož adresu uchováváme v a) uložit číslo b, musíme použít konstrukci (*a)=b. V případě dynamické struktury píšeme její identifikátor doplněný zleva hvězdičkou (ukládáme do struktury, jejíž adresa je v proměnné a_cd) a pomocí tečkové konvence poté určíme složku struktury, jíž jsou data určena. Po vykonání třetího a čtvrtého řádku těla funkce init tedy bude v a_cd adresa struktury, jejíž první složka obsahuje řetězec Kamil Střihavka a druhá složka řetězec VooDoo. Na třetím a čtvrtém řádku máme složené příkazy. Nejdříve se provede přiřazovací příkaz v závorce (řetězec naplní odpovídající složku dynamické struktury), a poté je daný řetězec předán odpovídajícímu editačnímu řádku (objeví se ve formuláři aplikace). V posledním kroku uložíme adresu struktury a_cd do první složky pole my_cds. Nyní, když umíme vytvořit dynamickou proměnnou, když ji umíme naplnit daty a když ukazatel na příslušný paměťovým blok umíme uložit do složky pole, snadno zvládneme přidávání nových disků do seznamu. Disk do seznamu přidává funkce add, která je volána jako odezva na stisk tlačítka AddBtn (obr. 3.13): // přidání disku do databáze void __fastcall TForm1::add(TObject *Sender) { tcd *a_cd; // ukazatel na první disk } a_cd = (tcd *) malloc( sizeof( tcd)); a_cd->singer = SingerEdit->Text; a_cd->title = TitleEdit->Text; my_cds[ index=count++] = a_cd; // alokace místa pro první disk // adresa prvého disku do databáze Srovnáme-li těla funkcí add a init, jistě si všimneme, že konstrukce (*a_cd).singer, která vystupuje v init, je ve funkci add nahrazena konstrukcí a_cd->singer. Oba uvedené typy zápisu jsou ekvivalentní, druhý typ zápisu je však jednodušší a přehlednější. 62 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Šipka z druhého typu zápisu není pro nás ničím novým. Setkávali jsme se s ní vždy, když jsme pracovali s nějakou proměnnou nebo nějakou funkcí komponentu, který jsme do našeho programu umístili z palety komponentů. Jednotlivé komponenty jsou totiž dynamickými datovými objekty, které obsahují složky (stejně jako struktury), a navíc mohou v sobě zahrnovat i funkce. Příkladem odkazu na složku komponentu je SingerEdit->Text, příkladem odkazu na funkci komponentu je OpenDialog1->Execute() – volání funkce Execute komponentu OpenDialog1. Vytváření volného paměťového místa pro komponent a jeho plnění daty (ta jsou většinou brána z inspektora objektů) dělá C++ Builder automaticky, a proto se o tyto úkony nemusíme starat. To platí ale pouze pro komponenty, které vkládáme do naší aplikace z palety komponentů. Rovněž listování seznamem disků lze naprogramovat velmi snadno: // posuv dopředu void __fastcall TForm1::forward(TObject *Sender) { if( (index+1)<count) // pokud nejsme na posledním { index++; // ...posuneme se o jedno CD show(); // ... zobrazíme údaje o něm } } // posuv dozadu void __fastcall TForm1::backward(TObject *Sender) { if( (index-1)>=0) // pokud nejsme na prvním CD { index--; // ...posuneme se o jedno CD show(); // ... zobrazíme údaje o něm } } CD v poli... dopředu a... ve formuláři v poli... dozadu a... ve formuláři Funkce forward je svázána s událostí OnClick tlačítka ForwardBtn, funkce backward je svázána se stejnou událostí tlačítka BackwardBtn (obr. 3.13). Zobrazení řetězců v editačních řádcích po vykonání posuvu má na starosti funkce: // zobrazení řetězců v edit.řádcích void show( void) { Form1->SingerEdit->Text = my_cds[ index]->singer; Form1->TitleEdit->Text = my_cds[ index]->title; } Posledním tlačítkem, jehož stisknutí je třeba ošetřit, je EraseBtn. Funkce erase, která je s popsanou událostí svázána, má za úkol uvolnit z paměti počítače tu dynamickou strukturu, jejíž řetězce jsou právě vypsány v editačních řádcích. Zdrojový kód funkce vypadá takto: // vymazání údajů o CD void __fastcall TForm1::erase(TObject *Sender) { int n; if( count>1) // jsou-li v databázi alespoň dvě CD... { free( my_cds[ index]); for( n=index; n<count-1; n++) // mazané CD přepíšeme... my_cds[ n] = my_cds[ n+1]; // ...následujícími CD count--; // snížíme počet CD o 1 Počítače a programování 2 } 63 show(); // aktualizujeme obsah formuláře } else // jinak zobrazíme okénko s upozorněním MessageDlg("Alespoň jedno CD\nbys mohl v programu nechat", mtWarning, TMsgDlgButtons() << mbYes, 0); Stejně jako v případě telefonního seznamu našich dívek nejprve testujeme, zda po vyjmutí disku z databáze zůstane v poli my_cds alespoň jedna položka. Pokud by tomu tak nebylo (program přechází do sekce else), zobrazíme okénko s varováním a žádost o vymazání položky ignorujeme. Je-li výše uvedená podmínka splněna, je volána funkce free, jejímž parametrem je ukazatel na dynamickou proměnnou. Funkce free uvolní paměťové místo, které bylo dynamické proměnné vyhrazeno. Toto uvolněné místo je pak k dispozici ostatním aplikacím. Ukazatel nadále obsahuje původní adresu – jedná se však o adresu „domu, který byl odstřelen“, aby se uvolnila „parcela pro novou stavbu“. Odvoláme-li se na takovou adresu, dojde k chybě. Abychom možnost vzniku popsané chyby eliminovali, posouváme všechny ukazatele, které inkriminovaný ukazatel následovaly, o jednu pozici níže. Adresa „odstřeleného domu“ je tak přepsána adresou budovy, která ještě existuje. Tím máme ošetřena všechna tlačítka na ploše formuláře. Většina programů pro MS Windows má v řádku pod hlavičkou hlavního formuláře umístěn systém samočinně se rozbalujících nabídek (menu). Abychom se s tvorbou menu prakticky seznámili, vytvoříme si jednoduché menu i pro náš program pro práci s dynamickými proměnnými. Hlavní menu programu snadno vytvoříme pomocí komponentu MainMenu. Komponent MainMenu se skrývá pod druhou ikonou zleva na záložce Standard palety komponentů. Komponent umístíme na libovolné místo v našem hlavním formuláři (po spuštění programu ikona reprezentující komponent zmizí). Pokud na ploše komponentu dvakrát klikneme levým tlačítkem myši, spustí se editor menu s prázdnými okénky (na obr. 3.14 vlevo dole a vpravo nahoře). Pro prázdné okénko zadáváme v inspektoru objektů obsah proměnných Caption (textový řetězec, který reprezentuje pro uživatele položku menu; zvýraznění znaku podtržítkem docílíme předřazením & tomuto znaku), Name (identifikátor položky menu; identifikátory použité v našem programu jsou uvedeny v tab. 3.8) a ShortCut (kód tzv. horké klávesy, která se automaticky zobrazí u pravého okraje menu; u položky Otevři jsme jako horskou klávesu zvolili F3, takže při jejím stisku kdykoli během programu vyvoláme akci otevření souboru). Pokud chceme oddělit položky menu tzv. separátorem (vodorovná čára), do proměnné Caption vepíšeme znaménko „mínus“. Na záložce Events vepíšeme do řádku vedle položky OnClick jméno funkce, která má být při výběru položky menu uživatelem volána (identifikátory funkcí v našem programu jsou uvedeny v tab. 3.8 ve sloupci funkce). V případě položky Konec žádáme ukončení chodu programu. Odpovídající funkce r_exit tedy bude mít následující zdrojový kód: // ukončení běhu programu void __fastcall TForm1::r_exit(TObject *Sender) { Close(); } Obr. 3.14 Menu programu pro práci s dynamickými proměnnými 64 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Řetězce, zobrazené v systému menu, můžeme rozdělit do dvou skupin – na položky menu a na submenu. Úkolem položky menu je volat funkci. Příkladem položky menu je výše uvedený Konec, který vyvolá funkci r_exit, sloužící k ukončení aplikace. Naproti tomu úkolem submenu není vyvolat programem definovanou funkci, ale rozbalit další úroveň menu. Jediným představitelem submenu je v našem příkladě Soubor – pokud klikneme na tento řetězec, nevyvolá se žádná funkce, ale „vyklopí se“ šedý obdélník s položkami menu Nový, Otevři, Ulož, se separátorem a s položkou menu Konec. Proto není Soubor ani uveden v tab. 3.8. položka identifikátor funkce Nový i_new r_new Otevři i_open r_open Ulož i_save r_save Konec i_exit r_exit O aplikaci i_about r_about Tab. 3.8 Položky menu Relativně nezajímavou položkou menu je položka Nový. Po této položce žádáme, aby byl z paměti uvolněn aktuální obsah naší databáze a aby byl nahrazen obsahem prázdným (přesněji obsahujícím pouze Kamila Střihavku). Funkce r_new tedy vypadá následovně: // otevření prázdné databáze void __fastcall TForm1::r_new(TObject *Sender) { int i; // pro cykly for( i=0; i<=index; i++) free( my_cds[ i]); } // uvolníme z paměti původní databázi init( Sender); V cyklu přes všechna CD v databázi postupně voláme pro odpovídající dynamické struktury funkci free (mažeme tedy i Kamila). Jakmile je databáze prázdná, voláme výše popsanou funkci init, která vytvoří nového Střihavku, uloží jeho adresu do první složky pole a nastaví proměnné index a count. Při výběru kterékoli ze zbývajících tří položek menu se objeví nový, speciální formulář, který je určen k plnění speciálních úkolů. Tyto speciální formuláře (okna) nazýváme dialogy (dialogs), protože slouží ke komunikaci mezi aplikací a uživatelem. Dialogy většinou bývají modální (modal) – pokud je modální dialog otevřený, jsou ignorovány všechny ostatní události aplikace. Při otevřeném modálním oknu tedy např. není možné stisknout tlačítko na ploše formuláře nebo vybírat položky menu. Pokud je otevřeno okno nemodální, lze se v aplikaci dále pohybovat a lze spouštět další události. Začněme dialogem, který se objeví při výběru položky menu O aplikaci (viz obr. 3.15). Jelikož se jedná o formulář na první pohled nestandardní, musíme ho naprogramovat sami. Z menu Builderu vybereme položku File/New Form. Otevřeme tak nový prázdný formulář a jemu odpovídající programovou jednotku. Jednotku uložíme pod jménem about.cpp prostřednictvím položky menu File/Save as. Pokud požadujeme, aby se náš formulář choval jako modální dialog, nastavíme v inspektoru BorderStyle= bsDialog. Dále upravíme Počítače a programování 2 65 rozměry formuláře (Height= 531, Width=258), do proměnné Caption vepíšeme řetězec O aplikaci a formuláři dáme jméno Name= AboutForm. Jak je vidět z obr. 3.15, na ploše formuláře se nacházejí tři komponenty – komponent Bevel1 typu TBevel (rámeček kolem obrázku), komponent Image1 typu TImage a komponent OkButton typu TButton. Se všemi třemi komponenty jsme se již setkali. Proto upozorněme jen na pár zajímavostí. Obrázek v našem dialogu je uložen v souboru Installd.jpg. Tento soubor zkopírujeme do adresáře s naším programem a prostřednictvím proměnné Picture komponentu Image1 ho zobrazíme v našem formuláři. U tlačítka OkButton můžeme prostřednictvím jeho proměnné ModalResult zadat standardní událost, která má být při jeho stisku generována. Seznam těchto standardních událostí je uveden v inspektoru v „kombu“ vedle uvedené proměnné. Dialog se automaticky uzavře, pokud se objeví událost mrOk nebo událost mrCancel. My jsme přiřadili tlačítku mrOk, protože to odpovídá jeho jménu. Naše volba však funkci programu nijak neovlivní. Tím je náš dialog dokončený (nemuseli jsme napsat ani slovo zdrojového kódu). Pokud bychom si otevřeli hlav- Obr. 3.15 Dialogový formulář "O aplikaci" ní soubor našeho projektu CD.cpp, našli bychom v něm dva nově vygenerované řádky: USEFORM("about.cpp", AboutForm); Application->CreateForm(__classid(TAboutForm), &AboutForm); Dialog byl samočinně přidán do našeho projektu, byl automaticky vytvořen (CreateForm) a za chodu programu postačí říkat, zda má být dialog zobrazen nebo zda má být skrytý. Zobrazení dialogu bude vyvoláno jako odezva na výběr položky O aplikaci hlavního menu MainMenu1. Abychom se však mohli z jednotky main.cpp, v níž je deklarován hlavní formulář naší aplikace, odvolávat na náš dialog AboutForm, který je uložen v jednotce about.cpp, musíme do jednotky main.cpp jednotku about.cpp zahrnout. Učiníme to zapsáním #include "about.h" na začátek zdrojového textu souboru main.cpp. Soubor about.h je tzv. hlavičkovým souborem formuláře AboutForm. Soubor je samočinně sestavován Builderem a má toto tělo: class TAboutForm : public TForm { __published: // IDE-managed Components TImage *Image1; TBevel *Bevel1; TButton *OkButton; private: // User declarations public: // User declarations __fastcall TAboutForm(TComponent* Owner); }; 66 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Jedná se o deklaraci proměnné, která popisuje náš dialog O aplikaci. V rámci této proměnné jsou deklarovány ukazatele na všechny komponenty, které jsme do formuláře umístili z palety komponentů. Pokud bychom prostřednictvím inspektoru deklarovali i odezvy na určité události, budou hlavičky odpovídajících funkcí rovněž zahrnuty do této deklarace. To lze pozorovat na příkladu hlavičkového souboru našeho hlavního formuláře. Automaticky vygenerovaný zdrojový kód jsme pro lepší srozumitelnost doplnili vlastními komentáři. class TForm1 : public TForm { __published: // IDE-managed Components TMainMenu *MainMenu1; // komponent HLAVNÍ MENU TMenuItem *Soubor1; // submenu SOUBOR TMenuItem *i_new; // položka NOVÝ TMenuItem *i_open; // položka OTEVŘI TMenuItem *i_save; // položka ULOŽ TMenuItem *N1; // separátor TMenuItem *i_exit; // položka KONEC TMenuItem *i_about; // položka O APLIKACI TLabel *SingerLabel; // návěští ZPĚVÁK TEdit *SingerEdit; // připojený editační řádek TLabel *TitleLabel; // návěští TITUL TEdit *TitleEdit; // připojený editační řádek TButton *ForwardBtn; // tlačítko >> TButton *BackwardBtn; // tlačítko << TButton *AddBtn; // tlačítko + TButton *EraseBtn; // tlačítko TBevel *Bevel1; // rám kolem tlačítek TOpenDialog *OpenDialog1; // viz dále TSaveDialog *SaveDialog1; // viz dále void __fastcall init(TObject *Sender); // inicializace void __fastcall add(TObject *Sender); // přidání CD void __fastcall forward(TObject *Sender); // listování dopředu void __fastcall backward(TObject *Sender); // listování dozadu void __fastcall erase(TObject *Sender); // vymazání CD void __fastcall r_new(TObject *Sender); // MENU - Nový void __fastcall r_open(TObject *Sender); // MENU - Otevři void __fastcall r_save(TObject *Sender); // MENU - Ulož void __fastcall r_exit(TObject *Sender); // MENU - Konec void __fastcall r_about(TObject *Sender); // MENU – O aplikaci private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; Zahrnutím hlavičkového souboru do nějaké jednotky dáváme této jednotce na vědomí, jaké proměnné a jaké funkce jsou jí k dispozici. Vraťme se zpět k našemu dialogu O aplikaci. V případě, kdy uživatel vybere odpovídající položku menu, chceme vytvořený dialog zobrazit. Proto deklarujeme funkci r_about, která bude samočinně volána jako reakce na událost OnClick položky menu i_about: // dialog "O aplikaci" void __fastcall TForm1::r_about(TObject *Sender) { AboutForm->ShowModal(); } Funkce ShowModal se postará o zobrazení vytvořeného dialogu a o jeho fungování. ShowModal běží tak dlouho, dokud se neobjeví modální výsledek (modal result) mrOk nebo Počítače a programování 2 67 mrCancel. Poté je činnost dialogu ukončena a dialog je skryt (v paměti počítače však nadále zůstává až do ukončení běhu aplikace). Funkce ShowModal vrací informaci o tom, jakým tlačítkem bylo okno uzavřeno (mrOk, mrCancel). V našem programu tuto vlastnost funkce ShowModal nevyužíváme (ve formuláři O aplikaci je totiž jedno jediné tlačítko). Posledními dvěma činnostmi, které musíme ještě naprogramovat, jsou ukládání informací o zadaných discích do souboru (položka menu Ulož) a načítání informací ze souboru do programu (položka menu Otevři). Aby mohl uživatel snadno určit, s kterým souborem se bude pracovat, doplníme naši aplikaci standardními dialogy pro načítání ze souboru a pro ukládání do souboru (obr. 3.16). Oba dialogy jsou k dispozici na paletě komponentů na záložce Dialogs. Ikony, které oba dialogy reprezentují, umístíme na libovolné místo v našem formuláři (při spuštění programu obě ikony zmizí – stejně jako ikona reprezentující hlavní menu). U obou dialogů vyplníme v inspektoru objektů obsah proměnných FileName=*.cdd (objeví se v editačním řádku vedle návěští Název souboru) a Filter (obsah editačního řádku vedle návěští Uložit jako typ, resp. Soubor typu). Proměnnou Obr. 3.16 Standardní dialogy pro načítání ze souboru a pro ukládání do souboru Filter vyplňujeme v dialogu Filter Editor; do sloupce Filter Name vepisujeme český řetězec popisující uživateli filtr, a do sloupce Filter zadáváme masku souboru (*.cdd je maska pro naši cd databázi, *.* je maska pro všechny soubory). Ukládání do souboru (včetně zobrazení dialogu) realizujeme kódem: // uložení databáze do souboru void __fastcall TForm1::r_save(TObject *Sender) { if (SaveDialog1->Execute()) // spustíme dialog pro zadání jména souboru { TStringList* to_file = new TStringList(); // seznam řetězců int i; // pro cykly to_file->Add( IntToStr( index)); } } // uložen index posledního cd for( i=0; i<=index; i++) // přes všechna cd { to_file->Add( my_cds[i]->singer); // jméno to_file->Add( my_cds[i]->title); // jméno } to_file->SaveToFile( SaveDialog1->FileName); // uloží v databázi interpreta cd seznam do souboru 68 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Dialog pro výběr jména souboru SaveDialog1 zobrazíme a spustíme voláním jeho funkce Execute. Tato funkce vrací hodnotu true (pravda), pokud uživatel zadá korektní jméno souboru a stiskne tlačítko Uložit. V popsaném případě se tedy vykonává blok příkazu if. Na prvních dvou řádcích bloku deklarujeme lokální proměnné. Celočíselná proměnná i bude využívána jako index pro cyklus for přes všechny disky v databázi. Proměnná to_file je ukazatel na typ TStringList (seznam řetězců). TStringList je datový typ, který je součástí Builderu, avšak není k dispozici na paletě komponentů. Proto musíme ukazatel to_file na tento datový typ vytvořit ručně voláním funkce new. Tato funkce je analogií k nám známé funkci malloc a slouží v jazyce C++ k vytvoření ukazatele na objekt5. Přímo v deklaraci tedy voláním new alokujeme v paměti počítače místo pro uložení seznamu řetězců a počáteční adresu tohoto místa uložíme do proměnné to_file. Klíčovou proměnnou seznamu řetězců TStringList je pole ANSI-řetězců Strings a celkový počet uložených řetězců Count. Mezi významné funkce patří Add( S), která přidá řetězec S na konec seznamu řetězců a zvedne počet řetězců Count o jedničku. Celý seznam řetězců lze uložit do diskového souboru voláním funkce SaveToFile( FileName), kde FileName je řetězec specifikující diskový soubor. Načítání řetězců z diskového souboru do běžící aplikace lze realizovat voláním funkce LoadFromFile( FileName). V naší funkci r_save, která je volána jako odezva na výběr položky menu Ulož, nejprve převedeme celkový počet disků v databázi snížený o jedničku (tj. obsah proměnné index) na řetězec a uložíme ho na první pozici. Tuto informaci pak využijeme pro cyklus for při načítání údajů o discích ze souboru do aplikace. Poté v cyklu bereme u každého CD jméno interpreta a název disku a plníme těmito řetězci proměnnou to_file. Jakmile to_file obsahuje všechny řetězce, uložíme je voláním funkce SaveToFile do diskového souboru. Jméno souboru, které uživatel zadal v dialogu, je k dispozici v proměnné SaveDialog1->FileName. Správnost uložení informací do diskového souboru můžeme snadno ověřit otevřením tohoto souboru v jednoduchém textovém editoru. Načítání údajů z diskového souboru do běžící aplikace je analogické: // načtení databáze ze souboru void __fastcall TForm1::r_open(TObject *Sender) { int i; // pro cykly for( i=0; i<=index; i++) free( my_cds[ i]); // uvolníme z paměti původní databázi if (OpenDialog1->Execute()) // spustíme dialog pro výběr souboru { TStringList *from_file = new TStringList(); // seznam řetězců tcd *a_cd; // pom.ukazatel na disk from_file->LoadFromFile(OpenDialog1->FileName); // seznam ze souboru count = ( index = StrToInt( from_file->Strings[0])) + 1; 5 Objektem rozumíme strukturu, která obsahuje jak datové složky tak funkce. Deklarace objektu začíná klíčovým slovem class (třída). S příklady deklarací tříd jsme se setkali při zkoumání obsahu hlavičkových souborů. Počítače a programování 2 } } 69 // ↑ počet načtených cd, nastavení indexu for( i=0; i<=index; i++) { a_cd = (tcd *) malloc( sizeof( tcd)); // alokace místa pro disk a_cd->singer = from_file->Strings[ 2*i+1]; // načtení interpreta a_cd->title = from_file->Strings[ 2*i+2]; // načtení jména cd my_cds[ i] = a_cd; // adresa načteného disku do databáze } show(); // vypsání údajů o posledním načteném disku V prvním bloku for uvolníme z paměti všechny dynamické struktury, které jsou před zahájením načítání ze souboru přítomny v našem programu. Poté spustíme dialog pro zadání jména otevíraného souboru. Pokud uživatel korektně zadá jméno souboru a stiskne tlačítko Otevřít, začne se vykonávat blok příkazu if. Již známým způsobem alokujeme v paměti místo pro proměnnou typu TStringList a jako pomocnou proměnnou deklarujeme ukazatel na disk. Voláním funkce LoadFromFile naplníme seznam řetězců from_file řetězci ze souboru. První řetězec ze souboru (obsahuje údaj o počtu disků v uložené databázi) převedeme na celé číslo (funkce StrToInt) a naplníme jím proměnné index (index posledního CD v databázi) a count=index+1 (počet CD v databázi). V cyklu přes všechna CD pak postupně alokujeme v paměti místo pro uložení disku (funkce malloc), plníme složky struktury a_cd řetězci ze souboru, a konečně, ukazatel na strukturu ukládáme do pole my_cds. V posledním kroku vypíšeme voláním funkce show údaje o posledním načteném CD do editačních řádků hlavního formuláře našeho programu. Tím je program dokončen. Jeho kompletní zdrojový kód je uložen v adresáři dynamic. 3.7.5 Kontrolní příklady Příklad 1. Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování zobrazovat na kreslicí ploše analogový ručkový přístroj, osciloskop s rastrem. Vytvořte prostředky k zadávání zobrazovaných hodnot (funkce, načítání, ukládání do souboru). Aplikace by měla zobrazit data jako naměřené hodnoty. Příklad 2. Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování zobrazovat na kreslicí ploše analogový ručkový přístroj, osciloskop s rastrem. Vytvořte prostředky k zadávání zobrazovaných hodnot (funkce, načítání, ukládání do souboru). Aplikace by měla zobrazit data jako naměřené hodnoty. Načtená data nahrajte do souboru. Příklad 3 Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování zobrazovat na kreslicí ploše analogový ručkový přístroj, osciloskop s rastrem. Vytvořte prostředky k zadávání zobrazovaných hodnot (funkce, načítání, ukládání do souboru). Aplikace by měla zobrazit data jako naměřené hodnoty. Načtená data nahrajte do souboru. Doplňte o kalibraci přístrojů na velikost zobrazované amplitudy veličin. Příklad 4 Navrhněte a sestavte program, který bude pomocí dosud poznaných prvků objektového programování zobrazovat na kreslicí ploše analogový ručkový přístroj, osciloskop s rastrem. Vytvořte prostředky k zadávání zobrazovaných hodnot (funkce, načítání, ukládání do souboru). Aplikace by měla zobrazit data jako naměřené hodnoty. Načtená data nahrajte do souboru. Doplňte o kalibraci přístrojů na velikost zobrazované amplitudy veličin. Doplňte o volbu vícekanálového zobrazení na osciloskopu, o prvek měnicí časovou základnu osciloskopu. Nastavte prostředky pro identifikaci tvůrce. 70 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 3.7.6 Kontrolní otázky 1. Jaké znáte další přívlastky datových typů? 2. Co je to struktura, kdy mluvíme o datovém typu? 3. Co je to identifikátor složek struktur? 4. Kde se ukládají a jak data struktury? 5. Co jsou to unie? 6. Jaký je rozdíl mezi uniemi a strukturou? 7. Co je to jmenovka unie? 8. Co je to výčtový typ proměnné, k čemu se používá? 9. Jaký je rozdíl mezi statickou a dynamickou proměnnou? 10. Co je to menu? 11. K čemu a jak se používají „horké“ klávesy? 12. Kde se nalezne separátor? 13. Jaký je rozdíl mezi položkou menu a submenu? 14. Jaké znáš dialogy (okna), jaký je mezi nimi zásadní rozdíl? 15. Myslíte si že již dokážete naprogramovat téměř všechno? 3.8 Náměty témat závěrečných projektů 1. Sestavte aplikaci, která pracuje jako analogové hodiny. 2. Sestavte aplikaci, která pracuje jako digitální hodiny. 3. Sestavte aplikaci, která pracuje jako budík. 4. Sestavte aplikaci, zobrazuje grafické soubory. 5. Sestavte aplikaci, která zobrazuje a pracuje s grafickými soubory. 6. Sestavte aplikaci, která pracuje jako spořič obrazovky. 7. Sestavte aplikaci, která pracuje jako „oči“, neustále sleduje kurzor myši. 8. Sestavte aplikaci, která pracuje pomocný program- „help“. 9. Sestavte aplikaci, která pracuje jako jednoduchý textový editor, volba písma barvy řádku. 10. Sestavte aplikaci, která umožní načtený text ze souboru zobrazit a vytisknout. 11. Sestavte aplikaci, která pracuje jako kalendář. 12. Sestavte aplikaci, která pracuje jako diář. 13. Sestavte aplikaci, která pracuje jako tabulkový editor. 14. Sestavte aplikaci, která pracuje jako kalkulačka. 15. Sestavte aplikaci, která pracuje jako zobrazovač 3D funkcí. 16. Sestavte aplikaci, která pracuje jako jednoduchý grafický editor. 17. Sestavte aplikaci, která zobrazuje některé fraktální obrazce. Počítače a programování 2 71 18. Sestavte aplikaci, která zobrazuje obsazení paměti aplikací. 19. Sestavte aplikaci, která pro svou funkci vÿužívá technologie DDE. 20. Sestavte aplikaci, která pro svou funkci používá DLL. 21. Sestavte aplikaci, která pracuje s dodaný hardwarem. 22. Sestavte aplikaci, která ovládá sériové i paralelní porty. 23. Sestavte aplikaci, která ovládá zvukovou kartu. 24. Sestavte aplikaci, která pracuje se schránkou (clipboard). 25. Sestavte aplikaci, která sleduje a zobrazuje právě probíhající události v systému. 26. Sestavte aplikaci, která pracuje s vlastním časovačem a řídí jím rychlost zobrazování grafických souborů. 27. Sestavte aplikaci, která zobrazuje Menděljevovu tabulku periodických prvků. 3.9 Závěr Společně jsme prošli základy programování v jazyce C a naučili jsme se tento jazyk používat ve spojení s vývojovým nástrojem Borland C++ Builder k vývoji jednoduchých aplikací pro operační systémy Windows. Tím jsme učinili první krok k tomu, abychom byli schopni vyvíjet praktické aplikace pro řešení problémů z oblasti elektrotechniky, informatiky a komunikačních technologií. Pevně věříme, že nabyté znalosti využijete i ve svém další studiu při práci na ročníkových projektech, na bakalářských a diplomových pracích. Musíme si však uvědomit, že náš základní kurs pokryl pouze základy programování. Tyto základy budete muset již sami ve své další práci rozvíjet, budete se muset sami seznamovat s dalšími možnostmi, které Borland C++ Builder nabízí, a sami budete muset bedlivě sledovat vývoj, jímž programovací nástroje procházejí. Určitě není třeba upozorňovat na skutečnost, že informační technologie se vyvíjejí velmi bouřlivě a že programátoři musejí studovat nové technologie celý život. Při tomto neustálém studiu vám přejeme mnoho úspěchů. 3.10 Literatura [1] ECKEL, B. Myslíme v jazyku C++. Praha: Grada Publishing, 2002. ISBN 8-0247-9009-2 [2] VIRIUS, M. Programovací jazyky C/C++. Praha: Gcomp, 1992. ISBN 8-0901-0735-4. [3] PRATA, S. Mistrovství v C++. Praha: Computer Press, 2001. ISBN 8-0722-6339-0 [4] KADLEC, V. Učíme se programovat v Borland C++ Builder a jazyce C++. Praha: Computer Press, 2001. ISBN 8-0722-6550-4 [5] HOLAN, T., NERUDA, R. C++ Builder v příkladech. Praha: BEN, 2002. ISBN 8-07300042-3 [6] HEROUT, P. Učebnice jazyka C. České Budějovice: Kopp, 1992. ISBN 8-0858-2821-9 [7] HEROUT, P. Učebnice jazyka C, 2.díl. Č. Budějovice: Kopp, 1992. ISBN 80-85828-50-2 72 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 4 MATLAB V předchozí kapitole jsme se seznámili s programovacím jazykem C a s vývojovým nástrojem Borland C++ Builder. Jak jsme poznali, Builder je obecným nástrojem založeným na jazyce C, který programátorovi umožňuje vyvinout v podstatě libovolnou aplikaci pro operační systémy Windows. I když tento nástroj nabízí širokou škálu hotových komponentů a řadu hotových matematických funkcí (viz knihovna s hlavičkovým souborem math.h), je využití Builderu k realizaci určitého výpočtu dosti časově náročné (sestavení formuláře, definování funkcí pro ošetření událostí, odladění výpočetního jádra, atd.). K realizaci určitého výpočtu (návrh elektronického obvodu, výpočet parametrů antény, číslicové zpracování signálu), který potřebujeme provést jen jednou a jen my sami (je po nás požadován pouze výsledek, a nikoli program k jeho dosažení), je vývoj ryzí aplikace pro Windows zbytečně pracný a časově náročný. Na trhu je totiž k dispozici celá řada speciálních matematických programů, v nichž stačí volat pouhé sekvence propracovaných, hotových funkcí, které umožní realizovat výpočet snadno a rychle. Jedním z takových matematických programů je MATLAB, The Language of Technical Computing (jak hlásá reklamní slogan). Program MATLAB je vyráběn společností the MathWorks Inc. V Česku a na Slovensku MATLAB distribuuje firma Humusoft. V našem skriptu budeme hovořit o MATLABu verze 6.1 (release 12.1). Celé jádro programu MATLAB je napsáno v jazyce C a celý produkt je s tímto jazykem úzce svázán. Uživatel si může koupit speciální kompilátor, který umožní převést sekvence matlabovských funkcí do jazyka C. Rovněž je k dispozici speciální knihovna (MATLAB C/C++ Math Library), která umožňuje matlabovské funkce volat z nezávislého programu, napsaného v jazyce C. Konečně, samotný program v MATLABu (sekvence volání matlabovských funkcí) má syntaxi podobnou jazyku C, takže si na ni velmi rychle zvykneme. MATLAB sestává ze základního výpočetního jádra, v němž je implementována bohatá knihovna matematických operací a základních podpůrných rutin (textové vstupy a výstupy, práce se soubory, grafická reprezentace výsledků), a dále z celé řady specializovaných knihoven (tzv. toolboxů). Tyto toolboxy si uživatel podle své potřeby přikupuje k základnímu jádru MATLABu. Příklady dostupných toolboxů jsou uvedeny v tab. 4.1. Toolbox Popis Simulink Modelování a simulace dynamických systémů Communications Blockset Bloky pro Simulink, určené k návrhu a modelování komunikačních systémů (generování signálů, kódování, modulace, …) Communications Toolbox Modelování pokročilých komunikačních systémů (náhodné signály, pokročilé kódování, číslicové modulace, speciální filtrace, …) Neural Network T. softwarová realizace umělých neuronových sítí Optimization T. obecné funkce pro optimalizaci systémů a zařízení Signal Proces. T. funkce používané pro zpracování signálů Wavelet Toolbox funkce pro analýzu a syntézu signálů a obrazů, které používají tzv. vlnek (wavelets) Tab. 4.1 Příklady toolboxů MATLABu Počítače a programování 2 73 Kromě oficiálních toolboxů, vyvíjených a distribuovaných společností the MathWorks, existuje celá řada toolboxů, které vznikají na universitách po celém světě a které jsou prostřednictvím Internetu volně dostupné všem zájemcům (ovšem bez jakýchkoli garancí správné funkce). V dalších podkapitolách se nejprve seznámíme s uživatelským rozhraním MATLABu. Poté si vysvětlíme podstatu maticové aritmetiky MATLABu a seznámíme se s důležitými funkcemi a rutinami. V závěru kapitoly předvedeme několik aplikací, vyvinutých v MATLABu. 4.1 Uživatelské rozhraní MATLABu Hlavní okno MATLABu vidíme na obr. 4.1. Pod modrou hlavičkou hlavního okna je umístěno standardní menu, které sestává ze šesti submenu. Obr. 4.1 Integrované uživatelské rozhraní MATLABu 6.1 Submenu File obsahuje příkazy pro standardní práci s tzv. m-soubory (m-soubory obsahují posloupnosti volání funkcí MATLABu). Příkaz New/M-file slouží k vytvoření prázdného, nového m-souboru. Pomocí příkazu Open otevřeme existující m-soubor. Z dalších položek se zmiňme o příkazu Set Path (slouží k nastavení cesty do pracovního adresáře), o položce Preferences (slouží k nastavení uživatelského prostředí MATLABu), o položce Print (vytiskne obsah příkazového okna – okno v pravé části hlavního okna MATLABu na obr. 4.1), a konečně o položce Exit MATLAB (ukončí běh programu). 74 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Submenu Edit obsahuje standardní editační příkazy jako kopírování vybraného textu z okna do schránky (Copy), vložení obsahu schránky do okna (Paste), mazání vybraného textu v okně (Delete), atd. V submenu View můžeme aktivovat ty části uživatelského prostředí, které chceme mít zobrazeny, můžeme deaktivovat části, které chceme skrýt, a můžeme provádět další nastavení uživatelského prostředí. Submenu Web obsahuje důležité Internetové adresy, vedoucí k aktuálním informacím o MATLABu. Submenu Window obsahuje položku Close All (uzavření všech otevřených m-souborů), a dále jmenný seznam všech otevřených m-souborů. Konečně submenu Help umožňuje přístup k veškeré nápovědě MATLABu, která je uložena na disku počítače. Pod řádkem s menu je umístěna nástrojová lišta s tlačítky, která odpovídají nejčastěji používaným položkám menu. Detail lišty a vysvětlení významu jednotlivých tlačítek přináší obrázek 4.2. nový m-soubor vyřízni text vlož text opakuj posl. změnu simulink zruš posl. změnu kopíruj text otevření m-souboru nápověda jméno pracovního adresáře tlačítko pro změnu adresáře Obr. 4.2 Nástrojová lišta hlavního okna MATLABu Pod nástrojovou lištou je již plocha hlavního okna MATLABu s pracovními okny. Nejdůležitější okno je na obr. 4.1 umístěno úplně vpravo a nese název Command Window (příkazové okno). Do příkazového okna vepisujeme volání funkcí MATLABu a MATLAB na každé volání bezprostředně reaguje. MATLAB tedy pracuje jako interpret – každý příkaz je v prvé fázi přeložen a v druhé vykonán (naproti tomu C++ Builder nejprve celý program přeložil do binárního kódu, a až poté začal být kód vykonáván). Podívejme se na obsah příkazového okna na obr. 4.1. Na prvém řádku nás symbol >> vyzývá k zadání příkazu. Textem A = [1.1, 4.3, 5.3; 7.2, 3.4, 2.0] vytvoříme matici o dvou řádcích a třech sloupcích. Prvky matice vepisujeme do lomených závorek po řádcích. Čísla v řádku oddělujeme čárkou (případně mezerou), řádky matice od sebe oddělujeme středníkem. Pokud po zapsání uvedeného textu stiskneme klávesu Enter, zadaná matice se v příkazovém okně vypíše ve tvaru, na který jsme u matic zvyklí. Před výpisem chybí symbol >>, což indikuje skutečnost, že se jedná o výpis. Chceme-li zabránit tomu, aby se zadaná matice v příkazovém okně vypsala, musíme ukončit řádek středníkem. Druhým příkazem, který uživatel zapsal do příkazového okna MATLABu, je vytvoření sloupcového vektoru B = [-7.1; 8.2]. V třetím kroku chceme vypočítat matici C, která odpovídá součinu matice A a vektoru B: Počítače a programování 2 1.1 7.2 51.23 7 . 1 − C = A T ⋅ B = 4.3 3.4 ⋅ = − 2.65 8 . 2 5.8 2.0 − 24.76 75 (4.1) V třetím příkazu v příkazovém okně jsme zapomněli matici A transponovat, a proto MATLAB ohlásil chybu. Ve čtvrtém příkazu jsme matici A již transponovali (operátor apostrof), takže výpočet proběhl korektně. Z uvedeného příkladu je vidět, že MATLAB pracuje maticově. Nemusíme tedy vzájemně násobit jednotlivé prvky matic a vektorů a součiny sčítat – stačí korektně zapsat maticovou operaci, a o zbytek se postará výpočetní jádro MATLABu, které je pro maticové operace optimalizováno. Psát celý program instrukci po instrukci do příkazového okna je u složitějších výpočtů nemyslitelné. Proto postupujeme tak, že sekvenci příkazů uložíme do textového souboru s příponou *.m (např. zkouska.m). Napíšeme-li pak v příkazovém okně za symbol >> jméno tohoto m-souboru (zkouska), MATLAB začne brát řádek po řádku jeho obsah a začne postupně vykonávat v něm uvedené instrukce. K psaní m-souborů se ještě vrátíme později. Vlevo nahoře od příkazového okna je okno se záložkami Workspace (pracovní prostor) a Launch Pad (doslovný překlad zní odpalovací základna). Základna poskytuje uživateli snadný přístup ke všem nástrojům, demonstračním příkladům a dokumentaci, které jsou nainstalovány v rámci MATLABu na uživatelově počítači. Uživatel tak může prostřednictvím základny vyhledat demonstrační program, který řeší podobnou problematiku jako on, může program otevřít a z jeho okna přenášet části kódu přímo do své aplikace. Převzatý kód pak stačí jen vhodným způsobem modifikovat. Pracovní prostor přináší informaci o proměnných alokovaných v rámci vykonávaného programu (v našem případě matice A, B a C). U každé proměnné je uvedena informace o rozměru matice (Size), o počtu bajtů zabíraných proměnnou v paměti (Bytes) a o třídě (Class), do níž proměnná patří. V našem případě jsou proměnné typu pole (array) racionálních čísel (double). Vlevo dole od příkazového okna je okno se záložkami Command History (historie příkazů) a Current directory (současný adresář). Současný adresář zobrazuje přesnou cestu do adresáře (kombo pod hlavičkou okna) a seznam souborů, které se v tomto adresáři nacházejí. Okno s historií příkazů obsahuje časovou posloupnost příkazů, které uživatel postupně zadával do příkazového okna. Příkazy zobrazené v okně s historií příkazů lze z tohoto okna přímo spouštět, lze je kopírovat do příkazového okna či vytvářeného m-souboru, atd. Nyní, když máme základní přehled o uživatelském prostředí MATLABu, můžeme se pustit do psaní vlastních programů. 4.2 Operace s maticemi Matice v MATLABu je dvojrozměrným polem reálných nebo komplexních čísel. MATLAB přímo podporuje mnoho maticových operací, které známe z lineární algebry (maticová aritmetika, soustavy lineárních rovnic, vypočet vlastních čísel, atd.). Kromě toho MATLAB nabízí mnoho dalších specializovaných funkcí pro vytváření matic, pro jejich zpracování a pro 76 Fakulta elektrotechniky a komunikačních technologií VUT v Brně jejich grafickou reprezentaci. Abychom však byli schopni matice zpracovávat, musíme je nejdříve vytvořit. 4.2.1 Vytváření matic S vytvořením reálné matice jsme se setkali již v podkapitole 4.1. Za symbol reprezentující matici umístíme rovnítko následované lomenými závorkami. Do lomených závorek postupně píšeme čísla, která tvoří řádek matice, a oddělujeme je čárkami. Jednotlivé řádky matice oddělujeme středníky. Samotný MATLAB nabízí uživateli funkce, které generují některé speciální typy matic samy. Funkce A=zeros(m,n) vytvoří matici A o m řádcích a n sloupcích, která je naplněna samými nulami. Např.: A = zeros( 3, 2) % nulová matice o třech řádcích a dvou sloupcích A = % komentář píšeme v MATLABu za znak „procento“ 0 0 0 0 0 0 Funkce B=ones(m,n) vytvoří matici B o m řádcích a n sloupcích, která je naplněna samými jedničkami. Např.: B = ones( 2, 3) B = 1 1 1 1 % matice jednotek o dvou řádcích a třech sloupcích 1 1 Funkce diag( V, k) vytvoří diagonální matici, která má na k-té diagonále umístěna čísla, jež jsou uloženy ve vektoru V jako jeho prvky. Např.: V = [ 1.1, 2.2, 3.3, 5.5] V = 1.1000 2.2000 D0 = diag( V, 0) D0 = 1.1000 0 0 0 0 2.2000 0 0 D1 = diag( V, 1) D1 = 0 0 0 0 0 1.1000 0 0 0 0 % čísla pro k-tou diagonálu 3.3000 5.5000 % vektor na hlavní (nultou) diagonálu 0 0 3.3000 0 0 0 0 5.5000 % vektor na první vedlejší diagonálu 0 2.2000 0 0 0 0 0 3.3000 0 0 0 0 0 5.5000 0 Mezi další funkce, které generují speciální matice, patří dále např. magic(n) (vrací matici o rozměru n×n, která je sestavena z celých čísel od 1 do n2 tak, aby součet prvků v jednotlivých řádcích a jednotlivých sloupcích byl vždy stejný) či pascal(n) (vrací Pascalovu matici řádu Počítače a programování 2 77 n: symetrická pozitivně definitní matice s celočíselnými prvky odpovídajícími Pascalovu trojúhelníku). Matici můžeme vytvářet také postupně. V prvém kroku vytvoříme nulovou matici požadovaných rozměrů a v dalších krocích nahrazujeme nuly v matici konkrétními čísly. Výrazem A(m,n) přitom rozumíme prvek matice A, který se nachází na m-tém řádku a v n-tém sloupci: A = zeros( 3, 2) A = 0 0 0 0 0 0 A(1,1)=1e3 A = % nulová matice o 3 řádcích a 2 sloupcích % do 1. řádku 1.sloupce vlož 1000 1000 0 0 0 0 0 Dosud jsme se zabývali pouze vytvářením matic reálných čísel. Podívejme se tedy, jakým způsobem lze vytvořit matici komplexní. Matici komplexních čísel vytváříme stejně jako matici čísel reálných. Tak např. C= [1+j*1, 2+j*1; 1+j*2, 2+j*2] vytvoří komplexní matici C se dvěma řádky a dvěma sloupci: 2+ j 1+ j C= 1 + j 2 2 + j 2 (4.2) Symbol j je předdefinovanou konstantou MATLABu, která obsahuje imaginární jednotku. Pro práci s komplexními maticemi slouží funkce real (vrací reálnou část matice), imag (vrací imaginární část matice), abs (vrací matici modulů komplexních čísel), angle (vrací matici argumentů komplexních čísel), conj (počítá komplexně sdruženou matici), atd. Dále se ještě zmíníme o operátoru apostrof. Když jsme jej v předchozí aplikovali na reálnou matici, postaral se tento operátor o její transpozici. Aplikujeme-li apostrof na komplexní matici,vytvoříme z původní matice matici Hermitovsky sdruženou (transponovanou a současně komplexně sdruženou). Způsob použití výše popsaných funkcí by měl být zřejmý z níže uvedených příkladů: C=[1+j*1, 2+j*1; 1+j*2, 2+j*2] C = 1.0000 + 1.0000i 1.0000 + 2.0000i real( C) ans = 1 1 imag( C) % komplexní matice o rozměru 2×2 2.0000 + 1.0000i 2.0000 + 2.0000i % matice reálných částí prvků matice C 2 2 % matice imaginárních částí prvků matice C 78 ans = 1 2 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 1 2 abs( C) ans = 1.4142 2.2361 % matice modulů prvků matice C 2.2361 2.8284 angle( C) ans = 0.7854 1.1071 % matice argumentů prvků matice C (v radiánech) 0.4636 0.7854 180*angle( C)/pi ans = 45.0000 63.4349 C' % matice argumentů prvků matice C (ve stupních) 26.5651 45.0000 % Hermitovsky sdružená matice k matici C ans = 1.0000 - 1.0000i 2.0000 - 1.0000i conj( C') 1.0000 - 2.0000i 2.0000 - 2.0000i % transponovaná matice k matici C % = Hermitovsky sdružená a následně komplexně sdružená ans = 1.0000 + 1.0000i 2.0000 + 1.0000i 1.0000 + 2.0000i 2.0000 + 2.0000i Matice s nimiž jsme dosud pracovali byly tzv. plné (full). Plné matice mají v paměti počítače alokován pro každý svůj prvek stejně veliký paměťový prostor. Pokud je většina prvků matice nulová, je popsaný způsob uložení matice neefektivní (pro uložení nuly rezervujeme v paměti šest bajtů stejně jako pro racionální číslo typu double). Popsaný problém vyřešili tvůrci MATLABu zavedením tzv. řídkých (sparse) matic. Při ukládání těchto řídkých matic si pamatujeme indexy (číslo řádku, číslo sloupce) a hodnotu pouze nenulových prvků matice (nulové prvky tudíž nezabírají žádnou paměť). MATLAB nikdy nevytváří řídké matice sám. O tom, zda má matice dostatečný počet nulových prvků, aby bylo efektivní deklarovat ji jako řídkou, musí rozhodnout programátor. Nulovou řídkou matici A o m řádcích a n sloupcích vytvoříme voláním A=sparse(m,n). Jeli matice B plnou maticí, kterou chceme konvertovat na matici řídkou, použijeme zápisu B= sparse(B). Naopak, máme-li řídkou matici C, kterou chceme převést na matici plnou, učiníme tak pomocí C=full(C). Provádíme-li s řídkými maticemi základní maticové operace (sčítání, odčítání, násobení, atd.), je výsledná matice opět řídká. Co se týká funkcí MATLABu, jež slouží pro práci s řídkými maticemi, jejich jména v sobě zahrnují písmena sp. Zatímco funkce ones(m,n) vytvořila plnou matici m×n ze samých jedniček, spones nahradí nenulové prvky matice jedničkama. Zatímco rand(m,n) vytvořila plnou matici m×n náhodných čísel s rovnoměrným rozložením, sprand vytvoří matici řídkou. K testování matice zda je řídká či nikoli slouží funkce issparse (pro řídkou matici vrací funkce hodnotu true – pravda). Počítače a programování 2 79 Práci s řídkými maticemi ilustrují následující příklady: F = rand( 5, 5) F = 0.4447 0.6154 0.7919 0.9218 0.7382 0.1763 0.4057 0.9355 0.9169 0.4103 S = sprand( 5, 5, 0.1) S = (3,2) (3,3) (5,4) 0.8381 0.0196 0.6813 % plná matice 5×5 náhodných čísel od nuly do jedné % s rovnoměrným rozložením hustoty pravděpodobnosti 0.8936 0.0579 0.3529 0.8132 0.0099 0.1389 0.2028 0.1987 0.6038 0.2722 0.1988 0.0153 0.7468 0.4451 0.9318 % řídká matice 5×5 náhodných čísel od nuly do jedné % s rovnoměrným rozložením; zhruba 5⋅5⋅0.1 ≈ 3 % prvky matice jsou nenulové % řídké matice MATLAB vypisuje tímto způsobem: % indexy a hodnota nenulových prvků issparse( F) ans = 0 % je matice F řídká? % NENÍ issparse( S) ans = 1 % je matice S řídká? % ANO, JE A = F + full( S) % součet dvou plných matic... issparse( A) ans = 0 % ... je plná matice B = sparse( F) + S % součet dvou řídkých matic... issparse( B) ans = 1 % ... je řídká matice C = F + S % součet plné a řídké matice... issparse( C) ans = 0 % je plná matice Dosud jsme pracovali s klasickými maticemi. Pokud byl za identifikátorem proměnné v kulaté závorce jediný index, pracovali jsme s řádkovým vektorem (matice o rozměru 1×N). Pokud byly za identifikátorem dva indexy, pracovali jsme s klasickou maticí. Kromě toho MATLAB umožňuje i práci s vícerozměrnými poli (multidimensional arrays). Tato pole mají za identifikátorem proměnné tři a více indexů. Práci s vícerozměrnými poli si vysvětlíme na příkladě pole trojrozměrného. Máme-li pole A(m,n,o), značí m řádek v matici, n je sloupec v matici a o značí stránku, na níž je matice natištěna (viz obr. 4.3) Práce s vícerozměrnými poli je analogická práci s našimi klasickými dvojrozměrnými maticemi. Pro jejich vytváření používáme např.funkce zeros, ones, rand (s třemi argumenty pro trojrozměrná pole, se čtyřmi argumenty pro pole čtyřrozměrná6, atd.). Operace s vícerozměrnými poli můžeme provádět složku po složce nebo můžeme počítat maticově 6 Např. ones(2,3,2,2) vytvoří matice jedniček o rozměru 2×3, které jsou uspořádány do super-matice 2×2. Struktura čtyřrozměrného pole je naznačena na obr. 4.3. 80 Fakulta elektrotechniky a komunikačních technologií VUT v Brně s maticemi natištěnými na určité stránce pole; přímé operace s vícerozměrnými poli MATLAB nepodporuje. o a) ( m, n, o ) (1, 1, 3) (1, 2, 3) (1, 3, 3) (2, 1, 3) (2, 2, 3) (2, 3, 3) (1, 1, 2) (1, 2, 2) (1, 3, 2) (2, 1, 2) (2, 2, 2) (2, 3, 2) m (1, 1, 1) (1, 2, 1) (1, 3, 1) (2, 1, 1) (2, 2, 1) (2, 3, 1) n b) (1, 1, 1, 1) (1, 2, 1, 1) (1, 3, 1, 1) (1, 1, 1, 2) (1, 2, 1, 2) (1, 3, 1, 2) (2, 1, 1, 1) (2, 2, 1, 1) (2, 3, 1, 1) (2, 1, 1, 2) (2, 2, 1, 2) (2, 3, 1, 2) (1, 1, 2, 1) (1, 2, 2, 1) (1, 3, 2, 1) (1, 1, 2, 2) (1, 2, 2, 2) (1, 3, 2, 2) (2, 1, 2, 1) (2, 2, 2, 1) (2, 3, 2, 1) (2, 1, 2, 2) (2, 2, 2, 2) (2, 3, 2, 2) Obr. 4.3 Vícerozměrná pole v MATLABu: a) trojrozměrné b) čtyřrozměrné Posledním datovým typem MATLABu , o němž se zmíníme, jsou buňková pole (cell arrays). Jedná se o pole, v němž každý prvek může obsahovat data jiného typu (viz obr. 4.4). Buňkové pole z uvedeného obrázku bychom vytvořili pomocí následujícího zdrojového kódu: A{1,1} = [1+j, 1+2*j, 1+3*j; 1+j, 2+j, 3+j] A{1,2} = [1e-3, 1.111; 1e+0, 3.333; 1e+3, 5.555] A{2,1} = ' T E X T ' Odezva na uvedený zdrojový text by v příkazovém okně MATLABu vypadala následovně: A = [2x3 double] ' T E X T ' [3x2 double] [] Pokud jsou splněny podmínky lineární algebry, můžeme s jednotlivými buňkami pole provádět standardní maticové operace: B = A{1,1} * A{1,2} B = 1.0e+003 * 1.0010 + 3.0020i 3.0020 + 1.0010i 0.0100 + 0.0244i 0.0244 + 0.0100i Počítače a programování 2 81 Jen stručně poznamenejme, že MATLAB nám umožňuje rovněž práci s datovým typem struktura (structure). Práce se strukturami v MATLABu je velmi podobná práci se strukturami v jazyce C; proto se zde tímto datovým typem nebudeme zabývat. 1 + 1i 1 + 2i 1 + 1i 2 + 1i 1.111 1e+0 3.333 1e+3 5.555 1 + 3i 3 + 1i 'TEXT' Obr. 4.4 1e-3 [ ] Buňkové pole v MATLABu Tím máme probráno vytváření základních typů matic a polí, které nám dává MATLAB k dispozici. Nyní, když máme matice vytvořeny, můžeme se soustředit na operace s nimi. 4.2.2 Aritmetické operace Jak jsme již naznačili, MATLAB upřednostňuje vykonávání aritmetických operací rovnou s celými maticemi. Mějme matice A a B o rozměru 2×2: A = [3, 1; 1, 3] A = 3 1 1 3 B = [1, 2; 2, 1] B = 1 2 2 1 Tyto matice můžeme sečíst: C = A + B C = 4 3 3 4 Od matice A můžeme odečíst matici B (nebo naopak): D = A - B D = 2 -1 -1 2 82 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Můžeme vypočíst součin obou matic: E = A * B E = 5 7 7 5 Trošku složitější situace vznikne, pokud chceme matici A podělit maticí B. Nejprve s využitím standardní funkce MATLABu inv invertujeme matici B, a poté invertovanou maticí B násobíme zprava matici A: F = A * inv( B) F = -0.3333 1.6667 1.6667 -0.3333 Tento přímý přístup k výpočtu však autoři MATLABu nedoporučují. Přímý výpočet inverzní matice je totiž časově relativně náročný a je náchylný k případným numerickým nestabilitám. V případě řešení soustavy lineárních rovnic A⋅x = b (4.3a) (A je matice koeficientů N×N, x je sloupcový vektor neznámých N×1 a b je sloupcový vektor pravých stran N×1) nebo x⋅A = b (4.3b) (x je řádkový vektor neznámých 1×N, A je matice koeficientů N×N a b je řádkový vektor pravých stran 1×N) je proto doporučováno nahradit výpočet inverzní matice maticovým dělením, založeným na Gaussově eliminaci. A ⋅ x = b → x = A −1 ⋅ b → x = A \ b (4.4a) x ⋅ A = b → x = b ⋅ A −1 (4.4b) → x = b/A V případě (4.4a) dělíme vektor b maticí A zleva, což realizujeme zpětným lomítkem. V případě (4.4b) dělíme vektor b maticí A zprava, k čemuž slouží lomítko obyčejné. Vraťme se nyní k výše uvedené operaci F=A*inv(B). Tato operace odpovídá řešení maticové rovnice F⋅B = A. Jedná se tedy o případ (4.4b) s tím rozdílem, že všechny proměnné jsou matice o rozměrech 2×2. Matici F tedy můžeme vypočíst pomocí obyčejného lomítka, aniž bychom počítali invertovanou matici A: F = A / B F = -0.3333 1.6667 1.6667 -0.3333 Vidíme, že výsledek je v obou případech stejný. Aritmetické operace s maticemi, o nichž jsme se dosud bavili, musely vždy splňovat pravidla lineární algebry. Pokud tomu tak nebylo, MATLAB nás vždy upozornil červeným hlášením v příkazovém okně na chybu (viz obr. 4.1). Nicméně, kromě ryzích maticových operací můžeme v MATLABu provádět rovněž operace po složkách. Máme-li sloupcový Počítače a programování 2 83 vektor A=[1;2;3] a sloupcový vektor téhož rozměru B=[3;2;1], potom součin po složkách značíme C=A.*B. Vektor C na levé straně má stejný rozměr jako vektory A, B na straně pravé. První prvek vektoru C je roven součinu prvého prvku vektoru A s prvým prvkem vektoru B, atd.: 1 ⋅ 3 3 C = 2 ⋅ 2 = 4 3 ⋅1 3 (4.5) Obdobně je tomu při dělení po složkách ./ a při umocňování po složkách .^. Další informace o aritmetických operacích v MATLABu jsou k dispozici v nápovědě7. 4.2.3 Logické operace Základními třemi logickými operacemi v MATLABu je logický součin AND (značíme &), logický součet OR (značíme |) a negace NOT (značíme ~). Logický operátor AND vrací nenulovou hodnotu, jsou-li oba jeho operandy nenulové. Logický operátor OR vrací nenulovou hodnotu, je-li alespoň jeden z jeho dvou operandů nenulový. Operátor NOT vrací nulu pro nenulový operand a naopak. Realizace logických operací v MATLABu by měla být zřejmá z následujícího příkladu: u = [0.0, 0.1, 1.0, 1.1, 1.0, 0.1, 0.0] v = [0, 2, 0, 2, 0, 2, 0] u&v = 0 1 0 1 0 1 0 u|v = 0 1 1 1 1 1 0 1 0 0 0 0 0 1 1 0 1 0 1 0 1 ~u = ~v = % logický součin % logický součet % negace vektoru u % negace vektoru v Další informace o logických operacích v MATLABu jsou k dispozici v nápovědě. 7 Mluvíme-li o nápovědě, máme tím na mysli tři zdroje informací. Prvním zdrojem jsou tištěné příručky, které uživatel dostává spolu s instalačním CD programu MATLAB. Druhým zdrojem je elektronická verze příruček, k níž se uživatel nejsnáze dostane prostřednictvím Launch Padu. Třetím zdrojem je nápověda příkazového řádku; napíšeme-li v příkazovém okně MATLABu help termin, vypíše MATLAB do svého příkazového okna všechny základní údaje o termínu termin. Zajímá-li nás význam funkce clc, napíšeme help clc a v příkazovém okně se nám zobrazí CLC Clear command window. CLC clears the command window and homes the cursor. See also HOME. 84 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 4.2.4 Relační operace Relační operátory MATLABu jsou vyjmenovány v tab. 4.2. < menší než <= menší než nebo rovno > větší než >= větší než nebo rovno == rovná se ~= nerovná se Tab. 4.2 Relační operátory MATLABu Relační operátory můžeme použít pro porovnání dvou matic stejného rozměru; operátory totiž vždy pracují položku po položce. Použití relačních operátorů by mělo být zřejmé z následujícího příkladu: A = [0, 1, 2, 3, 5] B = [5, 3, 2, 1, 0] % dva vektory stejných rozměrů A>B = 0 0 0 1 1 % operátor větší než A>=B = 0 0 1 1 1 A<B = 1 1 0 0 0 A<=B = 1 1 1 0 0 A==B = 0 0 1 0 0 A~=B = 1 1 0 1 1 % operátor větší než nebo rovno % operátor menší než % operátor menší než nebo rovno % operátor rovná se % operátor nerovná se Další informace o relačních operacích v MATLABu jsou k dispozici v nápovědě. 4.2.5 Kontrolní příklady Příklad 1. Vytvořte nulovou matici A o rozměru 4×4. Do matice uložte na pozici (m,n), kde m je číslo řádku a n je číslo sloupce, následující čísla: (2,3): 3j, (1,4): exp(-1), (3,2): 2+2, (4,3): -6j. Matici A transponujte. Příklad 2. Vypočítejte reálnou a imaginární část matice A z předchozího příkladu. Vypočítejte matici modulů a matici fází z předchozího příkladu. Příklad 3. Převeďte matici A z prvního příkladu na řídkou matici. Vygenerujte řídkou matici B o rozměru 4×4, která obsahuje náhodné prvky od nuly do jedné s rovnoměrným rozložením; přibližně pět prvků matice má být nenulových. Vypočítejte součin matic A a B. Otestujte, zda je výsledná matice řídká či nikoli. Bude-li výsledná matice řídká, převeďte je na matici plnou. Příklad 4. Vytvořte vícerozměrné pole P, které má na třech stránkách natištěny matice o rozměru 4×4. Matice na stránkách naplňte náhodnými čísly s rovnoměrným rozložením. Náhodné prvky na první straně budou od 0 do +2, na druhé straně od –1 do +1 a na třetí straně Počítače a programování 2 85 od –2 do +2. Vypočtěte matici S o rozměru 4×4, která bude dána součtem matic na prvé, druhé a třetí straně vícerozměrného pole P. Příklad 5. Deklarujte sloupcový vektor B = [1, 2, 3, 4]T a řádkový vektor D = [4, 3, 2, 1]. Vypočtěte takový sloupcový vektor b, aby byla splněna maticová rovnice S⋅b = B. Vypočítejte takový řádkový vektor d, aby byla splněna maticová rovnice d⋅S = D. Matice S odpovídá výsledku z předchozího příkladu. Příklad 6. Vypočítejte logický součin a logický součet vektorů b a d z předchozího příkladu. 4.2.6 Kontrolní otázky 1. Které funkce použijeme k vytvoření plné matice o rozměru 2×3, jež je naplněna samými nulami, samými jedničkami a náhodnými čísly od 0 do 1 s rovnoměrným rozdělením? 2. Kterou funkci použijeme k vytvoření matice o rozměru 4×4, jež má na první vedlejší diagonále prvky [3.0, 2.0, 1.0]? 3. Jakým způsobem vytvoříme komplexní matici, která má na hlavní diagonále prvky [1+j, 2+2j, 3+3j]? 4. Jakým postupem vypočteme transponovanou komplexní matici a Hermitovsky sdruženou komplexní matici? 5. Které funkce slouží k výpočtu modulu a argumentu komplexního čísla? 6. Co jsou to plné a řídké matice? 7. Kterou funkci musíme použít, abychom zjistili, zda je matice plná nebo řídká? 8. Jaké musejí být matice A a B, aby byl jejich součet řídkou maticí? 9. Co jsou to vícerozměrná pole? 10. Lze v MATLABu pracovat s vícerozměrnými poli maticově? 11. Co jsou to buňková pole? 12. Matice A má rozměr 3×2. Jaký musí být rozměr matice B, aby při zápisech A+B a A*B nenahlásil MATLAB chybu? 13. Matice A a matice B mají rozměr 3×2. Jakou instrukci musíme v MATLABu použít, abychom vypočetli matici C o rozměru 3×2, jejíž jednotlivé složky jsou dány součinem odpovídajících složek matic A a B (prvek matice C v prvém řádku a prvém sloupci je dán součinem prvků v prvém řádku a prvém sloupci matic A a B, atd.)? 14. Jak lze efektivně vyřešit maticovou rovnici A⋅x = B, kde A je matice koeficientů, x je sloupcový vektor neznámých a B je vektor pravých stran? 15. Jaké prvky musejí mít řádkové vektory a a b, aby logický součin a&b = [0,1,1,0]? 16. Jaké prvky musejí mít řádkové vektory a a b, aby logický součet a|b = [0,1,1,0]? 17. Které relační operátory jsou implementovány v MATLABu? 18. Jaké druhy nápovědy máme v MATLABu k dispozici a jakým způsobem se dostaneme k jejich obsahu? 86 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 4.3 M-soubory Dosud jsme v MATLABu všechny výpočty prováděli tak, že jsme do příkazového okna postupně zapisovali jednotlivé instrukce našeho programu. Tento přístup lze použít jen u krátkých jednoduchých výpočtů. V případě rozsáhlejších programů zapisujeme sekvence instrukcí do textových m-souborů (m-files). Voláme-li pak vytvořený m-soubor v příkazovém okně MATLABu, MATLAB postupně vykonává jednotlivé instrukce uvedené v tomto souboru. M-soubor je tedy obdobou funkce, s níž jsme pracovali v jazyce C. M-soubory můžeme vytvářet v libovolném textovém editoru. My však budeme využívat editor m-souborů, který je přímo součástí MATLABu. Tento editor totiž umožňuje program krokovat, nabízí možnost prohlížení obsahu proměnných, umožňuje označit řádky, na nichž se má výpočet zastavit atd. Zdrojový text programu má v editoru barevně zvýrazněnou syntaxi, řádky zdrojového textu jsou očíslovány. nový m-soubor otevři m-soubor ulož m-soubor tiskni m-soubor vyhledej text vyhledej funkci vlož/vymaž přerušení programu zruš všechna přerušení programu ukonči ladění programu opakuj poslední editaci dokonči program zruš poslední editaci další krok (vystup z funkce) přilep text ze schránky další krok (vstup do funkce) kopíruj text do schránky další krok (nevstupuj do funkce) vyřízni text do schránky Obr. 4.5 Editor m-souborů, který je součástí MATLABu Obrázek editoru m-souborů, který je součástí MATLABu, je nakreslen na obr. 4.5. Editor má standardní menu, jehož nejdůležitější položky jsou rovněž umístěny na panelu nástrojů. Popis funkce jednotlivých tlačítek panelu uvádíme opět na obr. 4.5. M-soubory můžeme rozdělit do dvou velkých skupin – na skripty (scripts) a na funkce (functions). V následujících odstavcích si vysvětlíme, co to skripty a funkce jsou a jaký je mezi nimi rozdíl. 4.3.1 Skripty Skriptem rozumíme m-soubor, který obsahuje pouze posloupnost volání příkazů nebo funkcí. Skript nemá žádnou hlavičku. Pokud skript uložíme do souboru name.m, stačí do příkazového okna MATLABu napsat jméno souboru name, a skript je spuštěn. Skriptu nemůžeme předávat data z jeho vnějšku a ani od něj nemůžeme očekávat vrácení nějaké hodnoty. Skript je tedy schopen pracovat pouze s globálními proměnnými pracovního prostoru MATLABu. Práci se skripty si ukážeme na příkladu. Úkolem skriptu my_sort bude vygenerovat náhodný řádkový vektor o 20 prvcích (volání standardní funkce rand), prvky seřadit od nejmenšího čísla po největší (volání standardní funkce sort) a seřazená čísla vykreslit v okně s grafem (volání standardní funkce plot). Počítače a programování 2 87 Našemu skriptu my_sort, který bude uložen v souboru my_sort.m, tedy bude odpovídat následující zdrojový text: v = rand( 1, 20); u = sort( v); plot( u); % vygenerování náhodného vektoru % seřazení prvků podle velikosti % vykreslení seřazených čísel do grafu Vepíšeme-li do příkazového okna MATLABu řetězec my_sort, bude vykonána sekvence uvedených tří instrukcí, a objeví se okno s grafem (obr. 4.6). Popsaný program je uložený v adresáři sorting. 4.3.2 Funkce Základní odlišnost funkce od skriptu spočívá v tom, že funkci můžeme definovat vstupní parametry a že funkce může vracet hodnoty parametrů výstupních. Deklarujeme-li uvnitř funkce proměnné, jsou to obecně proměnné lokální, jež existují jen během vykonávání těla funkce. Prvním řádkem zdrojového textu funkce je hlavička: function y = average( x) Hlavička začíná klíčovým slovem function. Obr. 4.6 Dvacet seřazených náhodných čísel, reprezentovaných lomenou čarou Poté následuje seznam výstupních parametrů (v našem případě proměnná y). Seznam výstupních parametrů je následován rovnítkem a jménem funkce (average). Jméno funkce je následováno kulatými závorkami, jež obsahují seznam vstupních parametrů (v našem případě jediný parametr x). M-funkci doporučujeme ukládat vždy do souboru, jehož jméno odpovídá názvu funkce (v našem případě by to byl soubor average.m). Má-li funkce více výstupních parametrů, vepíšeme jejich seznam do lomených závorek a jednotlivé proměnné oddělíme čárkami; např.: function [x,y,z] = sphere( theta, phi, rho) Pokud funkce naopak nemá žádné výstupní parametry, vynecháme specifikaci výstupních parametrů a rovnítko function to_printer( x) nebo seznam výstupních parametrů nahradíme prázdnými lomenými závorkami function [] = to_printer( x) Povinná hlavička funkce může být následována nepovinnou nápovědou (tzv. H1 line): % AVERAGE Počítám střední hodnotu prvků vstupního vektoru x. Napíšeme-li pak do příkazového okna MATLABu help average, je výše uvedený vysvětlující text v tomto okně vypsán. Pokud chceme vytvořit rozsáhlejší vysvětlující text, musí naše vysvětlení následovat bezprostředně za řádkem H1. Vysvětlující text ukončíme prázdným řádkem nebo řádkem s příkazem. 88 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Za vysvětlujícím textem pak již následuje samotné tělo m-funkce. Celá funkce pro výpočet střední hodnoty prvků vektoru x by tedy vypadala následovně: function [r, i] = average( x) % AVERAGE Počítám střední hodnotu reálné a imaginární části prvků řádkového % vektoru x. Střední hodnota reálné části je uložena v prvním % výstupním parametru. Střední hodnota imaginární části je uložena % v druhém výstupním parametru. n = size( x, 2); r = sum( real( x), 2)/n; i = sum( imag( x), 2)/n; Standardní funkce size(x,2) vrací počet sloupců vektoru x; počet řádků vektoru x vrací size(x,1). Reálnou část komplexního čísla vrací standardní funkce real, imaginární část komplexního čísla vrací imag. Standardní funkce sum(x,2) vrací vektor součtů prvků x přes všechny sloupce (součet prvků v řádku); funkce sum(x,1) vrací vektor součtů prvků x přes všechny řádky (součet prvků v sloupci). Proměnná n, do níž ukládáme počet sloupců vstupního řádkového vektoru x (tedy počet prvků vstupního vektoru) je lokální proměnnou naší funkce. Proměnná n je tedy přístupná jen v rámci těla naší funkce a existuje pouze v době vykonávání těla této funkce. Uvedená m-funkce je uložena v adresáři averaging. 4.3.3 Globální proměnné, lokální funkce Globální proměnná m-funkce je taková proměnná, která sice existuje pouze po dobu vykonávání těla m-funkce. V rámci m-funkce píšeme seznam globálních proměnných za klíčové slovo global; proměnné v seznamu globálních proměnných oddělujeme mezerami. Seznam globálních proměnných, uvedených klíčovým slovem global, se musí opakovat rovněž v tělech volaných m-funkcí, které k nim mají mít přístup. Pokud chceme deklarovat funkci, která bude volána pouze z těla naší hlavní m-funkce (nepožadujeme, aby k ní měly přístup ostatní m-funkce, a nepředpokládáme, že by mohla být tato funkce volána samostatně), deklarujeme ji standardním způsobem na konci m-souboru, který obsahuje naši hlavní funkci. Hovoříme pak o lokální funkci. Práci s globálními proměnnými a lokálními funkcemi si vysvětlíme na jednoduchém příkladu. Našim úkolem je naprogramovat m-funkci main se dvěma vstupními řádkovými vektory se stejným počtem prvků a s žádným výstupním parametrem. Funkce main má za úkol vyčíslit pro oba vstupní vektory funkci r=sinc(s)=sin(s)/s, má vypočíst rozdíl mezi funkčními hodnotami funkce sinc a původními vstupními vektory a rozdíl má vykreslit do grafu. Vstupní vektory naplníme v příkazovém okně MATLABu pomocí příkazů x = (-100:100)*pi/ 50; y = (-100:100)*pi/200; % pi je standardní konstantou MATLABu, % která reprezentuje Ludolfovo číslo Vektory x a y sestávají z celkem 201 celočíselných prvků <-100, -99, …, -1, 0, 1, …, 99, 100>. O vytvoření celočíselného vektoru se stará operátor dvojtečka, který inkrementuje levý parametr (-100) tak dlouho, dokud není dosaženo parametru horního (+100). Vektory x a y pak následně předáme m-funkci main: Počítače a programování 2 89 function [] = main( x, y) % MAIN je hlavní funkcí programu pro práci s globálními proměnnými a % s lokálními funkcemi global m n; m = sinc( x); n = sinc( y); % deklarace globálních proměnných v hlavním programu % opakuje se i ve funkcích, které k nim mají mít přístup % volání lokální funkce [nx,ny] = differ( x, y); % (x,y) předány jako param., (m,n) jsou globální figure plot( 0:size(nx,2)-1, nx, 'r-'); hold on plot( 0:size(ny,2)-1, ny, 'b-'); hold off % % % % % otevření okna pro grafický výstup vykresl. prvků nx, červená čára podrž graf pro vykresl. další křivky vykresl. prvků ny, modrá lomená čára další křivka se nebude přidávat function b = sinc( a) % % % % lokální funkce funkce dostupná jen z těla main počet prvků vektoru a hledání nulové hodnoty ve vektoru a N = size( a, 2); [val,ind] = min( abs( a)); b(1:ind-1) = sin( a(1:ind-1))./a(1:ind-1); b(ind) = 1; b(ind+1:N) = sin( a(ind+1:N))./a(ind+1:N); % funkč.hodnota sinc pro a≠0 % funkč.hodnota sinc pro a=0 % funkč.hodnota sinc pro a≠0 Prázdné lomené závorky nalevo od rovnítka v hlavičce říkají, že funkce main neprodukuje žádné výstupní hodnoty. Pod hlavičkou následuje stručný vysvětlující text pro nápovědu v příkazovém okně MATLABu. Na dalším řádku následuje deklarace dvou globálních proměnných m a n (uvozeny klíčovým slovem global, odděleny mezerou). Do těchto globálních proměnných ukládáme funkční hodnoty funkce sinc. Funkce sinc je deklarována na konci m-souboru main.m jako lokální m-funkce. Na prvním řádku zjistíme počet prvků vstupního řádkového vektoru a (s funkcí size jsme se seznámili v předchozí podkapitole). Následně voláme funkci min, která do výstupního parametru val uloží hodnotu nejmenšího prvku svého vstupního Grafický výstup programu pro práci Obr. 4.7 vektoru (v našem případě absolutní s globálními proměnnými a lokálními funkcemi hodnota vstupního vektoru a) a do výstupního parametru ind index tohoto nejmenšího prvku. U vektoru a předpokládáme, že hodnoty jeho prvků monotónně vzrůstají a že procházejí nulou. Za tohoto předpokladu ukládáme do proměnné ind index jediného nulového prvku, a můžeme tak ošetřit dělení nulou, které by jinak při vyčíslování sinc nastalo. Jelikož lim sin ( x ) x = 1 x→0 (4.6) 90 Fakulta elektrotechniky a komunikačních technologií VUT v Brně uložíme tuto hodnotu do příslušného prvku výstupního vektoru natvrdo. Prvky s nižším indexem nežli ind a prvky s vyšším indexem nežli ind pak vyčíslujeme klasicky. V dalším kroku počítáme rozdíl prvků vstupních vektorů x, y a prvků globálních vektorů m, n (o tento výpočet se stará m-funkce differ). Jakmile máme k dispozici vektory rozdílů nx, ny, otevřeme okno pro graf (volání standardní funkce figure) a vykreslujeme průběhy voláním standardní funkce plot. Prvním parametrem funkce plot je vektor x-ových souřadnic, druhým parametrem je vektor y-ových souřadnic a třetím parametrem je specifikace křivky (symbol – značí lomenou čáru, b je modrá barva čáry a r značí červenou barvu čáry). Další možnosti, které se vztahují ke specifikaci parametrů křivky, jsou uvedeny v nápovědě MATLABu. Jelikož chceme do jediného grafu vykreslit dvě křivky, musíme původně vytvořený graf podržet pro další kreslení. Podržení grafu se aktivuje voláním hold on a deaktivuje voláním hold off. Nyní se podívejme na obsah m-souboru differ.m, který obsahuje volanou stejnojmennou m-funkci: function [nx,ny] = differ( x, y) % DIFFER počítá rozdíl mezi vstupními vektory a vektory globálními global m n; nx = m - x; ny = n - y; % funkci upozorňujeme na existenci globálních vektorů % vyčíslení výstupních parametrů Hodnoty vektorů x, y předáváme funkci differ jako parametry. Hodnoty vektorů m, n předávat nemusíme, protože se jedná o proměnné globální (proto se v těle m-funkce differ opakuje deklarace globálních proměnných m, n). Celý program je uložen v adresáři global. 4.3.4 Řízení běhu programu Při psaní programů v jazyce C jsme si na vlastní kůži vyzkoušeli, že při algoritmizaci úlohy se neobejdeme bez speciálních příkazů, které nám umožňují podmíněně větvit program, které nám umožňují vytvářet cykly a s jejichž pomocí můžeme právě vykonávané cykly přerušovat. Jelikož námi vyvíjené m-funkce jsou obdobou programů napsaných v jazyce C, jsou právě popsané příkazy přirozenou součástí MATLABu. Jelikož řídicí příkazy MATLABu jsou velmi blízké řídicím příkazům jazyka C, namísto detailního popisu uvedeme ilustrativní příklady jejich použití. Všechny příklady jsou uloženy v adresáři flow_control. Příkaz if – else – elseif umožňuje vykonat sekvenci příkazů pouze tehdy, je-li splněna určitá logická podmínka. Příkaz použijeme v těle m-funkce testing(a), která vytiskne do příkazového okna text Parametr je kladný pro a>0, text Parametr je záporný pro a<0 a text Parametr je nulový pro a=0. function [] = testing( a) if a>0 % testuj libovolný parametr, je-li kladný fprintf(1, 'Parametr je kladný'); elseif a<0 % testuj nekladný parametr, je-li záporný fprintf(1, 'Parametr je záporný'); else % parametr je nekladný a současně nezáporný Počítače a programování 2 91 fprintf(1, 'Parametr je nulový'); end K vytištění textu do příkazového okna MATLABu používáme standardní funkci fprintf (id,string), kde id je odkaz na výstupní zařízení (pro příkazové okno id=1) a string je řetězec, který chceme zobrazit. Je-li číslo a kladné, vykoná se příkaz následující za řádkem if a řízení programu je přeneseno na konec bloku (za řádek s klíčovým slovem end). Není-li číslo kladné (je nekladné), provádíme test za klíčovým slovem endif. Pokud je číslo záporné, vykoná se příkaz následující za řádkem elseif a řízení programu je přeneseno na konec bloku. Není-li číslo ani kladné ani záporné (ani jedna z testovacích podmínek není pravdivá), vykoná se řádek následující za klíčovým slovem else. Příkaz switch – case – otherwise rozhoduje na základě určité logické podmínky, která sada instrukcí má být vykonána. Příkaz použijeme v těle m-funkce charts (a_type), která vygeneruje data pro prostorový graf a která na základě parametru a_type rozhodne o typu grafu, který bude pro reprezentaci vygenerovaných dat použit. Obr. 4.8 Typy grafů, z nichž vybíráme pomocí přepínače switch. Síťový graf mesh (Figure No. 1). Plošný graf surf (Figure No. 2). Plošný graf surfl (Figure No. 5). Konturový graf contour (Figure No. 6). Vstupní parametr a_type m-funkce charts může nabývat libovolné celočíselné hodnoty. 92 Fakulta elektrotechniky a komunikačních technologií VUT v Brně Pro a_type=1 mají být data reprezentována síťovým grafem. Data lze do síťového grafu vynášet voláním funkce mesh(x), kde x je reálná matice. Pro a_type=2 mají být data reprezentována plošným grafem. Vizualizaci dat pomocí plošného grafu má na starosti standardní funkce surf(x), kde x je reálná matice. Barevnou paletu pro vykreslení grafu určíme voláním funkce colormap(palette), kde palette je předdefinovaná barevná paleta. Seznam těchto předdefinovaných palet nalezneme v nápovědě MATLABu. Pro a_type=3 jsou data opět reprezentována plošným grafem. V tomto případě však využijeme funkci surfl. Na rozdíl od výše zmíněné funkce surf předpokládá surfl nasvícení plochy grafu bodovým zdrojem světla, což může zlepšit srozumitelnost grafické reprezentace vynášených dat. Pokud chceme potlačit síť, kterou vidíme u grafu vykresleného pomocí funkce surfl, musí být volání surfl následováno textem shading interp (barvy grafu jsou interpolovány, aby vznikl dojem postupně se měnícího odstínu). Pro jinou hodnotu a_type nežli 1, 2 a 3 mají být data reprezentována konturovým grafem. Konturový graf vytvoříme voláním funkce contour(x), kde x je opět reálná matice vizualizovaných dat. Právě uvedenému popisu odpovídá následující zdrojový text: function [] = charts( a_type) figure num = peaks( 25); % otevření prázdného okna pro graf % vygenerování matice 25x25 switch a_type case 1 % mesh( num); case 2 % surf( num); colormap( bone); case 3 % surfl( num); shading interp; colormap( copper); otherwise % contour( num); colormap( hot); end a_type=1 % sítový graf a_type=2 % standardní plošný graf % nastavení barevné palety grafu a_type=3 % plošný graf s bodovým osvícením % spojitá změna barevného odstínu % nastavení barevné palety grafu a_type různé od 1, 2, 3 % konturový graf % nastavení barevné palety grafu Za klíčovým slovem switch následuje jméno proměnné, jejíž obsah určuje větvení programu. Jednotlivé větve programu pro daný obsah proměnné jsou uvedeny klíčovým slovem case, které je následováno specifikovanou hodnotou proměnné. Pokud proměnná nenabude žádné ze specifikovaných hodnot, je vykonáván kód, následující klíčové slovo otherwise. Celý příkaz je ukončen klíčovým slovem end. Data pro graf jsou generována voláním standardní funkce MATLABu peaks(N). Tato funkce vrací matici o rozměru N×N, jejíž prvky odpovídají vzorkům dvojrozměrné Gaussovské distribuce. Funkce charts nevrací žádnou vypočtenou hodnotu. Příkaz while slouží k opakování sekvence instrukcí po dobu, dokud výraz za klíčovým slovem while vrací v reálné části samé nenulové hodnoty. Jakmile je výraz nulový, smyčka while je přerušena. Počítače a programování 2 93 Použití příkazu si ukážeme na příkladu m-funkce, která prostřednictvím příkazového okna žádá uživatele o zadání čísla z intervalu od nuly do devíti. Pokud uživatel požadovanou hodnotu zadá, funkce korektně zadanou hodnotu vrátí. Pokud uživatel požadovanou hodnotu nezadá, m-funkce zopakuje výzvu k zadání korektní hodnoty. Požadovanou činnost lze naprogramovat pomocí následujícího kódu: function out = correct a = input('Číslo od 0 do 9: '); while (a<0) | (a>9) a = input('Číslo od 0 do 9: '); end % výzva k zadání hodnoty % pokud hodnota nevyhovuje % opakujeme výzvu k zadání out = a; % funkce vrátí korektní hodnotu K zadání hodnoty parametru prostřednictvím příkazového okna slouží standardní funkce MATLABu a=input(string), kde string je řetězec, který se má zobrazit v příkazovém okně. Funkce input vrací hodnotu, zadanou uživatelem. Výraz za klíčovým slovem while nabude nenulové hodnoty v případě, kdy je zadané číslo větší než nula (první závorka má nulovou hodnotu) a kdy je současně zadané číslo menší než devět (druhá závorka má nulovou hodnotu). Logický součet dvou nulových hodnot je totiž rovněž nulový, a smyčka while je tím přerušena. Výše uvedená funkce je uložena v m-souboru correct.m. Příkaz for slouží k vytvoření cyklu, u něhož je přesně stanoven počet opakování. Použití příkazu for si ukážeme na programu, jehož úkolem je vygenerovat uživatelem stanovený počet náhodných čísel a vypočíst jejich geometrický průměr: function out = geometric( N) sum = 1; for n=1:N sum = sum*rand; end % počítáme součin N náhodných čísel out = sum^(1/N); % počítáme N-tou odmocninu součinu Počet náhodně generovaných čísel zadáváme prostřednictvím vstupního parametru N. Ve smyčce přes N cyklů pak postupně vzájemně násobíme jednotlivé náhodně vygenerované hodnoty od nuly do jedné (standardní funkce MATLABu rand). Odmocninu z výsledného součinu pak počítáme umocněním součinu sum hodnotou 1/N. Uvedená funkce je uložena v m-souboru geometric.m. Pomocí příkazu continue můžeme ukončit vykonávání aktuální iterace smyčky while nebo for a můžeme zahájit novou iteraci smyčky. Příkaz continue využijeme ve variaci na výše popsanou funkci geometric. M-funkce geometric2(x) vrací geometrický průměr prvků vstupního vektoru x. Příkaz continue použijeme k ošetření situace, kdy prvek vstupního vektoru x nabývá nulové hodnoty, a geometrický průměr tak vychází nulový. Pokud ve smyčce narazíme na nulový prvek, inkrementujeme počítadlo nulových činitelů p a aktuální iteraci přerušíme. Do součinu tak nezahrneme nulový činitel a snížíme o jedničku řád odmocniny. Tváříme se tedy, jako by nulový prvek vstupního vektoru neexistoval: 94 Fakulta elektrotechniky a komunikačních technologií VUT v Brně function out = geometric2( x) N = size( x, 2); % počet prvků vstupního řádkového vektoru sum = 1; p = 0; % počáteční nastavení proměnné pro součin % počet vynechaných činitelů for n=1:N if x(n)==0 p = p + 1; continue; end sum = sum*x(n); end % přes všechny prvky vstupního vektoru % je-li prvek nulový % inkrementuj počítadlo nulových prvků % přeruš iteraci out = sum^(1/(N-p)); % počítáme odpovídající odmocninu součinu % pro nenulový prvek počítej součin Uvedený zdrojový text je uložen v m-souboru geometric2.m. Příkaz break je podobný právě popsanému příkazu continue. Příkaz break rovněž ukončí aktuální iteraci smyčky, avšak namísto zahájení další iterace (jako tomu bylo v případě continue) opouštíme celou smyčku (žádná další iterace již neproběhne). Pomocí příkazu break modifikujeme původní m-funkci geometric2 tak, aby se v případě výskytu nulového prvku vstupního vektoru x objevilo v příkazovém okně MATLABu hlášení Jeden z prvků je nulový, průměr nabývá nulové hodnoty a aby funkce v tomto případě vrátila prázdnou matici: function out = geometric3( x) N = size( x, 2); sum = 1; % počet prvků vstupního řádkového vektoru % počáteční nastavení proměnné pro součin for n=1:N % přes všechny prvky vstupního vektoru if x(n)==0 % je-li prvek nulový fprintf(1, 'Jeden z prvků je nulový, průměr nabývá nulové hodnoty'); sum = []; % funkce vrátí prázdnou matici break; % přeruš cyklus end sum = sum*x(n); % pro nenulový prvek počítej součin end out = sum^(1/N); % počítáme odpovídající odmocninu součinu Uvedený zdrojový text je uložen v m-souboru geometric3.m. Příkaz return je obdobou příkazu break. Rozdíl mezi oběma příkazy spočívá v tom, že return způsobí odchod z právě vykonávané m-funkce, zatímco break se postará pouze o odchod z vykonávaného cyklu. Příkaz return použijeme v programu, který má za úkol načíst denní teploty, které byly naměřeny od 0:30 do 24:00 v půlhodinových intervalech. Teploty byly do souboru uloženy pomocným skriptem to_file.m: % naměřené teploty A = [-17.1, -18.5, -18.5, -15.0, -17.3, -18.6, -18.3, -13.5, -17.5, -18.7, -18.0, -11.5, -17.7, -18.7, -17.5, -09.2, -18.0, -18.7, -17.0, -07.1, -18.3,... -18.6,... -16.0,... -04.6,... Počítače a programování 2 -03.3, -00.6, -03.8, -11.1, -01.1, -01.0, -04.5, -12.3, -00.0, -01.5, -05.4, -13.5, 95 -00.0, -02.0, -07.2, -14.9, -00.0, -02.6, -08.1, -16.0, fid = fopen( 'jan0202.dat', 'w'); fwrite( fid, A, 'float32'); fclose( fid); -00.3,... -03.2,... -09.0,... -17.2]; % otevření souboru pro zápis % zapsání matice A do souboru % uzavření naplněného souboru Standardní funkce MATLABu fopen otevře soubor jan0202.dat (řetězec v prvém parametru), a to pouze pro zápis (znak w v druhém parametru). Funkce vrátí odkaz na otevřený soubor. Obsah matice teplot A zapíšeme do souboru volání funkce fwrite. Prvním parametrem je odkaz na otevřený soubor fid, druhý parametr obsahuje identifikátor zapisované matice a pomocí třetího parametru specifikujeme formát, ve kterém mají být číselné hodnoty do souboru uloženy (32-bitová reprezentace racionálního čísla v pohyblivé desetinné čárce). Další možné formáty lze nalézt v nápovědě MATLABu. Jakmile je soubor naplněn daty, uzavřeme jej voláním funkce fclose. Nyní předpokládejme, že nám jsou dodávány soubory s denními teplotami, jejichž formát je kompatibilní s právě popsaným způsobem ukládání. Našim úkolem je denní teploty načíst, vynést je do sloupkového grafu a vypočíst průměrnou denní teplotu. Řešení všech těchto úkolů v sobě zahrnuje m-soubor measuring.m: function out = measuring filename = 'jan0202.dat'; temp = from_file( filename); if temp~=[] hour = 0.5:0.5:24; bar( hour, temp); out = sum( temp)/48; else out = []; end % nastavení jména souboru % načtení teplot ze souboru % pokud načítání proběhlo korektně % interval časů od 0:30 po 24:00 % teploty do sloupkového grafu % fce vrátí průměrnou denní teplotu % pokud načítání proběhlo nekorektně % fce vrátí prázdnou matici Nejprve do pomocné proměnné filename uložíme řetězec, který obsahuje název zpracovávaného souboru teplot. V druhém kroku voláme svou vlastní funkci from_file, která do pomocné proměnné temp uloží řádkový vektor denních teplot. V případě, že je se souborem nebo s daty něco v nepořádku, uloží funkce do temp prázdnou matici. Funkci from_file se budeme detailně věnovat za chvíli. Pokud temp není prázdnou maticí, uložíme do vektoru hour údaj o časech, kdy byly naměřeny odpovídající teploty, volním standardní funkce bar vytvoříme sloupkový graf (viz Obr. 4.9 Sloupkový graf denních teplot 96 Fakulta elektrotechniky a komunikačních technologií VUT v Brně obr. 4.9) a do výstupní proměnné out uložíme údaj o průměrné denní teplotě. Pokud je temp prázdnou maticí, m-funkce measuring rovněž vrátí prázdnou matici (příkaz za klíčovým slovem else). Nyní ještě pár poznámek k popsaným krokům. Prvním prvkem vektoru hour je 0.5 (první číslo na pravé straně), což odpovídá času 0:30. Další prvky vektoru hour MATLAB počítá postupným zvyšováním první hodnoty o přírůstek, který je dán číslem mezi dvojtečkami. Zvyšování je vykonáváno tak dlouho, dokud není dosaženo horní hranice intervalu (poslední číslo na pravé straně). Standardní funkce pro sloupkový graf má dva parametry. Prvním parametrem je vektor x-ových hodnot (vodorovná souřadnice grafu), druhým parametrem je vektor y-ových hodnot (svislá souřadnice). Oba vektory musejí mít přirozeně stejný rozměr. Při výpočtu průměrné hodnoty voláme standardní funkci sum, která sečte všechny prvky vektoru temp, a součet dělíme celkovým počtem změřených teplot. Dále se zaměřme na funkci from_file, která slouží k načítání teplot ze souboru a k základnímu testování korektnosti načtených dat. Funkce je uložena v m-souboru from_file.m: function out = from_file( filename) fid = fopen( filename, 'r'); % otevření souboru s teplotami if fid<=0 % chyba při otevření souboru fprintf( 1, 'Soubor s daty nenalezen'); out = []; % funkce vrátí prázdnou matici return; % ukončení běhu funkce end [A, count] = fread( fid, 48, 'float32'); fclose(fid); [val, ind] = min( A); if val<-50 fprintf( 1, 'Chybná data'); out = []; return; end % nalezení nejnižší teploty % pokud teplota nerealisticky nízká [val, ind] = max( A); if val>+50 fprintf( 1, 'Chybná data'); out = []; return; end % nalezení nejvyšší teploty % pokud teplota nerealisticky vysoká out = A; % funkce vrátí prázdnou matici % ukončení běhu funkce % funkce vrátí prázdnou matici % ukončení běhu funkce % pokud jsou všechny testy negativní % funkce vrátí matici denních teplot M-funkci zahajujeme otevřením souboru s teplotami. Soubor nyní otvíráme pouze pro čtení (druhým parametrem funkce fopen je znak r). Funkce fopen vrací odkaz na otevřený soubor fid. Pokud fid obsahuje záporné číslo, došlo při otvírání souboru k chybě. Tento chybový stav je ošetřen příkazem if – do příkazového okna MATLABu vytiskeme hlášení o chybě (dříve popsaná funkce fprintf), výstupní proměnné out přiřadíme prázdnou matici a pomocí return ukončíme běh funkce (vrátíme se do nadřazeného m-souboru measuring, z něhož je funkce from_file volána). Počítače a programování 2 97 Pokud otevření souboru proběhne korektně, je sekvence příkazů v rámci if ignorována a program pokračuje voláním standardní funkce MATLABu fread (čtení dat ze souboru). První parametrem funkce fread je odkaz na soubor, z něhož mají být data načítána (odkaz na soubor uložila do proměnné fid funkce pro otevření souboru fopen). Druhým parametrem je počet načítaných položek (v našem případě čteme 48 teplot). Poslední parametr udává formát, v němž jsou čísla uložena (racionální čísla float32). Funkce fread vrací matici načtených hodnot A a počet načtených hodnot count. Jakmile jsou teploty ze souboru načteny, soubor uzavřeme voláním funkce fclose. V dalším provádíme jednoduchý test korektnosti načtených dat. Je-li nejnižší teplota v souboru nerealisticky nízká (nižší než -50°C) nebo nerealisticky vysoká (vyšší nežli +50°C), vytiskneme do příkazového okna MATLABu chybové hlášení, přiřadíme výstupní proměnné prázdnou matici a voláním příkazu return se vrátíme (při ignorování zbytku m-funkce) do nadřazené funkce measuring. Tím je celý program hotov. Příkaz return je zároveň posledním řídicím příkazem, který máme v MATLABu k dispozici. 4.3.5 Kontrolní příklady Příklad 1. Vytvořte m-funkci, která požádá uživatele o zadání dvou racionálních čísel prostřednictvím příkazového okna MATLABu. Funkce vrátí součin a podíl těchto dvou čísel. U podílu ošetřete dělení nulou. Příklad 2. Vytvořte m-funkci, která bude mít dva vstupní parametry x a f. Parametr x je číselný, parametr f je znakový. Znakový parametr f bude udávat typ číselné operace, která se má provést s číselným parametrem x. Při f=='s' počítáme sinus (sin), při f=='c' kosinus (cos), při f=='t' tangens (tan), při f=='e' exponenciální funkci (exp), při f=='n' přirozený logaritmus (log), při f=='d' desítkový logaritmus (log10). Naše m-funkce má vrátit odpovídající funkční hodnotu. Pokud bude do druhého parametru vložen jiný znak, než jsme uvedli, má naše funkce vrátit prázdnou matici. Příklad 3. Vytvořte svou vlastní m-funkci pro výpočet faktoriálu. Příklad 4. Vytvořte m-funkci, která má znakový vstupní parametr z a číselný výstupní parametr y. Funkce požádá uživatele o zadání libovolného řetězce8. Poté je zadaný řetězec prohledáván tak dlouho, dokud v něm není objeven stejný znak, jaký uživatel zadal do vstupního parametru z. Funkce má vracet pozici nalezeného znaku v zadaném řetězci. Příklad 5. Modifikujte funkci z předchozího příkladu tak, aby přítomnost znaku 1 v řetězci způsobila ukončení chodu funkce a výpis hlášení V řetězci je jednička do příkazového okna MATLABu. 8 Zapsáním a='kdkbx' uložíme do proměnné a sekvenci znaků, tedy řetězec. První znak řetězce je přístupný jako první položka pole a, tj a(1)=='k', druhý znak jako druhá položka pole a, tj. a(2)=='d', atd. Počet znaků v řetězci zjistíme pomocí nám dobře známé funkce size. 98 Fakulta elektrotechniky a komunikačních technologií VUT v Brně 4.3.6 Kontrolní otázky 1. Jaké jsou základní rozdíly mezi skriptem a funkcí? 2. Jakým způsobem můžeme vytvořit základní nápovědu k vlastní m-funkci pro příkazové okno MATLABu? 3. Jakým způsobem deklarujeme v m-funkci globální proměnné? Co musíme vykonat, abychom se v jiné m-funkci mohli na tyto globální proměnné odkazovat? 4. Co je to lokální m-funkce? 5. Jaké m-funkce lze použít k vizualizaci vektoru hodnot a jaké k vizualizaci matice hodnot? 6. Které m-funkce slouží pro práci s diskovými soubory? 7. Kterou funkcí lze realizovat textový výstup do příkazového okna MATLABu? Kterou funkci použijeme, žádáme-li uživatele pro zadání hodnoty prostřednictvím příkazového okna MATLABu? 8. Lze vzájemně porovnat příkaz if – else – elseif s příkazem switch – case – otherwise? Pokud je srovnání možné, jaké jsou základní rozdíly mezi oběma příkazy a co mají oba příkazy společného? 9. Lze vzájemně porovnat příkaz break s příkazem continue? Pokud je srovnání možné, jaké jsou základní rozdíly mezi oběma příkazy a co mají oba příkazy společného? 10. Lze vzájemně porovnat příkaz break s příkazem return? Pokud je srovnání možné, jaké jsou základní rozdíly mezi oběma příkazy a co mají oba příkazy společného? 11. Které příkazy MATLABu slouží k vytváření cyklů? V čem se tyto příkazy od sebe vzájemně odlišují? 12. Jak lze pomocí operátoru dvojtečka vytvořit posloupnost celých čísel 0, 1, …, 10 a jak posloupnost čísel racionálních 0.0, 0.1, …, 1.0? 13. Jakým způsobem zapíšeme, že funkce vrací dva sloupcové vektory o stejném počtu prvků? 14. Jakým způsobem zapíšeme, že funkce nemá žádné vstupní parametry? 4.4 Příklady elektrotechnických výpočtů V následujících odstavcích si uvedeme příklady několika programů pro výpočty z různých oblastí elektrotechniky. Na těchto příkladech si vysvětlíme další možnosti MATLABu jakými jsou např. numerické integrování či pokročilejší tvorba grafů. 4.4.1 Numerické integrování MATLAB umožňuje uživateli vyčíslit určité integrály, které v současné době neumíme vyřešit analyticky. Do právě popsané situace se velmi často dostáváme při řešení problémů v oblasti antén. Představme si, že našim úkolem je vypočíst vyzařování symetrického dipólu do různých směrů. Symetrický dipól si můžeme představit jako přímý drát, který je uprostřed přerušený napájecí štěrbinkou. Napájecí štěrbinka je připojena k vysokofrekvenčnímu oscilátoru, který na ramenech dipólu vytváří rozložení proudu. Toto rozložení proudu (tzv. proudová distribuce) je zdrojem vyzařování elektromagnetických vln anténou do okolí. Počítače a programování 2 99 Přesný analytický výpočet rozložení proudu na ramenech symetrického dipólu není známý, a proto je třeba počítat proudovou distribuci numericky. V prvém kroku rozdělíme anténní vodič na malé segmenty stejné délky ∆ (obr. 4.10). Budeme přitom předpokládat, že na každém jednom segmentu je amplituda proudu konstantní (na různých dvou segmentech však mohou být amplitudy veliké různě). l ~ ~ ~ 2 exp − j 2π a 2 + (ξ − z ) ψ (ξ ) = ∫ dz 2 2 4π a + (ξ − z ) −∆ 2 (4.7) 1 2 3 4 ∆ ∆ N-1 N 2a V druhém kroku počítáme vzájemné impedance jednotlivých segmentů. Vzájemná impedance Zm,n nám říká, jak proud na n-tém segmentu In přispívá k napětí na m-tém segmentu Um. Vzájemná impedance je přitom úměrná integrálu +∆ 2 ∆ ∆ ∆ ∆ Obr. 4.10 Symetrický dipól a jeho diskretizace Ve vztahu (4.7) značí a poloměr anténního vodiče, ξ je svislá vzdálenost středů segmentů antény, jejichž vzájemnou impedanci počítáme, a ∆ značí délku jednoho segmentu antény. Určitý integrál ve vztahu (4.7) neumíme analyticky vypočíst, a proto ho musíme vyčíslit numericky. Výpočet určitého integrálu funkce f(x) na intervalu x∈<a, b> si můžeme představit jako výpočet plochy pod křivkou, která f(x) f f2 1 f3 odpovídá funkčním hodnotám funkce f(x). Tuto plochu lze přibližně vypočíst tak, že f4 interval <a, b> rozdělíme na podintervaly, f5 uprostřed každého podintervalu vyčíslíme f6 funkční hodnotu f(xi), vynásobíme ji délf7 kou příslušného podintervalu ∆xi a všechny součiny f(xi) ∆xi sečteme (viz obr. 4.11). x x x x x x x x a 1 2 3 4 5 6 7 b Pokud chceme numericky vypočíst Obr. 4.11 Numerický výpočet určitého integrál (4.7), musíme vytvořit m-funkci, integrálu funkce f(x) na intervalu <a,b>. která odpovídá integrandu (4.7): function psi=green( z, x, a) R = sqrt( a^2 + (x-z).^2); psi = exp(-j*2*pi*R)./(4*pi*R); První vstupní parametr odpovídá integrační proměnné z. Za tuto proměnnou MATLAB při numerické integraci samočinně dosazuje vektor diskrétních hodnot nezávislé proměnné (souřadnice středů podintervalů) z = [-∆/2, -∆/2+δ, -∆/2+2δ, …, +∆/2], kde δ = ∆/N∆ a N∆ je počet úseků, na něž je při numerické integraci rozdělen každý jeden segment antény. Od naší funkce se pak očekává, že vrátí vektor odpovídajících funkčních hodnot ψ. Proto je zapotřebí vytvořit tělo funkce tak, aby si poradilo se vstupním vektorem z a aby vypočetlo odpovídající vektor psi. V naší funkci green řešíme tuto situaci výpočtem po složkách. Dalšími vstupními parametry jsou vzdálenost x středů segmentů, jejichž vzájemnou impedanci počítáme, a poloměr anténního vodiče a. Zatímco první parametr funkce, v níž je naprogramován integrand, je povinný, ostatní parametry jsou nepovinné a slouží nám jen 100 Fakulta elektrotechniky a komunikačních technologií VUT v Brně k předávání dalších hodnot, které jsou k vyčíslení integrandu zapotřebí. Druhý a třetí vstupní parametr by mohly být nahrazeny globálními proměnnými. Nyní, když máme naprogramovánu funkci v integrandu, můžeme se soustředit na samotnou integraci. O tu se stará standardní funkce MATLABu quad8. Jelikož v případě naší antény potřebujeme integrál vyčíslit pro všechny možné vzdálenosti středů segmentů antény x, voláme funkci quad8 v cyklu a vyčíslené integrály ukládáme do vektoru psi: psi = zeros( 1, N+1); % numerical integration for m=1:(N+1) x = (m-1)*delta; psi(m) = quad8( 'green', -delta/2, +delta/2, 1e-5, [], x, a); end Prvním parametrem funkce quad8 je řetězec se jménem funkce, která slouží k vyčíslování funkce v integrandu (v našem případě green). Druhým parametrem je dolní integrační mez, třetí parametr obsahuje horní integrační mez. Na základě zadaných mezí MATLAB generuje vektor hodnot nezávislé proměnné, který je dosazován za první parametr funkce green. Čtvrtý parametr specifikuje požadovanou přesnost numerického výpočtu integrálu (v našem případě 10-5). Pátý parametr nezadáváme (dosazením prázdné matice MATLABu říkáme, že má pracovat s jeho přednastavenou hodnotou). Šestý a sedmý parametr jsou předávány funkci green jako druhý a třetí parametr v hlavičce funkce green. Tím jsou všechny potřebné integrály numericky vyčísleny. V dalším kroku z nich sestavujeme tzv. impedanční matici Z, její inverzí získáme admitační matici Y. Sloupec admitanční matice, který odpovídá napájecí štěrbině, pak obsahuje aproximaci rozložení proudu na anténě. Ze známého proudového rozložení počítáme velikost vyzařování antény do různých směrů, a toto vyzařování zobrazujeme v polárním grafu: polar( theta, E/max(E)) Prvním parametrem standardní funkce polar je vektor úhlů v radiánech, druhým parametrem je vektor velikostí vynášených hodnot pro dané směry. Oba vektory musejí mít přirozeně stejný rozměr. Obr. 4.12 Rozložení proudu (vlevo) a směrová charakteristika (vpravo) symetrického dipólu o délce 500 mm a o poloměru anténního vodiče 2 mm. Dipól je napájen napětím 1 Volt na kmitočtu 300 MHz. Dipól byl rozdělen na 33 segmentů. Dipól byl umístěn ve vakuu. Výsledky výpočtů, získaných popsaným programem, jsou znázorněny na obr. 4.12. Celý program je uložen v adresáři dipole. Počítače a programování 2 101 4.4.2 Výpočet spektra signálu Každý signál lze složit z určitého počtu harmonických signálů s různou amplitudou a s různou fází (v limitním případě může být počet dílčích harmonických signálů nekonečný). Velikost amplitud jednotlivých harmonických složek je popsána spektrální amplitudovou (modulovou) charakteristikou, velikost fází spektrální charakteristikou fázovou. O převod časového průběhu signálu do spektrální oblasti se stará Fourierova transformace. Pro převod signálu ze spektrální oblasti do oblasti časové je třeba použít zpětnou (inverzní) Fourierovu transformaci. Pro začátek uvažujme jednu periodu harmonického signálu. Perioda je nakreslena na obr. 4.13. Tento obrázek současně demonstruje možnosti, které nám MATLAB nabízí pro popisování grafů. Zobrazený graf vytvoříme pomocí skriptu harmonic.m, který je Obr. 4.13 Jedna perioda harmonického signálu. Ukázka možností popisování grafů. uložen v adresáři spectra: N = 32; n = 0:N-1; % počet vzorků na jednu periodu % vektor umístění jednotlivých vzorků theta = n*(2*pi)/N; y = sin( theta); % diskretizace vodorovné osy % harmonický signál figure; plot( theta, y); xlabel('0 \leq \Theta \leq 2 \pi') ylabel('sin(\Theta)') title('Plot of sin(\Theta)') text(pi,0,'\leftarrow sin(\pi)') % % % % % % otevření okna pro graf vykreslení průběhu popis vodorovné osy popis svislé osy titulek grafu popisek Uvedený skript rovnoměrně rozloží v intervalu od nuly do 2π radiánů celkem N hodnot; hodnoty jsou uloženy do vektoru theta. V následném kroku počítáme pro vektor argumentů theta vektor odpovídajících funkčních hodnot funkce sinus. Tím máme definován průběh, který chceme vynést do grafu. Voláním standardní funkce figure otevřeme okno pro graf. Voláním standardní funkce plot(theta,y) vyneseme do grafu sinusový průběh (theta je vektor vodorovných souřadnic vynášených bodů, y je vektor svislých souřadnic vynášených bodů). V dalších krocích vytvořený obrázek popisujeme. Funkce xlabel slouží k popisu vodorovné osy, funkce ylabel k popisu osy svislé (svislý popis je standardně otočen o 90° oproti popisu vodorovnému). Pokud chceme opatřit graf nadpisem, použijeme funkci title. Jelikož umístění uvedených popisů je dané, jediným parametrem popsaných tří funkcí je řetězec, který má být zobrazen. Pokud chceme do řetězce vložit speciální znaky, musíme na příslušnou pozici řetězce vepsat zpětné lomítko, které je bezprostředně následováno názvem speciálního znaku. V našem příkladu se jedná o znaky \leq ↔ ≤, \Theta ↔ Θ, \pi ↔ π. Voláním funkce text můžeme vkládat do libovolného bodu na ploše grafu popisky. První dva parametry funkce text určují souřadnice bodu, na jehož pozici bude popiska umístěna. Souřadnice bodu musejí odpovídat souřadnému systému grafu. Třetím parametrem 102 Fakulta elektrotechniky a komunikačních technologií VUT v Brně funkce text je pak řetězec, který chceme do grafu vepsat. Pro vkládání speciálních znaků do popisky platí stejná pravidla jako u dříve diskutovaných funkcí (viz šipka vlevo \leftarrow). Nyní se však vraťme zpět k našim signálům a k jejich spektrům. Jak je vidět z našeho skriptu harmonic.m, v paměti počítače je spojitý signál reprezentován jeho diskrétními hodnotami (vzorky). Každý vzorek je reprezentován dvěma hodnotami, a to modulem a fází. Tuto dvousložkovou reprezentaci používáme jak v oblasti časové tak v oblasti spektrální. U každého signálu tedy budeme do jediného grafického okna vykreslovat pod sebe dva grafy. V horním grafu bude vykreslena modulová charakteristika signálu, v dolním grafu charakteristika fázová. K vytvoření více grafů v jednom okně slouží funkce MATLABu subplot(m,n,p). Funkce subplot vytvoří v rámci jediného grafického okna soustavu obdélníkových oblastí; uspořádaných do matice m×n. K jednotlivým oblastem přistupujeme prostřednictvím parametru p. Levé horní oblasti odpovídá p=1. Hodnota parametru p se postupně zvyšuje zleva doprava z prvního řádku k řádku poslednímu. U matice oblastí 2×2 má pravé horní pole index p=2, levé dolní pole index p=3 a pravé dolní pole index p=4. Každá oblast obsahuje svůj vlastní souřadný systém, svou vlastní soustavu souřadných os. Do popsaných souřadných oblastí pak umisťujeme jednotlivé grafy. Aby byly naše grafické výstupy pro uživatele dobře srozumitelné, doplníme každý graf názvem a popisem os. Popsaná reprezentace diskrétního signálu, který odpovídá sinusové funkci z obr. 4.13, je znázorněna na obr. 4.14a. Vzorky v modulové charakteristice jsou v obou periodách kladné. O záporném charakteru vzorků v druhé půlperiodě nás informuje fázová charakteristika – fáze vzorků je rovna π radiánům. Fourierovu transformaci, která slouží k výpočtu spekter diskrétních signálů, nazýváme diskrétní Fourierovou transformací. Tuto diskrétní transformaci lze pro případ 2N vzorků realizovat výpočetně velmi efektivním algoritmem, který se jmenuje rychlá Fourierova transformace (Fast Fourier Transform, FFT). Algoritmus FFT je v MATLABu standardně implementován ve funkci fft(x), kde x je vektor vzorků časového průběhu signálu. Aplikujeme-li funkci fft na náš diskrétní harmonický signál z obr. 4.14a, získáme spektrum, které je zobrazeno v obr. 4.14b. Modulová charakteristika obsahuje jedinou spektrální složku, která leží na pozici odpovídající kmitočtu harmonického signálu. Fáze spektrálních složek má přibližně lineární průběh. a) Obr. 4.14 b) a) Vzorky harmonického signálu v čase. b) Modulová a fázová charakteristika harmonického signálu. Počítače a programování 2 103 Pokud potřebujeme ze známého spektra vypočíst časový průběh diskrétního signálu, musíme použít zpětnou diskrétní Fourierovu transformaci. MATLAB nám opět nabízí implementaci rychlé verze této transformace. Rychlá zpětná Fourierova transformace (Inverse Fast Fourier Transform, IFFT) je implementována ve funkci ifft( X), kde X je komplexní vektor spektrálních složek signálu. Pokud funkci ifft aplikujeme na spektrum z obr. 4.14b, obdržíme časovou sekvenci vzorků, která je identická s průběhem zobrazeným v obr. 4.14a. Nyní se pokusme aplikovat právě popsaný postup na poněkud složitější signál. Složitější signál bude dán součtem tří harmonických signálů o různých kmitočtech a různých amplitudách. První signál v součtu je identický s harmonickým signálem z předchozího příkladu. Druhý signál v součtu má čtyřnásobně vyšší kmitočet a trojnásobně větší amplitudu než základní signál. Konečně poslední signál v součtu má desetinásobně vyšší kmitočet a dvojnásobně větší amplitudu ve srovnání se základním signálem. Reprezentace signálu v časové a ve spektrální oblasti je znázorněna na obr. 4.15. Obr. 4.15 Časová a spektrální reprezentace signálu, sestávajícího ze tří harmonických složek Program, realizující popsané výpočty, je uložen v souboru spectra.m v adresáři spectra. Hlavní tělo m-funkce spectra je stejné pro libovolný signál. Různé signály jsou v rámci mfunkce naprogramovány ve formě lokálních funkcí signal_1 (harmonický signál, obr. 4.14) a signal_2 (signál sestávající ze tří harmonických složek, obr. 4.15). Zdrojový kód programu vypadá následovně: function spectra N = 32; n = 0:N-1; % počet vzorků na jednu periodu % vektor umístění jednotlivých vzorků y1 = signal_1( n, N); % časový průběh signálu figure; % otevření grafického okna subplot( 2, 1, 1); % bar( 0:N-1, abs( y1)); % xlabel('n') % ylabel('|y(n)|') % title('Modul vzorků v čase') horní obdélníková oblast modulová charakteristika popis vodorovné osy popis svislé osy % nadpis subplot( 2, 1, 2); bar( 0:N-1, angle( y1)); xlabel('n') ylabel('arg( y(n))') dolní obdélníková oblast fázová charakteristika popis vodorovné osy popis svislé osy % % % % 104 Fakulta elektrotechniky a komunikačních technologií VUT v Brně title('Fáze vzorků v čase') % nadpis Y1 = fft( y1); % výpočet spektra figure; % otevření grafického okna subplot( 2, 1, 1); % horní obdélníková oblast bar( 0:N-1, abs( Y1)); % modulová charakteristika xlabel('k') % popis vodorovné osy ylabel('|Y(k)|') % popis svislé osy title('Modul spektrálních složek') % nadpis subplot( 2, 1, 2); % dolní obdélníková oblast bar( 0:N-1, angle( Y1)); % fázová charakteristika xlabel('k') % popis vodorovné osy ylabel('arg( Y(k))') % popis svislé osy title('Fáze spektrálních složek') % nadpis y2 = ifft( Y1); % zpětná Fourierova transformace figure; subplot( 2, 1, 1); % bar( 0:N-1, abs( y2)); % xlabel('n') % ylabel('|y(n)|') % title('Modul vzorků v čase') horní obdélníková oblast modulová charakteristika popis vodorovné osy popis svislé osy % nadpis subplot( 2, 1, 2); % bar( 0:N-1, angle( y2)); % xlabel('n') % ylabel('arg( y(n))') % title('Fáze vzorků v čase') dolní obdélníková oblast fázová charakteristika popis vodorovné osy popis svislé osy % nadpis function y = signal_1( t, T) y = sin( 2*pi*t/T); function y = signal_2( t, T) y = sin( 2*pi*t/T) + 3*sin( 4*2*pi*t/T) + 2*sin( 10*2*pi*t/T); Uvedený program přímo vybízí k experimentování s dalšími signály. Stačí deklarovat novou lokální funkci (změna počtu harmonických, změna kmitočtů, amplitud a fází jednotlivých harmonických) a jménem nové funkce nahradit řetězec signal_1 v řádku, označeném tučným komentářem Časový průběh signálu. 4.4.3 Kontrolní příklady Příklad 1. Vytvořte program, který numericky vyčíslí integrál π y = ∫ sin ( x ) dx 0 Výsledek, vypočtený vašim programem, ověřte analytickým výpočtem. Příklad 2. Vytvořte program, který vykreslí do grafu průběh funkce π y = ln (ϕ ) + arc tg − ϕ , 2 ϕ ∈ 0.5; 2.0 Počítače a programování 2 105 Funkci vyčíslujte s krokem ∆ϕ = 0.02. Vykreslený průběh řádně popište. Příklad 3. Vytvořte signál, který je dán součtem čtyř harmonických signálů o různých kmitočtech, amplitudách a fázích. Kmitočty, amplitudy a fáze jednotlivých složek jsou uvedeny v následující tabulce. signál č. kmitočet amplituda fáze 1 1 3 -0.8 2 3 1 +1.3 3 7 4 0.0 4 11 7 -2.1 Kmitočty jsou udány v násobcích základního kmitočtu, fáze jsou v radiánech. Vypočítejte spektrum zadaného signálu. Reprezentaci signálu jak v časové tak ve spektrální oblasti vykreslete do čtyř grafů v rámci jediného grafického okna. Grafy řádně popište. 4.4.4 Kontrolní otázky 1. Jaký je základní postup při programování numerického integrování v MATLABu? Která standardní funkce slouží k numerickému integrování? 2. Kterou funkci můžeme použít k vytvoření polárního grafu? Jaké parametry má tato funkce? 3. Které funkce MATLABu slouží ke vkládání popisů do grafů? 4. Jak lze do popisu vložit speciální symboly? 5. Jak lze v rámci jednoho grafického okna vytvořit více nezávislých grafů? Kterou funkci je k tomu možno využít? 6. Které funkce slouží k výpočtu rychlé Fourierovy transformace a k výpočtu transformace zpětné? 4.5 Závěr V třech lekcích jsme se seznámili se základy programování v MATLABu. V tuto chvíli bychom měli umět sestavit jednoduché programy, které efektivně využívají možností maticově orientovaných výpočtů, měli bychom být schopni naprogramovat vstupy a výstupy programů do souborů a do příkazového okna MATLABu, měli bychom umět efektně graficky reprezentovat výsledky našich výpočtů. Tyto základní znalosti a dovednosti budete (již sami) dále rozvíjet ve svém následném studiu. V mnoha předmětech se seznámíte se specializovanými toolboxy, které slouží k řešení technických problémů v různých oblastech elektrotechniky. Bude to jistě práce mnohem zajímavější a zábavnější, nežli tomu bylo v našem základním kursu. Mnoho úspěchů při této vaší další práci s MATLABem vám přejí autoři.
Podobné dokumenty
Počítačové modely pro výuku elektroniky
v mnoha oborech v tak různém pojetí, že dodnes neexistuje jednotná teorie modelování
ani jednotná terminologie.
Kašpar v [11] na str. 309 uvádí: „Jak vyplývá z historie modelování ve fyzice,
předst...
Modulární systém kurzů programovacích nástrojů Borland
Výchovně-vzdělávací cíle předmětu
Žák získá základní znalosti a dovednosti v programování v jazycích C, C++ - navržení
algoritmu, implementace algoritmu ve zvoleném programovacím jazyce, -schopnost...
1. Seznámení s C++ Builderem
5. Nejdůležitější vlastností je vlastnost Name (jméno). Pomocí této vlastnosti se odkazujeme na formulář, případně na jiné objekty umístěné na formuláři. Hodnota této vlastnosti musí být identifiká...
C++ Builder 4.0 - Tvorba software
nazveme ho třeba okno.cpp a uložíme ho do adresáře Projects\01. Přitom se zároveň uloží i hlavičkový
soubor (okno.h) a soubor s popisem okna (okno.dfm). Poté se nás prostředí zeptá na jméno projekt...
Diferenciální rovnice a jejich použití v elektrotechnice – práce s
Pro kreslení směrového pole bohužel v MATLABu není hotová funkce. Můžeme však
použít funkci quiver, která je určena pro kreslení obecného vektorového pole v rovině.
Syntaxe je
quiver( x, y, u, v)
k...
Programování v jazyce C++ pro 4. ročník
Použitá literatura: Václav Kadlec - Učíme se programovat v C++Builder a jazyce C++
vlastnosti, použití
1. data, která se strojově zpracovávají
2. vše co nám nebo něčemu podává (popř. předává) zprávu o věcech nebo událostech, které se
staly nebo které nastanou
Smyslem zpracování dat je vytvoření info...
Cracking 4 newbies…
existuje program Hiew ale ten já nepoužívám, není
proč.
Jak již jsem napsal, musíte umět aspoň základy
assembleru. Nemusíte v něm nutně umět programovat
(to neumím ani já, tedy…jen základy, nic moc...