Hlavní navigace

Pojďte s námi programovat !

1. 5. 2008

Sdílet

c++ Jelikož jsme se v předcházející lekci obeznámili s datovými typy a proměnnými, můžeme postoupit opět o...


c++

Jelikož jsme se v předcházející lekci obeznámili s datovými typy a proměnnými, můžeme postoupit opět o krůček dál. V dnešní lekci si ukážeme, jak do proměnných ukládat hodnoty a připravíme také první smysluplný program.



Proměnné jako datové kontejnery

Proměnnou jsme definovali jako datový kontejner s určitou alokační kapacitou, která je závislá na datovém typu proměnné. Proměnnou ve zdrojovém kódu jazyka C++ vytváříme definičním příkazem. Po své definici proměnná sice existuje, ovšem aby byla užitečná, musíme do ní uložit jistou hodnotu. Hodnotu do proměnné umístíme přiřazovacím příkazem, jehož všeobecná podoba je následující:

Z dosavadních akademických zkušeností plyne, že studenti nejlépe pochopí strukturu přiřazovacího příkazu, když si jej rozdělí na tři části. Prostřední část tvoří přiřazovací operátor, jenž je reprezentován symbolem „rovná se“, tedy =. Přiřazovací operátor, nebo také operátor přiřazení, patří do poměrně početné množiny operátorů, s nimiž lze v jazyce C++ pracovat. (Po pravdě řečeno, v C++ máme k dispozici kolem padesáti různých operátorů.) Operátor je symbol, který je spojen s provedením určité programové operace. Charakter této operace je různý a liší se napříč celou množinou operátorů. Kupříkladu aritmetický operátor + nám umožňuje vypočítat součet dvou hodnot: přitom může jít o součet dvou celočíselných konstant jako třeba ve výrazu 10 + 12, nebo o součet hodnot, jež jsou uloženy ve dvou samostatných proměnných. Námi představený přiřazovací operátor slouží k přiřazení hodnoty do proměnné. Levá část přiřazovacího příkazu je tvořena identifikátorem (názvem) proměnné, do níž bude hodnota posílána (ukládána). Konkrétní hodnota, kterou hodláme do proměnné uložit, nachází své stanoviště na pravé straně od přiřazovacího operátoru. Když si to tedy shrneme, celý přiřazovací příkaz se skládá z levé strany (proměnné), přiřazovacího operátoru (=) a pravé strany (hodnoty, jež bude přiřazena do proměnné). Kupříkladu níže zapsaný fragment zdrojového kódu jazyka C++ nejprve definuje proměnnou pocetVyrobku a pak do ní uloží množství prodaného zboží:

Celočíselná proměnná pocetVyrobku je definována v prvním příkaze, poté je inicializována. Termínem inicializace proměnné označujeme příkaz, v němž dochází k uložení hodnoty do dotyčné proměnné. Analogicky hodnota, která má být v proměnné uskladněna, se označuje jako inicializační hodnota proměnné. Jazyk C++ nám dovoluje spojit definici a inicializaci proměnné do jednoho příkazu. Tím si můžeme ušetřit trochu psaní:

První řádek v těle hlavní funkce main představuje tzv. definiční inicializaci, v jejímž rámci nejprve zakládáme proměnnou určeného datového typu a následně okamžitě do již existující proměnné ukládáme její inicializační hodnotu. Definiční inicializaci je vhodné uplatnit především tehdy, když již při vytváření proměnné umíme stanovit její inicializační hodnotu. Poznamenejme, že ne vždy je tomu tak. Kupříkladu můžeme chtít, aby hodnotu, která bude posléze přiřazena do proměnné, zadal uživatel.
Jestliže v jednom přiřazovacím příkazu uložíme do proměnné určitou hodnotu, proměnná bude tuto hodnotu uchovávat, dokud do ní nepřiřadíme jinou hodnotu (případně dokud neskončí životní cyklus proměnné).

Výstup, který tento jednoduchý program produkuje, je znázorněn na obrázku. Podotkněme, že zdrojový kód programu přeložíte příkazem Build <NázevProjektu>, resp. Build Solution (klávesová zkratka F7). Po přeložení zdrojového kódu a sestavení spustitelného souboru lze program spustit volbou Start Without Debugging z nabídky Debug, nebo odpovídající klávesovou zkratkou Ctrl+F5.
Tento kratičký program předvádí velice důležitou techniku, jak pomocí objektu cout a výstupního operátoru (<<) zobrazit na výstupu hodnotu, která je uložená v požadované proměnné. Budeme-li chtít zobrazit v dialogovém okně příkazového řádku hodnotu proměnné A, pak nejjednodušší řešení je:

