Počítače a programování 2 - UTEE

Transkript

Počítače a programování 2 - UTEE
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

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...

Více

Modulární systém kurzů programovacích nástrojů Borland

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...

Více

1. Seznámení s C++ Builderem

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á...

Více

C++ Builder 4.0 - Tvorba software

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...

Více

Diferenciální rovnice a jejich použití v elektrotechnice – práce s

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...

Více

Programování v jazyce C++ pro 4. roční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++

Více

vlastnosti, použití

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...

Více

Cracking 4 newbies…

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...

Více