VYSOKÁ ŠKOLA BAŇSKÁ TECHNICKÁ UNIVERZITA
KURZ C++ Materiál ze systému http://aha.cs.vsb.cz:8080/aha/kurzcpp Autoři: Jiří Šebesta, Jakub Sztefek
* pdf připravil PavelSVK VŠB 2005
Úvod do C++ C++ je reakcí programátorů jazyka C na objektově orientované programování. Jazyk C++ je vytvořen na pevných základech jazyka C. Přináší nám podporu objektově orientovaného programování (a mnoha dalších nových prvků). Přitom však nebyla obětována ani původní výkonnost jazyka C, ani jeho elegance a pružnost. C++ byl vytvořen Bjarne Stroustrupem v roce 1979 v Bellových laboratořích v Murray Hill v New Jersey. Původně se jmenoval C s třídami. Jeho jméno bylo teprve v roce 1983 změněno na C++. Od té doby prošel jazyk C++ třemi zásadními revizemi. První z nich byla v roce 1985, druhá v roce 1990 a třetí proběhla během standardizačního procesu. Práce na standardizaci C++ započala před několika lety. Hrubý koncept plánovaného standardu byl vytvořen 25. ledna 1994. V tomto konceptu se komise ANSI/ISO C++ (American National Standards Institute a International Standards Organization) držela zásad definovaných Stroustrupem a přidala některá další. V podstatě však zůstal původní návrh C++ zachován. Brzy po dokončení hrubého konceptu standardu došlo k události, která způsobila, že byl standard podstatně rozšířen - bylo to vytvoření knihovny standardních šablon STL (Standard Template Library) Alexandrem Stěpanovem. STL je sada generických rutin, které se mohou používat pro manipulaci s daty. Jsou výkonné a elegantní, bohužel však poněkud rozsáhlé. Následně po vytvoření hrubého konceptu schválila komise začlenění STL do normy C++, čím se původní rozsah C++ podstatně zvětšil. Nejzávažnější ovšem bylo, že zahrnutí STL mimo jiné zpomalilo proces standardizace C++. Je pravdou, že standardizace C++ trvala mnohem déle, než kdokoliv čekal. Nicméně 14. listopadu 1997 prošel hotový koncept komisí a standard pro C++ se tak stal skutečností.
I. Neobjektové základy jazyka Tato část obsahuje první část kurzu. Je zaměřena na neobjektové vlastnosti jazyka C++.
Kapitola 1. - Jazyk C++ Obsah 1.1. 1.2. 1.3. 1.4. 1.5. 1.6.
Základní pojmy Objektově orientované programování (OOP) Zapouzdření Polymorfismus Dědičnost Test
V této části si vysvětlíme základní pojmy jako je objektově orientované programování, zapouzdření, polymorfismus a dědičnost. Závěrem si řekneme něco o třídách a přetěžování funkcí.
1.1. Základní pojmy V této kapitole se seznámíme s některými základními pojmy ze světa počítačů. Mluvíme-li o programování musíme začít pojmem algoritmus. Algoritmu můžeme rozumět jako předpisu pro řešení nějakého problému. Algoritmus je předpis, který se skládá z kroků a který zabezpečí, že na základě vstupních dat jsou poskytnuta požadovaná data výstupní. Navíc každý algoritmus musí mít následující vlastnosti: • • • •
jasný (algoritmus musí být srozumitelný), rezultativní (algoritmus vede ke správnému výsledku), konečný (požadovaný výsledek musí být poskytnut v "rozumném čase") opakovatelný (při použití stejných vstupních dat musí algoritmus vždy dospět k témuž výsledku).
Takto vytvořený algoritmus můžeme zapsat ve formě nějakého programovacího jazyka, v našem případě tedy v jazyce C++. Vytvoříme tak zdrojový text (.cpp). Zdrojový text musí být do spustitelného tvaru přeložen překladačem. Ten zároveň provádí kontrolu syntaxe. Překladač je program, který čte zdrojový kód a převádí ho na ekvivalentní program. Důležitou součástí tohoto procesu překladu jsou diagnostické zprávy, kterými překladač informuje uživatele například o přítomnosti chyb ve zdrojovém kódu. Výsledkem kompilace zdrojového kódu je objektový soubor (.obj). Stále se však nejedná o spustitelný soubor. Ten získáme až po spuštění sestavovacího programu (linker). Programy v jazyce C++ se vytvářejí propojením jednoho nebo více objektových souborů s jednou nebo více knihovnami. Knihovny jsou buď součástí překladače, nebo si je můžeme sami vytvořit popř. zakoupit. Postup při vytvoření spustitelného souboru je tedy následující: • • •
Vytvoření zdrojového textu s příponou .cpp. Tento zdrojový soubor se zkompiluje do souboru s příponou .obj. soubor .obj se propojí se všemi potřebnými knihovnami, čímž vznikne spustitelný program.
1.2. Objektově orientované programování (OOP) Objektově orientované programování je výkonný způsob, jak přistupovat k úloze programování. Již od svých ranných začátků bylo programování spojováno s rozličnými metodologiemi. První programy byly realizovány pouhým nastavením přepínačů na čelním panelu počítače. Tento postup však byl vhodný pouze pro velice malé programy. Později vytvořený jazyk symbolických instrukcí již umožňoval psaní delších programů. K dalšímu vývoji došlo v roce 1955, kdy byl vytvořen programovací jazyk vysoké úrovně FORTRAN. S využitím programovacích jazyků vysoké úrovně byl programátor schopen psát dlouhé a složité programy. Další rozvoj přinesl vývoj strukturovaných programovacích jazyků ALGOL a PASCAL. Strukturované programování se opírá o dobře definované řídící struktury, bloky kódu, vyloučení příkazu GOTO (nebo jeho minimalizace), funkce, rekurzi a lokální proměnné. Podstatou strukturovaného programování je začlenění programu do jeho základních
vymezovacích prvků. S využitím strukturovaného programování může průměrný programátor vytvořit a udržovat programy až o délce několika tisíc řádků. Když však program přesáhl určitou velikost, zklamalo i strukturované programování. Bylo potřeba vytvořit nový přístup k programování, který by dovolil psát složitější programy. K tomuto účelu bylo vytvořeno objektově orientované programování. OOP vzalo nejlepší myšlenky včleněné do strukturovaného programování a zkombinovalo je s výkonnými novými koncepty, které umožňovaly organizovat programy mnohem efektivněji. Objektově orientované programování podněcuje k rozkladu problému na elementární prvky. Každá komponenta se stává samostatným a nezávislým objektem, který obsahuje své vlastní instrukce a data, vztahující se k tomuto objektu. Všechny objektově orientované jazyky sdílejí následující tři vlastnosti: • Zapouzdření (encapsulation) • Polymorfismus - schopnost výskytu v mnoha formách (polymorphism) • Dědičnost (inheritance)
1.3. Zapouzdření Zapouzdření je mechanismus, který svazuje dohromady kód a data a zabezpečuje je před vnějšími zásahy či zneužitím. V OOP může být kód s daty slučován takovým způsobem, že vznikají tzv. nezávislé "černé skříňky". Spojením kódu s daty vzniká objekt. Jinými slovy lze říci, že objekt je instrument, který podporuje zapouzdření. Uvnitř objektu může být kód nebo data nebo obojí, jednak jako privátní (private) vzhledem k objektu, nebo jako veřejná (public). Privátní kód nebo data jsou známá a dostupná pouze pro jinou část daného objektu. Znamená to, že privátní kód nebo data nejsou dostupné z jiné části programu mimo objekt. Když jsou kód nebo data veřejná, mohou k nim přistupovat i jiné části programu. Typicky jsou veřejné prvky objektu využity k zajištění řízeného rozhraní k privátním elementům objektu.
1.4. Polymorfismus Polymorfismus je vlastnost, která umožňuje, aby bylo jedno jediné jméno použito pro dva nebo více souvisejících, ale technicky různých účelů. Ve vztahu k OOP umožňuje polymorfismus určit jedním jménem celou obecnou třídu procesů. Uvnitř obecné třídy procesů je pak volba konkrétního procesu dána typem dat. Obecně lze polymorfismus charakterizovat jako: "jedno rozhraní, mnoho metod". Výhodou polymorfismu je, že omezuje přílišnou složitost tím, že určením obecné třídy procedury povolí jediné rozhraní. Je pak záležitostí překladače, aby vybral konkrétní proceduru, vhodnou pro danou situaci.
Příklad: V jazyce C byly definovány funkce abs(), labs() a fabs() které vracely absolutní hodnotu z čísel integer, long integer a float. V C++ který podporuje polymorfismus mohou být všechny tyto funkce volány pod jediným jménem abs(). Tato vlastnost polymorfismu se nazývá vícenásobná definice funkce. Polymorfismus může být použit také na operátory.
Příklad: V jazyce C je znaménko + užito ke sčítání integer, long integer, znaku nebo hodnoty v pohyblivé řádové čárce. Ve všech případech překladač pozná, který typ aritmetiky má použit. V C++ je možné tento koncept podle vlastního uvážení rozšířit na další typy dat. Tato vlastnost polymorfismu se nazývá vícenásobná definice operátoru. Polymorfismus tedy dovoluje vytvářet standardní rozhraní k příslušným procesům.
1.5. Dědičnost Dědičnost je proces, při němž může jeden objekt získat vlastnosti jiného objektu. Přesněji řečeno objekt může zdědit obecnou sadu vlastností a do ní může přidat takové vlastnosti, které jsou specifické pouze pro něj. Dědičnost je důležitá, protože dovoluje objektu podporovat koncept hierarchické klasifikace.
Příklad: Popis domu. Dům je částí obecné třídy nazvané budova. Budova je zase částí obecnější třídy nazvané stavba, která je opět součástí obecné třídy objektů nazvané zhotovené člověkem. Třída potomka dědí veškeré vlastnosti spojené s rodiči a přidává si k nim své vlastní charakteristiky. Bez využití uspořádané klasifikace, by měl každý objekt definovány všechny charakteristiky, které se k němu vztahují. Prostřednictvím dědičnosti je možné zadat třídu (či třídy) do níž daný objekt patří a přiřadit mu specifické vlastnosti, které mají všechny objekty dané třídy.
Test - kapitola 1 1. Výsledkem kompilace je: Your answer:objektový soubor This answer is correct. 2. Mají překladač (kompilátor) a sestavovací program (linker) stejnou funkci? Your answer:ne This answer is correct. 3. Jak se jmenuje vlastnost, která umožňuje, aby bylo jedno jediné jméno použito pro dva nebo více různých účelů? Your answer:Polymorfismus This answer is correct. 4. Můžem si programátor sám vytvářet knihovny ? Your answer:ano This answer is correct. 5. Programy v jazyce C++ se vytvářejí propojením: Your answer:jednoho nebo více objektových souborů s jednou nebo více knihovnami. This answer is correct. 6. Jak se nazývá proces, při němž může jeden objekt získat vlastnosti jiného objektu? Your answer:Dědičnost This answer is correct.
Kapitola 2. - První programy Obsah 2.1. 2.2. 2.3. 2.4. 2.5.
První program Opakování části programu Funkce a metody Úvod do tříd Test
V této části si zkusíme napsat první jednoduché programy. Na kterých si ukážeme, co se musí k "vlastnímu programu" přidat, aby překladač dokázal převést zdrojový kód do spustitelného tvaru.
2.1. První program V každé učebnici programování se začíná programem, který má na obrazovku vypsat slova "Ahoj svete!". A tak ani tento kurz nebude vyjímkou. hello.cpp // hello.cpp #include
using namespace std; // prostor jmen int main() { cout << "Ahoj svete!" << endl; return 0; } // int main()
Nyní se můžeme podívat na to co má obsahovat každý zdrojový text: • • •
funkci main, jejímž zavoláním začíná každý program. pomocí preprocesoru začleňovat informace o deklaracích, které jsou pro překladač nezbytné. Zde je to řádek #include . informace o prostoru jmen do něhož budeme moci přistupovat using namespace std;, zde se jedná o standartní prostor jmen.
Protože je C++ rozšířenou množinou jazyka C, jsou všechny prvky jazyka C obsaženy také v C++. Snad nejběžnějším prvkem specifickým pro C++ je jeho přístup ke konzoli I/O. Můžeme stále používat funkce printf() a scanf(), ale C++ nám nabízí nový a lepší způsob jak provádět tyto operace a to pomocí I/O operátorů. Výstupní operátor je << a vstupní >>. Pro výstup na obrazovku proto použijeme příkaz: cout << "Prikaz jazyka C++";
který způsobí, že se na obrazovku počítače vypíše řetězec. Cout je předefinovaný datový proud (stream), který je při spuštění programu v C++ automaticky připojen ke konzoli. Je to podobné jako stdout v jazyce C. Použitím výstupního operátoru << lze provést výstup jakéhokoliv základního typu jazyka C++. cout << 25.125;
Obecný formát: cout << výraz;
Pro vstup hodnot z klávesnice poté používáme vstupní operátor >>. Následující příkaz například načte hodnotu typu integer do cislo. int cislo; cin >> cislo;
Obecný formát: cin >> výraz;
Příklad: Následující program požádá uživatele o zadání celého čísla. Poté vytiskne zadané číslo a jeho dvojnásobek a druhou mocninu. vstup.cpp //vstup.cpp #include using namespace std; int main(void) { int i; cout << "Zadejte celociselnou hodnotu: "; cin >> i; cout << "Zadana hodnota byla " << i << endl; cout << "Jeji dvojnasobek je " << i*2 << " a druha mocnina je " << i*i; return 0; }
2.2. Opakování části programu Opakované činnosti zpravidla do programu nezapisujeme opakovaně, ale snažíme se překladači sdělit, že má danou část programu vykonat několikrát. tato konstrukce se nazývá cyklus.
Příklad: Vypočtěme a zobrazme hodnoty prvních deseti násobků zadaného celého čísla. K řešení daného příkladu použijeme cyklus while. řídící podmínka cyklu je podmínka na začátku cyklu. Pokud je splněna provede se tělo cyklu nasobky.cpp // nasobky.cpp #include // vstupy a vystupy #include // formatovani vystupu using namespace std; int main() { const int od = 1; // dolni mez const int po = 10; // horni mez int cislo; cout << "Zadej cele cislo : " ; cin >> cislo ; int cinitel = od ; while (cinitel <= po) { int soucin = cislo * cinitel; cout << setw(4) << soucin; cinitel = cinitel + 1; } // while(cinitel <= po) return 0; } // int main()
Výstup programu nasobky.cpp: Zadej cele cislo: 7 7 14 21 28 35 42 49 56 63 70
2.3. Funkce a metody Funkce v jazyce C++ mají jednoznačné jméno a mají určen počet a typ argumentů a typ návratové hodnoty. návratový výraz: return ... ; přetížení funkce: stejné pojmenování funkce, která se liší argumenty. Funkcím, které jsou součástí tříd, říkáme metody. S daty se pak komunikuje prostřednictvím metod, které jsou součástí rozhraní.
Příklad: Naším úkolem je realizace zápisu hodnot různých typů na standartní výstup. Použité typy budou celá a racionální čísla, řetězec a námi vytvořený objekt. V programu jsou vytvořeny přetížené funkce tisk() , které se liší typem jejich argumentu (int, double a string). To je dostatečná informace pro překladač aby, aby při volání funkce tisk poznal, která přetížená funkce odpovídá našemu požadavku.
tiskni.cpp // tiskni.cpp #include // vstupy a vystupy #include <string> // retezcove definice a metody using namespace std; class polar{ double r, fi; public: polar(double a, double b) { r = a; fi = b;}; // konstruktor objektu double dej_r(void) {return r;}; double dej_fi(void) {return fi;}; }; void tisk(int num) { cout<< "cele cislo: " << num << endl; } // void tisk(int num) void tisk(double num) { cout << "racionalni cislo : " << num << endl; } // void tisk(double num) void tisk(strings) { cout << "retezec : " << s << endl; } // void tisk(strings) void tisk(polar p) { cout << " r : " << p.dej_r() << " fi: " << p.dej_fi() << endl; } // void tisk(polar p) int main() { int i = 29; double x = 2003.07; string s = "kousek textu"; polar y(65.43, 2.1); // tvorba objektu tridy polar tisk (i); tisk (x); tisk (s); tisk (y); return 0; } // int main( )
Výstup programu tiskni.cpp: cele cislo: 29 racionalni cislo: 2003.07 retezec: kousek textu r: 65.43 fi: 2.1
2.4. Úvod do tříd Snad nejdůležitějším prvkem jazyka C++ je třída. Je to mechanismus používaný k vytváření objektů. Jako taková je třída srdcem mnoha prvků jazyka C++. Třída je deklarována klíčovým slovem class. Syntaxe deklarace class vypadá v obecném tvaru takto: class jméno-třídy
{
// privátní funkce a proměnné public: // veřejné funkce a proměnné } seznam-objektů
Seznam objektů je v deklaraci třídy nepovinný. Stejně jako strukturu, můžeme deklarovat objekty třídy později - až budou potřeba. Zatímco jméno třídy je také technicky nepovinné, z praktického hlediska je vždy potřeba. Je to proto, že jméno třídy se stává novým typem jména použitého k deklaraci objektů třídy. Funkce a proměnné deklarovány uvnitř deklarace třídy jsou označovány jako členy této třídy. To znamená, že jsou přístupné pouze pro ostatní členy třídy. Pro deklaraci členů veřejné třídy se použije klíčové slovo public:. Všechny funkce a proměnné takto deklarované jsou přístupné pro členy třídy a i pro další části programu, který obsahuje třídu. Příklad jednoduché deklarace třídy: class mojetrida { int a; // privatni promenna public: void nastav_a(int num); int cti_a(); };
Tato třída má pouze jednu privátní proměnnou a a dvě veřejné funkce nastav_a(int num) a cti_a(). Funkce které jsou deklarovány uvnitř třídy se nazývají členské funkce. Jelikož a je privátní není dostupná pro žádný kód vně mojetrida. Ovšem veřejné funkce mohou být volány každou částí programu, která mojetrida obsahuje. Ačkoliv jsou funkce nastav_a(int num) a cti_a() deklarovány, nejsou ještě definovány. Abychom definovaly členskou funkci musíme spojit typové jméno třídy se jménem funkce a to pomocí dvojice dvojteček ::. Dvojice dvojteček se nazývá operátor rozlišení oblasti. Ukažme si jak mohou být funkce definovány: void mojetrida::nastav_a(int num) { a = num; } int mojetrida::cti_a() { return a; }
Obecný tvar pro definici členské funkce:
return-type jméno-třídy::jméno-funkce(seznam-argumentů) { // tělo funkce }
Deklarace mojetrida nedefinueje žádný objekt typu mojetrida. Definuje pouze typ objektu, když bude deklarován. Následující řádek deklaruje dva objekty typu mojetrida. mojetrida obj1, obj2;
Poté co vytvoříme objekt třídy může se program odkazovat na jeho veřejné členy pomocí tečkových operátorů. obj1.nastav_a(10); obj2.nastav_a(5);
Tyto příkazy nastaví kopii obj1 na 10, kopii obj2 na 5. Každý objekt obsahuje vlastní> kopii všech dat deklarovaných uvnitř třídy. Tedy obj1 je odlišné od a vázaného na obj2. Deklarace třídy je pouze logická abstrakce, jež definuje nový typ. Určuje, jak bude objekt daného typu vypadat. Teprve deklarace objektu vytváří fyzickou entitu daného typu. Objekt totiž zabírá paměť, ale definiční typ ne. Každý objekt třídy má svou vlastní kopii každé z proměnných deklarovaných uvnitř třídy.
Test - kapitola 2 1. Chceme-li zopakovat nějakou část kódu několikrát použijeme: Your answer:cyklus This answer is correct. 2. Kterým příkaz použijeme pro vstup hodnot z klávesnice ? Your answer:cin This answer is correct. 3. Je následující deklarace třídy správná? class mojetrida { int a; public: void nastav_a(int num); int cti_a(); };
Your answer:ano This answer is correct. 4. Který z těchto příkladů je tečkový operátor ? Your answer:. This answer is correct. 5. Co znamená přetížení funce? Your answer:Stejné pojmenování funkce, která se liší argumenty. This answer is correct. 6. Kterým příkazem posíláme data na standardní výstup ? Your answer:cout This answer is correct.
Kapitola 3. - Základní typy, konstanty a proměnné Obsah 3.1. Identifikátory a klíčová slova 3.2. Komentáře 3.3. Čísla v počítači a v C++ 3.4. Konstanty 3.5. Celočíselné konstanty 3.6. Racionální konstanty 3.7. Znakové konstanty 3.8. Proměnné a ukazatele 3.9. Definice uživatelských typů 3.10. Test V této části se seznámíme s identifikátory, klíčovými slovy a komentáři. dále se naučíme vytvářet konstanty a proměnné základních datových typů.
3.1. Identifikátory a klíčová slova Identifikátory Identifikátory jsou jména, která dáváme proměnným, funkcím, typům,... • • • •
délka až 1024 znaky, první symbol písmeno nebo podtržítko, následuje libovolná kombinace písmen, číslic a podtržítek, rozlišují se malá a velká písmena!
Klíčová slova Při použití klíčových slov platí stejná pravidla jako v jazyce C. Proto je tedy nesmíme používat v jiném významu, než jak určuje norma ISO C++, což znamená, že nesmí být použita jako jména proměnných nebo funkcí. Tabulka 3.1.1: Klíčová slova jazyka C++. inline short
asm
do
typeid
auto
double
int
signed
typename
bool
dynamic_cast
long
sizeof
union
break
else
mutable
static
unsigned
case
enum
namespace
static_cast
using
catch
explicit
new
struct
virtual
char
extern
operator
switch
void
class
false
private
template
volatile
const
float
protected
this
wchar_t
const_cast
for
public
throw
continue
friend
register
true
default
goto
reinterpret_cast
try
delete
if
return
typedef
while
3.2. Komentáře Komentáře umisťujeme do zdrojového kódu z důvodu jeho lepší čitelnosti. Některé důležité vlastnosti kódu popisujeme právě v komentářích. Na zakomentovanou část programu nejsou kladeny prakticky žádná omezení. V jazyce C++ můžeme vkládat poznámky do textu stejným způsobem jako v jazyce C. Tedy buď pomocí párových značek /* */ : /* Toto je "parovy" komentar a jeho pokracovani * / ... /* dalsi parovy komentar if ( uk->chyba ) { ts->pom_info ++; } // vlozeny radkovy komentar else stale jsme v komentari */
Nebo pomocí // za nimiž je vše až do konce řádku považováno za komentář. int a, b, c;
// jednořádkový komentář
odsazovač (bílý znak, prázdné místo, white space): mezera, tabulátor, nový řádek, posun řádku, návrat vozíku, nová stránka a vertikální tabulátor.
3.3. Čísla v počítači a v C++ V počítači jsou všecnhy informace zakódovány pomocí nul a jedniček (dvojková soustava). Pouze celá čísla jsou v počítači uložena přesně, reálná čísla jsou uložena pomocí reálné mantisy a exponentu. Umístěním modifikátoru unsigned před požadovaný datový typ, vytvoříme typ "bezznaménkový". Takto vytvořený datový typ může obsahovat pouze kladné hodnoty a nulu.
Tabulka 3.3.1: Základní datové typy a jejich rozsah v 32bitovém prostředí. datový typ bitů význam bool
nedef.
logická hodnota
char
8
znak
wchar_t
16
UNICODE znak
short
32
krátké celé číslo
int
32
celé číslo
long
32
dlouhé celé číslo
enum
32
výčtový typ
float
32
racionální číslo
double
64
racionální číslo s dvojitou přesností
long double
80
ještě delší racionální číslo
pointer
32
ukazatel
Vztahy pro rozsah základních číselných typů: short ≤ int ≤ long float ≤ double ≤ long double dále platí, že char vyžaduje 8 bitů. limits.h, float.h norma IEEE 754
3.4. Deklarace, definice a konstanty deklarace určuje typ objektu definice definuje hodnotu proměnné či posloupnost příkazů funkce. Jeden pojmenovaný objekt může být definován pouze jedenkrát, ale může být vícekrát deklarován. Konstanty pojmenované hodnoty, definované po klíčovém slově const Lze definovat konstanty všech základních datových typů. Z konstant odpovídajících si datových typů můžeme vytvářet konstantní výraz. Konstantní výrazy nesmí obsahovat žádný z následujících operátorů: • • • •
přiřazení, inkrementace a dekrementace, funkční volání, čárka.
Příklad: Jak je vidět na příkladu, můžeme definovat konstanty všech základních datových typů. Identifikátor meze představuje dvouprvkové pole typu float. Na identifikátorech arabska_hodn[] a rimska_znk[] vidíme, že pokud uvedeme všechny požadované hodnoty, nemusíme definovat dimenzi pole. Zápisem sizeof(arabska_hodn)/sizeof(int) zjišťujeme kolik prvků má pole arabska_hodn. konstant.cpp // konstant.cpp # include # include <string> using namespace std; const bool pravda = true; const int konstanta = 123; const celociselna = -987 ; const double CPlanck = 6.6256e-34; const char male_a = 'a' ; const string retezec = "Konstantni retezec." ; const float meze[2] = {-20, 60} ; const char rimska_znk[] = {'I', 'V', 'X', 'L', 'C', 'D', 'M'} ; const int arabska_hodn[] = {1, 5, 10, 50, 100, 500, 1000} ; void main () { cout << pravda << endl << konstanta << endl << celociselna << endl << CPlanck << endl << male_a << endl << retezec << endl ; for (int i = 0; i < sizeof(arabska_hodn) / sizeof(int); i++) cout << "i = " << i << ", rimsky : " << rimska_znk[i] << ", arabsky " << arabska_hodn[i] << endl; } // void main ()
Výstup programu konstant.cpp: 1 123 -987 6.6256e-034 a Konstantni retezec. i=0, rimsky:I, arabsky i=1, rimsky:V, arabsky i=2, rimsky:X, arabsky i=3, rimsky:L, arabsky i=4, rimsky:C, arabsky
1 5 10 50 100
i=5, rimsky:D, arabsky 500 i=6, rimsky:M, arabsky 1000
3.5. Celočíselné konstanty Celočíselné konstanty jsou tvořeny zápisem celého čísla v desítkové, v osmičkové, nebo v šestnáctkové soustavě. Základ číselné soustavy určuje uvození celočíselných konstant: • • •
0 v osmičkové soustavě , 0x nebo 0X v šestnáctkové soustavě , (A až F); libovolná jiná číslice v desítkové soustavě Tabulka 3.5.1: Celočíselné konstanty v desítkové a šestnáctkové soustavě. desítkový šestnáctkový 123
0x7b
-987
0xfc25
255
0xff
4567
0x11d7
-4567
0xee29
Je-li číslo ukončeno písmeny u, U, jedná se o čísla bez znaménka. Je-li číslo ukončeno písmeny l, L, jedná se o čísla s datovým typem long např. 254UL (254 typu unsigned long)
Pozor na čísla v osmičkové soustavě, mohou vám způsobit nečekané problémy! /*POZOR - cisla zacinajici nulou jsou v osmickove soustave */ int a[] = {128, 231, 127, 162, 067, 121, 034, 171};
3.6. Racionální konstanty Racionální konstanty jsou do paměti počítače ukládány pomocí mantisy a exponentu. např. 12.34e5 je zápisem racionálního čísla 1234000 Standartně je racionální konstanta typu double. Pokud chceme aby konstanta byla typu long double, připojíme za číslo L, např. 12.34e5L.
Počty platných číslic podle IEEE 754: 7 float , 15 double, 19 long double. ISO C++ norma rozsah exponentů pro všechny racionální datové typy je 10ˆ-38 až 10ˆ+38, přesnost typu float je nejméně šest platných číslic, přesnost typů double a long double pak nejméně deset platných číslic.
Přetečení a podtečení Přetečení a podtečení nastane, pokud číslo přesáhne meze pro datový typ uvedené v tabulce 3.6.1. Přesáhne-li hodnota horní mez nastane přetečení a nastává chyba. Pokud je číslo příliš malé je výsledkem prostě nula. Tabulka 3.6.1: Rozsahy racionálních datových typů. bitů mant. exp. rozsah absolutních hodnot
typ float
32
24
8
3,4 x 10ˆ-38 až 3,4 x 10ˆ+38
double
64
53
11
1,7 x 10ˆ-308 až 1,7 x 10ˆ+308
long double
80
64
15
3,4 x 10ˆ-4932 až 1,1 x 10ˆ+4932
3.7. Znakové konstanty Znakové konstanty jsou tvořeny znakem uzavřeným mezi apostrofy. Například: 'a' 'b' 'c' Nastává ovšem problém jak řešit některé speciální znaky (např. apostrof). Zde si pomáháme symbolem zpětné lomítko a jedním či více následujícími znaky. Těmto posloupnostem se říká escape sekvence.
posl.
Tabulka 3.7.1: Escape sekvence. jméno CTRL význam
\a
Alert (Bell)
G
pípnutí
\b
Backspace
H
návrat o jeden znak
\f
Formfeed
L
odstránkování
\n
Newline
J
na začátek nového řádku
\r
Carriage return M
na začátek aktuálního řádku
\t
Horizontal tab I
na další tabelační pozici
\v
Vertical tab
stanovený přesun dolů
\\
Backslash
zpětné lomítko
\'
Single quote
apostrof
\"
Double quote
uvozovky
\?
Question mark
otazník
K
\OOO
znak zadaný osmičkově
\xHH
znak zadaný šestnáctkově
Pro češtinu byl vytvořen standard UNICODE. V rámci tohoto kódování používáme datový typ wchar_t a před prvním apostrofem uvádíme písmeno L. Například 'č' zapíšeme L'č'. Konstantní řetězce: Řetězec je posloupnost (pole) jednoho či více znaků . "dve slova" "a" "Ahoj!" "Cela tato veta tvori jeden retezec." kódování UNICODE: L"konstantní řetězec"
3.8. Proměnné a ukazatele Proměnné jsou paměťová místa přístupná prostřednictvím identifikátoru jejichž hodnotu můžeme během výpočtu měnit. Proměnné definujeme uvedením datového typu, jenž je následován jedním či více identifikátory. // promenne a jejich pripadna inicializace int a, b, c, pocet = 0; float x, prumer = 0.0, odchylka = 0.0; double y;
Přímé pojmenování paměšového místa se nazývá proměnná. Nepřímému pojmenová ní se říká ukazatel. adresový operátor & dereference adresy * // korektni ziskani adresy do ukazatele int i, *pi; // vytvoreni promenne a ukazatele na int pi = &i; // korektni ziskani adresy *pi = 123; // prace se ziskanou adresou // nyni mame jak v *pi tak v i hodnotu 123
Obvyklou chybou u začínajících programátorů je použití ukazatele bez získání adresy. // pokus o pouziti neinicializovaneho ukazatele // pozor , chybny kod : int *p; *p = 123;
Pokud chceme inicializovat ukazatel a nemáme na mysli konkrétní datový typ, můžeme ho inicializovat pomocí prázdného typu void. Takto vzniklý ukazatel však nelze dereferencovat bez přetypování.
3.9. Definice uživatelských typů Pokud programátor potřebuje vytvořit a pojmenovat, nějaký svůj datový typ, použije konstrukci typedef
Konečně může programátor vytvořit vlastní datový typ z hodnot, které uvede v seznamu(výčtu) enum. Takový datový typ se obecně jmenuje výčtový. Výčtový datový typ umožňuje vytvořit nové typy a proměnné, jejichž hodnoty se omezují na množinu uvedených hodnot. Můžeme například deklarovat typ BARVA, který bude nabývat následujících hodnot: CERVENA, MODRA, BILA a ZELENA. enum BARVA { CERVENA, MODRA, BILA, ZELENA };
Každá výčtová konstanta (CERVENA, MODRA, BILA, ZELENA) má celočíselnou hodnotu. Jestliže neurčíme jinak, bude mít první konstanta hodnotu 0 a další vždy o jedničku větší. Výčtové konstanty můžeme inicializovat na konkrétní hodnoty a těm, které zůstanou neinicializovány, se přiřadí hodnota podle předcházejících konstant. enum BARVA { CERVENA=100, MODRA, BILA=300, ZELENA }; /* * * *
konstanta konstanta konstanta konstanta
CERVENA MODRA BILA ZELENA
má má má má
hodnotu hodnotu hodnotu hodnotu
100 101 300 301
* * * */
Příklad První konstrukce ukazuje definici menu_fcn ukazatele na funkci a následně vytvoření tříprvkového pole. Poté následuje konstrukce obsahující enum uvedená opět definicí nového datového typu typedef. u-typedef.cpp // definice uživatelských typů typedef void (*menu_fcn) (void); menu_fcn command[3];
// definice typu // použití nového typu
typedef enum { // definice výčtu Back = 8, Tab = 9, Esc = 27, Enter = 13, Down = 0x0150, Left = 0x014b, Right = 0x014d, Up = 0x0148, NUL = 0x0103, Shift_Tab = 0x010f, Del = 0x0153, End = 0x014f, Home = 0x0147, Ins = 0x0152, PgDn = 0x0151, PgUp = 0x0149 } key_t; // pojmenování výčtu ... key_t znak; // proměnná typu výčet a testování ... else if ((znak == Left) || (znak == Back)) ... else if (znak == Enter) ... else if (znak == Esc) ... else if ...
Test - kapitola 3 1. Konstanty definujeme po klíčovém slově: Your answer:const This answer is correct. 2. Maximální délka identifikátoru v normě ISO C++: Your answer:1024 znaků This answer is correct. 3. Mezi základní datové typy C++ nepatří: Your answer:number This answer is correct. 4. Určete jaké hodnoty vypíše program po svém provedení: int *a; *a=123; cout << *a << endl;
Your answer:program skončí chybovým hlášením This answer is correct. 5. Určete, který z těchto znaků nepovažujeme za escape sekvenci: Your answer:\w This answer is correct. 6. Komentář se v C++ uzavírá mezi tyto symboly: Your answer:/* */ This answer is correct. 7. Jaká bude v proměnné i hodnota po vykonání následující části programu? int i; i = 10 + 0173;
Your answer:133 This answer is correct. 8. Rozlišují se u identifikátoru malá a velká písmena Your answer:ano This answer is correct. 9. Rozsah racionálního datového typu int je: Your answer:32 bitů This answer is correct. 10. Určete jaké hodnoty vypíše program po svém provedení: int i,*pi; i=123; pi=&i; *pi=456; cout << *pi << " " << i << endl;
Your answer:456 456 This answer is correct.
Kapitola 4. - K čemu jsou operátory Obsah 4.1. Operátory jazyka C++ 4.2. Operátor přiřazení 4.3. Aritmetické výrazy 4.4. Logické hodnoty a operátory 4.5. Relační operátory 4.6. Bitové operátory 4.7. Adresový operátor 4.8. Reference 4.9. Podmíněný operátor 4.10. Přetypování výrazu 4.11. Správa paměti 4.12. Test V této kapitole se seznámíme s operátory jazyka C++ a jejich rozdělením.
4.1. Operátory jazyka C++ Operátor je znak či víceznakový symbol představující nějakou operaci. Operátory používáme zejména k vytváření výrazů. Zapíšeme-li například a-b, hovoříme o výrazu s operandy a a b a operátorem +. Všechny operátory jazyka C++ jsou uvedeny v tabulce 4.1.1.
pri.
operátor
Tabulka 4.1.1: Operátory jazyka C++ popis
asoc. l-hodn.
1
( )
volání funkce
→
ano
1
[ ]
indexování
→
ano
1
.
přístup k položkám
→
ano
1
->
přístup k položkám přes ukazatele
→
ano
1
::
rozlišení platnosti
→
ano
2
! ~
logická a bitová negace
←
ne
2
++
inkrementace ane deknerementace
←
ne
2
+
unární plus a mínus
←
ne
2
(typ)
přetypování
←
ne
2
*
dereference ukazatele
←
ne
2
&
získání adresy
←
ne
2
sizeof
velikost v bajtech
←
ne
--
2
new
alokace paměti
←
ne
2
delete
uvolnění paměti
←
ne
2
const_cast
přetypování
←
ano
2
static_cast
přetypování
←
ano
2
dynamic_cast
přetypování
←
ano
2
reinterpret_cast přetypování
←
ano
2
typeid
identifikace typu
←
ne
3
.* ->*
deref. třídních ukazatelů
.→
ano
4
*
/
násobení, dělení, modulo
.→
ne
5
+
-
sčítání, odečítání
.→
ne
6
<<
bitový posun vlevo, vpravo
.→
ne
7
<
>
menší než, větší než, menší nebo rovno, větší nebo rovno .→
ne
8
=
!=
rovno, nerovno
.→
ne
9
&
bitová konjunkce
.→
ne
10 ^
bitová nonekvivalence
.→
ne
11 |
bitová disjunkce
.→
ne
12 &&
logická konjunkce
.→
ne
13 ||
logická disjunkce
.→
ne
14 ?:
podmíněný výraz
←
ano
15 =
přiřazení
←
ano
15 += −= *= /= %=
složená přiřazení
←
ano
15 &= ˆ= |=
další složená přiřazení
←
ano
15 >>= <<=
další složená přiřazení
←
ano
16 ,
postupné vyhodnocení!
.→
ano
%
>> <=
>=
pri. - priorita operátoru, operátory výše v tabulce mají vyšší prioritu. asoc. - asociativita operátoru, šipka ukazuje směr vyhodnocení operátoru. l-hodn. - o operátoru říká, může-li být výsledek použit vlevo od příkazu přiřazení.
4.2. Operátor přiřazení Při většině výpočtů, musíme výsledek z pravé strany přiřadit na stranu levou. Jako příklad můžeme uvést všeobecně známou Pythagorovu větu: c = √(a² + b²) Aby překladač dokázal tento vztah zpracovat, musíme jej nejprve přepsat do zápisu v jazyce C++. c = sqrt(a*a + b*b);
Operátorem přiřazení v jazyce C++ je symbol =. Vlevo od = musí být výraz, odkazující se do paměti. Napravo od = musí být výraz, jehož hodnotu chceme po vyčíslení na dané místo v paměti přiřadit. Typ výrazu je určen typem operandů. Jsou-li operandy stejného typu, je výsledek téhož typu. Jinak je typu "největšího z uvedených operandů". Operátor přiřazení lze využít i pro současnou inicializaci více proměnných toutéž hodnotou. int a, b, c; a = b = c = -1;
ISO norma C++ nedoporučuje používat výrazy, kde se vlevo i vpravo od operátoru přiřazení mění stejná proměnná. cc = cc++ * 2; a[i] = i++;
4.3. Aritmetické výrazy Aritmetické výrazy konstruhujeme z operandů a aritmetických operátorů +, -, *, / a % (sčítání, odčítání, násobení, dělení a zbytek po celočíselném dělení). Aritmetické výrazy si ukážeme na následujícím příkladu.
Příklad: Příklad ukazuje inicializaci hodnot proměnných o1, o2, o3 a dále po očekávaných výsledcích i poslední neočekávanou hodnotu. Tato neočekávaná hodnota vznikla z důvodu přetečení. op-int01.cpp //op-int01.cpp #include // vstupy a vystupy #include // formatovani vystupu using namespace std; int main(){ int o1 = 123 ,o2 = 456 ,o3 = 295, v1, v2, v3; int c1 = 20000, c2 = 20001, vc; v1 = o1 * o2; v2 = o3 / 2; v3 = o3 % 2; cout << o1 << " * " << o2 << " = " << setw(5) << v1 << endl; cout << o3 << " / 2 = " << setw(5) << v2 << e n d l ; cout << o3 << " % 2 = " << setw(5) << v3 << e n d l ;
endl;
vc = c1 * c2 ; cout << endl << " nyni pozor :" << endl; cout << setw(10) << c1 << " * " << setw(10) << c2 << " = " << vc <<
endl;
cout << setw(10) << vc << " * " << setw(10) << 10 << " = " << vc*10 <<
return 0 ; } // int main()
Výstup programu op-int01.cpp : 123 * 456 = 56088 295 / 2 = 147 295 % 2 = 1 nyni pozor: 20000 * 20001 = 400020000 400020000 * 10 = -294767296
Manipulátory : formátování šířky výstupu na hodnotu, kterou manipulátoru předáváme (např. setw(5)). Přetečení : 32bitový překladač má omezený počet platných míst pro celá čísla. pokud násobíme velká čísla může dojít k celočíselnému přetečení.
Smíšené aritmetické výrazy Podívejme se nyní na aritmetické operace, v nichž argumenty jsou opět celočíselné, ale levá strana je racionální.
Příklad: Na příkladu vydíme, že výpočet probíhá podle operandů z pravé strany. Proto podíl 25/3 dává výsledek 8.0 a nikoliv 8.333. op-int-f.cpp int main(){ int i, j; double r, x; j = i = 5; j *= i; r = j / 3; x = j * 3; cout <<"i= " << i << " j= " << j << " r= " << r << " x= " << x << endl; return 0; } //int main()
Výstup programu op-int-f.cpp : i=5 j=25 r=8 x=75
4.4. Logické hodnoty a operátory Programovací jazyk C++ zavádí po logické hodnoty datový typ bool. Datový typ bool rozlišuje pouze dvě hodnoty, true a false (pravda a nepravda). Celočíselným převodem logických hodnot, získáme hodnoty 1 a 0. Logické operátory jsou &&, || a ! (konjunkce, disjunkce a negace), známé také jako logické and, or a not. Pravidla pro určení výsledku základních logických operací jsou uvedeny v tabulce 4.4.1. Tabulka 4.4.1: Výsledky základních logických operací. A B !A A && B A || B false
false
true
false
false
false
true
true
false
true
true
false
false
false
true
true
true
false
true
true
4.5. Relační operátory Relační operátory jsou: <, >, <=, >=, == a != (menší než, větší než, menší nebo rovno, větší nebo rovno, rovno a nerovno). Výsledkem relačních operací jsou logické hodnoty pravda a nepravda.
4.6. Bitové operátory Bitové operátory umožňují provádět operace nad jednotlivými bity. Mezi bitové operátory patří <<, >>, &, |, ~ a ^ (bitový posun vlevo, bitový posun vpravo, bitová konjunkce, bitová disjunkce, bitová negace a bitová nonekvivalence). Bitové operace jsou možné pouze s celočíselnými hodnotami. Pravidla pro určení výsledku bitových operací uvádí tabulka 4.6.1.
b1
Tabulka 4.6.1: Výsledky základních bitových operací. ~b1 b1 & b2 b1 | b2 b1 ^ b2 b2 not
and
or
xor
0
0
1
0
0
0
0
1
1
0
1
1
1
0
0
0
1
1
1
1
0
1
1
0
Bitový posun, MSB, LSB – při bitovém posunu vlevo (vpravo), se jednotlivé bity posouvají vlevo (vpravo). Na pozici nejvíce vpravo (nejvíce vlevo) se "přilepí" nula.
Bitová konjunkce &, disjunkce |, a nonekvivalence ˆ (and, or, xor) provádí příslušnou binární operaci s každým párem odpovídajících si bitů celočíselných operandů. Bitová negace ~ je unární, bitový doplněk.
Příklad: Na příkladu vidíme ukázku základních bitových operací a posunů. op-bit01.cpp // op-bit01.cpp #include // vstupy a výstupy #include // formátování výstupu using namespace std; int main() { int vlevo1 = 1 << 1; int vlevo7 = 1 << 7; cout.setf(ios::showbase); // ukáže základ číselné soustavy cout << " 1 << 1 = " << dec << setw(6) << vlevo1 << hex << setw(6) << vlevo1 << endl; cout << " 1 << 7 = " << dec << setw(6) << vlevo7 << hex << setw(7) << vlevo7 << endl; int vpravo1 = -1 >> 1; int vpravo8 = 512 >> 8; cout << " -1 >> 1 = " << dec << setw(6) << vpravo1 << hex << setw(13) << vpravo1 << endl; cout << "512 >> 8 = " << dec << setw(6) << vpravo8 << hex << setw(6) << vpravo8 << endl; int k = int d = int non cout << cout << cout << endl;
13 & 6; 13 | 6; = 13 ^ 6; " 13 & 6 = " << dec << setw(6) << k << hex << setw(6) << k << endl; " 13 | 6 = " << dec << setw(6) << d << hex << setw(6) << d << endl; " 13 ^ 6 = " << dec << setw(6) << non << hex << setw(6) << non <<
k = 2 & 1; d = 2 | 1; non = 2 ^ 1; cout << " 2 & 1 = " << dec << setw(6) << k << hex << setw(6) << k << endl; cout << " 2 | 1 = " << dec << setw(6) << d << hex << setw(6) << d << endl; cout << " 2 ^ 1 = " << dec << setw(6) << non << hex << setw(6) << non << endl; return 0; } // int main()
Výstup programu op-bit01 v 32bitovém prostředí : 1 << 1 = 2 0x2 1 << 7 = 128 0x80 -1 >> 1 = -1 0xffffffff 512 >> 8 = 2 0x2 13 & 6 = 4 0x4 13 | 6
= 15 0xf 13 ^ 6 = 11 0xb 2 & 1 = 0 0 2 | 1 = 3 0x3 2 ^ 1 = 3 0x3
4.7. Adresový operátor Adresový operátor &, umožňuje získat adresu objektu, na nějž je aplikován. Jeho použití souvisí s použitím ukazatelů.
Příklad: S pomocí adresového operátoru získáme adresu proměnné a přiřadíme ji ukazateli. Poté můžeme na výstup vypsat jak hodnotu proměnné tak její adresu op-addr.cpp // op-addr.cpp #include using namespace std; int main() { int i = 123, *pi; pi = &i; cout << "promenna i=" << i << " je umistena na adresu: " << pi << endl; return 0; } // int main()
Výstup programu op-addr.cpp: promenna i=123 je umistena na adresu: 0012FF7C
4.8. Reference S pomocí adresového operátoru můžeme vytvářet i konstrukci zvanou reference (odkaz), viz ukázka zdrojového textu. int i = 0; // celociselna promenna int & oi = i; // oi je vytvoreno jako odkaz na i oi = 2; // umisti 2 do promenne i (pres odkaz oi)
Vytvořená proměnná i je plnohodnotnou proměnnou s vlastnostmi, odpovídajícími umístění. Identifikátor oi je vytvořen jako odkaz na i.
4.9. Podmíněný operátor Podmíněný operátor ? je jediným ternárním operátorem v jazyce C++. To znamená, že se jedná o jediný operátor, který očekává tři hodnoty.
vysledek = (podminka) ? splneno : nesplneno;
Tento zápis by se dal číst takto: jestliže je podmínka splněna, přiřaď do výsledku splněno, jinak přiřaď nesplněno.
Příklad: Příklad počítá absolutní hodnotu zadaného čísla pomocí podmíněného operátoru. op-cond.cpp // op-cond.cpp #include using namespace std; int main() { int i, abs_i; cout << endl << "Zadej cele cislo: "; cin >> i; abs_i = (i < 0) ? -i : i; cout << "abs(" << i << ") = " << abs_i << endl; return 0; } // int main()
Výstup programu op-cond.cpp zadání čísla -123: Zadej cele cislo: -123 abs(-123) = 123
4.10. Přetypování výrazu Typ výrazu je v C++ určen typem operandů. Jazyk C++ nám pak umožňuje přetypovat výraz podle potřeby. Přetypování provádíme tak, že před hodnotu, kterou chceme přetypovat, napíšeme v kulatých závorkách typ, který chceme získat. (typ) vyraz int j; double r; r = j / 3;
V předcházejícím případě by byl příkaz vyhodnocen celočíselně a teprve poté by byl konvertován na datový typ double (např. při 25/3 by byl výsledek 8.0). int j; double r; r = (double) j / 3;
V předcházejícím případě byl výraz již přetypován a tudíž dělení probíhá nad daty typu double (např. při 25/3 již bude očekávaný výsledek 8.3333).
4.11. Správa paměti Pro práci s dynamickou alokací paměti zavádí C++ operátory new a delete. S pomocí new můžeme alokovat paměť a naopak operátorem delete takto alokovanou paměť vracíme zase zpět.
Příklad: V příkladu se pokusíme vysvětlit použití operátorů new a delete.
Hned první příkaz int *ptr = new int(3); deklaruje ptr jako ukazatel na celočíselný datový typ int a hned mu dynamicky alokuje paměť, který současně inicializuje hodnotou 3. Příkazem delete ptr; vrátíme nepotřebnou vyalokovanou paměť.
new-del.cpp // new-del.cpp #include using namespace std; int main() { int *ptr = new int(3); *ptr += 7; cout << "*ptr = " << *ptr << endl; delete ptr; int pocet; cout << "Kolikaprvkove pole chces vytvorit?:"; cin >> pocet; double *pole = new double[pocet]; int i = 0; while (i < pocet) { pole[i] = i + (double) i / 100; cout << pole[i] << endl; i++; } // while (i < pocet) delete [] pole; return 0; } // int main()
Test - kapitola 4 1. Určete jaké hodnoty vypíše program po svém provedení: int a, b, c=6; a=(c==2) ? 2:4; b=(a > c) ? c++:--a; cout << a << " " << b << "
2. 3. 4. 5.
" << c;
Your answer:3 3 6 This answer is correct. Při binární operaci 32 >> 1 je výsledek: Your answer:16 This answer is correct. Při binární operaci 10 ^ 7 je výsledek: Your answer:13 This answer is correct. Určete, který z těchto operátorů nepatří mezi binární: Your answer:! This answer is correct. Určete jaké hodnoty vypíše program po svém provedení: int a=3, b=8; double c,d; b+=a; c= b/a; d=c*b cout << c << "
" << d;
Your answer:Zahlásí chybu při překladu This answer is correct. 6. Určete jaké hodnoty vypíše program po svém provedení: int a; a = 51%4; cout << a;
Your answer:3 This answer is correct. 7. Určete jaké hodnoty vypíše program po svém provedení: int a=123,b=9,c,d; c=a%b; d=c--; cout << c << " " << d;
Your answer:5 6 This answer is correct. 8. Určete jaké hodnoty vypíše program po svém provedení: int a=10,b=4,c,d; c=a/b; d=c-- - ++b; cout << c << " " << d;
Your answer:1 -3 This answer is correct. 9. Seřaďte tyto opearátory podle jejich priority (vlevo největší): Your answer:-> ++ << & && This answer is correct. 10. Při binární operaci 32 << 4 je výsledek: Your answer:512 This answer is correct.
Kapitola 5. - Řízení vykonávání programu Obsah 5.1. Výrazový příkaz 5.2. Blok 5.3. Podmíněný příkaz if-else 5.4. Přepínač - switch 5.5. Cykly 5.6. Příkaz skoku 5.7. Výjimky 5.8. Oblast platnosti identifikátoru 5.9. Prostor jmen 5.10. Test této části si popíšeme pojem výrazový příkaz, co je to blok. Jak se rozhodovat, kterou část kódu vykonáme a kterou nebudeme vykonávat. Ukážeme si, jak funguje přepínač (switch). Také se naučíme několik způsobů jak vykonávat skupinu příkazů opakovaně. Nakonec kapitoly se seznámíme s pojmem výjimka, popíšeme si oblast platnosti identifikátoru a objasníme koncept zvaný prostor jmen.
5.2. Blok Pokud chceme použít v programových konstrukcích namísto jediného příkazu skupinu příkazů, uzavřeme tyto příkazy do složených závorek { a }. Tuto skupinu příkazů uzavřených mezi složené závorky nazýváme blokem. Jednotlivé příkazy se vykonávají v bloku v pořadí, v jakém jsou v něm zapsány. Blok může opět obsahovat další programové konstrukce a další bloky.
Příklad: Ukázkovým příkladem použití takového bloku může být příkaz if-else: if(výraz) { příkaz1; příkaz2; ... příkazN; } else { příkaz1; příkaz2; ... příkazN; }
Pokud je výraz vyhodnocen jako pravdivý provedou se všechny příkazy uvedené v bloku za if, jinak se provedou příkazy bloku následující za else.
Blok může obsahovat kromě lokální proměnných a příkazů i libovolné lokální deklarace a definice. Zapsat je můžeme jen na začátku bloku a jejich platnost je omezena na blok a případné další vnořené bloky. Několik poznámek k bloku: • • •
lokální proměnné v bloku mají paměťovou třídu auto vnořený blok nemusí být ukončen středníkem každé tělo funkce je blokem
5.3. Podmíněný příkaz if-else Tímto příkazem rozumíme příkaz if-else. Je to příkaz, který se používá k řízení chodu programu nejčastěji. Syntaxe úplného podmíněného příkazu je následující: if (podmínka) příkaz1 else příkaz2
Je-li podmínka splněna, tedy dává hodnotu true, vykoná se příkaz1, pokud podmínka splněna není, tedy jejím výsledkem je false, vykoná se příkaz2. Po vykonání jednoho z příkazů (popř. bloku) pokračuje chod programu za podmíněným příkazem. Poznámky: • • •
podmínka musí být uzavřena v kulatých závorkách jednoduché příkazy příkaz1 či příkaz2 jsou ukončeny středníkem, který je jejich součástí
místo některého ze zmíněných dvou příkazů může být umístěn blok , za který ovšem středník neuvádíme
Příklad: Program vypočítá a zobrazí podíl dvou reálných čísel zadaných uživatelem. Pokud je dělitel roven nule, vypíše program uživateli, že nulou nelze dělit. Jinak vypočte podíl.
if-else1.cpp /********************* * soubor if-else1.cpp *********************/ #include using namespace std; int main() { double a, b; cout << "Zadej dve racionalni cisla:"; cin >> a >> b; if (b == 0.0) cout << "\a\nNulou delit nelze!\n"; else { double podil = a / b; cout << "Jejich podil je: " << podil << endl; } return 0; } // int main()
Ukázkové výstupy programu if-else1.cpp: Zadej dve racionalni cisla:1 2 Jejich podil je: 0.5 Zadej dve racionalni cisla:3.4 0 Nulou delit nelze! Zadej dve racionalni cisla:22 7 Jejich podil je: 3.14286
Kromě úplného podmíněného příkazu můžeme použít i variantu bez else. Tato varianta vypadá takto: if (podmínka) příkaz
Je-li podmínka splněna, tedy dává hodnotu true, vykoná se příkaz, jinak se nevykoná nic. Chod programu pak pokračuje za podmíněným příkazem.
Dalším možným způsobem, jak využívat podmíněný příkaz, je vkládat příkaz if do příkazu ifelse. Tato konstrukce bývá často nazývána if-else-if a syntaxe vypadá takto: if (podmínka1) příkaz1 else if (podmínka2) příkaz2 else if (podmínka3) příkaz3 ... else if (podmínkaN) příkazN else příkazN+1
V této programové konstrukci bude proveden právě jeden z variantních příkazů. Pokud není v poslední části klíčové slovo else s příkazem příkazN+1 uvedeno, nemusí se provést žádný z příkazů.
Příklad: Program zjistí jaký alfanumemerický znak uživatel zadal. Případně vydá zprávu o zadání znaku jiného. Využijeme zde výše uvedené konstrukce if-else-if, abychom si usnadnili práci při vykonávání případných nadbytečných testů, protože jakmile je jedna z podmínek splněna, provede se jí odpovídájící příkaz a celý složený podmíněný příkaz je ukončen. V příkladu můžeme vidět použití složené podmínky v příkazu if. if-else2.cpp /********************** * soubor if-else2.cpp **********************/ #include using namespace std; int main() { char znak; cout << "Zadej alfanumericky znak:"; cin >> znak; cout << endl << "Zadal jsi "; if ((znak >= 'a') '' (znak <= 'z')) cout << "male pismeno"; else if ((znak >= 'A') '' (znak <= 'Z')) cout << "velke pismeno"; else if ((znak >= '0') '' (znak <= '9')) cout << "cislici"; else cout << "Nezadal jsi alfanumericky znak!"; cout << endl; return 0; } // int main()
5.4. Přepínač - switch Přepínač, pro nějž je v jazyku C++ vyhrazeno klíčové slovo switch, se používá pro jako příkaz pro vícenásobný výběr. Syntaxe tohoto příkazu vypadá takto: switch (podmínka) { case konst1: příkaz1 case konst2: příkaz2 ... case konstN: příkazN default: příkazD }
Po klíčovém slově switch následuje podmínka. Podmínka musí být uzavřena v kulatých závorkách a její výsledek musí být konvertovatelný na hodnotu celočíselného typu. Poté následuje tělo přepínače. To je tvořeno klíčovým slovem case, celočíselnou konstantou a dvojtečkou, případně speciální návěstí tvořené klíčovým slovem default následovaným dvojtečkou. Celočíselná konstanta použitá jako návěští daného přepínače musí být jedinečná. Po vyhodnocení podmínky se skočí na příslušnou část těla příkazu switch, pokud vyhodnocená podmínka odpovídá některé celočíselné konstantě (konst1,konst2,...,konstN) některého z case návěští. Program pokračuje prováděním příkazů umístěných za daným návěštím až do konce přepínače. Pokud shoda nalezena není a existuje návěští default, pak program provádí příkazy umístěné za tímto návěštím. Pokud návěští default neexistuje, je tělo přepínače přeskočeno a program pokračuje za celým příkazem switch.
V případě, že vyhodnocená podmínka odpovídá některému z case návěští, budou se provádět nejen všechny příkazy uvedené k tomuto návěští, ale i všechny příkazy umístěné v následujících case návěštích. Přerušit vykonávání těla přepínače můžeme umístěním příkazu break, s jehož pomocí bude program pokračovat za tělem přepínače. Tímto způsobem můžeme vytvořit větvení chodu programu v požadovaném počtu výpočetních větví.
Příklad: Program simuluje házení kostkou. Po spuštění zvolí "náhodně" číslo v mezích od 1 do 6 a pomocí příkazu switch přiřadí do pomocného řetězce, které z čísel padlo. Poté toto číslo vypíše. Všimněme si případ case 2:, který není ukončený příkazem break a tudíž se provedou i všechny následující příkazy až do následujícího příkazu break. switch-1.cpp /********************* * soubor switch-1.cpp **********************/ #include #include <string> #include using namespace std; int main() { string s; cout << "Hazim kostkou..." << endl; srand(time(NULL)); switch (rand() % 6 + 1) { case 1: s = "jednicka"; break; case 2: case 3: s = "dvojka nebo trojka"; break; case 4: s = "ctyrka"; break; case 5: s = "petka"; break; default: s = "sestka"; break; } cout << "Padla " << s << endl; return 0; } // int main()
Ukázkové výstupy programu switch-1.cpp: Hazim kostkou... Padla dvojka nebo trojka Hazim kostkou... Padla petka.
Hazim kostkou... Padla jednicka.
5.5. Cykly Cyklus je část programu, která je v závislosti na podmínce prováděna opakovaně. Touto částí programu může být jediný příkaz, ale častěji je jí blok. Cykly můžeme rozdělit podle toho, provede-li se tělo alespoň jedenkrát, a cykly, kde tělo nemusí být provedeno vůbec. Výběr vhodného cyklu je pak ponechán na uživateli.
Cyklus while Cyklus while je cyklus s podmínkou na začátku, tj. že podmínka cyklu while se testuje před průchodem cyklu. Tento cyklus tedy vůbec nemusí proběhnout. Syntaxe příkazu while je následující: while (výraz) příkaz
Po klíčovém slově while následuje testovací výraz, který musí být uzavřen v kulatých závorkách. Pokud má tento výraz hodnotu true, provádí se tělo cyklu. Po provedení všech příkazů v těle cyklu se opět testuje testovací výraz. To se provádí opakovaně dokud nemá testovací výraz hodnotu false. V takovém případě se přenese chod programu za tento cyklus. V cyklu se mohou objevit i příkazy break a continue s tímto významem: • •
Příkaz break uvedený v těle cyklu ukončí provádění příkazů těla cyklu a přenese chod programu za probíhající cyklus. Příkaz continue rovněž ukončí provádění příkazů těla cyklu. Řízení ovšem předá na řídicí podmínku cyklu. U cyklu typu while jde o první příkaz za while. Podmínka je vyhodnocena a podle výsledku bude opět vykonáno tělo cyklu, nebo je řízení předáno za cyklus.
Příklad: Program simuluje házení kostkou dokud nepadne POCET-krát šestka a počítá kolik pokusů k tomu bylo nutných. Tento údaj pak vypíše na výstup. while-1.cpp /********************* * soubor while-1.cpp *********************/ #include #include <string> #include using namespace std; int main() { static int celkem, pocet; const int POCET = 10; cout << "Hazim kostkou dokud mi nepadne " << POCET << "krat sestka..." << endl; srand((unsigned) time(NULL)); while (pocet < POCET) { celkem++; if ((rand() % 6 + 1) == 6) pocet++; } cout << "A je to! Hodu bylo celkem " << celkem << endl; return 0; } // int main()
Ukázkový výstup programu while-1.cpp: Hazim kostkou dokud mi nepadne 10krat sestka... A je to! Hodu bylo celkem 103
Cyklus for Cyklus for se používá jako cyklus pro známý počet opakování těla cyklu. Tělo cyklu může být tvořeno jedním příkazem nebo blokem. Cyklus for se provádí vícekrát nebo vůbec ne, dokud je hodnota nepovinného testovacího výrazu podmínka true. Syntaxe příkazu for je následující: for (inic_příkaz podmínka; výraz) příkaz
Výraz inic_příkaz je nepovinný a je proveden před prvním průchodem testu. Typicky se používá pro inicializaci proměnných před cyklem. Pak následuje podmínka, která je opět nepovinná. Při jejím uvedení se tato podmínka testuje před každým průchodem cyklu. Po každém provedení těla cyklu příkaz provede program nepovinný výraz výraz. Typicky se jedná o přípravu další iterace cyklu. V cyklu se mohou opět objevit i příkazy break a continue s významem stejným jako u cyklu while.
Příklad: Program vytiskne část ASCII tabulky znaků s kódy 32 až 127 včetně. for-1.cpp /********************** * soubor for-1.cpp **********************/ #include using namespace std; int main() { cout << endl; for (int znak = 32; znak < 128; znak++) { if (znak % 16 == 0) cout << endl; cout << (char) znak; } cout << endl; return 0; } // int main()
Ukázkový výstup programu for-1.cpp: !"#$%&'()*+,-./ 0123456789:;<=>? @ABCDEFGHIJKLMNO PQRSTUVWXYZ[\]^_ `abcdefghijklmno pqrstuvwxyz{|}~Ś
Cyklus do Cyklus do je cyklus s podmínkou na konci, tj. podmínka cyklu do se testuje až po průchodu cyklem. Tento cyklus tedy musí proběhnout aspoň jednou. Syntaxe příkazu do: do příkaz while (výraz);
Po klíčovém slově do následuje příkaz, což může být jeden příkaz nebo blok. Pak následuje klíčové slovo while a v kulatých závorkách podmínka. V cyklu se mohou opět objevit i příkazy break a continue s významem stejným jako u cyklu while.
Příklad: Program vytiskne opět část ASCII tabulky znaků s kódy 32 až 127 včetně. Výstup bude stejný jako u předchozího příkladu u cyklu for. dowhile1.cpp /********************** * soubor dowhile1.cpp **********************/ #include using namespace std; int main() { int znak = 32; cout << endl; do { cout << (char) znak++; if (znak % 16 == 0) cout << endl; } while (znak < 128); return 0; } // int main()
Poznámky k cyklům: •
pomocí cyklu typu while jsme schopni naprogramovat jakýkoliv cyklus
5.6. Příkaz skoku Jazyk C++ podporuje i příkaz nepodmíněného skoku nazvaný goto. Většina programátorů však příkaz goto nepoužívá, protože narušuje strukturu programu a pokud je používán často stává se napsaný kód nepřehledným. Později se může stát, že bude velice těžké pochopit správnou funkci takto napsaného programu. Syntaxe příkazu goto je následující: navěští: příkaz; goto návěští;
5.7. Výjimky Výjimky patří k doporučeným a moderním programátorským technikám. V jazyce C++ slouží jako mechanismus obsluhy chyb. A nesporně souvisí i s řízením chodu programu. Tento mechanismus výjimek je založen na třech klíčových slovech: • • •
try - zde uvádíme všechny výjimky, které chceme monitorovat catch - popisuje jak reagovat na vzniklé výjimky throw - slouží k vyvolání výjimky
Syntaxe je následující: try { hlídaný blok throw výjimka; } catch (typ_výjimky id){ zpracování výjimky - "handler" }
Do bloku za klíčové slovo try umístíme tu část kódu, ve které se mohou vyskytnout problémy. Pokud v hlídaném bloku nastane problém, musí být vyvolána výjimka. Jsou dvě možnosti, jak se dá výjimka vyvolat. Buď se jedná o typ výjimky, který podporuje ISO norma C++. Pak se o vyvolání výjimky postará některá z knihoven. Nebo se o vyvolání postará programátor pomocí klíčového slova throw spolu s typem výjimky, kterým může být celé číslo, řetězec nebo jiný objektový typ. O zachycení výjimky se stará kód uvedený za klíčovým slovem catch. Součástí catch je i pár kulatých závorek, obsahující buď výpustku ... - pak jsou zachyceny výjimky všech typů, nebo typ zachycované výjimky a případně i identifikátor id.
Příklad: Program načte řetězec, který zadá uživatel a vypíše jeho délku. Pak se snaží vypsat jednotlivé znaky řetězce. V cyklu for jsme však úmyslně uvedli, že má proběhnout právě 10-krát. A to při řetězcích s menší délkou než 10 vyvolá výjimku.
vyjimka-str.cpp /************************ * soubor vyjimka-str.cpp ************************/ #include #include #include #include
<exception> <stdexcept> <string>
using namespace std; int main() { string zadano; cout << "Zadej dlouhy retezec:"; // cin >> zadano; // načte jen do prvního odsazovače getline(cin, zadano, '\n'); cout << "retezec je dlouhy " << zadano.size() << " znaky" << endl; try {
for (int i = 0; i < 10; i++) { // misto 10 raději pišme zadano.size() cout << zadano.at(i) << ' ';
} } catch (out_of_range e) { cout << "Zachycena vyjimka!" << endl; cout << "Jeji popis je: " << e.what() << endl; } return 0; } // int main()
Ukázkové výstupy programu vyjimka-str.cpp: Zadej dlouhy retezec:zadavam dlouhy retezec retezec je dlouhy 22 znaky z a d a v a m d l Zadej dlouhy retezec:ahoj retezec je dlouhy 4 znaky a h o j Zachycena vyjimka! Jeji popis je: invalid string position
5.8. Oblast platnosti identifikátoru Pod tímto pojmem rozumíme to, v jak velké části programu má daný identifikátor svou platnost. Tedy kde s ním můžeme pracovat a kde se na něj můžeme odkazovat. Jméno identifikátoru má platnost v programu či bloku, v němž je identifikátor deklarován či definován a není-li překryto. Tzn. není-li třeba v nějakém vnořeném bloku použit identifikátor se stejným jménem. Oblast platnosti identifikátoru: • • • •
Na úrovni souboru začíná platnost deklarace místem deklarace a končí na konci souboru. V rámci bloku je deklarace platná do konce bloku. Argumenty funkce mají rozsah od místa deklarace argumentu v rámci funkce až do ukončení vnějšího bloku funkce. U deklarace funkce končí platnost touto deklarací. Jméno makra je platné od jeho definice až do místa, kdy je definice odstraněna.
5.9. Prostor jmen Jazyk C++ nám dává mocný nástroj, jak řešit problémy s případnými konflikty v oblasti identifikátorů. Standardní prostor jmen používáme, zapíšeme-li v našem programu tento řádek: using namespace std;
Chceme-li používat vlastní prostor jmen, musíme ho deklarovat podle následujícího syntaktického schématu: namespace prostor_jmen {seznam deklarací}
Nejdříve uvedeme klíčové slovo namespace. Pak následuje prostor_jmen, pomocí kterého se na náš prostor jmen odkazujeme. A ve složených závorkách seznam našich deklarací. K identifikátorům v našem prostoru jmen pak přistupujeme možnými dvěma způsoby: • •
buď použití našich identifikátorů předchází konstrukce using prostor_jmen nebo použitím operátoru čtyřtečka :: ve formě prostor_jmen::identifikátor
Příklad: Příklad použití identifikátoru id na třech úrovních. Na globálni úrovni, v nově vytvořeném jmenném prostoru CPP_pro_zelenace a na lokální úrovni v rozsahu platnosti těla funkce main().
scope-jmena.cpp /********************************** * scope-jmena.cpp * definice a použití prostoru jmen **********************************/ #include const char *id = "soubor scope_jmena.cpp"; // globální konstanta namespace CPP_pro_zelenace { const char * id = "Saloun. C++ pro zelenace."; // konstanta ve jmenném prostoru CPP_pro_zelenace } int main() { const char *id = "telo funkce main"; // lokální konstanta ve funkci main() std::cout << id << std::endl; // lokální std::cout << ::id << std::endl; // globální std::cout << "Dobra kniha: " << CPP_pro_zelenace::id << std::endl; return 0; } // int main()
Ukázkový výstup programu scope-jmena.cpp: telo funkce main soubor scope_jmena.cpp Dobra kniha: Saloun. C++ pro zelenace.
Test - kapitola 5 1. Určete jaké hodnoty vypíše program po svém provedení: int a = 2; if(a == 10) cout << 5;
Your answer:nevypíše žádnou hodnotu This answer is correct. 2. Mechanismus výjimek není založen na tomto klíčovém slově: Your answer:case This answer is correct. 3. Lokální proměnnou definovanou v bloku lze použít ? Your answer:v tomto bloku a ve všech vnořených blocích This answer is correct. 4. Určete jaké hodnoty vypíše program po svém provedení: int a=2, b=3; for(;a < 6;a++) { if(a == 3) continue; b+=3; } cout << b;
Your answer:12 This answer is correct. 5. Určete jaké hodnoty vypíše program po svém provedení: int a = 1, b = 1; if(a < b) a = 1; else if (a == b) a = 2; else if (a > b) a = 3; cout << a;
Your answer:2 This answer is correct. 6. U kterých z těchto cyklů se musí jeho tělo vykonat alespoň jednou: Your answer:Cyklus do This answer is correct. 7. Umožňuje nám jazyk C++ vytvářet vlastní prostor jmen ? Your answer:ano This answer is correct. 8. Určete jaké hodnoty vypíše program po svém provedení: int a=2, b=0; b=--a; switch (b) { case 1: a=5; b++; case 2: b=3; case 3: b*=a; break; case 4: b--; default: b=7; } cout << b;
Your answer:15 This answer is correct. 9. Co je to blok ? Your answer:skupina příkazů uzavřených mezi složené závorky This answer is correct.
10. Určete jaké hodnoty vypíše program po svém provedení: int a = 5; if(a < 10) { a=12; if (a < 10) a = 2; } cout << a;
Your answer:12 This answer is correct.
Kapitola 6. - Funkce Obsah 6.1. Popis funkce 6.2. Argumenty funkcí a způsob jejich předávání 6.3. Paměťové třídy 6.4. Rekurze 6.5. Cizí funkce 6.6. Přetěžování identifikátoru funkce 6.7. Přetížení operátorové funkce 6.8. Imlicitní hodnoty argumentů funkce 6.9. Inline funkce 6.10. Funkce s proměnným počtem argumentů 6.11. Argumenty příkazového řádku 6.12. Test V této části si vysvětlíme pojem funkce, způsoby tvorby vlastních funkcí, předávání argumentů funkcím a vše co s tímto pojmem souvisí. Na závěr této kapitoly si vysvětlíme vytváření funkcí s proměnným počtem argumentů.
6.1.Popis funkce Funkce tvoří základní stavební kámen procedurálního programování. Funkce řeší nějaký ucelený problém. Neměla by být příliš rozsáhlá, protože by se tak stala nepřehlednou a obtížně modifikovatelnou. A proto pokud je problém příliš složitý, volá na pomoc další funkce. Existuje množství variant použití funkce. Funkce může, ale nemusí, vracet návratovou hodnotu. Funkce může, ale nemusí, mít argumenty. Každý C++ program obsahuje alespoň jednu funkci main(). Ta je vyjímečná tím, že touto funkcí začíná provádění každého programu a typ návratové hodnoty by měl být int nebo void. Použijeme-li první uvedený návratový typ int, pak takový program může snadněji spolupracovat s navazujícími úlohami. Použijeme-li jako typ návratové hodnoty typ void, pak zamýšlíme tuto úlohu jako nezávislou. Pro popis funkce použijeme následující syntaxi: typ jméno(formální argumenty) { tělo funkce }
Když začínáme psát funkci, musíme nejdříve určit typ její návratové hodnoty. Poté nazveme funkci jménem (jméno), abychom mohli funkci v programu identifikovat a na funkci se v programu odvolávat. Za identifikátorem funkce následuje pár kulatých závorek, ve kterých uvádíme formální argumenty funkce. Poslední a jednou z nejdůležitějších částí je tělo funkce, kde pomocí příkazů určujeme chování (činnost) funkce. Tato část je uzavřená do složených závorek { a }. Celý tento způsob vytvoření funkce se nazývá definice funkce.
Pojem formální argumenty používáme z prostého důvodu, že tyto argumenty zastupují v těle funkce skutečné argumenty, se kterými byla funkce volána. V případě, že funkce nemá žádné formální argumenty, necháme za identifikátorem funkce prázdný pár kulatých závorek. V jazyce C bylo nutné uvést klíčové slovo void. Funkce může také obsahovat proměnný počet argumentů, použijeme-li při deklaraci formálních argumentů výpustku - ... . Pokud je argumentů více, oddělujeme je navzájem čárkami. Každý argument musí mít samostatně určen datový typ. Voláním funkce říkáme, že chceme provést kód, který je funkcí definován. Při volání funkce musí být za jménem funkce vždy uvedeny závorky. V nich pak mohou být uvedeny argumenty, které při volání funkci předáváme. Pokud závorky za jménem funkce neuvedeme, jde o adresu funkce. Adresou funkce rozumíme adresu vstupního bodu funkce. V místě, odkud funkci voláme, jí předáváme skutečné argumenty, které představují identifikátory proměnných a konstant, nebo výrazy. Syntaxe je následující: jméno(skutečné argumenty)
Tělo funkce obsahuje kód funkce a je uzavřené do bloku.Tedy může obsahovat lokální proměnné. V těle funkce také musíme určit, jakým způsobem získáme návratovou hodnotu. Návratová hodnota funkce musí být výsledkem výrazu uvedeného jako argument příkazu return. Typ tohoto výrazu se musí shodovat, nebo být alespoň slučitelný, s deklarovaným typem návratové hodnoty funkce. Syntaxe příkazu return je následující: return výraz_vhodného_typu;
Existuje možnost vracet nejen hodnotu daného typu, ale i ukazatel na hodnotu daného typu, odkaz (referenci),ukazatel na funkci, ukazatel na pole, atd. Funkce nesmí vracet funkci a pole. Pokud nás návratová hodnota funkce nezajímá, tak ji prostě jako výsledek volání nepoužijeme. Pokud ovšem chceme deklarovat, že funkce nevrací žádnou hodnotu, pak použijeme klíčové slovo void. Činnost funkce jejichž návratový typ není void, musí vždy končit příkazem return. Funkce typu void končí rovněž dosažením konce bloku, který tvoří tělo funkce.
Příklad: Příklad definice funkce: int isqr (int i) { return i * i; }
a jejího volání: ... int vysledek vysledek=isqr(4);
6.2. Argumenty funkcí a způsob jejich předávání Argumenty můžeme funkcím jazyka C++ předávat třemi způsoby: • • •
hodnotou (kopií) adresou odkazem
Před každý formální argument musíme při deklaraci či definici funkce uvést jeho datový typ. Současně s tím určujeme i způsob, jakým se budou hodnoty skutečných argumentů předávat argumentům formálním. Dále platí, že pořadí formálních a skutečných argumentů si odpovídá. Tedy že první skutečný argument je v těle funkce zastupován prvním formálním argumentem, druhý druhým, atd. Přirozeně se vyžaduje, aby jejich datové typy byly stejné, nebo alespoň slučitelné. Přesná pravidla uvádí norma ISO C++. Kdybychom chtěli, aby funkce vrátila pouze jednu návratovu hodnutu, stačil by nám jeden způsob předávání argumentů funkcím. Obecně ale potřebujeme, aby funkce vrátila více než návratovou (tedy jednu) hodnotu. Obdobný problém vzniká při předávání rozsahlého pole. Z tohoto důvodů existují v jazyce C++ tří způsoby předávání argumentů funkcím.
Předávání hodnotou Při předávání hodnotou jsou funkci předány kopie hodnot skutečných argumentů. Formální argumenty se odkazují na tyto kopie. Díky tomu se změna hodnoty formálního argumentu nepromítne do změny hodnoty argumentu skutečného.
Předávání adresou Při předávání adresou se mění hodnoty skutečných argumentů. Toto řešení zpočívá v tom, že nepředáváme hodnoty skutečných argumentů, ale jejich adresy. Formální argumenty pak budou ukazatelé na příslušný datový typ.
Předávání odkazem Předávání odkazem rovněž mění hodnoty skutečných argumentů. Tento způsob je novinkou v jazyce C++, v jazyce C tento způsob nenalezneme. Princip je v tom, že formální argumenty se díky činnosti překladače stanou synonymy argumentů skutečných. Díky tomu se změny prováděné na formálních argumentech v těle funkce realizují přímo v místě skutečných argumentů.
Příklad: Program ukazuje jak zaměnit hodnoty dvou proměnných pomocí výše uvedených tří variant. Ukazuje, že zaměnit hodnoty pomocí předáváním hodnotou nelze.
fn-argumenty.cpp
/********************************* * soubor fn-argumenty.cpp * předávání argumentů funkcím *********************************/ #include using namespace std; void zamenit_hodnotou_nejde(int fa, int fb) { int pomocna = fa; fa = fb; fb = pomocna; } // void zamenit_hodnotou_nejde(int fa, int fb) void zamen_ukazatelem(int *fa, int *fb) { int pomocna = *fa; *fa = *fb; *fb = pomocna; } // void zamen_ukazatelem(int *fa, int *fb) void zamen_odkazem(int& fa, int& fb) { int pomocna = fa; fa = fb; fb = pomocna; } // void zamen_odkazem(int& fa, int& fb) int main() { int sprvni= 123, sdruhy = -456; cout << "pocatecni stav" << endl; cout << "sprvni= " << sprvni<< ", sdruhy = " << sdruhy << endl << endl; zamenit_hodnotou_nejde(sprvni, sdruhy); cout << "po volani fce 'zamenit_hodnotou_nejde()'" << endl; cout << "sprvni= " << sprvni<< ", sdruhy = " << sdruhy << endl << endl; zamen_ukazatelem(&sprvni, &sdruhy); cout << "po volani fce 'zamen_ukazatelem()'" << endl; cout << "sprvni= " << sprvni<< ", sdruhy = " << sdruhy << endl << endl; zamen_odkazem(sprvni, sdruhy); cout << "po volani fce 'zamen_odkazem()'" << endl; cout << "sprvni= " << sprvni<< ", sdruhy = " << sdruhy << endl; return 0; } // int main()
Ukázkový výstup programu fn-argumenty.cpp: pocatecni stav skutecny1 = 123, skutecny2 = -456 po volani fce 'zamenit_hodnotou_nejde()' skutecny1 = 123, skutecny2 = -456 po volani fce 'zamen_ukazatelem()' skutecny1 = -456, skutecny2 = 123 po volani fce 'zamen_odkazem()' skutecny1 = 123, skutecny2 = -456
6.3. Paměťové třídy Při deklaraci proměnné můžeme nepovinně uvést paměťovou třídu, čímž překladači říkáme, kde chceme naši proměnnou umístit. Jedná se o klíčová slova auto, extern, register, static a typedef. Paměťovou třídu můžeme upřesnit ještě tzv. typovou částí. Jedná se o klíčové slovo const a klíčové slovo volatile. Vysvětlení nalezneme v následujících tabulkách: Tabulka 6.3.1: Paměťové třídy Paměťová výklad třída umístění na zásobník, auto neinicializované nevytvářet, bude připojen z jiného extern modulu přání umístit do registru procesoru, register neinicializované umístění do datového segmentu, static inicializované nulou nemá paměťový význam, typedef pojmenovává novou definici Tabulka 6.3.2: Modifikátory v C++ Modifikátor význam const vyjadřuje neměnitelnost vyjadřuje neustálou proměnnost volatile nekešovat Pokud paměťovou třídu neurčíme při deklaraci, platí existují implicitní pravidla pro určení paměťové třídy. Tabulka 6.3.3: Pravidla pro implicitní určení paměťové třídy paměťová objekt třída,výklad,umístění globální static a extern, inicializovaný proměnné nulou, DS lokální auto, neinicializovány, zásobník proměnné formální auto, neinicializovány, zásobník argumenty definice funkce extern, definice dle kódu, CS
6.4. Rekurze Rekurze je proces, ve kterém je něco definováno samo sebou. Aplikujeme-li to na počítačový jazyk, rekuze znamená, že funkce může volat sama sebe. Ne všechny programovací jazky podporují rekurzi. Jazyk C++ však ano. Je důležité vědět, že neexistují vícenásobné kopie rekurzivní funce. Existuje pouze jediná. Když je volána funkce, vyhradí se v zásobníku místo pro její argumenty a lokální proměnné. Když je tedy funkce volána rekurzivně, začíná pracovat s novou sadou argumentů a lokálních proměnných, ale kód, který tvoří funkci, zůstává stejný. Zásobník je datová struktura, ze které můžeme jako první vyjmout hodnotu, kterou jsme do zásobníku vložili jako poslední. Typu této struktury říkáme LIFO (od anglického last in first out). O provozování zásobníku se programátor nestará.
Příklad: Program vypočte faktoriál zadaného čísla pomocí rekurzivní funkce. fact-r.cpp /******************* * soubor fact-r.cpp * faktoriál rekurzí *******************/ #include using namespace std; double fact(long n) { if (n == 0L) return 1.0L; else return n * fact(n-1); } // double fact(long n) int main() { static long n; cout << "Pro vypocet faktorialu zadej prirozene n:"; cin >> n; cout << n << "! = " << fact(n) << endl; return 0; } // int main()
Ukázkové výstupy programu fact-r.cpp: Pro vypocet faktorialu zadej prirozene cislo n:5 5! = 120 Pro vypocet faktorialu zadej prirozene cislo n:69 69! = 1.71122e+098
Pokud při rekurzi vyžadujeme sdílení nějaké proměnné všemi vnořenými voláními rekurzivní funkce, můžeme takovou proměnnou navíc, tj. kromě jejího datového typu, deklarovat jako
static. Proměnná pak není umístěna v zásobníku s paměťovou třídou auto, ale v datovém
segmentu programu, kde má vyhrazeno pevné místo a navíc je inicializovaná hodnotou 0. Vyhrazené paměťové místo je společné všem vnořeným rekurzivním voláním funkce. Tím se liší od typické lokální proměnné třídy auto, která má pro každé vnoření rekurze další nové místo na zásobníku.
Příklad: Program rozšiřuje předchozí příklad o statickou celočíselnou proměnnou h. Při každém volání fact() zobrazíme její hodnotu, i hodnotu n. fact-rst.cpp /******************************************* * soubor fact-rst.cpp * faktoriál rekurzí se statickou proměnnou *******************************************/ #include using namespace std; double fact(long n) { static int h; double navrat; cout << "hloubka= " << ++h << "\tn= " << n << endl; if (n == 0L) navrat = 1.0L; else navrat = n * fact(n-1); cout << "hloubka= " << h-- << "\tn= " << n << "\tnavrat= " << navrat << endl; return navrat; } // double fact(long n) int main() { long n; cout << "Pro vypocet faktorialu zadej prirozene n:"; cin >> n; cout << n << "! = " << fact(n) << endl; return 0; } // int main()
Ukázkový výstup programu fact-rst.cpp: Pro vypocet faktorialu zadej prirozene cislo n:5 hloubka= 1 n= 5 hloubka= 2 n= 4 hloubka= 3 n= 3 hloubka= 4 n= 2 hloubka= 5 n= 1 hloubka= 6 n= 0 hloubka= 6 n= 0 navrat= 1 hloubka= 5 n= 1 navrat= 1 hloubka= 4 n= 2 navrat= 2 hloubka= 3 n= 3 navrat= 6 hloubka= 2 n= 4 navrat= 24 hloubka= 1 n= 5 navrat= 120 5! = 120
6.5. Cizí funkce V C++ můžeme používat i funkce jazyka C. Výčet těchto hlaviček ukazuje následující tabulka: Tabulka 6.5.1: Hlavičky původních knihoven jazyka C Standardních hlavičky knihoven jazyka C++ ukazuje následující tabulka: Tabulka 6.5.2: Hlavičky knihoven jazyka C++ <list> <streambuf> <string> <map> <set> <deque> <memory> <sstream> <exception> <stack> <stdexcept> Deklarace funkce obsahuje typ návratové hodnoty spolu s typy a identifikátory případných argumentů. Určení identifikátorů těchto argumentů však není nezbytné. Stačí, uvedeme-li identifikátory až při definici funkce. Deklarace funkce je ukončena středníkem. Je to vlastně informace pro překladač, určuje rozhraní funkce. Vícenásobné stejné deklarace jsou umožněny a překladači nevadí. Definice funkce je až na středník stejná jako její deklarace. Zmíněný středník je nahrazen začátkem bloku, v němž je definována činnost funkce. Překladem definice funkce se vytváří základ
přeloženého funkčního kódu našeho programu. Definice funkce tedy má paměťové nároky. Pokud uvedeme pouze definici funkce, na kterou se později v souboru odvoláváme, slouží tato definice současně jako deklarace. Definici smíme uvést jen jednou. V případě neshody deklarace a definice funkce ohlásí překladač chybu. ISO C++ vyžaduje deklaraci každé funkce, kterou chceme použít. Tento požadavek výrazně zvyšuje bezpečnost - umožňuje typovou kontrolu. Proto musíme začleňovat hlavičky tehdy, když používáme standardní funkce - hlavičky obsahují jejich deklarace. Deklarace našich vlastních funkcí umísťujeme především do hlavičkových souborů. Ty obsahují pouze deklarace funkcí, případně definice tříd, konstant... V každém případě hlavičkový soubor nesmí obsahovat objekty, které mají paměťové nároky. Výjimkou jsou inline funkce.
6.6. Přetěžování identifikátoru funkce Přetěžováním identifikátoru funkce rozumíme skutečnost, že existuje více funkcí se stejným identifikátorem. Jedinou podmíkou je, že se funkce musí lišit počtem či typem argumentů. Podle těchto kritérií se pak rozhoduje, která funkce se provede.
Příklad: Program realizuje zápis hodnot různých typů do standardního výstupu. Použité typy budou celá a racionální čísla, řetězec a námi vytvořený objekt. Použijeme k tomu přetížené funkce tiskni(). tiskni.cpp /********************** * soubor tiskni.cpp **********************/ #include // vstupy a výstupy #include <string> // řetězcové definice a metody using namespace std; class polar { double r, fi; public: polar(double a, double b) {r = a; fi = b;}; // konstruktor objektu double dej_r(void) {return r;}; double dej_fi(void) {return fi;}; }; void tisk(int num) { cout << "cele cislo: " << num << endl; } // void tisk(int num) void tisk(double num) { cout << "racionalni cislo: " << num << endl; } // void tisk(double num) void tisk(string s) { cout << "retezec: " << s << endl; } // void tisk(string s)
void tisk(polar p) { cout << "r: " << p.dej_r() << " fi: " << p.dej_fi() << endl; } // void tisk(polar p) int main() { int i = 29; double x = 2003.07; string s = "kousek textu"; polar y(65.43, 2.1); // tvorba objektu třídy polar tisk(i); tisk(x); tisk(s); tisk(y); return 0; } // int main()
Ukázkový výstup programu tiskni.cpp: cele cislo: 29 racionalni cislo: 2003.07 retezec: kousek textu r: 65.43 fi: 2.1
6.7. Přetížené operátorové funkce Operátory v C++ mohou mít jeden, dva, nejvýše však tři operandy. Podobně jako funkce, která může mít několik argumentů, můžeme operátory v C++ přetěžovat. Vyjímkou jsou operátory: . .* :: ?: . Ty přetížit nelze. Přetížení operátorů C++ umožňuje předefinovat operátor jako tzv. přetíženou operátorovou funkci. Přetížený operátor může být podle kontextu používán v původním významu, nebo ve významu, který definuje odpovídající přetížená operátorová funkce. C++ chápe operátor jako funkci s jedním či dvěma argumenty. Podmínkou přetížení je, že alespoň jeden z operandů musí být objektového typu (instancí třídy) definovaný pomocí class (nebo struct). Přetížení zachovává původní prioritu i asociativitu operátorů. U unárních operátorů ++ a -- lze rozlišovat jejich prefixový či postfixový zápis. Přetížení new a delete umožňuje variantní alokaci či dealokaci volného paměťového prostoru (haldy). Přetížený operátor new musí: • •
vracet ukazatel typu void* mít první argument typu size_t
Přetížený operátor delete:
• • •
nesmí mít žádnou návratovou hodnotu (ani její určení jako void) musí mít ukazatel typu void* jako svůj první argument může mít druhý argument typu size_t (jako nepovinný)
Možnosti přetížení operátoru: •
•
•
•
operátor funkčního volání (): zápis x(arg1, arg2); odpovídá x.operator() (arg1, arg2); operátor přístupu do pole []: zápis x[y]; odpovídá x.operator[] (y); operátor členského přístupu ->: zápis x->m; odpovídá (x.operator->())->m; operátor přiřazení = musí být deklarován jako členská metoda: X& operator= (X& x); s možnými modifikátory const a volatile Přiřazení úzce souvisí s kopírovacím konstruktorem.
Příklad: Program umožňuje snižování a zvyšování hodnoty čítače objektu třídy Citac, v mezích od 0 do konstanty mez. Přetížení unárních operátorů ukazuje prefixovou i postfixovou definici. Ukazuje také přetížení operátoru funkčního volání. opf-un.cpp /******************************* * soubor opf-un.cpp * přetížení unárního operátoru *******************************/ #include using namespace std; const int mez = 12345; class Citac { unsigned int hodnota; public: Citac(void) {hodnota = 0;} void operator++(int) { // postfixová verze if (hodnota < mez) hodnota++; } void operator--() { // prefixová verze if (hodnota > 0) hodnota--; } unsigned int operator()() { // metoda operátoru() return hodnota; } }; // class Citac
int main() { Citac citac; for (int i=0; i < 5; i++) citac++; // Citac.operator++() citac--; // varování při překladu - warning cout << "hodnota je " << citac() << endl; // Citac.operator() () } // int main()
6.8. Imlicitní hodnoty argumentů funkce Jazyk C++ umožňuje nastavení implicitních (default) hodnot argumentů funkcím. Činí tak při deklaraci nebo definici funkce, u níž implicitní hodnoty argumentů požadujeme. Implicitní hodnoty můžeme přiřazovat argumentům podle jejich pořadí uvedení v deklaraci nebo definici funkce pouze zezadu. To je dáno normou ISO C++.
Příklad: //deklarace: int fn(int i=1, int j=2); // O.K. int fn(int i=1, int j); // chyba, není odzadu //volání fn(); // jako fn(1,2) fn(5); // jako fn(5,2) fn(7,8); // jako fn(7,8), ne implicitní hodnoty //definice int fn(int i, int j) {... // O.K. implicitní hodnoty z deklarace int fn(int i, int j = 8) {... // nelze, implicitní hodnoty jsou i v deklaraci
6.9. Inline funkce Funkce inline je jako každá funkce - až na modifikátor inline. Překladač každou funkci přeloží příkaz za příkazem. Rozdíl mezi inline funkcí a každou jinou funkcí je ten, že normální funkci překladač po přeložení umístí někam do paměti. A při volání funkce z různých míst programu, se přenese vykonávání programu z místa volání do místa, které odpovídá tělu funkce. Jakmile se provádění funkce dokončí, musí se provádění programu přenést přesně za místo, z něhož byla funkce předtím zavolána. Naproti tomu inline funkci překladač neumístí do paměti hned, ale až když je funkce volána, je přeložený kód funkce uložen přímo do vytvářeného kódu programu bez volání funkce. A to v každém výskytu volání funkce.
Výhody inline funkcí: •
snížení časové režie systému, mělo by dojít ke zrychlení aplikace
Nevýhody inline funkcí: •
zvětšují výslednou délku programu
Poznámka: •
umístění definice inline funkce do hlavičkového souboru je možné
6.10. Funkce s proměnným počtem argumentů Dosud víme, že hodnoty skutečných argumentů jsou umisťovány do zásobníku. Pokud tedy dokážeme to, co díky deklaraci formálních argumentů překladač umí, tedy provést jejich jednoznačné přiřazení, máme vyhráno. Problémem však je, že díky deklaraci i volající místo ví, jakého typu očekává volaná funkce argumenty. Podle toho je předá. Při proměnném počtu argumentů je předání nesnadné. Jak v místě volání (skutečné argumenty), tak v místě přijetí (formální argumenty). Popsanou situaci lze řešit několika makry a pravidly. Díky nim si téměř nemusíme uvědomit, že pracujeme se zásobníkem (ukazatelem na něj). Jediné, co k tomu potřebujeme, je mít mezi argumenty funkce nějaký pevný bod (poslední pevný argument pro získání správného ukazatele do zásobníku). Další argumenty budeme deklarovat jako proměnné (...). Pak použijeme standardní makra. Pro jednoduchost zpracování jsou typy hodnot předávaných skutečných argumentů konvertovány na int a double. Makra pro proměnný počet argumentů (viděli jsme i pojem výpustky, ale my přece argumenty nevypouštíme, my pouze dopředu neznáme jejich počet) jsou tři, va_start, va_arg a va_end. Jejich deklarace vypadá následovně: void va_start(va_list ap, lastfix); type va_arg(va_list ap, type); void va_end(va_list ap);
kde • • • •
va_list formálně představuje pole proměnných argumentů va_start nastaví ap na první z proměnných argumentů předaných funkci (probíhá fixace
na poslední neproměnný deklarovaný a předaný argument lastfix) va_arg se jako makro rozvine ve výraz stejného typu a hodnoty, jako je další očekávaný argument (type) va_end umožní volané funkci provést případné operace pro bezproblémový návrat zpět return
Poznamenejme, že při použití těchto maker musí být volány v pořadí va_start - před prvním voláním va_arg nebo va_end. Po ukončení načítání by měl být volán va_end. Dále
poznamenejme, že va_end může ap změnit tak, že při jeho dalším použití musíme znovu volat va_start. Počet skutečně předaných hodnot může být předán jako poslední neproměnný argument, nebo technikou nastavení zarážky.
Příklad: Program vypočte hodnoty tří polynomů různého stupně. fn-argn.cpp /************************************************** * soubor fn-argn.cpp * funkce s proměnným počtem argumentů různých typů **************************************************/ #include #include using namespace std; double polynom(short pocet, ...) /************************************************** * vyčíslí polynom an*x^n + an-1*x^n-1 + ... a1*x + a0 * počet = počet všech koeficientů (n+1), následuje * x typu double, koeficienty typu int **************************************************/ { double hodnota = 0.0, x; va_list ap; va_start(ap, pocet); x = va_arg(ap, double); while (pocet-- != 0) hodnota = hodnota * x + va_arg(ap, int); va_end(ap); return hodnota; } int main(void) { double x, f; x = 2.0; /* pol. st. 2; 2x^2 + 3x + 4, x=2 -> 18 */ f = polynom(3, x, 2, 3, 4); cout << "p(" << x << ") = " << f << endl; x = 3.0; /* pol. st. 3; x^3, x=3 -> 27 */ f = polynom(4, x, 1, 0, 0, 0); cout << "p(" << x << ") = " << f << endl; x = 5.0; /* pol. st. 4; 2x^4 - 10x^3 + 2x^2 + 3x - 1, x=5 -> 64 */ f = polynom(5, x, 2, -10, 2, 3, -1); cout << "p(" << x << ") = " << f << endl; return 0; }
Ukázkový výstup programu fn-argn.cpp: p(2) = 18 p(3) = 27 p(5) = 64
6.11. Argumenty příkazového řádku Argumenty příkazového řádku získáme pomocí zavedení dvou formálních parametrů u funkce main(). Syntaxe je následující: int main(int argc, char *argv[]) { ...
kde •
argc je prvním argumentem funkce main() a udává počet předaných argumentů (1 =
•
argv je druhým argumentem funkce main() a je to pole řetězců obsahující hodnoty
jméno programu, 2 = jméno programu a jeden argument, ...) předaných argumentů
Příklad: Program vypíše argumenty příkazového řádku, s nimiž byl program spuštěn. cmd-ln.cpp /*************************** * soubor cmd-ln.cpp * argumenty příkazového řádku ***************************/ #include using namespace std; int main(int argc, char *argv[]) { int i; for (i = 0; i < argc; i++) cout << i << ' ' << argv[i] << endl; return 0; } // int main(int argc, char *argv[])
Ukázkový výstup programu cmd-ln.cpp: cmd-ln.exe Ceckari vsech zemi, spojte se! 0 cmd-ln06.exe 1 Ceckari 2 vsech 3 zemi, 4 spojte 5 se!
Test - kapitola 6 1. Co znamená modifikátor volatile? Your answer:vyjadřuje neustálou proměnnost This answer is correct. 2. Nevýhodou inline funkcí je, že: Your answer:zvětšují výslednou délku programu This answer is correct. 3. Implicitní hodnoty můžeme přiřazovat argumentům podle jejich pořadí uvedení v deklaraci/definici: Your answer:pouze zezadu This answer is correct. 4. Co znamená rekurze? Your answer:volání funkce ze sebe samé, ať přímo nebo prostřednictvím jiné funkce This answer is correct. 5. Který ze způsobů předávání argumentů funkcím nemění hodnoty skutečných parametrů: Your answer:hodnotou This answer is correct. 6. Lze vytvářet v jednom programu více funkcí se stejným jménem? Your answer:ano This answer is correct. 7. Návratovou hodnotu funkce uvádíme jako argument za příkazem: Your answer:return This answer is correct. 8. Implicitně má lokální proměnná definovaná v bloku paměťovou třídu: Your answer:auto This answer is correct. 9. Určete jaké hodnoty vypíše program po svém provedení: void funkce(int a, int& b) { a++; b++; } int main() { int x=1,y=1; funkce(x,y); cout << x << y; return 0; }
Your answer:12 This answer is correct. 10. Který z následujících operátorů nelze přetížit? Your answer::: This answer is correct.