Pokud přicházíte z jazyka C, pak jistě zaregistrujete mnohem snazší a elegantnější syntaxi jazyka C++ (zejména ve srovnání s voláním funkce printf, kterou znají všichni programátoři v „céčku“). Vše probíhá asi takhle: Objekt cout nalezne v operační paměti proměnnou A a zjistí hodnotu, která je v ní uložena. Poté celočíselnou hodnotu proměnné A převede na posloupnost textových znaků, které odešle do výstupního datového proudu. Z výstupního datového proudu jsou znaky následně sériově vyjímány a zobrazovány v okně příkazového řádku. Pro programátory je důležité, že objekt cout uskutečňuje veškeré činnosti (hlavně ty konverzního charakteru) ve své vlastní režii. Vzhledem k tomu, že objekt cout před námi ukrývá technické aspekty aktivit, které provádí, umožňuje nám pracovat na vyšší úrovni abstrakce. Na příkaz cout << A; ale můžeme nahlížet také jako na tok informací. Směr toku je dán orientací výstupního operátoru, jež pluje zprava doleva. Vezmeme-li v potaz tuto vizuální pomůcku, pak to vypadá, jako kdyby informace tekly do objektu cout, a tak tomu doopravdy je!
V jazyce C++ smíme v jednom definičním příkazu sestrojit větší počet proměnných. Nezbytnou podmínkou ovšem je, aby měly všechny přítomné proměnné identický datový typ. Opět si pomůžeme praktickou ukázkou:

Ačkoliv jsme zapsali pouze jeden příkaz, dochází k definování tří celočíselných proměnných. Všimněte si, že všechny proměnné mají definovaný typ int a v definičním příkazu jsou jejich identifikátory odděleny čárkami. Někdy se možnost definovat více proměnných v jednom definičním příkazu chápe jako vícenásobná definice.
Jestliže máte zkušenosti s jinými programovacími jazyky, patrně vás bude zajímat, zda mají definované proměnné své implicitní inicializační hodnoty. Dodejme, že v některých programovacích jazycích překladač automaticky ukládá do definovaných proměnných implicitní (čili výchozí) hodnoty. Bohužel v jazyce C++ není na položenou otázku jednoznačná odpověď. Je to proto, že překladač C++ přistupuje jinak k lokálním a jinak zase ke globálním proměnným. O proměnné říkáme, že je lokální, když se její definiční příkaz nachází v těle jisté funkce. Pokud se vrátíme zpátky k předcházejícímu výpisu zdrojového kódu, pak zřetelně vidíme, že všechny tři proměnné jsou definovány v těle hlavní funkce main. Proto jsou lokálními proměnnými. Pro lokální proměnné platí, že překladač jazyka C++ do nich neukládá žádné implicitní inicializační hodnoty. Toto chování překladače je v pořádku, poněvadž jej předepisuje standard ISO pro jazyk C++. Máme-li před sebou definice lokálních proměnných, pak můžeme prohlásit, že nevíme, jaké hodnoty jsou v těchto proměnných uloženy. Techničtěji řečeno: tyto hodnoty nejsou definovány, a proto se v proměnné může objevit cokoliv, co se předtím nacházelo na místě, kdy je nyní daná lokální proměnná situována.
Zkusme se na chvíli zastavit u životních cyklů lokálních proměnných. Jak již víme, lokální proměnná se rodí v definičním příkazu a poté žije tak dlouho, dokud probíhá zpracování příkazů umístěných ve funkci, v níž je lokální proměnná použita. Z uvedeného plyne, že životní cyklus lokální proměnné je velice úzce spjat s životním cyklem samotné funkce, která definici lokální proměnné obsahuje. Konkrétně tři výše definované lokální proměnné budou podrobeny destrukci ihned poté, co bude zpracována návratová hodnota hlavní funkce main (tuto hodnotu vracíme příkazem return 0;).
Na druhou stranu, vedle lokálních proměnných se na našich programátorských stezkách budeme občas potkávat i s globálními proměnnými. Globální proměnné jsou v ostrém kontrastu s proměnnými lokálními, poněvadž jejich definice se nacházejí na globální úrovni, tedy vně všech funkcí, jež tvoří program jazyka C++. Ano, zatím máme pouze hlavní funkci main, a proto za globální proměnnou budeme považovat proměnnou, jejíž definiční příkaz je uložen mimo těla této funkce. V nadcházející ukázce zdrojového kódu vystupuje proměnná kritickyBod v roli globální proměnné:

