|
| |||||||||
České vysoké učení technické |
| ||||||||
Fakulta strojní | |||||||||
Katedra technické matematiky | |||||||||
Skripta jsem se snažil psát maximálně stručně a důraz jsem kladl na příklady, které jsou všechny odladěny (většinou jsem použil produktu firmy Borland Turbo C++), takže syntaktické chyby ani chyby v algoritmizaci (snad s výjimkou volby optimálního algoritmu a chyb, které vznikly při přenosu programů do skript) by se v nich neměly vyskytovat.
V prvé kapitole jsou základní struktury jazyka C++ vysvětleny pouze na příkladech. Tuto kapitolu by si měl každý čtenář důkladně promyslet, a odzkoušet jak odladěné příklady, tak zadaná cvičení. Ve druhé kapitole jsou uvedeny podrobnosti typu: Jak se zadávají konstanty? Kolik a jakých znaků smí mít jméno nějakého objektu? Jaké jsou základní typy hodnot, jaké jsou standardní operace v jazyku atd. Ve třetí kapitole se čtenář dozví další důležité podrobnosti o jazyku C++, kterými jsem nechtěl z pedagogických důvodů zatěžovat v prvních dvou kapitolách. Čtvrtá kapitola pojednává podrobně o polích a funkcích, o kterých čtenář skript, který došel až sem, již dost ví, a o ukazatelích (jiný český termín je spoj), které byly čtenáři prakticky zamlčeny. Pátá kapitola pojednává o strukturách, třídách (i když s nejdůležitějšími vlastnostmi tříd se čtenář seznámí až v kapitole šesté), unionech a takových podrobnostech jako jsou tzv. bitová pole. Šestá kapitola je věnována objektově orientovanému programování, které je vysvětleno na různě složitých příkladech, kdy jsem se vždy snažil vycházet od jednoduchých příkladů, ale závěrem jsem se nevyhnul ani příkladům dosti složitým, aby vynikl důvod, pro který tento programátorský styl vznikl. Sedmá kapitola je věnována předpřekladači (nebo, chcete-li, preprocesoru), který usnadňuje programování jak v jazyku C, tak v jazyku C++.
Závěrem bych chtěl upozornit, že ten, kdo se chce seznámit se speciálními možnostmi konkrétní implementace jazyka C++, tj. např. grafickými a matematickými knihovnami, možnostmi optimalizace programu apod., ten nechť tato skripta odloží. Ten kdo se chce obecně, ale relativně podrobně seznámit s jazykem C++, ten by snad mohl toto skriptum použít jako užitečnou pomůcku.
Závěrem bych chtěl poděkovat studentu Jiřímu Vávrovi ze Strojní
fakulty ČVUT a doktorandu Ing. Luďku Šárovi z Ústavu
termomechaniky AV ČR za pečlivé přečtení rukopisu a oponentu
Ing. Svenu
Ubikovi, systémovému programátorovi z Informačního centra FS ČVUT za cennépřipomínky k úpravě
předem děkuji
Praha 11. července 1998
Vlastní program začíná slovem main(), které v tomto případě
dává
kompilátoru jazyka informaci, že se jedná o tzv. hlavní
program. Program v jazyku C++ může
sestávat z jednotlivých programových jednotek, kterým říkáme
funkce. Každá funkce, může mít
argumenty uvedené v okrouhlých závorkách za
jménem funkce. Protože hlavní program je považován také
za funkci, jsou za jménem main okrouhlé závorky. Později
uvidíme, že i hlavní program smí mít argumenty. Složené závorky
označují začátek a konec hlavního programu a příkaz cout
je tzv. proudová funkce pro výstup.
Zajistí (s pomocí operátorů proudového výstupu <<) výstup
řetězce znaků, které jsou zapsány
mezi uvozovkami
a znaku \n , který je zapsán mezi
apostrofy. Tento znak znamená přechod na nový řádek. Každý
příkaz v jazyku C++ končí středníkem. Přesvědčte
se, že lze též napsat:
První řádek programu je příkazem pro tzv.
předpřekladač, který před překladem programu
v tomto případě zajistí, aby byl zahrnut (anglický termín
include) tzv. hlavičkový soubor iostream.h, který
v jazyku C++ (obvykle) umožňuje proudové funkce pro
vstup a výstup používat.
Tento program využívá hlavičkového souboru stdio.h
(standardní input/output),
který se používá i v jazyku C a jednu jeho funkci pro výstup (
printf)
na standardní výstupní zařízení.
Příklad 1.2. Zapišme tentýž text jako v příkladu do
souboru, který např. pojmenujeme out.dat.
První řádek informuje předpřekladač, že má zavést soubor
fstream.h, který umožňuje (obvykle)
definovat libovolně pojmenované proudové funkce. V našem případě
fout. Text za znaky // je v jazyku C++ považován až
do
konce řádku za poznámku.
Je vhodné nestandardní soubory uzavřít
(i když v tomto případě po ukončení programu se uzavře
automaticky). Chceme-li soubor z nějakého důvodu uzavřít před
ukončením programu, je tento příkaz povinný.
V této variantě zápisu jsme použili poznámek
uzavřených mezi
znaky /* a */ .
Oba typy poznámek jsou v C++ použitelné a mohou se
libovolně střídat. Je vhodné jednořádkové
poznámky uvozovat znaky
// a víceřádkové poznámky mezi znaky /* a */.
Ukazatel zmíněný v poznámce budeme probírat podrobně v
kapitole 4. Důležité upozornění: jazyk C i C++ rozlišují mezi malými a velkými písmeny: tedy FILE,
File a file jsou tři různá jména.
Příklad 1.3. Sestavme program pro přečtení dvou hodnot
ze standardního vstupního zařízení a jejich přiřazení proměnným
x a i.
Tento zápis je dostatečně jasný z poznámek.
Zde je nová funkce scanf, která zajišťuje vstup hodnot,
zdánlivě
složitějším způsobem než cin. Právě tak jako funkce
printf
využívá tzv. formátu. Zde konverze f zajistí
vstup
reálné hodnoty a konverze i zajistí vstup celočíselné
hodnoty. U proměnných x a i je povinný symbol
&.
Pamatujme si prozatím, že tento znak ve funkci scanf musí
být u jednoduchých
proměnných a indexovaných
proměnných. Vysvětlení bude možné, až
se naučíme pracovat s ukazateli.
Příklad 1.4. Sestavme program pro přepočet palců na
centimetry a obráceně. Budeme číst číselnou hodnotu a znak:
p
(převod palců na centimetry), c (převod centimetrů na
palce).
V případě chybného zadání znaku se vytiskne výstražná zpráva.
V programu jsme použili definici nového typu, počáteční přiřazení
(které se okamžitě realizuje při překladu zdrojového programu),
definici symbolické konstanty
(na rozdíl od proměnné se symbolické konstantě nesmí
v průběhu plnění přiřadit nová hodnota), přiřazovacího
příkazu (kdy v nejjednodušším případě
se proměnné na levé straně přiřadí hodnota výrazu na straně
pravé) a podmíněného příkazu:
V příkazu cout << palec << "\" =" << cm << "cm\n";
jsme potřebovali zapsat v řetězci znak uvozovka
(
"). To jazyk C a C++ umožní předřazením znaku zpětné
lomítko (\) před znak
uvozovka.
Příklad 1.5. Sestavme program pro tutéž úlohu, ale použijme
tzv. přepínače.
Přepínač pracuje tak, že se procházejí jednotlivé
alternativy case a nalezne-li se shoda mezi výrazem za
slovním symbolem switch a výrazem za case, provedou
se příkazy za tímto case. Pak by se prováděly další příkazy
bez ohledu na další case; abychom tomuto nežádoucímu jevu
zabránili, musíme na konci každé alternativy přepínače umístit
příkaz
break, který přepínač ukončí. Alternativa uvedená slovním
symbolem default se provede v případě, že nevyhovuje žádná
jiná alternativa. Alternativa default je nepovinná a
příkaz break
je v ní zbytečný, ale je zvykem jej na toto místo psát..
Příklad 1.6. Sestavme program pro přepočet palců na
centimetry a obráceně. Budeme číst číselnou hodnotu a znak (
p - převod palců na centimetry, c - převod centimetrů na
palce, k - konec čtení) a budeme je postupně zapisovat do
souboru muj_s.dat. V případě, že čteme chybný znak,
program
bude požadovat opravu. Po přečtení hodnot soubor uzavřeme pro
zápis, otevřeme pro čtení, budeme z něj číst a výsledky tisknout
na standardním výstupním zařízení.
U příkazu cout jsme použili tzv.
manipulátorů
(v systému, kde byly laděny programy pro tato skripta, jsou
manipulátory definovány v souboru iomanip.h.
Např. manipulátor setw(6)
použitý v cout nastaví šířku slova na výstupu na hodnotu
6. Manipulátor setprecision(2) nastaví přesnost pro reálné
hodnoty na 2 desetinná místa.
Hlavičkový soubor iostream.h je součástí souboru
fstream.h,
takže nemusí být uveden. V programu jsme použili cyklus
while. Jeho význam je jednoduchý. Příkaz uvedený za příkazem
(eventuálně příkazy uzavřené v příkazových závorkách)
while se provádí tak dlouho, dokud je
výraz
v závorkách za slovem
while různý od nuly. V našem případě jsme použili tedy
nekonečný cyklus a k ukončení jsme použili příkaz
skoku a návěští, které se
odděluje od příkazu, ke kterému se má provést skok, dvojtečkou.
Je dobrou praxí, používat příkazů skoku co nejméně, protože někdy
znepřehledňují program. V našem případě je tento příkaz
vhodný v místě, kde kontrolujeme správnost vstupu. V druhém
případě, kdy pomocí tohoto příkazu ukončujeme program, je lepší
využít příkazu while.
Příklad 1.7. Sestavme program pro tisk tabulky
Fahrenheit - Celsius. Pro přepočet použijte vzorce
Program je snad opět z poznámek srozumitelný. V příkazu
while se provádí více příkazů, a proto jsou uzavřeny do
složených (příkazových) závorek. Je nutno upozornit na zlomek
5/9, který jsme zapsali v přiřazovacím příkazu
5./9. a mohli jsme ho zapsat i ve tvarech:
5.0/9.0 nebo 5./9 popř. 5/9. apod.
To je v jazyku C i C++ tzv. celočíselné dělení.
Výsledkem 5/9 je celočíselný výsledek
dělení (po odříznutí zlomkové části) a to je nula. Právě tak
výsledek dělení 3/2 je hodnota 1, výsledkem 11/5 je
hodnota 2 atd. Fortranisté jsou na tuto skutečnost zvyklí
a vědí, že se celočíselné dělení dá v mnoha případech s výhodou
použít, ale v mnoha úlohách může být jeho přehlédnutí zdrojem
těžko objevitelných chyb.
Na tomto místě znovu opakujeme, že v jazycích C++
a C je pravdivostní hodnota
pravda realizována hodnotou různou od nuly a hodnota
nepravda hodnotou nula. Hodnoty true
a false známé z jiných programovacích jazyků tedy v C++ i C explicitně neexistují.
2. Příkazy cyklu for a do-while
Příkazy cyklu jsou velmi důležitou a naštěstí nijak komplikovanou
částí každého programovacího jazyka. V minulém odstavci jsme již
probrali příkaz while a nyní se zmíníme o příkazu
for.
Význam ukážeme opět na příkladu. Příklad 1.7 naprogramujeme
pomocí tohoto příkazu.
Význam příkazu for lze popsat pomocí příkazu while
následovně:
V příkazu jsme využili možnosti definice typu parametru cyklu
přímo v příkazu for; to je v jazyku C++ možné.
Definice proměnné pak platí až do konce funkce nebo bloku
ve funkci (podrobněji v kap. 3 v čl. 3.1 a 3.2).
Samozřejmě, že jsme mohli psát
ale i
Z ukázek je vidět, že v příkazu for lze jednotlivé složky
příkazu vynechávat, podle toho, jak se nám hodí.
Dalším příkazem cyklu v jazyku C++ je cyklus
do-while.
Syntaxe příkazu je
do
příkaz
while(výraz)
Nejprve se provede příkaz a pak se vyhodnotí výraz.
Je-li pravdivý (tedy přesně řečeno různý od nuly) příkaz
se provede znovu atd. Je-li nepravdivý (roven nule), cyklus
se ukončí. Program z příkladu 1.7 můžeme pomocí příkazu
do-while napsat takto (Je samozřejmé, že příkaz může být
složený):
V příkazu do-while se provede příkaz alespoň jednou,
protože tzv. zkouška konce se provádí po provedení
příkazu.
V této části si ukážeme několik jednoduchých programů pro práci se
znaky.
Řekli jsme si, že funkce getchar čte z klávesnice
(přesněji ze standardního vstupu) a putchar vypisuje
znakové
hodnoty na obrazovku (přesněji na standardní výstup). To ovšem
můžeme snadno v moderních operačních systémech změnit tzv.
přesměrováním, kdy vyvoláním pracovního
programu, který se např. jmenuje mujprog, ve tvaru
zajistíme, že standardním vstupem pro něj bude soubor
mujvst
a výstupem mujvys. Totéž samozřejmě docílíme, budeme-li
ve zdrojovém programu mujprog.cpp psát příkazy
getc(mujvst) a putc(nějaká znaková veličina,
mujvys).
Příklad 1.8. Napišme program který znak po znaku kopíruje
vstup na výstup.
Relační operátor != má význam . Cyklus
while tedy probíhá, dokud se nepřečte znak konec
souboru, pak se cyklus ukončí. Znak
konec souboru pro konkrétní systém je uložen v souboru
stdio.h. Je
označen jménem EOF. Funkce getchar vrací
celočíselnou hodnotu, a proto i proměnnou c jsme definovali
jako celočíselnou ( typ int) a ne typu char. Je to
stručně řečeno proto, že některé znaky nejsou typu char
(např. znak konce souboru EOF).
Programátor v jazyku C nebo C++ ovšem napíše
program stručněji:
Příklad 1.9. Napišme program, který spočítá znaky ve
vstupním
textu.
V programu se objevil nový operátor ++, který znamená
zvětšení o jednotku. Je možné psát nc=nc+1, ale ++nc
je
stručnější a mnohdy efektivnější. Operátory
inkrementace mohou být prefixové
++nc nebo postfixové nc++.Rozdíl si
vysvětlíme později. Vedle operátorů inkrementace máme
i operátory dekrementace -,
které se obdobně využívají při odečítání jedničky.
V programu jsem využili nového typu long, který je
stručnějším zápisem long int; u typu long je
zaručeno,
že že maximální hodnota tohoto typu nebude menší než maximální
hodnota typu int. Konverze v printf a scanf pro
tento typ je %ld .
Příklad 1.10. Sestavme program, který spočítá řádky zapsané
v souboru
muj_sou . Předpokládáme, že každý řádek je ukončený
znakem
nový řádek ( \n ).
Příklad 1.11. Sestavme program, který spočítá slova, řádky
a znaky
zapsané v souboru msbr. Předpokládáme, že slovo je
posloupnost
znaků, která neobsahuje znaky mezera, tabelátor (\t), nebo
nový řádek
(\n).
Čtenář při troše přemýšlení jistě porozumí popsanému algoritmu.
Všimněme
si snad jen pro úplnost zřetězeného příkazu
který se realizuje takto
a pro zopakování toho, že v jazycích C a C++
neexistují tzv.
logické (Booleovské) proměnné:
chceme-li je explicitně použít, musíme to realizovat např. tak,
jako
v tomto programu.
Příklad 1.12. Sestavme program, který spočítá výskyty
jednotlivých
číslic, mezer, tabelátorů, nových řádek a ostatních znaků.
V programu je použito jednorozměrné pole tj. vektor cis,
jež má deset prvků
o indexech měnících se od nuly do devíti. Typické pro pole
v jazyku C i C++ je právě indexování od nuly. Mez udaná v definici
tedy
znamená pocet prvků pole a ne horní mez indexu. To umožňuje
v cyklu
for psát ostrou nerovnost. V programu jsme použili nový
hlavičkový soubor
conio.h,
který v systému, ve kterém byly programy laděny slouží k ovládání
obrazovky; např. funkce clrscr() maže obrazovku.
Příklad 1.13. Sestavme program, který vypočítá součet matic
typu (m,n), kde m ú 5 a n ú 5 podle vzorce
cij = aij+bij, i = 1,2,...m; j = 1,2,...,n. Matice budeme
číst ze
souboru vzorek.dat a výsledek bude zapsán do téhož
souboru.
Program doplněný poznámkami je snad dostatečně čitelný.
Upozorňujeme jen na zápis dvojrozměrných polí,
tj. matic, který
je oproti většině programovacích jazyků poněkud atypický, ale
později uvidíme, že má své výhody. Též je nutno dát pozor na meze
indexů.
Parametry cyklů i
a j jsme
definovali při prvém použití způsobem povoleným v C++; standardní
způsob zápisu je samozřejmě přípustný.
Funkce v jazyku C i C++ tvoří samostatné programové
jednotky, pravě tak jako v jazyku Fortran.
Příklad 1.14. Zapišme funkci, která umo'ní celočíselné umocňování
celočíselné hodnoty. Umocňování není standardní operací jazyka a
bývá součástí matematické knihovny příslušných systémů. Obvykle
se jmenuje pow.
Funkci nebo skupinu funkcí lze překládat samostatně a propojení
závisí na konkrétním systému. Program z příkladu 1.14 jsme zapsali
jako jeden soubor. Pracujeme-li v operačním systému UNIX
a jmenuje-li se soubor ukazka.cc, lze obvykle
přelo'it
program příkazem
kdy pracovní (spustitelný) program se bude jmenovat uk.
Budeme-li program z příkladu 1.14 psát jako dva soubory
- hlavni.cc a funkce.cc, pak budeme postupovat
např. takto
Příkaz zajistí překlad souborů do sestavitelných modulů (majících
v unixu postfixovou koncovku o a příkazem
se realizuje sestavení obou modulů do
spustitelné verze
uk. Kombinací je samozřejmě mnoho, ale práce se soubory
a funkcemi
je přehledná a praktická.
Překlad a sestavení mů'eme však provést najednou zápisem
Napíšeme-li jen c++ program.cc funkce.cc bude mít výsledný
produkt standardní jméno a.out.
V produktech firmy Borland je první případ (překlad souboru
ukazka.cpp
Parametry funkce, tzv. formální parametry,
které stojí na místě jednoduché proměnné nebo
indexované proměnné (prvku pole), jsou, není-li řečeno jinak,(viz)
volány hodnotou; to znamená, 'e se jim po
zavolání funkce přiřadí
hodnota skutečných parametrů. Hodnota
formálních parametrů se uvnitř funkce mů'e
jakkoliv měnit, ale hodnota skutečných parametrů zůstane po
ukončení funkce nezměněna. Ve funkci mocnina jsme toho vyu'ili
a ušetřili jsme jednu proměnnou.
Funkce v tomto případě vrací reálnou hodnotu, a proto jsme ji
museli definovat jako reálnou (float).
Příklad 1.15. Sestavme program pro čtení řádků znakového
souboru a tisku nejdelšího z nich.
Algoritmus mů'eme stručně slovně popsat takto:
Jestli'e (je delší ne' poslední nejdelší
)
ulo' řádek a jeho délku;
Tiskni nejdelší řádek;
Nazdar\0V případě, 'e vytváříme řetězec sami, nesmíme zapomenout znak
'\0'.
doplnit. Zde také je nutno upozornit na rozdíl mezi zápisem
'x'
a "x"; 'x' je znak, kde'to "x" je řetězec o
dvou
znacích. Sestává ze znaku x a tzv. prázdného znaku
\0.
Zvláštní zmínku si ovšem zaslou'í tzv. externí proměnné, pou'ité
v příkladu 1.15.
Externí proměnné a pole se inicializují jen
jednou v procesu plnění programu. Neprovede-li se inicializace
mají napočátku externí proměnné hodnotu nulovou.
Sestává-li program z více souborů, pak je externí proměnná
definována v jednom souboru a lze ji tam použít shora popsaným
způsobem; ve druhém souboru se externí proměnná musí deklarovat
ve funkci, deklarací extern.
V našem programu jsme museli definovat proměnnou ms jako
externí. V souboru hrad jsme s ní pracovali bez jakékoliv
deklarace, v souboru frad jsme ji museli ve funkci
getline explicitně deklarovat. Proměnnou i v souboru
frad jsme definovali na začátku souboru a implicitně
použili v obou funkcích getline a copy. V druhém
případě by bylo vhodnější definovat proměnnou v obou funkcích
zvlášť, náš záměr zde byl čistě pedagogický. Na tomto
místě je třeba upozornit, že extern i a extern int i
znamená totéž.
Externí proměnné je vhodné užívat uvážlivě, protože mohou být
(právě tak jako proměnné v popisu common v jazyku fortran) zdrojem nepříjemných chyb a mohou znepřehledňovat
program.
Závěrem si uveďme schéma na vysvětlenou:
Poznámka: Pozorný čtenář si jistě všiml, že používáme dva pojmy:
definice a deklarace proměnné nebo pole. V jazyku
C a C++ se těchto pojmů užívá takto:
nebo ještě stručněji
Na tomto velmi jednoduchém příkladu rekurzívní funkce si můžeme
snadno vysvětlit princip výpočtu. Představme si, že funkci
fakt zavoláme s parametrem 4. Formálnímu parametru se tedy
přiřadí hodnota 4. Provede se zkouška, zda se n
rovná nule nebo jedné. To se ale nerovná, protože se rovná
čtyřem. Vyvolá se tedy znovu funkce fakt, ale tentokrát
s novým parametrem, který má hodnotu n-1, tedy hodnotou tři.
V tomto
dalším bloku se provede další zkouška, a protože stále není
hodnota parametru rovna nule nebo jedničce vyvolá se opět
funkce
fakt s hodnotou dvě, a pak ještě jednou s hodnotou jedna.
Teprve nyní se vrací hodnota jedna, ta se násobí v funkci (podle algoritmu) dvěma, výsledek se opět v funkci násobí třemi a v původně volané funkci čtyřmi. Konečný
výsledek je tedy 24. Z tohoto příkladu vidíme, že jsme sice
rekurzívním zápisem zápis zjednodušili, ale místo jednoho
parametru jsme potřebovali (aniž bychom to explicitně zadali)
parametry čtyři. Obecně platí, že rekurzívní zápisy spotřebují
více paměti než zápisy nerekurzívní a také více strojového času.
Přesto mnohdy je zjednodušení a zpřehlednění zápisu tak významné,
že rekurzivitu využíváme.
Písmeno l uvedené před d, o a x označuje,
že v seznamu argumentů funkcí odpovídá této konverzi forma (long) a u konverzi f nebo e označuje, že
odpovídající argument není typu float, ale double,
tj., že nabývá reálné hodnoty s dvojnásobnou přesností. Písmeno
L označuje v tomto případě, že odpovídající argument je
typu long double.
Zastavme se ještě u tisku řetězce. Uvedeme nejzajímavější
příklady aplikace konverze %s pro tisk řetězce:
Nejneobhospodarovavatelnejsi
Návod:
Sn = 1+x+[(x2)/ 2!]+ź+[(xn)/( n!)],
kde n je nejmenší číslo, pro které platí
|[(xn)/( n!)]| < e
Čísla x a e jsou dána vstupními daty.
Vypočet realizujte:
Návod: Pro sestavení algoritmu je nutné si uvědomit, že každý
příspěvek
k částečném součtu řady lze vypočítat z předchozího příspěvku ze
vztahu
Návod: Vyjděte z vlastností NSD:
pro x > y platí NSD(x,y) = NSD(x-y,y),
pro x < y platí NSD(x,y) = NSD(x,y-x),
pro x = y platí NSD(x,y) = x = y.
kde n ú 10.
Návod: Použijte vzorce s = ĺi = 1naii.
Návod:
cij = cij+ĺk=1laikbkj, i = 1,2,.ź,m; j = 1,2,ź,n.
Návod: Využijte vlastnosti kódu ascii, kde číselné kódy
malých a velkých písmen mají konstantní rozdíl; obě abecedy jsou
spojité.
Jméno, nebo v odborné terminologii synonymum
identifikátor, je v jazyku C++
posloupnost maximálně 31 písmen, číslic a znaků podtržení (
_) začínající písmenem. Znak _ se považuje za písmeno.
Lépe je používat jména kratší než delší (maximálně osm, spíše
šest). Slovní symboly (např. for,
else, int,
while apod. jsou tzv. vyhrazená slova, která se nesmějí
používat jako jména. Malá a velká písmena
se v C++ rozlišují: haha, Haha a
HaHa
jsou tři různá jména a For není slovní symbol.
Na tomto místě se zmíníme o tzv. výčtovém typu.
Dovoluje zpřehlednit program. Výčtový typ lze definovat několika
způsoby. Uvedeme ten, který je v C++ nejjednodušší.
Příklad 2.1. Sestavme program , který přečte hodnotu
proměnné barva a bude-li barva červená, resp. žlutá, resp.
zelená, bude se tisknout text STUJ, resp. PRIPRAV SE,
resp. VOLNO.
Mohli bychom samozřejmě pracovat s čísly 0, 1, 2, ale shora
uvedeným způsobem se zápis algoritmu zpřehlední.
Implicitně zadané hodnoty položkám typu se mohou změnit, např.
zápis
enum DEN{po=1,ut,st,ct,pa,so,ne}
enum D{po,ut,st,ct,pa=1,so=12,ne}
4444444444444
¯
4444444444444 Matematický zápis Zápis v C++ Matematický zápis
Zápis v C++
Zápisy číselných konstant jsou jasné z předchozí tabulky.
Celočíselné konstanty se píší tak jako v matematice. U reálných
konstant píšeme desetinnou tečku a exponent označujeme písmenem
e nebo E (jako v jiných programovacích jazycích)
a znaky
píšeme mezi apostrofy. V C++ můžeme zapisovat
i osmičkové
nebo hexadecimální konstanty, tak
jak je v tabulce uvedeno.
Některé
nezobrazitelné znaky je možné zapsat pomocí změnových
posloupností, např. \n (nový
řádek), \t
(tabelátor), \0 (prázdný znak),\\ (zpětné
lomítko), \' (apostrof) apod. Mimoto se může zápisem
'\ddd' realizovat libovolný bitový řetězec, kde (d je
osmičková číslice), popř.'\xdd', kde d je
hexadecimální číslice.
Napíšeme-li v programu konstantu desítkovou konstantu 48
nebo '\60' nebo '\x30', znamená to v kódu ascii znak nula; napíšeme-li desítkově 95 nebo '\137'
nebo '\x5f', je to v kódu ascii znak _.
Definujeme-li např. v programu symbolickou konstantu:
const char nova_str='\14';
V tomto odstavci probereme podrobnosti o aritmetických, logických
a dalších operátorech a zmíníme se o prioritách operací v C++
1. Operátor o kterém jsme ještě nehovořili je operátor
operace . Např. 5%4 - > 1, 3%4 - > 3,
10%2 - > 0.
Příklad 2.2. Sestavme část programu, která zjistí, je-li
rok přestupný.
V programu jsme použili operace konjunkce a disjunkce, známých
z výrokové logiky.
Operátory jsme již
používali v 1. kapitole a nyní naše znalosti
doplníme o velmi důležitou podrobnost.
Příklad 2.3. Napišme funkci, která z řetězce r vymaže
znaky zn.
V programu je naznačena velká modifikovatelnost příkazu
for. Prosím čtenáře, aby se zamyslel, proč není možné v tomto
případě napsat r[++j]=r[i];
V jazyku C++ samozřejmě můžeme, právě tak jako v jiných
programovacích jazycích psát přiřazovací příkazy typu:
V jazycích jako Algol 60 nebo Algol 68 existuje
konstrukce zvaná podmíněný výraz. Taková
konstrukce existuje i v C++. Příkaz
nebo
Napsané varianty programu jsou trochu školními případy. Chtěl
jsem na nich opět nejen zopakovat použití externích proměnných a
to, že polím je možné přiřadit počáteční hodnoty, ale
i jak se to dělá.(Podrobněji v čl. .) Proměnná n
je ovšem definována jako externí celkem zbytečně. V druhém
případě musel být výraz
Relační operátory jsou
Výrazy spojené logickými operátory konjunkce
&&
a disjunkce || se vyhodnocují
zleva doprava a
vyhodnocování se zastaví v okamžiku, kdy je známa pravdivost
nebo nepravdivost výsledku. Priorita operátoru && je
vyšší než priorita operátoru || a oba mají nižší prioritu
než operátory rovnosti. Operátor negace
! jako unární operátor má jednu z nejvyšších priorit.
Logické operátory po bitech
není možné aplikovat na typy float nebo double. Mají
vyšší prioritu než operace disjunkce a konjunkce. Jsou to
operátory:
Nuluje se vše s výjimkou posledních sedmi bitů hodnot proměnné
c
Tzv. .Slouží k nastavení binárních jedniček
do míst, kde jsou jedničky v proměnné
nebo konstantě MASKA
Bity výrazu (proměnné) n se posunou o i
míst vlevo. Zprava se doplní nulami.
Totéž co v předchozím případě, ale posun se provádí vpravo.
Nulami se doplní zleva v případě, že proměnná n je typu
unsigned. Není-li proměnná typu unsigned mohou se bity
v některých systémech doplnit nulami (tzv. logický
posuv, ale v jiných hodnotou znaménkového
bitu (aritmetický posuv).
Nuluje se šest posledních bitů veličiny x. Čtenář jistě při
troše přemýšlení odpoví správně na otázku, proč je tento zápis
vhodnější než např. zápis x = x & 0177700.
Pod pojmem typová konverze se míní převod
proměnné určitého typu na typ jiný např. int na
float. Většina typových konverzí probíhá
implicitně, bez potřeby
zásahu programátora. Platí:
unsigned int ¯ na ¯ unsigned int na unsigned int
Toto pravidlo platí i pro podmíněný výraz.
Je-li např. proměnná i typu int a proměnná f
typu float, je výraz (n > 0) ? i : f typu
float.
zn += 65; ¯ 65 je konvertováno na char, tj. zn
je přiřazen znak A 65 je konvertováno na char, tzn. zn je
přiřazen znak A
k = 'Z' + 32; ¯ k je přiřazena hodnota 90
k je přiřazena hodnota 90
k = 'Z' + 32; ¯ k je přiřazena hodnota 90
d se přiřadí hodnota 10.0
V posledním případě se zn konvertuje na int a pak na
double. Výsledek součinu je double a je to hodnota 470.,
která se opět zkonvertuje na int a přiřadí proměnné k.
Je nutno upozornit, že hodnoty v příkladech uvedené platí
v případě, že systém pracuje s kódem ASCII.
Předchozí popsané případy se provádějí automaticky bez našeho
přispění. Můžeme však provádět typové konverze
explicitně, které se říká
přetypování. Má tvar:
(double) float_výraz převod celého čísla na
jeho znakový ekvivalent převod znaku na jeho
celočíselný ekvivalent
(double) int_výraz převod celého čísla na reálné
Přetypování je nutné v různých případech, např. při použití
ukazatelů (viz). V původně definovaném jazyku
C (viz např. [3]), bylo nutné při nesouladu
formálních a skutečných parametrů provést přetypování skutečného
parametru (tak je tomu ostatně ve většině programovacích jazyků).
Tedy např.
V jazyku C++ to není nutné, protože jsou zavedeny povinné
prototypy, ale přesto je vhodné přetypování uvést, protože
zpřehledňuje program. V jazyku C++ se více než
přetypování používá tzv. explicitní typová konverze. Spíše než
dříve uvedený zápis píšeme v C++:
nemáme-li ovšem odvahu napsat
printf("%d %d\n",++n,power(z,n));Správný (nebo spíše bezpečný) zápis
Samozřejmě, že se vyhneme i zápisům typu:
a[i] = ++i; // !!! Být či nebýt, to je oč tu běží!!!
Nyní již dokážeme realizovat i dosti složité programy v jazyku
C++. Budeme muset probrat ještě velmi důležité pojmy jako
struktury a uniony a hlavně ukazatele a třídy, ale v této
kapitole zatím probereme jen některé podrobnosti, o kterých jsme
z pedagogických důvodů nechtěli hovořit v kapitolách předchozích,
ale které nemůžeme opominout.
Výraz jako x = 0 nebo i++ nebo printf(...) se
stane příkazem, následuje-li za ním středník: x = 0; nebo
i++; nebo printf(...);
V jazyku C a C++ je středník koncovým
znakem příkazu
a ne oddělovačem, jako v jazycích typu Algol nebo
Pascal. Proto se také musí např. psát
i když středník před slovním symbolem else je
v programovacích jazycích obvykle chybný.
Složené závorky se používají pro sdružení příkazů v tzv. složený
příkaz, který jsme již mnohokrát použili. Jsou-li ve složeném
příkazu uvedeny definice proměnných, jedná se o tzv.
blok. Bloky mohou být do sebe vřazeny.
Příklad 3.1. Sestavme školní program, kterým si osvětlíme
princip blokové struktury.
Definice, které jsou v bloku uvedeny, platí pouze uvnitř tohoto
bloku. Vně bloku může být každý z definovaných identifikátorů
užíván pro jiné účely. Tak každý blok zavádí novou úroveň
označování. Každý identifikátor, který je v bloku definován, je
v něm lokální, tj. má tyto vlastnosti:
V příkladu 3.1 jsou automatické proměnné a, b, c z hlavního
programu přístupné v bloku b1. V bloku b2 a b3
je přístupná pouze proměnná a, protože identifikátory
b, c jsou užity jinak. Proměnné i, j z bloku b1
jsou přístupné jen v tomto bloku a po jeho ukončení jejich
hodnoty přestávají být definovány. Proměnné b, c, d
definované v b2 jsou přístupné i v bloku b3
a přestávají být definovány po ukončení bloku b2. Pro
pečlivého čtenáře je třeba uvést, že v příkladu 3.1 se budou
tisknout postupně hodnoty:
Závěrem tohoto odstavce se letmo zmíníme o tom, že definice
a deklarace nemusí být umístěny na začátku programové jednotky
nebo bloku, ale mohou být zapsány na kterémkoli místě před prvým
použitím definovaného nebo deklarovaného objektu.
Původně definovaný
jazyk C toto zakazoval (viz např. [3]).
K odkazu
Napíšeme-li např. program z příkladu 3.1 takto:
Operátor :: se užívá i ve spojitosti s tzv. třídami
(viz ).
Příklad 3.2. Sestavme funkci pro obracení řetězce znaků.
Funkce strlen je obvykle standardní funkcí jazyka C++
a bývá součástí souboru string.h. Zjišťuje délku řetězce bez
koncového znaku '\0'. Protože indexace v jazyku C++ začíná
od nuly a znak '\0' nepřehazujeme, museli jsme napsat
strlen(s)-1.
Příklad je pro tuto operaci typický, protože se
operátor čárka v jazycích C a C++ užívá hlavně
v příkazech for a while.
Příklad 3.3. Sestavme program, který vynechá v textu
koncové mezery, tabelátory a nové řádky.
V programu využijeme funkci getline, kterou jsme již
sestavili v příkladu 1.15.
Příkaz break v tomto případě ukončí práci vnitřního cyklu,
nerovná-li se poslední kontrolovaný znak ani mezeře, ani
tabelátoru a ani znaku nový řádek.
Tento příkaz zajistí ukončení jednoho průchodu cyklem a narozdíl
od příkazu break se používá pouze v cyklech.
V následujícím naznačení funkce f jsou jako registrové
proměnné použity oba formální parametry a automatická proměnná
m.
Příklad 3.4. Sestavme funkci pro výpis násobků libovolného
čísla hodnotami 11 až 20.
V praxi se na registrové proměnné kladou jistá omezení, které
odrážejí možnosti použitého počítače. Registrové proměnné musí
být vždy automatické (lokální) . V každé funkci můžeme
v registrech držet jen několik proměnných a tyto mohou být jen určitých
typů. Pro nadbytečné nebo nepřípustné definice se označení
register ignoruje.
Návod: Seřaďte číslice odzadu a pak použijte funkci obrat
ze cvičení č. 20.
V této ne příliš smysluplné části programu jsme demonstrovali
základní
práci s ukazateli. Ukazatel se v deklaraci nebo definici pozná
podle znaku *, který předchází jménu ukazatele. V našem
příkladu je ukazatelem veličina px. Příkaz px=&x; zajistí,
že se ukazateli px přiřadí hodnota adresy proměnné x.
Operátor & se nazývá operátor adresy.
Od tohoto okamžiku je v programu ekvivalentní zápis
*px a x do doby, než je ukazateli px přiřazena jiná
hodnota. Tedy příkazy y=*px; z=*px+1; jsou ekvivalentní
příkazům y=x; z=x+1;. Též v příkazu printf jsme
školně napsali *px, i když jsme mohli psát x.
Příkazem px=py jsme zajistili, že px a py
ukazují na totéž místo v paměti, tedy v našem případě na místo,
které je přiděleno proměnné x.
Závěrem tohoto odstavce by bylo vhodné, aby se čtenář laskavě
přesvědčil, že zápis *px++ není totožný se zápisem
(*px)++, kdežto zápis *++px je totožný se zápisem
*(++px). V těchto případech je vhodné, aby se priorita operací
vždy vyznačila závorkami. Vyhneme se tak nepříjemným omylům a
program zpřehledníme.
Jednoduché proměnné, jak jsme si řekli v čl. 5, jsou
volány hodnotou, není-li řečeno jinak. Je-li řečeno jinak, volají
se odkazem. Jinak může být v jazyku
C++ řečeno dvěma způsoby. První způsob je obvyklý
i v jazyku C a provádí se takto:
Druhý způsob je typický pro jazyk C++ a provádí se takto:
Čtenář se může přesvědčit, že následující dva úseky programu jsou
algoritmicky správně; kdežto třetí je algoritmicky (nikoliv ovšem
syntakticky) chybný.
Platí-li že pa ukazuje na a[0], pak pa+1 ukazuje na
a[1], pa+i na a[i]. Napíšeme-li *(pa+i), pak je
to totéž jako bychom napsali a[i]. Místo pa = &a[0]
smíme zapsat pa = a.
Tyto zápisy vypadají zpočátku velmi nezvykle, ale v programu se
často využívají.
Příklady:
V poslední variantě se využilo skutečnosti, že poslední znak v
řetězci je vždy nula a je-li výraz v závorkách za příkazem
while nulový příkaz while se ukončí. Vyniká zde stručnost
zápisu v tomto jazyku, která je však mnohdy na účet přehlednosti.
Použije-li se slovní symbol const s ukazatelem, jsou
konstantní data spojená s ukazatelem, kdežto ukazatel konstantní
není.
Při deklaraci const char *name ="PROKOP";
Je přípustný příkaz
name="BUBEN";
ale nikoliv příkaz
name[0]='B';
Chceme-li konstantní ukazatel a nikoliv údaj, na který ukazuje,
píšeme
char *const JMENO="hana";
Pak
JMENO[0]='d';
je správně, kdežto
JMENO="dana";
je chybně. Chceme-li mít konstantní ukazatel i údaj, na který
ukazuje, píšeme
const char *const KONST="NAZDAR";
Tato poněkud méně přehledná pravidla jsou zachycena v ukázkách
v příkladu 4.1.
Příklad 4.1.
Funkce main se může volat se dvěma argumenty. Prvý -
označme ho argc, což bývá zvykem, ale není to podmínkou -
obsahuje počet argumentů tzv. povelového řádku,
tj. řádku, kterým vyvoláváme příslušný program. Druhý
- označme jej, jak je zvykem, ale nikoliv povinností, argv
je ukazatel na pole znakových řetězců, z nichž každý označuje
jeden argument.
Příklad 4.2. Sestavme program, který bude realizovat známý
příkaz echo (ozvěna) operačního systému UNIX.
Nechávám na čtenáři, aby si vyzkoušel správnost zápisu variant.
Je třeba jen upozornit, argc udává počet všech argumentů
včetně názvu programu, kterým přeložený program vyvoláváme. Prvky
pole ukazatelů argv ukazují vždy na začátek příslušného
argumentu. Má-li tedy povelový řádek např. tvar:
echo Jak se do lesa vola, tak se z lesa ozyvamá automaticky argc hodnotu 11 a argv[0] ukazuje
na začátek řetězce echo, argv[1] ukazuje na začátek
řetězce Jak a např. argv[10] ukazuje na začátek
řetězce ozyva.
Příklad 4.3. Sestavme program pro výpočet integrálu
Simpsonovou metodou. Simpsonovu metodu i integrovanou funkci
zapišme jako samostatné funkce. Integraci proveďme tak, že zadáme
v příkazovém řádku vedle jména programu ještě počet výpočtů a
počáteční dělení. V případě, že zadáme chybný počet argumentů,
program nás bude instruovat, jak povelový řádek zadat. V tomto
příkladu integrujeme funkci ň01,5[(sinx)/( x)] dx
V příkladu jsme si ukázali vedle použití povelového řádku (kdy
zbytečně dlouhé názvy argumentů jsem zvolil proto, abych
explicitně upozornil na možnost volby jmen argumentů; argc
a *argv[] se používají proto, že to tak zavedli tvůrci
jazyka C pánové Kerninghan a Ritchie [3], není to
však dogma a lze využívat různých mnemotechnických názvů)
i využití jazyka C++ v numerické matematice, kdy je často
vhodné použít jako skutečný parametr funkci. Tento způsob jsme
si mohli ukázat až v okamžiku, kdy jsme si vysvětlili mechanismus
ukazatele. Formální parametr, který stojí na místě funkce, musí
být totiž deklarován jako ukazatel na funkci vracející hodnotu.
Jak jsme již napsali v poznámce v proceduře, mohli jsme zapsat
tento formální parametr jako
double(*f)(double), my jsme však použili příkazu
typedef
v zápisu typedef double(*FUK)(double) čímž jsme
definovali typ FUK jako ukazatel na funkci mající jeden
argument typu double a vracející hodnotu typu double.
Zápis formálního parametru má pak jednodušší formu: FUK f.
Funkci f můžeme zapisovat běžným způsobem jako v jiných
programovacích jazycích a ne, jak se traduje (a je ovšem také
správné) a je uvedeno v poznámce ve funkci integral.
Doplňme si nyní naše znalosti o příkazu typedef, přestože
jsme se o něm zmínili již např. v čl. 1.
Příkaz
typedef int CEL, *CELUK; zajistí, že místo
int ahoj, *metr; lze psát
CEL ahoj; CELUK metr;což se může jevit jako zbytečné ale v případech jako
(kdy v případě a je deklarováno pole tří ukazatelů na celá
čísla; v případě
b je deklarován ukazatel na pole tří celých čísel;
v případě c je deklarován identifikátor funkce vracející
ukazatel na celé číslo a mající jeden argument typu int;
a konečně v případě d je deklarován ukazatel na funkci
vracející celé číslo a mající jeden celočíselný argument) nám
příkaz typedef pomůže zpřehlednit zápis programu, protože
shora uvedené zápisy přehledností nevynikají. Napíšeme-li
ale v
příkazu typedef některou z výše uvedených konstrukci, pak
jméno stojící na místě id1 až id4 označuje nový typ.
Takže
zajistí, že proměnná ukp je ukazatel na pole tří celých čísel.
Zápis
sděluje kompilátoru, že f1 je identifikátor funkce s jedním
celočíselným argumentem, vracející ukazatel na celé číslo atd.
Zápis se zjednodušší a toho jsme právě využili v příkl. 4.3.
Jak jsme si již řekli v 5.1 v jazyku C++ můžeme
inicializovat i automatická pole,
což je v jazyku C
zakázáno. Platí, právě tak jako u jednoduchých proměnných, že
externím polím se přiřazuje hodnota právě jednou při plnění
programu, kdežto automatickým polím se přiřazuje hodnota vždy při
vstupu do funkce nebo bloku. Zde si proto na příkladech ukážeme
techniku počátečního přiřazení, aniž bychom rozlišovali mezi
externími a automatickými poli.
int x[] = {1,3,5,7,9,11};
Pak y[0][0] = 1; y[0][2] = 5;, čímž naznačujeme,
že se hodnoty přiřazují po řádcích. Čtvrtý řádek se
v našem případě případě inicializuje nulami.
Téhož přiřazení dosáhneme zápisem:
int x[4][3] = {1,3,5,2,4,6,8,7,9};
int y[4][3] = {{10},{9},{8},{7}};
pak použití a i b může být podobné tím, že
a[10][10] i b[10][10] je správně zapsaný odkaz na celé
číslo. a je však skutečné pole (přidělí se mu 400
paměťových míst) a při výpočtu se používá
obvyklý výpočet indexů pravoúhlého pole (matice) pomocí mapovací
funkce. Pro b se však přidělí 20 paměťových míst pro
ukazatele. Každý z těchto ukazatelů je třeba nejprve
inicializovat, má-li ukazovat na nějaké pole celých čísel.
Budeme-li předpokládat, že každý z těchto ukazatelů bude ukazovat
na pole o dvaceti prvcích, budeme v takovém případě potřebovat
420 paměťových míst. Pole ukazatelů tedy potřebuje více
místa v paměti a vyžaduje počáteční inicializaci. Má to však dvě
výhody:
V následující ukázce programu se tiskne vždy totéž a sice hodnota
prvku a[5][9] tj. v tomto případě hodnota 45.
Příklad 4.4. Sestavme funkce, které umožní přečíst,
vytisknout a sečíst matice libovolného typu. Využijme ukazatelů
na vícerozměrná pole. Funkce použijme v programu.
Před funkcí v jazyku C++ smí být uveden slovní symbol
inline. Takovéto funkce jsou tzv. vnitřní funkce, jejichž
operační část se zapisuje přímo na místo, kde je funkce volána.
Tím, že odpadá odskok na podprogram, který běžnou funkci
realizuje, zrychluje se výpočet, ale prodlužuje se strojový kód,
zvláště, opakuje-li se volání funkce často na různých místech a
operační část funkce
je delší. Doporučujeme proto užívat jako vnitřní funkce jen
funkce krátké. Navíc příkazy (a slovní symboly) do, for,
while, goto, switch, break, continue a case se ve
vnitřních funkcích vyskytovat nesmějí. Použijí-li se, není to
chyba, ale funkce se implementuje jako normální funkce a na
slovní symbol inline se nebere zřetel. Typická vnitřní
funkce je uvedena v následujícím příkladu
verbatim
#include<stdio.h>
inline void pr_int(int a,int b)
printf("=
V jazyku C++ se smějí používat tzv. dosazené parametry.
Kdekoliv se tak učiní, musí být dosazené parametry uvedeny jako
poslední. Zápis
Příklad 4.5.
Z uvedeného příkladu vidíme, že musíme uvádět jen nedosazené
parametry. Neuvedeme-li dosazené parametry, dosadí se za tyto
parametry hodnota uvedená v záhlaví funkce nebo prototypu.
Přetížené funkce v jazyku C++ jsou takové funkce, které
mají stejné jméno v daném programu, ale různou operační část (tj.
rozdílný algoritmus). Systém rozlišuje takové funkce při volání
podle typu skutečných parametrů, které se musí u různých
stejně pojmenovaných funkcí lišit. Dříve než jsou přetížené
funkce definovány, musí být uvedeny slovním symbolem
overload (viz příklad 4.6).
Návod: Cyklem se přeskočí mezery, nové řádky a tabelátory. Znaky
čteme do automatické (lokalizované) proměnné (např. int c).
Je-li c == '+', resp. c == '-', přiřadí se např.
sgn = 1, resp. sgn = -1. Je-li c Î < '0','9' >
připočte
se k *p řádná hodnota. Při prvé neprázdné a nenulové hodnotě se
provede *p *= sgn a je-li c != EOF, vrátí se
c na vstup funkcí ungetch. Návrat: return c.
Proměnné typu union
mohou nejen nabývat různých hodnot (což není překvapivé), ale mohou
nabývat i hodnot
různých typů (což již překvapivé je).
Při nedostatku paměti může být někdy výhodné uložit
několik objektů do jednoho paměťového místa, obvykle je to
množina jednobitových příznaků v aplikacích jako jsou tabulky
symbolů v kompilátorech. Vzhledem k tomu, že jazyk C byl
vytvořen zkušenými systémovými programátory pro vlastní ulehčení
práce při tvorbě tak složitého operačního systému jako je UNIX, je jasné, že takovouto konstrukci museli zavést. V kapitole
se také zmíníme o práci s tzv. dynamickými objekty, které jsou
definovány a je jim vytvářeno místo v paměti (v tzv. hromadě)
v průběhu procesu plnění algoritmu. Budeme využívat ukazatelů,
tříd a unionů. Vynechal jsem záměrně struktury, abych
naznačil, že se stavbou struktury se seznámíme pouze na začátku a
pak místo se strukturami, budeme pracovat s třídami. Je to sice
poněkud atypické, ale třída může strukturu plně nahradit. Tím
ušetříme místo na důležitější konstrukce.
Příklad 5.1. Zapišme deklaraci typu datum, který bude
zahrnovat položky den, mesic, rok, cislo_dne_roce typu
int a jméno_měsíce typu char[10]. Daný typ
použijme v programu tak, že definujeme proměnnou typu datum,
které přiřadíme počáteční hodnoty a pak tyto hodnoty vytiskneme.
Typ datum jsme použili v hlavním programu
k popisu proměnné d1, které jsme hned přiřadili počáteční
hodnoty. Po vymazání obrazovky (příkazem clrscr()
v systému, kde byl program laděn) tiskneme jednotlivé položky
tak, že píšeme
jméno_struktury.položka
Existují další způsoby jak zajistit deklaraci shora uvedené
struktury např.
kdy můžeme definovat přímo proměnnou (nebo proměnné) požadované
struktury. Zápis uvedený v programu se mi však zdá pro C++
typický.
Nyní si zapíšeme tentýž příklad s tím, že místo struktury
použijeme třídu. Odlišuje se od struktury na první pohled tím že
místo slovního symbolu struct je zapsán slovní symbol
class a u položek uvedených ve třídě musíme zapsat slovní
symbol public, kterým dáváme kompilátoru informaci, že
položky jsou přístupné i příkazům mimo metody (procedury)
svázané s danou třídou.
Od tohoto okamžiku budeme používat pouze objekty typu
class (viz. kap. 6) s tím, že čtenář si může vždy za třídu
dosadit strukturu a
dostane tentýž výsledek. Na okamžik, kdy objekty typu class
začneme používat v širším smyslu než objekty typu struct,
upozorníme.
Ve strukturách a třídách mohou být položky, které jsou opět
strukturami nebo třídami. Následující příklad to demonstruje snad
dostatečně jasně. Jen je třeba si všimnout násobného použití
operátoru kvalifikace.
Příklad 5.2. Definujme typ osoba, která bude
obsahovat položky jmeno, adresa typu char[],
PSC, cislo_pojistky typu long int, mzda
typu double a narozeni, zamestnan typu datum.
V hlavním programu definujme proměnnou zaměstnanec typu
osoba, přiřaďme jí hodnoty a ty ihned vytiskněme.
Příklad 5.3. Vypočtěme z údaje, který získáme z konkrétního
data pořadové číslo dne v roce.
Zde jsme v programu použili operátoru -> (spojením znaků
minus a větší než); např. zápis d->den je ekvivalentní
zápisu (*d).den (jsou-li čtenáři nejasné závorky, nechť se
laskavě podívá na priority operací dříve uvedené), ale je
stručnější.
Jsou-li dva operátory -> zapsány "vedle
sebe", vyhodnocují se zleva doprava. Tedy zápis p->q->r je
totožný se zápisem (p->q)->r právě tak, jako zápis
Máme-li definovánu třídu T{int x, *int y}
a definujeme-li ukazatel na objekt typu T definicí T
*p, (ukazatel musíme v průběhu procesu samozřejmě inicializovat)
pak zápis ++p->x; zvětší o jedničku x a ne p,
protože implicitní závorky jsou v tomto případě ++(p->x);.
Chceme-li zvětšit hodnotu p ještě před přístupem k položce
x, musíme psát (++p)->x;. Chceme-li však zvětšit
hodnotu p po přístupu k x, píšeme
(p++)->x;. Zde jsou však závorky zbytečné. Hloubavý čtenář
jistě zjistí proč. Přestože autor těchto skript není přívržencem
zbytečných závorek, v podobných konstrukcích jazyka C a
C++ je jejich horlivým zastáncem.
Z dřívějších kapitol víme, že v jazyku C++ je možné
volání formálních parametrů adresou. Pak shora uvedený program má
tvar
a vlastní proměnnou mů'eme definovat (přesně viz program) jako
Příklad 5.4. Sestavme program, který vypočítá četnost
výskytu jednotlivých slovních symbolů v libovolném zdrojovém
programu psaném v jazyku C++.
Program by zasluhoval podrobnější vysvětlení, které by v těchto
stručných skriptech zabralo hodně místa. Předpokládám, 'e čtenář,
který aktivně přečetl skriptum a' do těchto míst, algoritmu
porozumí. Upozorním jen na několik skutečností:
velikost tab / velikost sl_symb
S výhodou se vyu'ije operátor sizeof, který při kompilaci
určí velikost jakéhokoliv objektu.
V příkladu jsme pro úplnost uvedli opět dva způsoby definic.
V levé části jsme definovali proměnou typu union, která
mů'e nabývat hodnot typu int, float a char *, kde'to
v pravé části jsme nejprve deklarovali nový typ r_tri a pak
jsme definovali proměnnou r_hod typu r_tri, která
mů'e nabývat hodnot typu int, float a char*. Nic nám
nebrání v jazyku C++, abychom napsali
pakli'e se nám to z nějakého důvodu hodí. Toté' platí pro typy
struct a class, u kterých jsme to pro stručnost
nezdůraznili.
Přístup ke členům (polo'kám) je obdobný jako u typů struct
a class, tedy
Po zavedení souborů pro proudový vstup (iostream.h),
standardní vstup (stdio.h) a ovládání obrazovky (
conio.h), které vyu'ívá systém, pod kterým byl program laděn,
deklarujeme typ r_tri pro tři různé typy, proměnnou
r_hod typu r_tri a definujeme
tři konstanty pro rozpoznání čteného typu. V hlavním programu
definujeme ukazatel typu char, kterému, co' je zcela nové, pomocí
mechanismu (jen' je typický pro C++) new, umo'níme,
aby ukazoval na místo pro
jeden znak v tzv.
hromadě pro dynamické objekty. Zatím nám stačí vědět, 'e objekty
v hromadě jsou přístupné jen prostřednictvím nějakého ukazatele.
Podrobněji se těmito objekty budeme zabývat v čl. . Pak
ma'eme obrazovku funkcí ze souboru conio.h, čteme typ
proměnné a podle přečteného typu čteme hodnotu proměnné. Čtení
hodnoty typu int a float je snad zcela jasné a při
troše přemýšlení čtenář rozkóduje i čtení řetězce. Výstup hodnoty
ji' neřeší nic nového. Programátorsky čistší by snad bylo pro tisk
řetězce pou'ít funkce putchar, ale proudová funkce
cout vykoná v tomto případě stejnou slu'bu. Toté' nelze říci
o nahrazení funkce getchar funkcí scanf nebo cin.
Čtenář se mů'e přesvědčit sám.
Využijeme-li však tzv. anonymního typu union, lze shora
uvedené zápisy psát takto:
V jazyku C a C++ lze pracovat s tzv. dynamickými
objekty. Jsou to objekty, je' se vytvářejí v průběhu procesu. Jsou
umísťovány v paměti, která se nazývá hromada a ka'dý objekt je
definován tak, 'e na něj ukazuje nějaký ukazatel. Pomocí
dynamických objektů, lze realizovat slo'ité algoritmy prací se
symboly, symbolické derivování, integrování apod. My zde uká'eme
jen několik jednoduchých programů pro pochopení principu.
K odkazu
Příklad 5.6. Napišme program, který uvolní v hromadě místo
pro pole padesáti celočíselných prvků, pole naplníme a vytiskneme
několik vybraných prvků.
Jako dobré cvičení doporučuji čtenáři, aby zpaměti určil, co se
bude tisknout postupně v posledních čtyřech příkazech tisku a
přesvědčil se o správnosti svého úsudku na počítači. Té' se mů'e
mů'e pokusit vytisknout toté', pou'ije-li indexované proměnné.
Příklad 5.7. Napišme program, který utvoří v hromadě
posloupnost objektů, které se říká seznam, ka'dý objekt bude
obsahovat celočíselnou hodnotu a ukazatel na další objekt.
Poslední objekt seznamu bude ukazovat na nulovou adresu.
Vytvoříme typ objekt, definujeme dva ukazatele na objekt
typu objekt. První bude ukazovat na začátek seznamu a druhý
v'dy na nový objekt, který se do seznamu zařazuje.
Vytvoříme seznam deseti objektů, které budou nabývat
celočíselných hodnot jedna a' deset. Seznam v tomto případě
vytváříme od konce. Postup by měl být jasný z programu.
Příklad 5.8. Napišme program, který bude vytvářet tentý'
seznam, ale sudé prvky se budou plnit hodnotami typu float a
liché hodnotami typu int.
V typu objekt přidáme anonymní union pro hodnotu,
která mů'e být typu float nebo int.
Na tomto místě je vhodné se zmínit o dynamicky definovaných vícerozměrných polí. (Matice a kubické matice.)
Podrobnější příklady jsou uvedeny ve [4]. Pro čtenáře by bylo dobrým cvičením,
kdyby tam uvedené příklady přepsal do jazyka C++, což je celkem mechanická práce. Jen je třeba si uvědomit rozdíly mezi
funkcí malloc a operátorem new. Píše-li programátor v jazyku C např.
Bitové pole si mů'eme představit jako strukturu, její' velikost
je ale pevně omezena velikostí typu int. Nejmenší délka
jedné polo'ky v bitovém poli je jeden bit. Bitové pole se
definuje jako struktura, ale ka'dá polo'ka je určena svým jménem
a délkou v bitech. Proto'e máme mo'nost definice jak
unsigned int, tak signed int, je vhodné v'dy uvést, je-li
polo'ka se znaménkem či bez znaménka.
Bitové pole má dvě základní oblasti pou'ití
Příklad 5.9. Napišme datum tak, aby se vešlo do jednoho
slova, do kterého lze zapsat celočíselná hodnota v systému,
ve kterém předpokládáme, 'e sizeof(int) dává hodnotu
2.
Polo'ka rok zabírá 7 bitů, tzn. 'e největší hodnotou, kterou
by bylo mo'né zobrazit je 27 - 1 = 127. Abychom mohli
zobrazit letopočty naší doby, odečetli jsme při inicializaci
hodnotu 1950. Tu pak musíme při pou'ití data v'dy přičíst.
Příklad 5.10. Napišme fragment kompilátoru, který manipuluje
s tabulkou symbolů.
S ka'dým identifikátorem v programu se
spojuje určitá informace (jedná-li se o objekt externí,
statický nebo slovní symbol apod.) Nejlépe se takováto informace
zakóduje mno'inou jednobitových příznaků v objektu typu int
nebo char. Bez pou'ití bitových polí, mů'eme postupovat
takto:
Pou'ijeme-li bitové pole, lze toté' naprogramovat elegantněji:
Výhodou bitových polí je příjemnější práce. Nevýhodou je, 'e
někde se bitová pole ukládají zleva doprava, jinde zprava doleva.
To někdy způsobuje jisté nepříjemnosti (obtí'nější přenositelnost
programů). Dále je třeba si
pamatovat, 'e bitová pole se mohou ukládat pouze do proměnných
typu int nebo unsigned; nejsou to pole a nemají
adresy. Není tedy mo'né pou'ívat operátor adresy &.
K odkazu
V této kapitole se seznámíme se základy objektově orientovaného
programování (dále jen OOP). Podrobný výklad se vymyká
záměru těchto skript a to
nejenom proto, že by přesáhla povolený počet autorských archů.
Skripta pojednávají o jazyku C++, který, mimo jiné, umožňuje
realizovat objektově orientované projekty. OOP je však
metodika,
která není svázána s konkrétním programovacím jazykem a
která umožňuje strukturovat a konstruovat systémy, přičemž výhody
OOP jsou tím zřetelnější, čím větší je projektovaný systém.
V těchto skriptech využijeme OOP k tomu, abychom se
podrobněji
seznámili se strukturou class, kterou již poněkud
povrchně známe z 5. kapitoly.
Použití této metodiky je výhodné zejména při tvorbě informačních systémů,
kde vlastnosti objektů OOP se blíží vlastnostem
reálných objektů (i když pochopitelně svět je ve své
mnohotvárnosti přeci jen složitější, než si myslí někteří
fundamentalističtí zastánci OOP), počítačové grafice a
někdy i ve vědeckých a technických výpočtech, speciálně při
složitých projektech.
Příklad 6.1. Definujme třídu, která zajistí přičítání a
odečítání jedničky k a od proměnné hodnota, která je
dostupná pouze metodám (v C++ funkcím zapouzdřeným ve
třídě.
V další alternativě tohoto příkladu budeme definovat funkce
třídy mimo tuto třídu, což se obvykle dělá, nelze-li funkce
definovat jako vnitřní.
Tento příklad byl řešen v příkladu 5.7. Nyní jen funkce
zapouzdříme do třídy objekt a vytvoříme z nich metody. Mezi
metodami je nová metoda
destruktor. Tuto metodu si můžeme představit jako
inverzní funkci ke konstruktoru. Konstruktor vytváří příslušný
objekt a přiřazuje datům počáteční hodnoty, je-li to nutné.
Destruktor uvolňuje objekt z paměti a ruší ho. V implemntacní
části jsme definovali statické proměnné zac_sez pro začátek
seznamu a pom_prv pro pomocný prvek, aby byly dostupné
právě pro metody dané třídy. V příkladu 6.5 si ukážeme si ukážeme
jinou možnost s využitím spřátelených funkcí.
Data a metody, jak již víme mohou být private, pak jsou
přístupné pouze metodám dané třídy, public, pak jsou
přístupná pro všechny podle pravidel platných pro data a funkce
v jazyku C++. Dále mohou být definovány metody a data jako
protected. Taková data jsou přístupná pro odvozené třídy
z třídy dané (potomky třídy).
Příklad 6.3. Sestavme systém OOP, ve kterém můžeme
definovat geometrické obrazce, tyto obrazce vytvářet, rušit,
posouvat, zvětšovat a zmenšovat. V systému definujme třídu
ümísti", jejího potomka "bod" a potomka bodu "kružnici". Systém
je otevřený, takže je možné definovat libovolný počet tříd
pro zobrazení dalších obrazců.
Tento příklad ukazuje výhody OOP právě při vytváření systémů
počítačové grafiky. Lze z něj pochopit princip dědičnosti a
polymorfismu. Nevýhodou je, že je nutno pracovat s konkrétní
grafickou knihovnou (byla použita grafická knihovna firmy
Borland). Vysvětlující poznámky jsou přímo v jednotlivých
souborech, protože je to stručnější a (podle autorova
názoru i) přehlednější.
Spřátelelené funkce a třídy se využijí obvykle ve složitých
projektech, které se vymykají této publikaci. Abychom však
nezůstali jen u planého popisu, sestavíme dva školní příklady,
které sice nejsou příliš duchaplné, ale techniku použití "přátel"
snad dostatečně osvětlí.
Příklad 6.4. Sestavme program, ve kterém třída
zvec disponuje metodou, která zvětší privátní údaj třídy
o jedničku. Dále sestavme spřátelenou funkci, která bude schopna
tento privátní údaj vytisknout.
Program je jednoduchý a snad i jasný. Zajímavé je na něm jen
umístění prototypu spřátelené funkce. Je lhostejné, je-li umístěn
v části private nebo public. Funkce pracuje s formálním
parametrem typu zvec, který voláme odkazem pro ušetření
místa. Je samozřejmé, že lze použít i standardní volání hodnotou,
které je opět rychlejší.
Opět je třeba zdůraznit, že i spřátelenou třídu je možno uvést
v oblasti public i private. Pro zjednodušení
algoritmu se v programu předpokládá klesající posloupnost
zařazených hodnot seznamu. Objekty seznamu se tvoří öd zadu",
tj. první udaný objekt z klávesnice je v seznamu umístěn jako
poslední.
nebo
To znamená, že pošleme zprávu "přičti hodnotu z2" objektu
z1 nebo zprávu objektu z2 o "přičtení objektu z1. To
je méně přehledné, než zápis
z1 + z2.
Operátor + můžeme tudíž přetížit o další typ na který je
aplikovatelný. Můžeme samozřejmě definovat operátor * jako
operátor sčítání; kompilátoru (a matematikům) to nevadí, ale
normálním lidem ano, proto se takovéto transakce nedoporučují.
Než budeme pokračovat ve výkladu, osvětleme si stručně slovní symbol
this. Tento slovní symbol (česky znamenající "tento, tato,
toto") je v jazyku C++ definován
jako ukazatel na objekt, který posílá zprávu sám sobě. Ve většině
případů se nemusí tento ukazatel explicitně zapisovat, ale někdy,
jak v závěrečném příkladu tohoto článku uvidíme, ano.
Binární operátory (tj. operátory mající dva operandy) jako
např. +, -, *, / atd. se mohou definovat jako metody
s jedním parametrem uvnitř
třídy (druhým parametrem je pak vždy this nebo jako
spřátelené funkce se dvěma parametry. Unární operátory (tj.
operátory s jedním operandem) se mohou
definovat jako metody bez parametru uvnitř třídy, nebo jako
spřátelená funkce s jedním parametrem. Tedy výraz
operand_1 B operand_2může být interpretován jako
operand_1.operatorB(operand_2)nebo
operatorB(operand_1,operand_2).
Uvažujme výraz
vyjádřený unárním operátorem U
operandU
nebo
Uoperand.
Ten může být interpretován jako
operand.operatorU()nebo
operatorU(operand).
Přetěžujeme-li operátory ++ a - nelze odlišit
prefixovou a postfixovou aplikaci. Někdy je použití spřátelené
funkce nezbytné. Např. z+27 lze zapsat jako
z.operator+(27) ovšem zápis 27+z zapsaný jako
27.operator(z) je nesmyslný, protože 27 není objekt
definovaný uživatelem. Uvedený příklad je natolik ilustrativní,
že lze doporučit binární operace definovat výhradně jako
spřátelené funkce.
Příklad 6.6. Zapišme program z příkladu 6.1 tak, že
počitadlo vylepšíme užitím přetížených operátorů ++ a
-.
Tento druhý příklad je mnohem rozsáhlejší. V jazyku C++ není
standardně definována práce se zlomky, a proto jsme museli
následující operátory přetížit tak, aby byly schopny
se zlomky pracovat.
Operátor ¯ Přetížení proudového operátoru na tisk zlomků.¯ Operátor Užití
Operátory definované ve třídě zlomek odpovídají přesně
právě vyjmenovaným operátorům. V sekci private třídy
zlomek je uvedena metoda kraceni, která umožní vypočítané
zlomky zkrátit, tedy např. -12/36 na -1/3.
Konstruktor umožní specifikovat uživateli čitatel a jmenovatel.
Není-li čitatel a jmenovatel specifikován, je čitatel roven nule
a jmenovatel roven jedničce (využilo se dosazených parametrů).
Čtenář si přečte (a doufám, že i prakticky ověří), že operace
+,-,*,/,==,!=,real a << jsou definovány jako
spřátelené funkce. Všechny s výjimkou funkce real mají dva
parametry, a proto není vhodné, aby byly definovány jako metody.
Funkce real má jeden parametr, a proto mohla být
definována jako metoda.
Výsledky testovacího programu byly zapsány do souboru
muj_vystup ve tvaru:
Předpřekladač jsme zatím používali intuitivně tak, že jsme
v příkladech v této publikaci prakticky vždy používali příkazy
#include, kterými jsme zaváděli hlavičkové soubory
např. #include<stdio.h> nebo naše uživatelské
soubory, např. #include"zlomek.cpp" nebo
#include"zlomek.h". Nyní se v závěru této publikace zmíníme
o předpřekladači (obvykle se předpřekladači říká preprocesor)
podrobněji.
Činnost předpřekladače se dá shrnout do několika bodů:
Všimněte si, 'e nesmíme v definici konstanty zapsat na konci
středník. Ten by se toti' zapsal s uvedenou hodnotou kdykoliv,
kdy' by předpřekladač narazil na příslušný text, např. DOLNI a to
by způsobilo syntaktické chyby. Mů'eme samozřejmě psát
Tento program vytiskne text: Toto je MAKRO a nikoliv
Toto je past, co' je právě ta past.
#define na_treti(x) ((x)*(x)*(x))
#define na_treti(x) x*x*x
#define cti(c) c=getchar()
if(r=getchar() == 'b')Na závěr tohoto odstavce ještě upozorníme na případ, 'e
direktiva s parametry mů'e být někdy tak dlouhá, 'e se nevejde na
jeden řádek. Pak ji přerušíme znakem zpětné lomítko a
pokračujeme na další řádce. Např. shora uvedené makro
na_treti lze zapsat:
Posledním, ale důle'itým, poučením bude, 'e mezi makrem a první
otevírací okrouhlou závorkou nesmí být mezera.
Příklad 7.1.
Předpřekladač "rozepíše" toto makro na:
Příklad 7.2.
Zde předpřekladač rozepíše makro na:
Příklad 7.3 Napišme jednoduchou funkci, která bude napsána
tak, aby byla přijatelná, jak pro původní jazyk C jak byl
definován v [3], tak pro ANSI C a C++.
Využijme podmíněného překladu ve třech základních variantách.
Poznámka k "elektronické" podobě skript
Skripta jsem přepsal do jazyka HTML, aby se studentům usnadnil přístup a zároveň aby byla demonstrována
užitecnost práce s tímto tvarem informací. Dnešním dnem rušim informaci: Contents under Construction
, ale za jakékoliv připomínky na své e-mailové adrese: vogel@marian.fsik.cvut.cz
nebo
vogel@fsid.cvut.cz
 
AutorObsah
Literaratura
Úvod do jazyka C++
Typy, operátory, výrazy
Podrobnosti k předchozím kapitolám
Ukazatelé, pole, funkce
Struktury, třídy, uniony, bitová pole
Základy objektově orientovaného programování
Poznámky k předpřekladači
Kapitola 1
V této kapitole se seznámíme s podstatnými prvky jazyka, aniž
bychom zabíhali do podrobností. Jazyk budeme vykládat na
příkladech. Tento způsob výkladu má svá úskalí, zejména pokud se
jedná o přesnosti popisu jazyka, ale jediným způsobem jak se
naučit programovací jazyk, je programovat, a proto zvolíme tuto
strategii. Budeme postupovat tak, že nejprve zadáme úlohu,
napíšeme program v jazyku C++ a program si vysvětlíme.
Úvod do jazyka C++
1. Vstup a výstup
Příklad 1.1. Sestavme program, který vytiskne text
Vitejte v kurzu jazyka C++
Řešení:
#include <iostream.h>
main()
{
cout << "Vitejte v kurzu jazyka C++" << '\n';
}
cout << "Vitejte v kurzu jazyka C++ \n";
#include <stdio.h>
main()
{
printf("Vitejte v kurzu jazyka C++\n");
}
Řešení:
#include<fstream.h>
main() // Zapis do souboru out.dat
{
// Nejprve otevreni souboru out.dat pro zapis
ofstream fout("out.dat");
// a vlastni zápis
fout << "Vitejte v kurzu jazyka C++\n";
// Uzavreni souboru
fout.close();
}
#include <stdio.h>
main()
/* Tentyz priklad prostredky, ktere jsou obvykle v jazyku C i C++
*/
{
/* Definice ukazatele typu FILE*/
FILE *fp;
/* Otevreni souboru out.dat pro zapis*/
fp=fopen("out.dat","w");
/* Zapis do souboru funkci fprint
ze souboru stdio.h*/
fprintf(fp,"Vitejte v kurzu jazyka C++\n");
/* Uzavrení souboru*/
fclose(fp);
}
Řešení:
#include <iostream.h>
main()
{
float x; // Definice realne promenne x
int i; // Definice celociselne promenne i
cout << "Cti x, i\n";
// Cteni hodnoty a x a i (zleva doprava)
cin >> x >> i;
// Tisk hodnoty promenne x a i (zleva doprava)
cout << "\nx = " << x << " i = " << i << '\n';
}
#include <stdio.h>
main()
{
float x;int i;
printf("Cti x, i\n");
// Ctení hodnoty x a i
scanf("%f %d",&x,&i);
// Tisk hodnoty x a i
printf("x = %f i = %d \n",x,i);
}
Řešení:
// Prepocet palcu na centimetry a obracene
#include <iostream.h>
main()
{
typedef float real;
/*Definice noveho typu (zde v C++ jsme standardní float
zamenili nestandardnim, ale v jinych jazycich obvyklym slovnim
symbolem real*/
const real PREPOCET=2.54; // Definice konstanty
real x,palec,cm;
char ch=0; //Definice promenne typu char s pocatecnim prirazenim
cout << "\nCti délku a co je zadano: p -- palce c -- cm\n";
cin >> x >> ch;
if(ch=='p') // Prepocet palcu na cm
{
palec=x;cm=PREPOCET*x;
}
else
if(ch=='c') // Prepocet centimetru
{
cm=x;palec=x/PREPOCET;
}
if(ch=='p'|| ch=='c')
cout << palec << "\" = " << cm << "cm\n";
else
cout << "\n Chybne zadani!!!\n";
}
Je-li hodnota
pravda různá od nuly, provede se příkaz, je-li rovna
nule
provede se jiný_příkaz. Příkaz i jiný_příkaz
musí končit středníkem a
příkaz i jiný_příkaz může být jeden příkaz,
nebo více příkazů - tzv. složený příkaz
- uzavřených do složených závorek. (Ty v jazyku C a C++
nahrazují slovní symboly begin a end známé z jiných
programovacích jazyků.) V podmínce if(ch=='p' || ch=='c')
se zjišťuje, je-li ch rovno () znaku p, nebo
(
|| disjunkce nebo-li
logický součet v jazyku C i C++) je-li rovno znaku c. Pozor na operátor rovná se
()
a operátor přiřaď ()
začátečníkům (a nejenom jim)
se někdy pletou. Je-li větev za else prázdná, může se
vynechat
(včetně
slovního symbolu else).
Řešení:
// Piseme jen vlastni algoritmus bez deklaraci, ktere se nemeni
//...
switch(ch)
{
case 'p':
palec=x;
cm=PREPOCET*x;
cout << palec << "\" = " << cm << "cm\n";
break;
case 'c':
cm=x;
palec=x/PREPOCET;
cout << palec << "\" = " << cm << "cm\n";
break;
default:
cout << "\n Chybne zadani!!!\n";
break;
}
// ...
Řešení:
// Vstup a vystup do souboru v C++
/* Program ocekava vstup do promenne ch a x, zapise vysledky do souboru
muj_s.dat, ktery predtim otevre. Po ukonceni zapisu (predpoklad cte
se pismeno k a x je libovolne) se soubor zavre pro vystup a otevre
pro vstup. Vypoctene hodnoty se prectou a vypisi na obrazovce.
Prepocitavaji se palce na cm a obracene */
#include <fstream.h>
#include <iomanip.h>
main()
{
const float prepocet=2.54;
float x=1,palec,cm; char ch=0;
// Vstup a zapis do souboru muj_s.dat
ofstream fout("muj_s.dat");
while(1)
{
ZNOVA:
cout << "\nZadejte, prosim, co se ma provadet:\n"
<< "p - zadana hodnota v palcich\n"
<< "c - zadana hodnota v cm\n"
<< "k - konec vypoctu\n"
<< " zadana hodnota\n";
cin >> ch >> x;
switch (ch){
case 'p':
palec=x; cm=x*prepocet; break;
case 'c':
cm=x; palec=x/prepocet; break;
case 'k':
goto KONEC;
default:
cout << "\n Chybny udaj zadava se c, p nebo k. Opakujte cteni\n";
x=0; ch='\0';
goto ZNOVA;
}
// Zapis do souboru
fout << palec << '\n';
fout << cm << '\n';
}
// Uzavreni souboru muj_s.dat pro zapis
KONEC: fout.close();
// Ctou se data ze souboru muj_s.dat
cout << "\n A nyni hodnoty: palec, cm vytisknu\n";
ifstream fin("muj_s.dat");
while(!fin.eof()) // ! je negace logickeho vyrazu
{
fin >> palec;
if(!fin.eof()){
fin >> cm;
cout << setw(6) << setprecision(2) << palec << "\" = " << cm
<< " cm\n";
}
}
// Uzavreni souboru muj_s.dat
fin.close();
cout << "Konec programu.\n";
}
C =
5
9
(F-32)
Stupne Fahrenheita Stupne Celsia
dddd ddd.dd
dddd ddd.dd
... ...
kde d je číslice, znaménko minus nebo prázdný znak.
Řešení:
/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius
pro fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
main()
{
// Definice konstant pro dolni a horni mez a krok zmeny
const int DOLNI=0,HORNI=300,KROK=20;
float fahr=DOLNI; // Definice promenne fahr s pocatecnim prirazenim;
float cels; /* Definice promenne, ktera bude nabyvat hodnot stupnu
Celsia */
// Tisk zahlavi tabulky
cout << " Stupne Fahrenheita Stupne Celsia\n";
/*Prikaz while, ktery se bude provadet dokud fahr bude mensi nebo
rovno 300*/
while(fahr<=HORNI)
{
cels=5./9.*(fahr-32);
cout << " " << setw(4) << fahr << " "
<< setw(5) << setprecision(2) << cels << '\n';
fahr=fahr+KROK;
}
}
/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius pro
fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
main()
{
// Definice konstant pro dolni a horni mez a krok zmeny
const int DOLNI=0,HORNI=300,KROK=20;
// Tisk zahlavi tabulky
cout << " Stupne Fahrenheita Stupne Celsia\n";
// Prikaz for
for(int fahr=DOLNI;fahr<=HORNI;fahr=fahr+KROK)
{
cout << " " << setw(4) << fahr << " "
<< setw(6) << setprecision(2) << 5./9.*(fahr-32) << '\n';
}
}
//...
fahr=DOLNI;
while(fahr<=HORNI)
{
cout ...;
fahr=fahr+KROK;
}
//...
//...
int fahr;
//...
for(fahr=DOLNI;fahr<=HORNI;fahr=fahr+KROK){//...
//...
int fahr=DOLNI;
//...
for(;fahr<=HORNI;fahr=fahr+KROK)
/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius pro
fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
main()
{
// Definice konstant pro dolni a horni mez a krok zmeny
const int DOLNI=0,HORNI=300,KROK=20;
int fahr=DOLNI; //Definice promenne fahr s pocatecnim prirazenim
// Tisk zahlavi tabulky
cout << " Stupne Fahrenheita Stupne Celsia\n";
// Prikaz do-while
do
{
cout << " " << setw(4) << fahr << " "
<< setw(6) << setprecision(2) << 5./9.*(fahr-32) << '\n';
fahr=fahr+KROK;
}while(fahr<=HORNI);
}
3. Několik jednoduchých programů
3.1 Vstup a výstup po znacích
V knihovnách jazyka C++ existují funkce, které po vyvolání
přečtou nebo zapíší jeden znak. Uveďme některé:
mujprog < mujvst > mujvys
3.2 Kopírování souboru
Řešení:
#include<stdio.h>
/* Kopie ze standardniho vstupniho souboru na standardni vystupni
soubor*/
main()
{
int c;
c=getchar();
while(c!=EOF)
{
putchar(c);
c=getchar();
}
}
#include <stdio.h>
main() // Kopirovani vstupu na vystup strucneji
{
int c;
while((c=getchar())!=EOF)
{
putchar(c);
}
}
Program nejprve v příkazu while přiřadí proměnné c hodnotu
a pak se testuje, je-li tato hodnota různá od EOF. Je-li
různá, provede se příkaz putchar, není-li různá, cyklus
a program se ukončí. Protože operátor přiřazení (=) má
nižší prioritu
než operátor (!=), musí být přiřazovací
příkaz uzávorkován. V jazyku C
a C++ je mnoho možností, jak psát stručné programy.
Necháme-li se však touto vlastností , může se nám
stát, že budeme psát zcela nesrozumitelné programy, kterým, po
čase, nebudeme rozumět ani my sami.
3.3 Počítání znaků
Řešení:
#include<stdio.h>
main()// Pocitani znaku ve vstupnim textu
{
long nc=0;
while(getchar()!=EOF) ++nc;
printf("Pocet znaku ve vstupnim textu = %ld\n",nc);
}
3.4 Počítání řádků
Řešení:
#include <stdio.h>
main() // Pocitani radku
{
int zn,nr=0; // zn - cteny znak, nr - pocet radku
FILE *ms; // Definice ukazatele typu FILE
ms=fopen("muj_sou","r"); // Otevreni souboru
while((zn=getc(ms))!=EOF)
if(zn=='\n') ++nr;
printf("Pocet radku= %d\n",nr);
fclose(ms); /* Zavreni souboru. V tomto pripade nepovinne. Po ukonceni
programu se vsechny otevrene soubory automaticky uzavrou
*/
}
3.5 Počítání slov
Řešení:
#include<stdio.h>
main() // Pocitani slov, radek a znaku v souboru msbr
{
typedef int BOOL;
FILE *sb=fopen("msbr","r"); // Otevreni souboru msbr
const BOOL ANO=1,NE=0; // Definice logickych konstant
int zn,nz,nr,ns; /* zn - cteny znak, nz - pocet znaku, nr - pocet
radku, ns - pocet slov */
BOOL ve_slove; /* ve_slove je logicka (Booleovska) promenna, nabyvajici
logickych hodnot ANO a NE */
ve_slove=NE; // A priorni predpoklad: nejsem uvnitr slova;
nz=nr=ns=0; // Vynulovani hodnot promennych
while((zn=getc(sb))!=EOF)
{
++nz; // Opakovani: ++nz je totozne s nz=nz+1;
if(zn=='\n')++nr;
if(zn==' ' || zn=='\t' || zn=='\n')
ve_slove=NE;
else
if(ve_slove==NE)
{
ve_slove=ANO; ++ns;
}
}
printf("\nV souboru je:\n \n pocet znaku = %d,\n pocet radku = %d,\n",
" pocet slov = %d\n",nz,nr,ns);
}
nz = nr = ns = 0;
nz = (nr = (ns = 0));
4. Pole
Řešení:
#include<stdio.h>
#include<conio.h>// Soubor pro ovládání obrazovky v systému TURBO C++
main() // Pocitani cislic a ostatnich znaku
{
int cis[10],bily_zn=0,ost_zn=0,zn;
FILE *ms;
ms=fopen("vzorek.dat","r");
clrscr(); // Mazani obrazovky v systému TURBO C++
for(int i=0;i<10;++i) cis[i]=0; // Nulovani prvku pole
// A vlastni realizace algoritmu
while((zn=getc(ms))!=EOF)
/* Je-li zn >= '0'a zaroven jeli zn <= '9' (operator &&),
vypocte se hodnota indexu: c - '0' a k prislusnemu prvku
se pricte jednicka */
if(zn>='0' && zn<='9') ++cis[zn-'0'];
else
if(zn==' ' || zn=='\t' || zn=='\n') ++bily_zn;
else ++ost_zn;
// Tisk vysledku
printf("\nCislice Pocet cislic\n\n");
for(i=0;i<10;++i) printf(" %d %d\n",i,
cis[i]);
printf("\n\nPocet: bilych znaku: %d a ostatnich %d",bily_zn,
ost_zn);
}
Řešení:
#include<stdio.h>
main()
{
FILE *vz=fopen("vzorek.dat","r"); //Otevreni souboru pro cteni
const int m=5,n=5;
int a[m][n],b[m][n],c[m][n];// Definice matic typu(m,n)
int ms,ns; // Definice skutecneho poctu radku (ms) a sloupcu (ns)
fscanf(vz," %d %d",&ms,&ns); // Cteni hodnot ms, ns
for(int i=0;i<ms;++i) // Cyklus i s definici parametru cyklu i
for(int j=0;j<ns;++j) // Cyklus j s definici parametru cyklu j
fscanf(vz,"%d ",&a[i][j]); // Cteni prvku matice A;
for(i=0;i<ms;++i)
for(j=0;j<ns;++j)
fscanf(vz,"%d ",&b[i][j]); // Cteni prvku matice B
for(i=0;i<ms;++i)
for(j=0;j<ns;++j)
c[i][j]=a[i][j]+b[i][j]; // Vypocet souctu (matice C)
fclose(vz); vz=fopen("vzorek.dat","w");
fprintf(vz,"Tisk souctu matic: C = A + B\n\nMatice A:"
"\n=========\n");
for(i=0;i<ms;++i) // Tisk matice A
{
fprintf(vz,"\n");
for(j=0;j<ns;++j)
fprintf(vz,"%d ",a[i][j]);
}// Konec Tisku matice A
fprintf(vz,"\n\nMatice B:\n=========\n");
for(i=0;i<ms;++i) // Tisk matice B
{
fprintf(vz,"\n");
for(j=0;j<ns;++j)
fprintf(vz,"%d ",b[i][j]);
} // Konec tisku matice B
fprintf(vz,"\n\nMatice C:\n=========\n");
for(i=0;i<ms;++i) // Tisk matice C
{
fprintf(vz,"\n");
for(j=0;j<ns;++j)
fprintf(vz,"%d ",c[i][j]);
} // Konec tisku matice C
}
5. Funkce
Řešení:
#include<stdio.h>
main() //Testovani funkce umocneni
{
int umoc(int,int); /* Toto je tzv. prototyp, ktery je v C++
povinny nepredchazi-li nahodou popis funkce jednotce, ve kte-
re je pouzivana. V prototypu je receno, jakeho je funkce typu
a kolik a jakeho typu jsou parametry. */
for(int i=0;i<=10;++i)
printf("%d na %d = %d\n",2,i,umoc(2,i));
}
int umoc(int m,int n) // Funkce umocnovani m na n
{
int p=1; //Pocatecni prirazeni hodnoty pomocne (!!!) promenne p
for(int i=1;i<=n;++i)p=p*m; //Primitivni zpusob vypoctu mocniny
return p; // Vraceni hodnoty funkce (mozny je i zapis return(p))
}
c++ ukazka.cc -o uk
c++ -c hlavni.cc funkce.cc
c++ hlavni.o funkce.o -o uk
c++ hlavni.cc funkce.cc -o uk
// Vyuziti vlastnosti volani hodnotou
float mocnina (float x,int n)
{
for (float p=1;n>0;n--) /* parametr n se meni, ale jemu
odpovidajici skutecny parametr zůstane beze změny */
p=p*x;
return p;
}
Řešení:
pokud (existuje další řádek)
/* hrad.cpp - Cteni radku ze souboru ms a tisk nejdelsiho z nich.
Program testuje soubor funkci frad.cpp */
#include <stdio.h>;
int getline(char[],int); /* Prototyp funkce getline. Informace
v nem obsazena rika, ze funkce vraci hodnotu typu int a ma dva
parametry: jeden parametr je vektor - do nej se ukladaji znaky
cteneho radku, druhy parametr je celociselna hodnota urcujici
maximalni pripustnou delku retezce. Funkce mohla byt tez zapsa-
na: getline(char[],int); protoze v takovem pripade se
automaticky rozumi, ze funkce vraci hodnotu typu int. Jsme-li
priznivci uplne informace, lze vsak maximalisticky psat
int getline(char s[],int m_del_r) */
void copy(char s1[],char s2[]); /* Prototyp funkce copy. Funkce kopiruje
hodnoty prvku vektoru s1 do prislusnych prvku vektoru s2. Mohli jsme
tez psat void copy(chr[],char[]).Slovni symbol void sdeluje kom-
pilatoru, ze se jedna o funkci, ktera nevraci hodnotu. V jinych pro-
gramovacich jazycich se takove programove jednotce rika podprogram
nebo vlastni procedura. Slovni symbol je povinny, nenapiseme-li ho,
jsme kompilatorem varovani, ze funkce ma vratit hodnotu typu int a
nevraci nic */
FILE *ms; /* Definice tzv. externi promenne, ktera se uziva v ruznych
funkcich jednoho nebo vice programovych souboru */
main()
{
const int MAXDEL=100;// Maximalni delka radku
int delka, // bezneho radku
max; // delka nejdelsiho radku
char radek[MAXDEL], // bezny radek
mradek[MAXDEL]; // nejdelsi radek;
ms=fopen("ms","r"); // otevreni souboru ms pro cteni a zapis
max=0;
while ((delka=getline(radek,MAXDEL))>0) /* getline vraci delku radku
a vektoru radek priradi retezec znaku radku */
if(delka>max) // Je-li cteni radek zatim nejdelsi z ctenych, pak
max=delka;
copy(radek,mradek); // Zapis radek do mradek
fclose(ms); // Uzavreni souboru pro cteni
ms=fopen("ms","w"); // Otevreni souboru pro zapis
if(max>0) //Cetlo se vubec neco?
// Tisk radku do souboru ms
fprintf(ms,"Radek %s\nje nejdelsi a ma delku %d\n",mradek,max);
}
// Soubor frad.cpp
#include<stdio.h>;
int i; // Externi promenna, ktera se smi uzivat v celem souboru frad.cpp
getline(char s[],int lim) // Cteni radku s maximalni delkou lim
{
extern FILE *ms; /* Deklarace (ne definice) externi promenne definovane
v souboru hrad.cpp (viz pozn. na konci odst. 1.5.1) */
char c;
/* V prikazu for je uzita operace konjunkce (tzv. logickeho
soucinu) znama z vyrokove logiky. (V C++ se znaci &&) */
for(i=0;i<lim-1&&(c=getc(ms))!=EOF&&c!='\n';i++)
s[i]=c;
if(c=='\n'){
s[i]='\n';
++i;
}
s[i]='\0'; //Oznaceni konce retezce v C++
return i;
}
void copy(char s1[],char s2[]) // Kopirovani s1 do s2
{
i=0;
while((s2[i]=s1[i])!='\0')
i++;
}
Poznámky: Pro úsporu místa bylo snahou vše vysvětlit
poznámkami přímo v programu. Zde jen tři vysvětlení:
5.1 Automatické a externí proměnné
K odkazu Proměnné ve funkci main, tj. např. delka,
max, radek, mradek jsou lokální v
main. Žádná funkce k nim nemá přístup. Totéž platí o jakékoliv
programové jednotce v níž je nějaká lokální proměnná definovaná.
Lokální proměnná začne existovat až po vyvolání funkce a její
hodnota se ztrácí po ukončení činnosti příslušné funkce.
V jazycích C a C++ se těmto proměnným říká
automatické proměnné. Při vstupu
do programové jednotky jim musí být vždy znovu přiřazena
hodnota, protože jinak by obsahovaly hodnotu náhodnou. Mohou se
inicializovat, tj.
může se jim přiřadit počáteční hodnota ihned
při definici. Pak se při každém vstupu do funkce inicializují
znovu. V jazyku C se automatická pole inicializovat
nesmějí (mají při vstupu do funkce vždy hodnotu nula), avšak v jazyku
C++ toto omezení neplatí.
Proměnné, které chceme používat ve více funkcích, a nechceme-li
nebo nemůžeme-li použít mechanismu formální - skutečný parametr,
zavedeme jako tzv. externí proměnné.
Tyto proměnné jsou definovány globálně pro celý soubor funkcí a
jsou dostupné pro všechny funkce souboru, .
Externí proměnné se musí definovat mimo funkce a deklarovat
buď explicitně deklarací extern,
nebo implicitně svým použitím v příslušné funkci.
// soubor1.cpp
int sp=0; // Definice externi promenne s inicializaci
float val[1000]; // Definice externiho pole bez inicializace
void main()
{
extern sp; // Nepovinna deklarace
// ...
}
funkce()
{
extern float val[]; // Nepovinna deklarace
// soubor2.cpp
extern sp; // Povinna deklarace
extern float val[]; //Povinna deklarace
/* Toto byly povinne deklarace externich promennych. Pak se mohou
pouzivat ve vsech funkcich souboru. Mohli jsem je ale tez
deklarovat jen ve funkcich, ktere dane promenne pouzivaji */
// ...
K odkazu
Obsah
5.2 Rekurzívní funkce
O funkci říkáme, že je rekurzívní, volá-li sama sebe ještě před
dokončením svého algoritmu. Známým a jednoduchým příkladem
rekurzívní funkce je výpočet faktoriálu.
long fakt(int n)
{
if(n==0||n==1) return(1);
else return (n*fakt(n-1));
}
long fakt(int n)
{
return (n==0||n==1)?1:n*fakt(n-1);//viz
}
6. Závěrečné poznámky k 1. kapitole
Po ukončení 1. kapitoly jsme schopni psát i dosti
komplikované programy. Nejdůležitější skutečnosti, které vyžaduje
zápis programu v C++, byly v této kapitole uvedeny; zde
uvedeme jeden nový poznatek, jeden upřesňující poznatek a jedno
důležité opakování.
7. Cvičení
členi = x/ičleni-1
a není
tedy nutné počítat ani
mocninu
ani faktoriál.
mocnina(int x,int n)
{
int v=1;
while (n)
{
while(n/2*2==n)
{
n /= 2; x *= x;
}
n--;v *= x;
}
return v;
}
Kapitola 2
V této kapitole nebudeme opakovat již probrané objekty, jen si
řekneme některé podrobnosti.
Typy, operátory, výrazy
1. Jména, typy, konstanty
1.1 Jména
1.2 Typy
V jazyku C++ jsou následující základní standardní
typy:
char, int, unsigned, float a double,
kterými definujeme proměnné, které mohou po řadě nabývat hodnoty:
znak, celočíselná hodnota, celočíselná hodnota bez znaménka, reálná
hodnota a reálná hodnota s dvojnásobnou přesností. Celočíselná
proměnná může být označena kvalifikátorem short nebo
long. Definice nebo deklarace: short int i a short
i jsou totožné; právě tak long j a long int j.
Dlouhé a krátké hodnoty můžeme definovat i pro typ
unsigned: např. short unsigned u a long unsigned u
(nebo unsigned short, unsigned long). Je možné také
psát unsigned char nebo char unsigned.
Dlouhá forma je možná i u proměnných typu double: např.
long double ld. Můžeme odvozovat další typy např. struktury
(viz), uniony (viz)a třídy (viz).
Řešení:
#include<iostream.h>
main()
{
enum BARVA{cervena,zluta,zelena}; // Deklarace typu BARVA
BARVA semafor; // Definice promenne semafor typu BARVA
cin >> semafor;/* Cteni hodnoty. Neni-li receno jinak,
plati cervena == 0, zluta == 1 a zelena == 2 */
switch(semafor)
{
case cervena: cout << "STUJ\n";break;
case zluta: cout << "PRIPRAV SE\n";break;
case zelena: cout << "VOLNO\n";break;
}
}
zajistí, že položka po == 1, ut == 2, st == 3 atd.
Zápis
zajistí, že ut == pa == 1, so == 12, ne == 13
.
1.3 Konstanty
-123 -123 10-4 1e-4
2,345 2.345 x 'x'
-0,5 -.5 (77)8 077
1,25 1.2e5 (1A)16 0x1a
definovali jsme novou stránku (pracujeme-li v kódu ASCII).
2. Operátory
2.1 Aritmetické operátory
Řešení:
Rok je přestupný, je-li letopočet
dělitelný čtyřmi a zároveň není dělitelný stem. Rok je však vždy
přestupný, je-li letopočet dělitelný čtyřmi sty.
//...
if(rok%4 == 0 && rok%100 != 0 || rok%400 == 0)
cout << "Rok je prestupny \n";
else
cout << "Rok neni prestupny";
//...
\
2.Operátory inkrementace a dekrementace
Řešení:
// Funkce vymaze znaky zn z retezce r
void vymaz(char r[],char zn)
{
int i,j;
for(i=j=0;r[i]!='\0';i++)
if(r[i]!=zn)
r[j++]=r[i]; // !!!!
r[j]='\0';
}
2.2 Přiřazovací operátory
i = i + 2; x = x * 2; z = z / y;
tyto příkazy však v C++ můžeme psát stručněji, takto:
i += 2; x *= 2; z /= y;
Těchto operátorů je celá řada, kromě uvedených např. ještě
%=.
Zbylé najde čtenář v článku , který pojednává
o prioritách operací v C++. Důležité je ovšem vědět, že tyto
operace mají velmi nízkou prioritu (stejnou jako přiřazovací
příkaz), a proto např.
x *= y + 1;
znamená totéž jako příkaz
x = x * (y + 1)
Těmito příkazy nejen zkracujeme zápis, ale můžeme i optimalizovat
výpočet, protože
výraz_1 výraz_1 op výraz_2;
je totožný se zápisem
výraz_1 op= výraz_2;
ale výraz_1 se vyhodnocuje jen jednou. Pak
ovšem příkaz
yypro[yypv[p3 + p4]+yypg[p1 + p2]] += 2;
vyskytuje-li se v cyklu, je nejen stručný, ale
též šetří strojový čas.
2.3 Podmíněný výraz
if(a > b) z = a; else z = b;
můžeme zapsat stručněji
z = a > b ? a : b;
nebo (jak je zvykem) méně stručně, ale přehledněji
z = (a > b) ? a : b;
Příklad 2.4. Sestavme program, který tiskne
n prvků pole tak, že na řádku je 10 prvků. Každý prvek je oddělen
od druhého mezerou a každý řádek (i poslední) je ukončen právě
jedním znakem . Pole a jeho velikost
definujme jako externí proměnné.
Řešení:
#include<stdio.h>
int a[15]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},n=15;
main()
{
for (int i=0;i<n;i++)
printf("%d%c",a[i],(i%10==9 || i==n-1) ? '\n' : ' ');
}
#include<iostream.h>
int a[15]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},n=15;
main()
{
for (int i=0;i<n;i++)
cout << a[i] << ((i%10==9 || i==n-1) ? '\n' : ' ');
}
(i%10==9||i==n-1) ? '\n' : ' '
uzavřen do okrouhlých závorek, protože jinak
systém reaguje chybně (viz).
2.4 Relační a logické operátory
> >= < <=
Všechny mají tutéž prioritu
(viz).
O stupeň nižší
prioritu mají operátory rovnosti a nerovnosti
== !=jejichž priority se také rovnají.
& operátor logického součinu po bitech
| operátor logického součtu po bitech
^ operátor nonekvivalence po bitech
<< operátor posunu vlevo
>> operátor posunu vpravo
~ unární operátor jednotkového doplňku
3. Typová konverze
unsigned int na long
long na unsigned long
unsigned long na float
float na double
double na long double
Příklady:
zn += 32; hodnota zn se zvětší o 32, tj. na 97, což je
znak a
zn -= '2'; hodnota zn se zmenší o 50, tj. na 47, což
je znak /
k = 'Z' + 32; k se přiřadí hodnota 122
k = 7.9876 k se přiřadí hodnota 7 (!!!)
k = d * zn;
Příklady a význam některých používaných konverzí:
(char) int_výraz převod celého čísla na jeho
znakový ekvivalent
(int) float_výraz odříznutí desetinné části
(double) float_výraz zvětšení přesnosti
//...
int i; double d;
//...
d=exp((double)i);
//...
//...
int i; double d;
//...
d=exp(double(i)); // double (i) je explicitni typova konverze
//...
//...
int i; double d;
//...
d=exp(i);
//...
3.1 Priority operací
Závěrem této kapitoly si uvedeme priority operací. Uvedeme
všechny operátory s tím, že nám ještě neznámé probereme později.
Tabulka priorit operací pro jazyk C++:
Operátor
::
() [ ] -> .
sizeof
! ~
++ -- +
- (
typ) * & new
delete
* / %
+ -
<< >>
< <= >=
>
= = ! =
&
^
|
&&
||
? :
= + = - = * = / =
% = >>= <<=
& = / = ^=
,
Poznamky:
K odkazu 1
++n;
printf("%d %d\n",n,power(z,n));
K odkazu 2
Obsah
Kapitola 3
Podrobnosti k předchozím kapitolám
1. Příkazy a bloky
...; if(a>b) max=a; else max=b;
Řešení:
#include<stdio.h>
main()
{
int a,b,c; // Lokalizovane promenne v programu
a=10;
b1:{int i,j; // Lokalizovane promenne v bloku b1
i=j=5;
c=i*j*a;
printf("Tisk v b1: c = %3d\n",c);
}
b=c*a;
b2:{float b,c,d; // Lokalizovane promenne v bloku b2
d=c=2.5;
b=c+4;
b3:{int p; // Lokalizovana promenna v bloku b3
p=5; d=p+b;
b=10*p+b;
printf("Tisk v b3: d = %4.1f b = %4.1f\n",d,b);
}
a=100+a; printf("Tisk v b2: a = %3d\n",a);
}
printf("Tisk v programu: a = %3d b = %4d c = %3d\n",a,b,c);
}
Bloky b1, b2, b3 jsou
podřazené hlavnímu programu. Hlavní program a blok b2 jsou
nadřazené bloku b3.
Tisk v b1: c = 250
Tisk v b3: d = 11.5 b = 56.5
Tisk v b2: a = 110
Tisk v programu: a = 110 b = 2500 c = 250
Za koncovými složenými závorkami složených příkazů a bloků se
nemusí dělat středníky.
2. Operátor ::
Operátor :: se v jazyku C++ užívá k řešení konfliktů
v přístupových právech k objektům. Máme-li např. automatickou
(lokální) proměnnou vekt_sum v nějaké funkci a potřebujeme-li
užít externí (globální) proměnnou, která se jmenuje též
vekt_sum, zapíšeme:
::vekt_sum, čímž jsme programu umožnili tuto externí
proměnnou použít.
#include<stdio.h>
int c; // Externi promenna
main()
{
int a,b,c; // Lokalizovane promenne v programu
a=10;
::c=1000; // Pracuje se s externi promennou
b1:{int i,j; // Lokalizovane promenne v bloku b1
i=j=5;
c=i*j*a; // Pracuje se s automatickou promennou
printf("Tisk v b1: c = %3d\n",c);
}
b=c*a;
b2:{float b,c,d; // Lokalizovane promenne v bloku b2
d=c=2.5;
b= c+4;
b3:{int p; // Lokalizovana promenna v bloku b3
p=5; d=p+b;
b=10*p+b;
printf("Tisk v b3: d = %4.1f b = %4.1f\n",d,b);
printf("Tisk zdanlive nepristupne promenne c = %d\n",::c);
}
a=100+a; printf("Tisk v b2: a = %3d\n",a);
}
printf("Tisk v programu: a = %3d b = %4d c = %3d\n",a,b,c);
}
bude se v bloku tisknout:
Tisk v b1: c = 250
Tisk v b3: d = 11.5 b = 56.5
Tisk zdanlive nepristupne promenne c = 1000
Tisk v b2: a = 110
Tisk v programu: a = 110 b = 2500 c = 250
Je nutno upozornit, že operátorem ::
lze zpřístupnit jen globální
proměnné, které jsou externí a statické (viz ), nikoliv
automatické proměnné, které jsou globální z hlediska blokové
struktury. Nedůvěřivý čtenář se může v původním programu
z příkladu 3.1 pokusit zpřístupnit proměnnou c ze záhlaví
hlavního programu v bloku b3. Uvidí, že neuspěje. Toto
nebývá v učebnicích jazyka C++ příliš zdůrazňováno.
3. Operátor čárka
Dvojice výrazů oddělených čárkou se vyhodnocuje zleva doprava
a výsledek má hodnotu pravého operandu. Čárky mezi parametry
nemají význam operátoru.
Řešení:
// Funkce obrati retezec: tedy napr. "abcd" zmeni na "dcba"
void obrret(char s[])
{
int c,i,j;
for(i=0,j=strlen(s)-1;i<j;i++,j--)
{ c=s[i];s[i]=s[j];s[j]=c;}
}
4. Příkaz break
Příkaz break jsme již použili ve spojitosti s příkazem
switch (přepínač), kdy jsme jednotlivé alternativy
ukončovali právě příkazem break. Další využití tohoto
příkazu je v ukončení příkazu cyklu, ve kterém je zapsán.
Řešení:
main() // Vynechani koncovych mezer, tabelatoru a novych radku
{
const int MAXLINE = 1000;
int n; char radka[MAXLINE];
while((n=getline(radka,MAXLINE))>0)
{
while(--n > 0)
if (radka[n] != ' ' && radka[n] != '\t' && radka[n] != '\n')
break;
radka[n+1]='\0';
cout << radka;
}
}
5. Příkaz continue
//... tato část programu vynechá záporné prvky
for(i=0;i<n;i++)
{
if(a[i]<0) continue // Vynechaji se vsechny prikazy do konce cyklu
//Zpracovani kladnych prvku
//...
}
V příkladu se zpracovávají v cyklu i pouze kladné prvky
podle nějakého algoritmu. Narazí-li na záporný prvek příkaz
continue zajistí, že se algoritmus neprovede i se
(v tomto příkladu) zvětší o jedničku a provádí se další krok
cyklu.
6. Registrové proměnné
Jazyk C i jazyk C++ umožňuje pracovat s tzv.
registrovými proměnnými. Počítač je
umístí v registrech počítače, takže přístup k nim se značně
zrychlí.
f(register int c, register int n}
{
register int m;
// ...
}
Řešení:
#include <stdio.h>
void nasob(register float k)
{
for (register j=11;j<=20;j++)
printf("%d * %f = %f\n",j,k,j*k);
}
V příkladu je použit formální parametr typu register float
a proměnná j, která je typu register int. Pro
celočíselnou proměnnou je možné slovní symbol int vynechat.
7. Cvičení
Kapitola 4
Ukazatel (též se užívá termínu spoj) je
proměnná (nebo symbolická konstanta) jejíž hodnotou je adresa
jiného objektu (proměnné, pole apod.) Pro zavedení těchto
lze např. uvést tyto důvody:
Ukazatelé, pole, funkce
1. Ukazatelé a adresy
Předpokládejme, že v programu jsou zapsány následující definice
a příkazy
//...
int *px,x,y,z,*py;
//...
px=&x;
//...
y=*px;
z=*px+1;
printf("x = %d odmocnina z x = %lf",sqrt((double)*px);
py=px;
//...
1.1 Ukazatelé a argumenty funkcí
void zamen(int *x,int *y)
{
int t=*x;*x=*y;*y=t;
}
main() {/*...*/zamen(&a,&b);/*...*/}
void zamen(int &x,int &y)
{
int t=x;x=y;y=t;
}
main() {/*...*/zamen(a,b);/*...*/}
void zamen(int x,int y)
{
int t=x;x=y;y=t;
}
main() {/*...*/zamen(a,b);/*...*/}
2. Ukazatelé a pole
Z čl. 4 víme, že např. při definici int a[10] se
v paměti vyhradí deset paměťových míst pro prvky pole indexované
od 0 do 9. Napíšeme-li v programu int *pa =
&a[0], pak pa ukazuje na místo, kde je umístěno
a[0]. (Pozor! Toto počáteční přiřazení je poněkud matoucí;
srovnejte se zápisem pa = &a[0], které má tentýž význam a které
je v intencích
mnemotechniky: bez hvězdičky adresa a s hvězdičkou hodnota, která
je na příslušné adrese uložena. Tato mnemotechnika většinou
platí.)
Funkci delret jsme již použili dříve jako knihovní funkci
strlen. Nyní vidíme, že její naprogramování je velmi
prosté.
delret(char s[])
{
for(int n=0;s[n]!='\0';n++);
return(n);
}
delret(char *s)
{
for(int n=0;*s!='\0';s++,n++);
return(n);
}
void zkoret(char t[],char s[])
{
int i=0;
while((s[i]=t[i])!='\0')i++;
}
void zkoret(char *t,char *s)
{
while((*s=*t)!='\0'){s++;t++;}
}
void zkoret(char *t,char *s)
{
while((*s++=*t++)!='\0');
}
void zkoret(char *t, char *s)
{
while(*s++=*t++);
}
3. Ukazatelé a konstanty
#include<stdio.h>
main()
{
// Definice ukazatele, ktery ukazuje na konstantu
const char *name ="PROKOP";
// name[0]='B'; // Chybny prikaz
name="BUBEN"; // Spravny prikaz
// Definice konstantniho ukazatele
char *const JMENO="hana";
JMENO[0]='d'; // Spravny prikaz
// JMENO=dana; //Chybny prikaz
// Definice konstantniho ukazatele ukazujiciho na konstantu
const char *const KONST="NAZDAR";
// KONST[0]='A'; // Chybne
// KONST="AHOJ; // Take chybne
printf("%s %s %s\n",name,JMENO,KONST);
}
4. Argumenty povelového řádku
Řešení:
#include<stdio.h>
// Nazev programu echo.cpp
main(int argc, char *argv[])
{
for(int i=1;i<argc;i++)
printf("%s%c",argv[i],(i<argc-1)?' ':'\n');
}
#include<stdio.h>
// Nazev programu ozvena.cpp
main(int argc, char *argv[]) // Ozvena jinak
{
while(--argc>0) //Staci while(--argc)
printf("%s%c",*++argv,(argc>1)?' ':'\n');
}
Řešení:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "integral.cpp"
#include "int.cpp"
// Jméno programu hlavint.cpp
// Program pracuje s externi funkci integral.cpp
// (Simpsonova metoda) a integrovanou funkci int.cpp
main(int pocet_parametru, char *pole_ukazatelu_na_parametry[])
{
int i,m,j;double g(double),integral(double,double,int,FUK);
if(pocet_parametru==3){
j=atoi(*++pole_ukazatelu_na_parametry); /* Funkce prevadejici retezec
znaku ulozenych v pameti na celociselnou hodnotu (soubor stdlib.h)*/
m=atoi(*++pole_ukazatelu_na_parametry);
for(i=1;i<=j;i++)
printf("Deleni intervalu: %d integral = %f\n",m*i,
integral(0.,1.5,m*i,g));
}
else
printf("Zadej: hlavint pocet_vypoctu zakladni_deleni");
return 0;
}
// Integrovaná funkce
double g(double x)
{
if (x==0)
return(1);
else
return(sin(x)/x);
}
// Simpsonova metoda
typedef double(*FUK)(double);
double integral(double a, double b, int n, FUK f)
// FUK f je totez jako bychom psali: double(*f)(double))
{
double s; double h=(b-a)/n; int k,i;
k=1;s=f(a)+f(b);
/*Toto je strucnejsi,hezci a rozumnejsi zapis nez
s=(*f)(a)+(*f)(b)*/
for(i=1;i<n;i++){
s+=(k+3)*f(a+i*h);
k=-k;
}
return(h/3*s);
}
typedef int (*UNP3C)[3]; UNP3C ukp;//...
typedef int *FVUNC(int);FVUNC f1;
5. Inicializace polí a polí ukazatelů
Příklady:
int y[4][3] = {{1,3,5},
{2,4,6},
{8,7,9}},
};
#include<stdio.h>
#include<conio.h>
char *jmeno_mesice(int);
main()
{
char t[]=" Jmeno mesice je %s\n";
clrscr();
for(int i=0;i<=13;i++)
printf(t,jmeno_mesice(i));
}
char *jmeno_mesice(int n)
{
static char *jmeno[]={"Nespravne zadani","leden","unor","brezen",
"duben","kveten","cerven","cervenec","srpen",
"zari","rijen","listopad","prosinec"};
return ( n < 1 || n > 12 ? jmeno[0] : jmeno[n]);
}
6. Ukazatelé a vícerozměrná pole
Vysvětleme si rozdíl mezi dvojrozměrným polem a polem ukazatelů.
Budeme-li mít definice
int a[20][20], *b[20];
#include <iostream.h>
#include<conio.h>
main()
{
int a[10][10],*b[10],i,j;
clrscr();
for(i=0;i<10;i++)
for(j=0;j<10;j++)
a[i][j]=i*j;
/* Ted se muze cyklem priradit &a[i][0] nebo (a+i)[0] popr.
strucneji a mene prehledne a[i] nebo *(a+i) */
for(i=0;i<10;i++)b[i]=&a[i][0]
// nebo b[i]=(a+i)[0] protoze obe je pripustne
cout<< " Tisk a[5,9] = "<< a[5][9] << " Tisk b[5,9] = "
<< b[5][9] << '\n';
cout<< " Tisk a[5,9] = "<< a[5][9] << " Tisk b[5,9] = "
<< (*(b+5))[9] << '\n';
}
Řešení:
/***********************************************************************
* Ukazka prace s maticemi v jazyku C++, bez uvazovani dynamickych ob- *
* jektu, ale prakticky s dynamickou alokaci dvojrozmernych poli na uro-*
* vni (zhruba) jazyka Fortran *
***********************************************************************/
#include<stdio.h>
void scmat(int *a[],int *b[],int *c[],int m,int n)
{
for(int i=0;i<m;i++)
for (int j=0;j<n;j++)
c[i][j]=a[i][j]+b[i][j];
}
void tisk(int *b[],int m,int n)
{
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
printf((j==n-1||j==9)?"%d\n"
: "%d ",b[i][j]);
}
void cti(int *b[],int m,int n)
{
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
scanf("%d",&b[i][j]);
}
main()
{
const m=10,n=20;
int a[m][n],b[m][n],c[m][n]; int ns,ms;
int *pa[m],*pb[m],*pc[m];
// Prirazeni adres prvku prvniho sloupce matic ukazatelum pa, pb, pc
for(int i=0;i<m;i++){pa[i]=&a[i][0];pb[i]=&b[i][0];pc[i]=&c[i][0];}
printf("\nCti pocet radku matice: ");scanf("%d",&ms);
printf("\nCti pocet sloupcu matice: ");scanf("%d",&ns);
cti(pa,ms,ns);
tisk(pa,ms,ns);
cti(pb,ms,ns);
tisk(pb,ms,ns);
scmat(pa,pb,pc,ms,ns);
tisk(pc,ms,ns);
}
7. Ještě několik podrobností o funkcích
7.1 Vnitřní funkce
7.2 Dosazené parametry
void default_f(int prvni,float druhy=2.345,char treti='t',
char *ctvrty="naprava")
kdežto zápis
void default_f(int prvni=1,float druhy,char treti='t',
char *ctvrty="naprava")
je chybný.
Aplikace dosazených parametrů je
v příkladu 4.5.
// Priklad na dosazene parametry
#include<stdio.h>
void default_f(int prvni,float druhy=2.345,char treti='t',
char *ctvrty="naprava")
{
printf("prvni = %d , druhy = %f , treti = %c , ctvrty = %s\n",prvni,
druhy,treti,ctvrty);
}
main()
{
default_f(32123);
}
7.3 Přetížené funkce
K odkazu
// Pretizene funkce
#include<stdio.h>
overload pr; /* Musi byt podle normy uvedeno, nektere systemy vsak
tuto deklaraci nevyzaduji*/
void pr(int ); //Prototyp
void pr(char *); //Prototyp
void pr(int *); //Prototyp
int cislice[10]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
main()
{
// a jeste jeden prototyp
void zobraz(const char *jmeno1,const char *jmeno2=" dosazen");
zobraz("Parametr1"); zobraz("Parametr2"," na obrazovce");
pr(7);pr("Ma ucta");pr(cislice);
}
void pr(int i)
{
printf("\nHodnota cisla je %d",i);
}
void zobraz(const char *jmeno1,const char *jmeno2)
{
printf("\n%s%s",jmeno1,jmeno2);
}
void pr(char *retez)
{
printf("\nHodnota retezce je %s",retez);
}
void pr(int *c)
{
printf("\n");
for(int i;i<10;i++) printf("%d",c[i]);
}
Čtenář se může přesvědčit, že program vytiskne:
Parametr1 dosazen
Prametr2 na obrazovce
Hodnota cisla je 7
Hodnota retezce je Ma ucta
0123456789
7.4 Funkce s nespecifikovaným počtem parametrů
U'itím tří teček v závorkách v záhlaví funkce
deklarujeme v C++, 'e funkce je definována pro
nespecifikovaný počet parametrů. Výklad této vlastnosti, která
umo'ní potlačit kontrolu typu parametru, ale hlavně umo'ní větší
flexibilitu při pou'ití funkce se vymyká těmto stručným skriptům.
Podrobnější výklad lze nalézt např. ve [8] na str. 242, 246
a 247 nebo ve [4]
8. Cvičení
Kapitola 5
V této kapitole se budeme zabývat strukturami,
což jsou množiny více
prvků i různého typu (narozdíl od polí, kde pole bylo množina
více prvků téhož typu), dále třídami,
což je zobecněný pojem
struktury v jazyku C++, které vedle toho, že mohou být
množinami prvků různých typů, mohou k sobě vázat různé algoritmy,
kterým se říká metody, jež pracují s prvky třídy, z nichž
některé
jsou přístupné jen těmto metodám. To je velice zjednodušeně
popsaná podstata pojmu třída, jejich důležitá role je v tzv.
objektově orientovaném programování, které sice přesahuje rozsah
těchto stručných skript, ale kterému věnujeme poslední kapitolu,
abychom se alespoň rámcově s tímto způsobem programování
seznámili. Typ union byl zřejmě převzat do jazyka C
z jazyka
Algol 68, kde se poprvé objevil.
Struktury, třídy, uniony, bitová pole
1. Struktury a třídy
K odkazu 1
K odkazu 2
Jak jsme se již zmínili, je struktura množinou prvků i různého
typu, které jsou z důvodu jednodušší manipulace sdružené pod
jedním jménem. Strukturám se někdy říká strukturované proměnné,
nebo, což je obvyklejší, záznamy, popř. věty. Prvkům struktury se
často říká položky.
Řešení:
Tečka v konstrukci je v tomto případě tzv. operátor
kvalifikace.
// Priklad na strukturu
#include<iostream.h>
#include<conio.h>
struct datum
{
int den;
int mesic;
int rok;
int cis_dne_v_roce;
char jmeno_mesice[10];
};
main()
{
datum d1={4,7,1621,185,"cervenec"};
clrscr();
cout << "Anno Domini " << d1.den << ". " << d1.mesic << ". "<< d1.rok
<<" byl 4. den v " << d1.jmeno_mesice << " a " << d1.cis_dne_v_roce
<< ". den v roce";
}
struct
{
int den;
int mesic;
int rok;
int cis_dne_v_roce;
char jmeno_mesice[10];
}d1;
// Priklad na trídu
#include<iostream.h>
#include<conio.h>
class datum
{
public:
int den;
int mesic;
int rok;
int cis_dne_v_roce;
char jmeno_mesice[10];
};
main()
{
datum d1={4,7,1621,185,"cervenec"};
clrscr();
cout << "Anno Domini " << d1.den << ". " << d1.mesic << ". "<< d1.rok
<<" byl 4. den v " << d1.jmeno_mesice << " a "
<< d1.cis_dne_v_roce << ". den v roce";
}
Řešení:
// Priklad na vnorene tridy (struktury)
#include<iostream.h>
#include<conio.h>
const int DELKA_JMENA=20, DELKA_ADRESY=20;
class datum // deklarace typu datum
{
public:
int den, mesic, rok;
};
class osoba // deklarace typu osoba (vyuziva vnorene tridy)
{
public:
char jmeno[DELKA_JMENA];
char adresa[DELKA_ADRESY];
long PSC;
long cislo_pojistky;
double mzda;
datum narozeni, zamestnan; // Vnorene tridy !!
};
main()
{
osoba zamestnanec={"Vorel Karel",
"Jecna 2, Praha 2",
12000, 924856787659, 75848, {26,11,1936},
{2,1,1962}};
cout << "Jmeno zamestnance:"<< zamestnanec.jmeno << '\n'
<< "Zamestnan od: " << zamestnanec.zamestnan.den << ". "
<< zamestnanec.zamestnan.mesic << ". " << zamestnanec.zamestnan.rok
<< '\n';
}
Řešení:
Úloha je příkladem použití proměnné typu
class nebo struct jako parametru ve funkci. V C++ lze skutečné a formální parametry propojovat třemi způsoby,
které jsme si
již ukázali dříve. V této úloze bychom mohli použít
volání hodnotou a výsledek (tj. ze zadaného data vypočítat
pořadové číslo dne v roce) by byl správný. Vhodnější je však
použít volání odkazem (pomocí operátoru * nebo &),
protože se vyhneme kopírování objektu, který může být rozsáhlý a
složitě členěný.
#include <iostream.h>
#include <stdio.h>
static int tab_dnu[2][13]=
{
{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31}
};
class datum{
public:
int den;
int mesic;
int rok;
} ;
den_v_roce(datum *d)
{
int i, den, prestupny;
den=d->den;
prestupny=d->rok%4 == 0 && d->rok%100 !=0 || d->rok%400 == 0;
for(i=1;i<d->mesic;i++)
den+=tab_dnu[prestupny][i];
return(den);
}
main()
{
datum k;
cout << "Cti den mesic rok\n";
scanf("%d %d %d",&k.den,&k.mesic,&k.rok);
cout << '\n' << k.den <<' ' << k.mesic << ' ' << k.rok << '\n' ;
cout << '\n';
cout << "Poradove cislo dne v roce: " << den_v_roce(&k) << '\n';
}
je totožný se zápisem
#include <iostream.h>
static int tab_dnu[2][13]=
{
{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31}
};
class datum{
public:
int den;
int mesic;
int rok;
} ;
den_v_roce(datum &d)
{
int i, den, prestupny;
den=d.den;
prestupny=d.rok%4 == 0 && d.rok%100 !=0 || d.rok%400 == 0;
for(i=1;i<d.mesic;i++)
den+=tab_dnu[prestupny][i];
return(den);
}
main()
{
datum k;
cout << "Cti den mesic rok\n";
cin >> k.den >> k.mesic >> k.rok;
cout << '\n' << k.den <<' ' << k.mesic << ' ' << k.rok << '\n' ;
cout << '\n';
cout << "Poradove cislo dne v roce: " << den_v_roce(k) << '\n';
}
K odkazu 1
K odkazu 2
Obsah
1.1 Pole struktur
Struktury jsou zvláště vhodné, pracujeme-li s poli proměnných,
které jsou svázány určitým vztahem. Uva'ujme např. program, který
počítá výskyt slovních symbolů jazyka C++ v nějakém programu.
Potřebujeme pole znakových řetězců jako vzory slovních symbolů a
pole celých čísel jako počítadla výskytu. Ke konkrétnímu slovnímu
symbolu musí patřit konkrétní počítadlo. Taková konstrukce by šla
realizovat dvěma poli, ale máme-li v jazyku k dispozici objekty
jako jsou struktury (a třídy), mů'eme obě pole realizovat např. typem
K odkazu
class sl_symb{
public:
char *slovo;
int poc;
};
static sl_symb tab[]={
{"auto",0},{"break",0},{"case",0},{"cdecl",0},{"char",0},
{"continue",0},{"default",0},
// ...
{"unsigned",0},{"void",0},{"volatile",0},{"while",0}
};
Řešení:
// Jmeno programu: poc_sl_s.cpp
const int LETTER = 'a';
const int DIGIT = '0';
#include <stdio.h>
#include <string.h>
#include <fstream.h>
#include <conio.h>
const int MAXWORD = 10;
class sl_symb{
public:
char *slovo;
int poc;
};
static sl_symb tab[]={
{"auto",0},{"break",0},{"case",0},{"cdecl",0},{"char",0},
{"continue",0},{"default",0},
{"do",0},{"double",0},{"else",0},{"enum",0},{"entry",0},
{"extern",0},{"far",0},
{"float",0},{"for",0},{"fortran",0},{"goto",0},
{"huge",0},{"if",0},{"int",0},{"long",0},{"near",0},
{"pascal",0},{"register",0},{"return",0},{"short",0},
{"signed",0},{"sizeof",0},
{"static",0},{"struct",},{"switch",0},{"typedef",0},
{"union",0},
{"unsigned",0},{"void",0},{"volatile",0},{"while",0}
};
const int NSLS = (sizeof(tab) / sizeof(sl_symb));
char getword(char*,int);int binary(char *, sl_symb [],int);
int type(int);
FILE *fd;
void main(int fg,char *nz[])
{
int a,b,pv=0;
char slovo[MAXWORD],ch[];
if(fg!=2)
{
cout << "\n Zadej: poc_sl_s jmeno_zkoumaneho_souboru\n";
return;
}
fd=fopen(*(++nz),"r");
clrscr();
if (fd==NULL)
cout<<"\n soubor "<<*nz<<" nelze otevrit";
else
{
cout<<"\n Zacinam pracovat"<<'\n';
cout<<'\n'<<'\t'<<"SL.SYMBOL"<< " "<<"CETNOST VYSKYTU"<<'\n';
while ((a=getword(slovo,MAXWORD))!=EOF)
if(a==LETTER){
if((b=binary(slovo,tab,NSLS))>=0)
tab[b].poc++;
}
for(b=0;b<NSLS;b++){
if(tab[b].poc>0){
if(++pv==20){
cout<<'\n'<<"\n ----- pokracovani -----";
gets( ch );
pv=0;
}
cout<<'\n'<<'\t'<<tab[b].slovo<<" "<<'\t'
<<tab[b].poc;
}
}
cout<<"\n Konec cinnosti ";
}
}
binary (char *slovo, sl_symb tab[],int n) //hledani v tabulce
{
int dolni,stred,horni,shoda;
dolni=0;horni=n-1;
while (dolni<=horni){
stred=(dolni+horni)/2;
if((shoda=strcmp(slovo,tab[stred].slovo))<0)
horni=stred-1;
else if (shoda>0)
dolni=stred+1;
else return(stred);
}
return(-1);
}
char getword (char *w,int lim) /*cteni dalsiho slova*/
{
int c,t;
if (type(c=*w++=getc(fd))!=LETTER){
*w='\0';
return (c);
}
while(--lim>0){
t=type(c=*w++=getc(fd));
if(t!=LETTER && t!=DIGIT){
ungetch(c);
break ;
}
}
*(w-1)='\0';
return (LETTER);
}
type (int c) // vraceni typu znaku v ASCII
{
int q;
if(c>='a' && c<='z'|| c>='A'&& c<='Z'){
return (LETTER);
}
else if(c>='0' && c<='9')
return (DIGIT);
else
return (c);
}
K odkazu 1
K odkazu 2
Obsah
2. Typ union
K odkazu
Proměnná typu union mů'e v různých okam'icích nabývat
různých typů hodnot.
Příklad:
union{ union r_tri{
int ihod; int ihod;
float fhod; float fhod;
char *phod; char *phod;
}r_hod };
r_tri r_hod;
union r_tri{
int ihod;
float fhod;
char *phod;
}r_hod;
Příklad 5.5. Definujme typ proměnné, která mů'e nabývat
hodnoty typu int nebo float nebo typu ukazatel na
char. Určeme, jaký typ hodnoty chceme přečíst, přečtěme ji a
vytiskněme ji.
Řešení:
#include<iostream.h> // Proudovy vstup
#include<conio.h> // Ovladani obrazovky
#include<stdio.h> // Standardni vstup
union r_tri{ // Deklarace typu promenne tri hodnot
int ihod;
float fhod;
char *phod;
};
r_tri r_hod; // Definice promenne pro tri typy hodnot
int const INT=1;
int const FLOAT=2;
int const STRING=3;
main()
{
int r_typ;
char *zr=new char[1];
clrscr();
cout << "Cti typ promenne (int=1 nebo float=2 nebo string=3)\n";
cin>>r_typ;
cout << "Cti hodnotu\n"; // Cteni jedne z hodnot
switch(r_typ){ // Cteni jedne z hodnot
case INT: cin >> r_hod.ihod;break;
case FLOAT: cin >> r_hod.fhod;break;
case STRING:r_hod.phod=zr;
while((*r_hod.phod=getchar())!='\n')
r_hod.phod++;
*r_hod.phod='\0';
r_hod.phod=zr;break;
default: cout << "Chybne zadani";return(0);
}
switch(r_typ){ // Tisk jedne z hodnot
case INT: cout << "celociselna hodnota: " << r_hod.ihod;break;
case FLOAT: cout <<"realna hodnota: " << r_hod.fhod;break;
case STRING: cout << "retezec: ";
for(;*r_hod.phod!='\0';++r_hod.phod)
cout << *r_hod.phod;
}
}
K odkazu
Obsah
2.1 Anonymní typ union
Položku typu union můžeme použít ve struktuře běžným
způsobem např. takto:
//...
struct ruzne{ // Deklarace typu ruzne
char *jmeno;
int *praporky;
int c_typ;
union{
int c_hod;
float r_hod;
char *u_hod;
}r_hodnota;
};
ruzne syn_tab[NSYN]; // Definice promenne typu ruzne
//...
// Polozka c_hod promenne tab_syn[i] je dostupna zapisem
// syn_tab[i].r_hodnota.c_hod
// ...
//...
struct ruzne{ // Deklarace typu ruzne
char *jmeno;
int *praporky;
int c_typ;
union{
int c_hod;
float r_hod;
char *u_hod;
}; // !!!!!!!!!!!!!
};
ruzne syn_tab[NSYN]; // Definice promenne typu ruzne
//...
/* Polozka c_hod promenne tab_syn[i] je pak dostupna jednodussím
zapisem: */
// syn_tab[i].c_hod
3. Příklady dynamických objektů
Řešení:
#include<stdio.h>
void main()
{
int *data;// int index;
data = new int[50]; // Vyhrazeno 50 celociselnych mist v hromade
/* Tim jsme definovali pole a s ukazatelem muzeme pracovat jako
s polem (coz bychom mohli - ctenar se muze presvedcit - i
kdybychom definovali misto jen pro jeden prvek. To ale neni
vhodne a ve slozitejsich pripadech i chybne). Kazdemu prvku
o indexu i priradime hodnotu 10*i */
for (int index=0;index<50;index++)
data[index]=10*index;
// Tisk vybranych dat
printf("\n++*data = %d",++*data);
printf("\n*++data = %d",*++data);
printf("\n*data++ = %d",*data++);
printf("\n(*data)++ = %d",(*data)++);
printf("\n*data = %d",*data);
delete(data); // Zruseni objektu
}
Řešení:
#include <stdio.h>
main()
/* Vytvoreni dynamicke struktury seznam, jeji naplneni,vytisteni
a zruseni */
{
struct objekt
{
objekt *dalsi;
int hodnota;
};
objekt *seznam, *novy_objekt;
seznam=NULL; // nebo seznam=0;
novy_objekt= new objekt;
// Vytvareni seznamu
for(int i=10;i>=1;i--)
{
novy_objekt->hodnota=i;novy_objekt->dalsi=seznam;
seznam=novy_objekt;
novy_objekt=new objekt;
}
// Tisk seznamu
novy_objekt=seznam;
for(i=1;i<=10;i++)
{
printf("Hodnota %d. objektu = %d adresa objektu %p\n",
i,novy_objekt->hodnota,novy_objekt);
novy_objekt=novy_objekt->dalsi;
}
// Uvolneni seznamu z dynamicke pameti
objekt *pred=seznam;delete (seznam,novy_objekt);
do
{objekt *predpred=pred;
pred=pred->dalsi;
delete predpred;
}while(pred!=NULL); // nebo while(pred=!=0);
}
K popisu typu jsme pou'ili standardní typ struct. Je
samozřejmé, 'e bychom mohli pou'ít i typu class. V tomto
odstavci budeme však pou'ívat typ struct, proto'e příklady
zde uvedené naprogramujeme v další kapitole s vyu'itím typu
class tak, 'e tento typ pou'ijeme v plné šíři pro
tzv. objektově orientované programování.
Řešení:
#include <stdio.h>
void main()
/* Vytvoreni dynamicke struktury seznam, jeji naplneni a vytisteni.
Naplni stridave celociselnymi hodnotami a hodnotami typu float.
Cviceni na ukazatele, struktury a uniony*/
{
struct objekt{
objekt *dalsi;
union{
int c_hodnota;
float f_hodnota;
};
};
objekt *seznam, *novy_objekt;
seznam=NULL;
// Vytvoreni seznamu
for(int i=10;i>=1;i--){
novy_objekt= new objekt;
if (i/2*2==i)
novy_objekt->f_hodnota=i;
else
novy_objekt->c_hodnota=i;
novy_objekt->dalsi=seznam;seznam=novy_objekt;
}
// Tisk seznamu
novy_objekt=seznam;
for(i=1;i<=10;i++){
i/2*2==i?printf("Hodnota %d. objektu =%f\n",i,novy_objekt->f_hodnota):
printf("Hodnota %d. objektu =%d\n",i,novy_objekt->c_hodnota);
novy_objekt=novy_objekt->dalsi;
}
// Uvolneni seznamu z dynamicke pameti
objekt *pred=seznam;delete (seznam,novy_objekt);
do
{objekt *predpred=pred;
pred=pred->dalsi;
delete predpred;
}while(pred!=NULL);
}
/*...*/
double **a=(double**)malloc(m*sizeof (double*));
/*...*/
for(i=0;i<m;i++)a[i]=(double*)malloc(n*sizeof(double));
/*...*/
pak v C++ píše
//...
double **a=new double*[m];
//...
for(i=0;i<m;i++)a[i]=new double[n];
//...
4. Bitová pole
Řešení:
#include<stdio.h>
main()
{
struct datum{
unsigned den: 5; // obsazuje bity 0 - 4
unsigned mesic: 4; // obsazuje bity 5 - 8
unsigned rok: 7; // obsazuje bity 9 - 15
};
datum dnes={26,11,2010-1950},zitra,pozitri;
zitra.den= dnes.den+1; pozitri.den=dnes.den+2;
printf("\nDnesni datum: %d. %d. %d, zitra je %d. "
"a pozitri je %d.\n",dnes.den,dnes.mesic,dnes.rok+1950,
zitra.den,pozitri.den);
}
Řešení:
// ...
const SLOVSYMB = 01, EXTERNAL = 02, STATIC = 04; unsigned flags=0;
/* Cisla jsou mocninami dvou: 01 = 001;
02 = 010;
04 = 100.
Pak se pristup k bitum stava zalezitosti operaci posuvu, maskovani
a jednotkoveho doplnku, napriklad*/
flags |= EXTERNAL | STATIC;
// nastavi bity EXTERNAL a STATIC ve flags, zatim co
printf("\nflags = %d",flags);
flags &= ~(EXTERNAL | STATIC);
// je vynuluje a podminka
if((flags & (EXTERNAL | STATIC))==0)...
// je splnena, jsou-li oba bity vynulovane.
// ...
// ...
struct{
unsigned je_slovni_symbol: 1;
unsigned je_extern: 1;
unsigned je_static: 1;
}Flags;
Flags.je_extern=Flags.je_static=1; // Nastavi bity
Flags.je_extern=Flags.je_static=0; // Nuluje bity
if(Flags.je_extern==0 && Flags.je_static==0)...
// Testuje, jsou-li bity vynulovany
//...
5. Cvičení
Kapitola 6
Základy objektově orientovaného programování
1. Úvod
OOP je technika, která pracuje s tzv. objekty. Objekt může
mít následující vlastnosti:
2. Příklady objektově orientovaného programování
v C++
K odkazu
V tomto článku se na příkladech seznámíme s OOP. Příklady
budeme stručně komentovat v odladěných programech. Předpokládáme,
že čtenář, který došel až k této kapitole, si
programy sám implementuje na počítači, eventuálně vylepší a
zjistí co se vlastně vytiskne. Výstupy budeme proto komentovat
jen výjimečně.
Řešení:
// Program definujici tridu, ktera je nazvana pocitadlo
// Nazev souboru pocitad1.h
class pocitadlo
{
private:
unsigned int hodnota;
public:
/* prototyp konstruktoru a definice vnitřní funkce. Slovní symbol
se nemusí psát. Konstruktor při definici typu pocitadlo zajistí
vytvoření objektu a inicializaci potřebných dat objektu. Jmenuje
se vzdy jako typ class*/
pocitadlo(){hodnota=0;}
// Nasleduji dalsi definice metod jako vnitřní funkce
void zvetseni(){if(hodnota<65535) hodnota++;}
void zmenseni(){if(hodnota>0) hodnota--;}
unsigned int zpristupnena_hodnota(){return(hodnota);}
}; // Vsimnete se, ze definice tridy musi koncit strednikem!!!
// Nasleduje program využívající trídy počitadlo
// Nazev souboru pocitad1.cpp
#include <stdio.h>
#include "pocitad1.h"
void main()
{
/* Definice objektu c1, c2 a automaticke pocatecni prirazeni
hodnoty pomocí konstruktoru */
pocitadlo c1,c2;
for(int i = 1;i <= 15; i++)
{
c1.zvetseni();
printf("\nc1 = %u",c1.zpristupnena_hodnota());
c2.zvetseni();
}
printf("\nPo ukonceni cyklu je c2 = %u",c2.zpristupnena_hodnota());
for (i=1;i<=5;i++)
c2.zmenseni();
printf("\nPo ukonceni druheho cyklu je c2 = %u",
c2.zpristupnena_hodnota());
printf("\nKonecna hodnota c1 =%u",c1.zpristupnena_hodnota());
}
// Program definujici tridu, ktera je nazvana pocitadlo
// Nazev souboru pocitad2.h
class pocitadlo
{
private:
unsigned int hodnota;
public:
pocitadlo(){hodnota=0;}
// Nasleduji jen prototypy
void zvetseni();
void zmenseni();
unsigned int zpristupnena_hodnota();
};
/* Nasleduji definice metod. Vsimnete si, ze kazda metoda je
oznacena jmenem tridy pred niz je uveden typ funkce. Je sa-
mozrejme, ze mezi definicemi muze byt uveden i konstruktor.
Tyto definice nemusi byt ani uvedeny v temze souboru jako trida,
ke ktere patri, ale mohou byt umisteny v souboru jinem. Souborum,
ktere maji v C++ priponu h se rika hlavickove soubory.*/
void pocitadlo::zvetseni(){if(hodnota<65535) hodnota++;}
void pocitadlo::zmenseni(){if(hodnota>0) hodnota--;}
unsigned int pocitadlo:: zpristupnena_hodnota(){return(hodnota);}
// Tento program vyuziva tridu pocitadlo
// Nazev souboru pocitad2.cpp
#include <stdio.h>
#include "pocitad2.h"
void main()
{
pocitadlo c1; // Automaticke pocatecni prirazeni hodnoty
pocitadlo c2; // " " " "
for(int i = 1;i <= 15; i++)
{
c1.zvetseni();
printf("\nc1 = %u",c1.zpristupnena_hodnota());
c2.zvetseni();
}
printf("\nPo ukonceni cyklu je c2 = %u",c2.zpristupnena_hodnota());
for (i=1;i<=5;i++)
c2.zmenseni();
printf("\nPo ukonceni druheho cyklu je c2 = %u",
c2.zpristupnena_hodnota());
printf("\nKonecna hodnota c1 =%u",c1.zpristupnena_hodnota());
}
Příklad 6.2. Napišme program, který utvoří v hromadě
posloupnost objektů, které se říká seznam, každý objekt bude
obsahovat celočíselnou hodnotu a ukazatel na další objekt.
Poslednímu objektu seznamu bude přiřazena hodnota NULL.
Řešení:
// Hlavickovy soubor seznam.h
#include <stdio.h>
#include<conio.h>
class objekt
{
private:
objekt *dalsi;
int hodnota;
public:
// Konstruktor
objekt(){}
// Prototypy
void vytvor_seznam(int pocet=1);
void tiskni_seznam();
void uvolni_pamet();
// Destruktor
~objekt(){delete(zac_sez,pom_prv);}
};
static objekt *zac_sez, *pom_prv; // Staticke promenne
/* Definice zapouzdrenych funkci. Tyto funkce nelze definovat jako
vnitrni. (Ctenar si muze zopakovat, proc tomu tak je.) */
void objekt::vytvor_seznam(int pocet)
{
zac_sez=NULL;pom_prv=new objekt;
for (int i=pocet;i>=1;i--)
{
pom_prv->hodnota=i;
pom_prv->dalsi=zac_sez;
zac_sez=pom_prv;
pom_prv=new objekt;
}
}
void objekt::tiskni_seznam()
{
pom_prv=zac_sez;
int i = 0;
do
{
i++;
printf("adresa %d. objektu = %p a jeho hodnota = %d\n",i,
pom_prv,pom_prv->hodnota);
pom_prv=pom_prv->dalsi;
} while(pom_prv!=NULL);
}
#include "seznam4.h"
main()
{
clrscr(); // Mazani obrazovky v systemu, kde byl program laden
objekt l; // Definice objektu typu objekt
l.vytvor_seznam(10); // Vytvoreni deseti objektu
l.tiskni_seznam(); // a jejich tisk
}
Řešení:
/**********************************************************************
* obrazce.h... hlavickovy soubor pro jednoduchy graficky projekt, kde *
* se definuji jednotlive obrazce na zaklade drive defino-*
* vanych objektu. Vyuziva se polymorfismu ( prototypy *
* funkci zacinajicich slovnim symbolem virtual). Pouziva-*
* ji se i data typu protected, pristupna funkcim zapouz- *
* drenych v odvozenych tridach. *
**********************************************************************/
// Definice booleovskych (logickych) promennych
enum boolean {false,true};
// Trida "umisti" popisuje umisteni x a y souradnic na obrazovce
class umisti {
protected: //Dovoluje odvozenym tridam vyuzivat "protected" data
int x,y;
public: // Tyto funkce jsou pristupne z vnejsiho prostredi
umisti(int px,int py) {x=px;y=py;} // Konstruktor
int getx(){return(x);}
int gety() {return(y);}
};
// Trida "bod" zajisti viditelnost nebo neviditelnost bodu
// Trida je potomkem "umisti"; "public" zajisti, ze objekty odvozene
// tridy mohou uzivat metody predka, nebyly-li tyto metody v odvo-
// zene tride predefinovany.
class bod:public umisti{
protected:
boolean viditelny; // Tridy odvozene z "bod" budou potrebovat pristup
public:
// Prototypy
bod(int px,int py); // Konstruktor
virtual void zhasni(); // Zhasinat lze ruzne obrazce, proto "virtual"
virtual void rozsvit(); // Totez plati pro rozsviceni
boolean je_viditelny();
void posun(int nx,int ny);
void presun(int presun_o);
};
// Trida kruznice je potomkem tridy "bod" a zaroven "umisti"
class kruznice: public bod {
protected:
// Novy udaj, ostatni se dedi
int polomer;
public:
/**********************************************************************
* Prototypy vcetne virtualnich. Nyni se jedna o rozsviceni a zhasnuti *
* kruznice. Tez o jeji zvetseni a zmenseni. Pod tymiz jmeny muzeme *
* zhasinat, rozsvecet, zvetsovat a zmensovat elipsu, ctverec, proste *
* libovolny obrazec. Vzdy se vi, co se ma zhasnout, rozsvitit atd., *
* i kdyz se zapozdrene funkce jmenuji vzdy stejne. To je typicky *
* polymorfismus. *
**********************************************************************/
kruznice(int px,int py,int pr);
virtual void zhasni();
virtual void rozsvit();
virtual void expanduj(int expanduj_o);
virtual void zmensi(int zmensi_o);
};
/**********************************************************************
* Prototyp specialni funkce uzite v souboru obrazce.cpp pro presun *
* obrazce. *
**********************************************************************/
boolean kam_presun(int &deltax,int &deltay);
/**********************************************************************
* obrazce.cpp - Tento soubor obsahuje definice pro tridy bod a kruzni-*
* ce deklarovane v souboru obrazce.h. Konstruktor *
* "umisti" je definovan jako vnitrni primo v souboru *
* obrazce.h *
**********************************************************************/
#include <stdio.h>
#include <graphics.h> // Grafika
#include <conio.h> // Prace s obrazovkou a klavesnici
#include "obrazce.h"
// Metody tridy bod
// Konstructor
bod::bod(int px, int py):umisti(px,py)
{
viditelny=false; // a priorne neviditelny
}
void bod:: rozsvit()
{
viditelny=true;
putpixel(x,y,getcolor()); // Uzije preddefinovanou barvu
}
void bod:: zhasni()
{
viditelny=false;
putpixel(x,y,getbkcolor()); // Vymaze bod barvou pozadi
}
void bod::posun(int nx,int ny)
{
zhasni();
x=nx; y=ny; // posun bodu
rozsvit();
}
// Funkce pro posuny obrazcu
boolean kam_presun(int &deltax,int &deltay)
{
int keychar;
boolean quit;
deltax=0; deltay=0;
do{
keychar=getch(); // Cti znak z klavesnice
if(keychar==13) // Cetl se "enter"
return(false);
if(keychar==0) // Zvlastni znak
{
quit=true; // Predpoklad: znak je vyuzitelny
keychar=getch();
switch(keychar){
case 72:deltay=-1;break; // Znak "sipka dolu"
case 80:deltay=1;break; // Znak "sipka nahoru"
case 75:deltax=-1;break; // Znak "sipka vlevo"
case 77:deltax=1;break; // Znak "sipka vpravo"
default:quit=false;// Nepripustny znak se ignoruje
}
};
}while (!quit);
return(true);
}
void bod:: presun(int presun_o)
{
int deltax, deltay, obrazecx, obrazecy;
rozsvit();
obrazecx=getx(); obrazecy=gety(); // Pocatecni poloha obrazce
// a ted obrazec popotahujeme po obrazovce
while (kam_presun(deltax,deltay)){
obrazecx += (deltax*presun_o);
obrazecy += (deltay*presun_o);
posun(obrazecx,obrazecy);
}
}
// Clenove tridy kruznice
// konstruktor
kruznice::kruznice(int px, int py, int pr):bod(px,py)
{
polomer=pr;
}
void kruznice::rozsvit()
{
viditelny=true;
circle(x,y,polomer); // Kresli se kruznice
}
void kruznice::zhasni()
{
unsigned int barva; // Uschovej stavajici barvu
barva=getcolor();
setcolor(getbkcolor());
viditelny=false;
circle(x,y,polomer);
setcolor(barva); // Vrat barvu, jinak se vse ztrati na prozadi
}
void kruznice::expanduj( int expanduj_o)
{
zhasni();
polomer += expanduj_o;
if (polomer < 0)
polomer = 0;
rozsvit();
}
void kruznice::zmensi(int zmensi_o)
{
// Vyuzije se funkce expanduj se zapornym argumentem
expanduj(-zmensi_o);
}
/************************************************************************
* UKAZOBJ.CPP - Ukazka vyuziti objektove orientovaneho programovani.Pro *
* vyuziti je nutne zavest soubor obrazce.cpp a graphics.h *
* a je nutno definovat konkretni cestu k ovladaci nejlepe:*
************************************************************************/
#define CESTA "\\tc\\bgi" /* Tento prikaz v jazyku C++ uzivany mene nez
v jazyku C, [protoze lze obvykle (i zde)
nahradit definici cons] bude podrobne
vysvetlen v kap.7. */
#include "obrazce.cpp"
int main()
{
int go=DETECT,gm,k;
kruznice *ukruznice = new kruznice(150,80,50);
/* Nasleduje prikaz umoznujici otevrit grafickou knihovnu v implementaci
BORLAND TURBO C++; v proměnné go se automaticky ulozi informace o pris-
lusnem ovladaci (driveru), promenna gm zajisti preklopení do spravneho
grafickeho rezimu, v konstante CESTA je ulozena cesta do adresare, kde
je ulozen ovladac */
initgraph(&go,&gm,CESTA);
// Popotahuj kruznici o pet obrazovych prvku (angl. pixel)
ukruznice->presun(5);
// Stiskni enter a popotahuj kruznici o 30 obrazovych prvku
ukruznice->presun(30);
// Stiskni enter a zvetsi kruznici na o 50 obrazovych prvku
ukruznice->expanduj(50);
k=getch();
// Stiskni enter a zmensi kruznici 95 obrazovych prvku
ukruznice->zmensi(95);
k=getch();
// Nasleduje prikaz, ukoncujici v implementaci BORLAND graficky rezim
closegraph();return(0);
}
K odkazu
Obsah
3. Spřátelené funkce a třídy
Zapouzdření zajišťuje, že data typu private nejsou
přístupná jiným objektům. To má velké výhody při
úpravách programových projektů a jejich údržbě, klade to však
velké nároky na projektanta, od kterého se očekává, že zajistí
dostatečný počet metod pro manipulaci s daty při všech
alternativách, které se mohou vyskytnout. To se někdy nemusí
podařit, a proto v C++ existuje mechanismus "přátel".
Přátelé (tedy spřátelené funkce a třídy) mají přístup ke všem
datům, právě tak, jako metody příslušné třídy. Můžeme si to
představit tak, že se do štítu, kterým každá třída chrání svá
data, navrtají otvory, kterými jsou data přístupná "přátelům".
Nebudeme zde rozebírat výhody a nevýhody těchto ötvorů", citem
však každý pozná, že (např. pro údržbu systému) čím méně otvorů a
"přátel" bude, tím lépe.
Řešení:
#include<stdio.h>
class zvec
{
private:
int hodn;
// Prototyp spratelene funkce
friend void print(zvec &);
public:
// Vnitrni funkce tridy (vcetne konstruktoru)
zvec(int n=0){hodn=n;}
void pricti(){hodn++;}
};
// Definice spřátelené funkce
void print(zvec &i){printf("hodnota = %d\n",i.hodn);}
#include"friendf.h"
main()
{ zvec z(10000); z.pricti();
/* Nasleduje pouziti spratelene funkce: */print(z);
z.pricti();/* a opet spratelena funkce: */print(z);
}
Příklad 6.5. Sestavme stejný program jako v příkladu 6.2
s tím rozdílem, že jednotlivé celočíselné hodnoty se budou číst
z klávesnice, při čemž nejprve udáme, kolik jich budeme číst. Navíc
budeme definovat spřátelenou třídu, která zařadí nový objekt na
správné místo seznamu, budeme-li předpokládat, že prvky seznamu
tvoří klesající posloupnost.
Řešení:
// Soubor friendc.h - hlavickovy soubor
#include<stdio.h>
#include <dos.h>
class objekt //Tato trida vlastne nahrazuje strukture
{
private:
objekt *dalsi;
int hodnota;
friend class seznam;
friend class zarad_objekt;
};
class seznam // Spratelena trida seznam
{
private:
objekt *zac_sez;
objekt *pom_prv;
friend class zarad_objekt;
public:
seznam()
{
zac_sez=NULL;
}
void vytvor_seznam(int);
void tiskni_seznam();
void cisteni_pameti();
~seznam(){cisteni_pameti();}
};
class zarad_objekt /* Spratelena trida zarad_objekt k objektu
i seznamu */
{
private:
objekt *novy_objekt;
objekt *predchozi_objekt;
public:
zarad_objekt(){novy_objekt=new objekt[1];}
void zarad(seznam &);
};
// Soubor friendc.cpp - implementace metod
#include"friendc.h"
void seznam:: cisteni_pameti()
{
objekt *pred=zac_sez;
if(pred==NULL)return;
do
{objekt *predpred=pred;
pred=pred->dalsi;
delete predpred;
}while(pred!=0);
}
void seznam::vytvor_seznam(int pocet=10)
{
printf("\nCti %d prvku \n",pocet);
zac_sez=NULL;pom_prv=new objekt;
for (int i=pocet;i>=1;i--)
{
scanf("%d",&pom_prv->hodnota);
pom_prv->dalsi=zac_sez;
zac_sez=pom_prv;
pom_prv=new objekt;
}
}
void seznam::tiskni_seznam()
{
pom_prv=zac_sez;
int i = 0;
while(pom_prv!=0)
{
i++;
printf("adresa %d. objektu = %p a jeho hodnota = %d\n",i,
pom_prv,pom_prv->hodnota);
pom_prv=pom_prv->dalsi;
}
}
void zarad_objekt::zarad(seznam &s)
{
printf("\nCti hodnotu noveho objektu\n");
scanf("%d",&novy_objekt->hodnota);
predchozi_objekt=s.pom_prv=s.zac_sez;
while(s.pom_prv!=NULL)
{
if(s.pom_prv->hodnota<=novy_objekt->hodnota)
if(s.pom_prv==s.zac_sez)
{novy_objekt->dalsi=s.pom_prv;
s.zac_sez=novy_objekt;break;
}
else
{
novy_objekt->dalsi=s.pom_prv;
predchozi_objekt->dalsi=novy_objekt;break;
}
else
if(s.pom_prv->dalsi==NULL)
{
novy_objekt->dalsi=NULL;s.pom_prv->dalsi=novy_objekt;
break;
}
{
predchozi_objekt=s.pom_prv;s.pom_prv=s.pom_prv->dalsi;
}
}
}
// Soubor frhlac.cpp - hlavni program
#include "friendc.cpp"
main()
{
seznam sez;zarad_objekt a;
printf("\nCti pocet clenu seznamu\n");
int n;
scanf("%d",&n);
sez.vytvor_seznam(n);
sez.tiskni_seznam();
a.zarad(sez);
sez.tiskni_seznam();
}
4. Přetížené operátory
V odstavci 7.3 jsme mluvili o přetížených funkcích. Nyní se
ještě stručně zmíníme o přetížených operátorech. Máme-li možnost
definovat objekty nových typů, musíme mít také možnost definovat
nové operátory, kterými realizujeme operace nad těmito objekty.
Můžeme si vymýšlet názvy nových operátorů, ale můžeme použít
operátorů, na které jsem zvyklí. Dejme si příklad. Představme si,
že si definujeme operaci sčítání nad nějakými dvěma námi
definovanými objekty z1, z2 (třeba komplexním hodnotami). Můžeme to
učinit standardním způsobem tak, že napíšeme
z1.pricti(z2);
z2.pricti(z1);
Řešení:
/***********************************************************************
* Hlavickovy soubor pro lepsi popis tridy pocitadlo s vyuzitim preti- *
* zenych operatoru ++ a -- *
***********************************************************************/
//Soubor vlp_pctd.h
#include<stdio.h>
class pocitadlo
{
private:
unsigned int hodnota;
public:
pocitadlo(){hodnota=0;} // Konstruktor s nulovanim data hodnota
// Prototypy operatoru
void operator++();
void operator--();
unsigned operator()();
};
//Implementace metod vylepsene tridy pocitadlo
//Soubor vlp_pctd.cpp
#include"vlp_pctd.h"
void pocitadlo::operator ++()
{
if (hodnota<65535)
hodnota++;
}
void pocitadlo::operator --()
{
if(hodnota>0)
hodnota--;
}
unsigned int pocitadlo::operator ()()
{
return hodnota;
}
//Test programu pro vylepsene pocitadlo
//File vlp_pctt.cpp
#include "vlp_pctd.cpp"
#include<stdio.h>
void main()
{
pocitadlo me_nepostradatelne_pocitadlo;
for(int i=0;i<12;i++)
{
me_nepostradatelne_pocitadlo++;
printf("\nme_nepostradatelne_pocitadlo = %d",
me_nepostradatelne_pocitadlo());
}
for(i=0;i<2;i++)
me_nepostradatelne_pocitadlo--;
printf("\nme_nepostradatelne_pocitadlo = %d",
me_nepostradatelne_pocitadlo());
}
Příklad 6.7. Sestavme systém umožňující práci se zlomky.
Řešení:
prirad Přiřaď čitatel a jmenovatel zlomku.
= Přiřaď jeden zlomek druhému.
op= Realizace operací op=, kde op je
+,-,*,nebo /.
== Rovnost dvou zlomků.
!= Nerovnost dvou zlomků.
+ Sčítání zlomků.
- Odčítání zlomků.
* Násobení zlomků.
/ Dělení zlomků.
real Konvertování zlomku na číslo desetinné.
<< Přetížení proudového operátoru na tisk zlomků.
//Hlavickovy soubor pro praci s racionalnimi cisly
// Soubor zlomek.h
#include<fstream.h>
#include<stdlib.h>
class zlomek
{
private:
void kraceni(zlomek &cis);
long citatel,jmenovatel;
public:
zlomek(long cit=0,long jmen=1)
{
citatel=cit;
jmenovatel=jmen;
kraceni(*this);
}
void prirad(long cit,long jmen)
//Prislusny citatel a jmenovatel se prirazedi racionalnimu cislu.
//Uziti:
//zlomek cislo;
//cislo.prirad(12,18);
{
citatel=cit;
jmenovatel=jmen;
kraceni(*this);
}
zlomek operator =(zlomek &cis)
/*Argument je volan odkazem, abychom se vyhnuli jeho kopii (uspori se
pamet)
*/
//Operator prirazeni
//Uziti:
//zlomek cis1,cis2;
//cis2=cis1: Toto znamena cis2.operator =(cis1);
{
citatel=cis.citatel; jmenovatel=cis.jmenovatel;
kraceni(*this);
return(*this);
}
zlomek operator = (long cele_cislo)
//Operator prirazeni
//Uziti:
//zlomek cis;
//cis=1234;
{
citatel=cele_cislo; jmenovatel=1;
return(*this);
}
zlomek operator +=(zlomek &cis)
//Operator pricteni
//Uziti:
//zlomek cis1,cis2;
//cis2+=cis1;
{
long stary_jmen=jmenovatel;
long stary_cit=citatel;
long j=cis.jmenovatel;
long c=cis.citatel;
jmenovatel*=j;
citatel=stary_cit*j+stary_jmen*c;
kraceni(*this);
return(*this);
}
zlomek operator -=(zlomek &cis)
//Operator odcitani
//Uziti:
//zlomek cis1,cis2;
//cis2-=cis1;
{
long stary_jmen=jmenovatel;
long stary_cit=citatel;
long j=cis.jmenovatel;
long c=cis.citatel;
jmenovatel *= d;
citatel=stary_cit*j-stary_jmen*c;
kraceni(*this);
return(*this);
}
zlomek operator *=(zlomek &cis)
//Operator nasobeni
//Uziti:
//num_n cis1,cis2;
//cis2*=cis1;
{
citatel *= cis.citatel;
jmenovatel *= cis.jmenovatel;
kraceni(*this);
return(*this);
}
zlomek operator /=(zlomek &cis)
//Operator deleni
//Uziti:
//zlomek cis1,cis2;
//cis2/=cis1;
//Je-li jmenovatel nula, operace se neprovede.
{
if(jmenovatel!=0)
{
citatel *= cis.jmenovatel;
jmenovatel *= cis.citatel;
kraceni(*this);
return(*this);
}
}
//Spratelene funkce pro realizaci binarnich operaci
friend zlomek
operator + (zlomek &cis1,zlomek &cis2);
//Operator pricteni
//Uziti:
//zlomek cis1,cis2,cis3;
//cis3=cis1+cis2;
//a to znamena: cis3=operator(cis1,cis2);
friend zlomek
operator - (zlomek &cis1,zlomek &cis2);
//Operator odecteni
//Uziti:
//zlomek cis1,cis2,cis3;
//cis=cis1-cis2;
friend zlomek
operator * (zlomek &cis1, zlomek &cis2);
//Operator nasobeni
//Uziti:
//zlomek cis1,cis2,cis3;
//cis3=cis1*cis2;
friend zlomek
operator / (zlomek &cis1,zlomek &cis2);
//Operator deleni
//Uziti:
//zlomek cis1,cis2,cis3;
//cis3=cis1/cis2;
friend int operator == (zlomek &cis1,zlomek &cis2);
//Operator rovnosti
//Uziti:
//zlomek cis1,cis2,cis3;
//if(cis1==cis2)...
friend int operator != (zlomek &cis1,zlomek &cis2);
//Operator nerovnosti
//Uziti:
//zlomek cis1,cis2,cis3;
//if(cis1!=cis2)...
friend double real(zlomek &cis);
//Konverze racionalniho cisla na realne
//Uziti:
//zlomek cis1;double r;
//r=real(cis1);
friend ostream &operator <<(ostream &s,zlomek &cis1);
//Proudovy vystup jako pretizeny operator
//Uziti:
//zlomek cis;
//cout<<cis;
};
//Implementace metod tridy zlomek (pro praci s racionalnimi cisly)
// Soubor zlomek.cpp
#include "zlomek.h"
#include<stdlib.h>
void zlomek::kraceni(zlomek &cis)
//Vraceni zkraceneho racionalniho cisla (napr. kraceni(20/30)=2/3)
{
int nsd; //nsd je nejvetsi spolecny delitel
if(cis.citatel==0)
{
cis.jmenovatel=1;
return;
}
nsd=(abs(cis.citatel)>
abs(cis.jmenovatel)?
abs(cis.citatel):
abs(cis.jmenovatel));
if(nsd==0) return;
do
{
if(nsd==1)break;
if((cis.citatel%nsd==0) &&
(cis.jmenovatel%nsd==0))break;
else nsd--;
}while(1);//Pozor! Simulace nekonecneho cyklu. Pohov!
cis.citatel/=nsd;cis.jmenovatel/=nsd;
//Prirazeni spravneho znamenka
if(cis.citatel<0&&cis.jmenovatel<0)
{
cis.citatel=-cis.citatel; cis.jmenovatel=-cis.jmenovatel;
}
else if(cis.citatel<0||cis.jmenovatel<0)
{
cis.citatel=-abs(cis.citatel);
cis.jmenovatel=abs(cis.jmenovatel);
}
}
ostream& operator << (ostream &s,zlomek &cis)
{
if(cis.jmenovatel!=1)
return (s << cis.citatel << '/' << cis.jmenovatel);
else
return (s << cis.citatel);
}
zlomek operator + (zlomek &cis1,zlomek &cis2)
{
long cvysledek,jvysledek;
jvysledek=cis1.jmenovatel*cis2.jmenovatel;
cvysledek=cis1.citatel*cis2.jmenovatel
+cis2.citatel*cis1.jmenovatel;
return zlomek(cvysledek,jvysledek);
}
zlomek operator - (zlomek &cis1,zlomek &cis2)
{
long cvysledek,jvysledek;
jvysledek=cis1.jmenovatel*cis2.jmenovatel;
cvysledek=cis1.citatel*cis2.jmenovatel
-cis2.citatel*cis1.jmenovatel;
return zlomek(cvysledek,jvysledek);
}
zlomek operator * (zlomek &cis1,zlomek &cis2)
{
long cvysledek,jvysledek;
jvysledek=cis1.jmenovatel*cis2.jmenovatel;
cvysledek=cis1.citatel*cis2.jmenovatel;
return zlomek(cvysledek,jvysledek);
}
zlomek operator / (zlomek &cis1,zlomek &cis2)
{
long cvysledek,jvysledek;
if(cis2.citatel!=0)
{
jvysledek=cis1.jmenovatel*cis2.citatel;
cvysledek=cis1.citatel*cis2.jmenovatel;
return zlomek(cvysledek,jvysledek);
}
}
int operator==(zlomek &cis1,zlomek &cis2)
{
return (float)cis1.citatel/(float)cis1.jmenovatel==
(float)cis2.citatel/(float)cis2.jmenovatel;
}
int operator!=(zlomek &cis1,zlomek &cis2)
{
return (float)cis1.citatel/(float)cis1.jmenovatel!=
(float)cis2.citatel/(float)cis2.jmenovatel;
}
double real(zlomek &cis)
{
return (double)cis.citatel/(double)cis.jmenovatel;
// nebo return double(cis.citatel/double(cis.jmenovatel)
}
//Test programu pro tridu class zlomek
//Soubor zlomekt.cpp
#include"zlomek.cpp"
#include<conio.h>
main()
{
//Predefinovani proudove funkce pro vystup do souboru muj_vystup
ofstream cout("muj_vystup");
zlomek a(4,5);
zlomek b(-10,6);
cout<<"a = "<<a<<"\nb = "<<b;
a*=b;
cout<<"\n(a*=b) = "<<a;
a=b;
cout<<"\nPo prirazeni a=b, a = "<<a;
a+=b;
cout<<"\n(a+=b) = "<<a;
a-=b;
cout<<"\n(a-=b) = "<<a;
a=b;a-=a;
cout<<"\nPo prirazeni a=b; a-=a; a = "<<a;
zlomek c(15);
a+=c;
cout<<"\n(a+=15) = "<<a;
a=1234567;
cout<<"\n(a=1234567); a = "<<a;
long d=4;
a=d;
cout<<"\n(a=long d=4) = "<<a;
a+=(b=5);
cout<<"\n(a+=(b=5)) = "<<a;
zlomek j(4,5); zlomek k(5,4);
zlomek l=j+k;
cout<<"\nj = "<<j<<" k = "<<k<<" zlomek l=j+k; l = "<<l;
l=l+1;
cout<<"\n(l+1) = "<<l;
l=l+k;
cout<<"\nk=5/4; (l=l+k) = "<<l;
zlomek e,f,g,h,i,m;
e=17;f=15;g=e/f;
cout<<'\n'<<e<<" / "<<f<<" = "<<g;
h=g;i=1;m=i*h;
cout<<'\n'<<i<<" * "<<h<<" = "<<m;
if(g==m)
cout<<"\nHodnota "<<g<<" se rovna"<<m;
else
cout<<"\nHodnota "<<g<<" se nerovna "<<m;
double r=real(g);
cout<<"\nHodnota zlomku "<<g<<" = "<<r;
zlomek cislo;
cislo.prirad(12,15);
cout<<"\nPo cislo.prirad(12,15); cislo = "<<cislo;
}
a = 4/5
b = -5/3
(a*=b) = -4/3
Po prirazeni a=b, a = -5/3
(a+=b) = -10/3
(a-=b) = -5/3
Po prirazeni a=b; a-=a; a = 0
(a+=15) = 15
(a=1234567); a = 1234567
(a=long d=4) = 4
(a+=(b=5)) = 9
j = 4/5 k = 5/4 zlomek l=j+k; l = 41/20
(l+1) = 61/20
k=5/4; (l=l+k) = 43/10
17 / 15 = 17/15
1 * 17/15 = 1
Hodnota 17/15 se nerovna 1
Hodnota zlomku 17/15 = 1.133333
Po cislo.prirad(12,15); cislo = 4/5
5. Závěrečná poznámka k OOP
V celé této kapitole a v kapitole předcházející jsme hovořili
o tom, že třídy jsou právě tím nástrojem, který umožňuje
objektově orientované programování. V literatuře se často
(např. [8]) uvádějí příklady, že objekty typu struct v C++ mohou zahrnovat také metody, ale není zde možnost rozlišit
private a public data i metody a ztrácí se tak
výhoda
zapouzdření. Mělo by tomu tak být, ale zkoušel jsem zaměnit
"nástroj" class "nástrojem" struct v implementaci BORLAND TURBO C++
a v překladači C++ v operačním systému LINUX a v obou
se objekty typu class a typu struct chovaly naprosto
stejně. Nedoporučuji tuto skutečnost využívat, ale považoval jsem
za vhodné se o ní závěrem zmínit.
6. Cvičení
Kapitola 7
Poznámky k předpřekladači
1. Direktiva #define
1.1 Direktiva #define bez parametrů
Tato direktiva je velmi často u'ívána v jazyku C jako
direktiva bez parametrů, např. začátek programu z příkladu 1.7
v jazyku C++:
/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius
pro fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
main()
{
// Definice konstant pro dolni a horni mez a krok zmeny
const int DOLNI=0,HORNI=300,KROK=20;
//...
musíme zapsat v jazyku C takto:
/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius
pro fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
/* Konstanty definovane stylem jazyka C (v jazyku C++ mozne, ale mene
obvykle */
#define DOLNI 0
#define HORNI 300
#define KROK 20
main()
{
//...
#define PI 3.1415926
ale i
#include<math.h> //Hlavickovy soubor pro matematickou knihovnu
#define PI (4 * atan(1.0)) // atan je arctg
...
Konstanty, kterým často říkáme makra, jsou-li takto definovány se
nerozvinou, jsou-li uzavřeny v uvozovkách, např.
#define MAKRO past
d#include<stdio.h>
main()
{printf("Toto je MAKRO");}
1.2 Direktiva #define s parametry
Při sestavování programů se často vyskytne případ, kdy mnohokrát
pou'íváme nějakou funkci, která je krátká, nebo při řešení
diferenciálních rovnic měníme často okrajové nebo počáteční
podmínky. Pak s výhodou mů'eme pou'ít direktivy s parametry.
Mů'eme např. psát:
Zdálo by se, 'e závorky jsou nadbytečné, není tomu tak. Kdybychom toti' napsali:
pak po volání:
na_treti(a+b)
se rozvine do:
a+b*a+b*a+b, co' je chybné. Ani
vynechání vnějších závorek není vhodné. Např.
se po volání:
if(cti(r) == 'b') rozvine do
známé chyby:
#define na_treti(x) ((x)\
*(x)*(x))
2. Operátory # a ##
Tyto operátory se neužívají tak často, a proto pro úsporu místa
je objasníme pouze na dvou příkladech, ze kterých si čtenář
význam snadno domyslí:
#define otevri_soubor(jmeno_souboru) fopen(#jmeno_souboru,"r")
main()
{// ...
FILE *ms;
ms=otevri_soubor(muj_soubor);
// ...}
//...
FILE *ms;
ms=fopen("muj_soubor","r");
//...
#define print_var(num) printf("var" #num " = %d\n",var##num)
//...
int var1,var2,var3;
//...
print_var(3);
//...
int var1,var2,var3;
//...
printf("var" "3" " = %d\n",var3);
//...
3. Podmíněný překlad
Při ladění programů pomáhají často profesionálně sestavené ladící
programy. Jejich nevýhodou je, že při snaze pokrýt co nejvíce
možností
jsou tyto programy někdy dost složité a mnoho programátorů proto
programy
píše tak, že do nich zapisují vlastní ladící tisky, které po
odladění pro zvýšení přehlednosti programu ruší, přičemž při
troše nepozornosti zruší víc než ladící tisky, z čehož vznikají
mnohé nepříjemnosti. V jazycích C a C++ je umožněn
velmi praktický
tzv. podmíněný překlad, který si na několika krátkých ukázkách
vysvětlíme.
Řešení:
#define K&R 0
#define na_treti(x) ((x)*(x)*(x))
#if K&R
f(x)
int x;
{return (na_treti(x)-4*x+7);}
#else
f(int x)
{ return (na_treti(x)-4*x+7);}
#endif
#undef K&R
#define na_treti(x) ((x)*(x)*(x))
#ifdef K&R
f(x)
int x;
{return (na_treti(x)-4*x+7);}
#else
f(int x)
{ return (na_treti(x)-4*x+7);}
#endif
#define K&R
#define na_treti(x) ((x)*(x)*(x))
#ifndef K&R
f(x)
int x;
{return (na_treti(x)-4*x+7);}
#else
f(int x)
{ return (na_treti(x)-4*x+7);}
#endif
Literatura
File translated from TEX by TTH, version 1.41.