Jako každá lokální proměnná, i globální proměnná musí mít svůj identifikátor a datový typ. Na rozdíl od lokálních proměnných překladač garantuje implicitní inicializaci korektně definovaných globálních proměnných. Proto i naše globální proměnná kritickyBod bude implicitně inicializována, což znamená, že do ní bude uložena výchozí hodnota, kterou je nula. Pokud vás zajímá, jaké jsou implicitní inicializační hodnoty, pak překladač postupuje přibližně takto: Jestliže je definovaná globální proměnná proměnnou integrálního (celočíselného) typu, pak je implicitní inicializační hodnotou nula v celočíselném vyjádření. Jestliže je typem globální proměnné jeden z primitivních reálných datových typů (float nebo double), pak je implicitní inicializační hodnotou také nula, ovšem v reálné interpretaci (jde o hodnotu 0.0 s implicitním typem double). U globálních logických proměnných (proměnných typu bool) je implicitní inicializační hodnotou logická nepravda (false).
Ve fragmentu zdrojového kódu je viditelný řádkový komentář, jenž je uveden dvěma symboly //, zapsanými za sebou bez mezery. Komentáře jsou doplňkem zdrojového kódu a slouží především k jeho dokumentaci. Jazyk C++ spolupracuje se dvěma typy komentářů: prvním je již vzpomenutý řádkový komentář, který označí jako komentář veškerý text nacházející se mezi symboly // a koncem řádku. Pokud budeme chtít, aby se náš komentář rozprostíral na více řádcích současně, můžeme s výhodou využít víceřádkový komentář. Dříve představený výpis zdrojového kódu by po zapracování víceřádkového komentáře vypadal takto:

Ke komentářům ještě malá poznámka: překladač komentáře ignoruje. Komentáře se proto neúčastní procesu kompilace a slouží pouze pro potřeby programátorů. V souvislosti s užitím lokálních a globálních proměnných definujeme i oblast zdrojového kódu, v němž je daná proměnná viditelná. Tato oblast se nazývá oblast platnosti a její rozsah se liší u lokálních a globálních proměnných. Oblastí platnosti lokální proměnné je tělo funkce, v němž je tato proměnná definována. Když si představíme hlavní funkci main s lokální proměnnou A a další funkci s názvem X (tuto funkci bychom sami navrhli), pak můžeme konstatovat, že z těla funkce X nemůžeme s lokální proměnnou A pracovat, poněvadž oblast platnosti této proměnné je vyhrazena pouze funkcí main. U globálních proměnných je situace odlišná, neboť oblastí platnosti globální proměnné je celý program jazyka C++. Globální proměnná je proto dosažitelná z těla jakékoliv funkce, která tvoří strukturu našeho programu. Řekněme, že bychom v programu identifikovali množinu funkcí F se čtyřmi prvky (funkcemi) f1, f2, f3 a f4, tedy F = {f1, f2, f3, f4}. Jestliže je v tomto programu definována globální proměnná B, pak s touto proměnnou smí manipulovat kterákoliv funkce z množiny F.

Management lokálních proměnných

Uvažujme následující znění hlavní funkce main:

Proměnné vznikají definičním příkazem. Definice proměnných je spojena s alokací paměti. V našem případě pracujeme s jednou 2bajtovou (x) a dvěma 4bajtovými proměnnými (y a z). Tyto proměnné jsou alokovány ve speciální paměťové sekci, které se říká zásobník (angl. stack).
Zásobník je automaticky spravovaná datová struktura, která pracuje podle organizačního modelu LIFO (Last-In-First-Out, poslední dovnitř a první ven). Model LIFO říká, že ze zásobníku může být odebrána pouze ta proměnná, která do něj byla uložena jako poslední. Funkci zásobníku lépe pochopíte, když si ho představíte jako věž, která je postavena z hracích kostek. Jednoduše vezmeme kostky a začněme stavět věž tak, že pokládáme jednu kostku na druhou. Máme-li smysl pro orientaci a detail, postavíte krásnou a vysokou věž. Tak jako klademe v roli malých modelářů kostky nad sebe, probíhá podobně rovněž proces alokování proměnných na zásobníku. Jestliže se budeme chovat v souladu s modelem LIFO, pak můžeme z naší věže odebrat v jednom kroku nanejvýš jednu kostku, a to takovou, jež sídlí úplně na vrcholu věže. Když se nad tím zamyslíte, jistě objevíte skrytou logiku věci. Kdybychom totiž odebrali kostku z půlky věže, způsobili bychom malou katastrofu: věž by se hned zhroutila.
Lokální proměnné jsou do zásobníku ukládány v takém pořadí, v jakém jsou vytvářeny (viz obrázek). V naší ukázce nejprve přichází na svět proměnná x typu short int, takže ta bude na zásobník položena jako první. V dalším kroku je zhotovena proměnná y typu int, která bude alokována „nad“ již vytvořenou proměnnou x. Nakonec dojde ke konstrukci proměnné z typu float – ta je poslední proměnnou, a proto bude zaujímat v zásobníku nejvyšší postavení.
V rámci hlavní funkce main nejsou s vytvořenými proměnnými uskutečňovány žádné další operace. Poté, co funkce prostřednictvím příkazu return vrátí svou návratovou hodnotu, končí své zpracování. Tehdy jsou ze zásobníku postupně odstraňovány doposud „živé“ proměnné. Likvidace lokálních proměnných je automatická a probíhá v opačném pořadí, než v jakém byly proměnné vytvořeny. Zkrátka a jasně, jako první zanikne proměnná z, poté proměnná y a nakonec proměnná x.

Praktická tvorba programů

Nadešel čas na praktické programování. Začneme s vývojem aplikace, jejíž pomocí si budou uživatelé moci vypočítat hodnoty svých indexů BMI. Nejdřív uvedeme kompletní zdrojový kód, poté k němu připojíme pečlivý doprovodný komentář.

Hmotnostní index BMI se počítá z hmotnosti a výšky uživatele, proto náš program bude muset s těmito daty pracovat. Abychom je mohli uchovat, definujeme dvě proměnné, z nichž jedna (hmotnost) je celočíselná a slouží k uchování hmotnosti uživatele, zatímco další (vyska) je reálná, přičemž je připravena na uskladnění výšky uživatele (měřené v metrech). Obě proměnné, hmotnost a vyska, jsou lokálními proměnnými. Program musí uživatele ve vhodném okamžiku instruovat, jak má postupovat. Z pohledu uživatele je první interakce svázána s prosbou o zadání uživatelovy hmotnosti. Když nám uživatel vyhoví a zadá svou hmotnost, ukládáme ji do předem připravené proměnné. Všimněte si, že realizaci vstupní datové operace má na starosti objekt cin. Příkaz cin >> hmotnost; představuje posloupnost následujících akcí:
Načtení hmotnosti uživatele ze vstupního datového proudu. Připomeňme, že ačkoliv uživatel zadá svou hmotnost v číselné podobě, ze vstupního datového proudu jsou odebírány ryzí textové znaky.
Textová reprezentace vstupních dat je převedena objektem cin do požadované celočíselné formy. Objekt cin uskutečňuje typovou konverzi mezi textovým řetězcem (posloupností textových znaků) a celočíselnou hodnotou.
Již celočíselná reprezentace uživatelovy hmotnosti je uložena do proměnné hmotnost.
Na příkaz cin >> hmotnost; můžeme opět nahlížet jako na tok informací. V tomto případě objekt cin načte uživatelem zadaná data a odešle je na paměťovou lokaci, kterou okupuje specifikovaná proměnná.
Pokračujeme zobrazením informační zprávy pro zadání uživatelovy výšky. Ačkoliv v našich zemích je desetinným oddělovačem čárka (,), od uživatele se očekává, že zapíše svou výšku s tečkou (.) coby desetinným oddělovačem. Jestliže tedy uživatel měří metr osmdesát, pak zapíše 1.8. Podobně jako hmotnost, i výšku uživatele přiřazujeme do přichystané proměnné.
Snad nejsložitější se může jevit následující přiřazovací příkaz:

Tento příkaz je definiční inicializací proměnné indexBMI. Definiční inicializací se rozumí definice proměnné, jež je následována její inicializací. Dobrá, ovšem jakou hodnotou je proměnná ve skutečnosti inicializována? Inu, hodnotou aritmetického výrazu, který se nachází napravo od přiřazovacího operátoru. V tomto případě bychom mohli aritmetický výraz popsat jako syntakticky správně zapsanou posloupnost operátorů a operandů. Když si výraz rozebereme, diagnostikujeme v něm dva aritmetické operátory: operátor / (lomítko) uskutečňující dělení a operátor * (hvězdička) provádějící násobení. I když o operátorech budou blíže pojednávat další lekce našeho kurzu, již nyní smíme učinit jemný úvod do této problematiky. Operátorem se rozumí symbol, který přikazuje provedení jisté operace. Oba uvedené operátory patří do kategorie aritmetických operátorů, z čehož plyne, že v jejich působnosti je uskutečňování aritmetických operací. Termín operand determinuje entitu, na níž je operátor aplikován. Operátory / a * pracují se dvěma operandy, a proto se řadí k tzv. binárním operátorům. Nás ovšem v tuto chvíli interesuje spíše postup, jak překladač určí hodnotu aritmetického výrazu, tedy hodnotu hmotnostního indexu uživatele. Ve skutečnosti se postupuje následovně:
Zjistí se hodnota proměnné hmotnost.
Zjistí se hodnota proměnné vyska.
Vypočte se druhá mocnina hodnoty, která je uložena v proměnné vyska. Za účelem určení druhé mocniny jednoduše vynásobíme hodnotu proměnné vyska jí samotnou. Všimněte si, že podvýraz (vyska * vyska) je uzavřen v závorkách. To je nezbytnost, poněvadž chceme, aby se nejprve vypočetla druhá mocnina výšky uživatele. Jak se dozvíte v lekcích věnovaných operátorům, pomocí závorek můžeme zvýšit prioritu uskutečňovaných operací.
Hodnota proměnné hmotnost je vydělena druhou mocninou hodnoty proměnné vyska.
Podíl, který získáme, bude ve tvaru desetinného čísla (čísla s desetinným rozvojem). Podíl bude uložen do proměnné indexBMI. Tím pádem dojde k inicializaci této proměnné.
Nakonec je vypočtený hmotnostní index zobrazen uživateli. Na obrázku je k vidění výstup našeho programu, který vypočítal hmotnostní index osoby, jež váží 70?kg a měří 1,77?m. Program pro výpočet indexu BMI demonstruje důležité techniky, s nimiž se musíme spřátelit. Předně: lokální proměnné mohou být definovány kdekoliv v těle funkce. Podle doporučeníhodných směrnic programování bychom měli lokální proměnné vytvářet těsně předtím, než je hodláme naplnit inicializačními hodnotami. Vstupně-výstupní datové operace uskutečňují samočinně objekty cin a cout. Jediné, co musíme těmto objektům sdělit, je, kam mají načtené informace odeslat (cin), resp. co mají zobrazit na výstupu (cout). Je důležité, abyste si uvědomili, že proměnné hmotnost a vyska jsou inicializovány objektem cin. Proč? Protože je to právě objekt cin, kdo je zodpovědný za načtení uživatelem zadaných vstupních dat, jejich konverzi a následné uložení dat do proměnných. Poněvadž inicializaci proměnných hmotnost a vyska provedl objekt cin, nepotřebujeme žádné přiřazovací příkazy. To ale nemůžeme prohlásit o proměnné indexBMI: tuto proměnnou musíme inicializovat sami, a proto se bez přiřazovacího příkazu neobejdeme.

Kontrolní otázky

Vysvětlete význam proměnných v programování.
Jak jsou do proměnných ukládány hodnoty?
Můžeme používat definované, ovšem neinicializované proměnné?
Je možné v definičním příkazu proměnné vynechat její datový typ?
Co je to definiční inicializace proměnné?
Co jsou to lokální a globální proměnné? Jaký je mezi nimi rozdíl?
Jak vypadá management lokálních proměnných?
Vysvětlete práci objektů cin a cout.
(Odpovědi na kontrolní otázky naleznete na ) 8 0201/CZ o

Poznámka: Aby byl fragment zdrojového kódu přeložen bez potíží, musí být před hlavní funkcí main uvedeny ještě dva řádky:

#include <iostream>
using namespace std;

Ty jsou odpovědné za vložení obsahu hlavičkového souboru iostream.h a zavedení standardního jmenného prostoru std. Pokud nebude uvedeno jinak, v dalším textu budeme vždy předpokládat, že zmíněné dva řádky jsou přítomné.
Poznámka: Definiční inicializaci proměnné indexBMI bychom mohli rozdělit na dva samostatné příkazy, z nichž jeden by proměnnou pouze definoval a další by ji inicializoval. Pak by to vypadalo takto:
float indexBMI;
indexBMI = hmotnost / (vyska * vyska);
Ačkoliv tato modifikace by nijak nezměnila fungování programu, je přinejmenším zbytečná. Původní definiční inicializace je na místě, protože jsme schopni již při zakládání proměnné indexBMI stanovit její inicializační hodnotu.

Byl pro vás článek přínosný?