1.1. Úvod do programování
Jedním z nejrozšířenějších programovacích jazyků se stal jazyk C a později i jeho objektově orientovaná nadstavba C++ . Výhody obou těchto jazyků předurčují jejich využití v mnoha oblastech počítačového světa. Vzhledem k jeho výborným vlastnostem se jazyk C stal velmi populárním a postupně se vyvinulo několik jeho verzí až po současný standard ANSI C++ (ANSI - American National Standard for Information Systems - Americký národní standard pro informační systémy). Tímto standardem se také budeme v našem kurzu zabývat.
Text jednotlivých kapitol je orientován převážně na popis jazyka v rámci probírané látky. Uvedené příklady a další doprovodné materiály nejsou jediným správným řešením (nekladu si nárok na dokonalost mých programů). Proto je třeba je považovat za pouze jedno z možných řešení problému a cílem je ukázat především popisované rysy jazyka. Jeden problém jde řešit mnoha různými cestami. Programátor by si měl vybrat cestu takovou, aby výsledný program byl co nejefektivnější, ale zároveň co nejspolehlivější - jinými slovy: líný programátor je špatný programátor. Programový kód, který zajišťuje stabilitu programu totiž často mnohonásobně zvětší výsledný program. Na druhou stranu platí, čím delší program, tím je program pomalejší. K tomuto problému se vrátíme ještě později.
V kurzu je užito dvou typů sazby. V sazbě běžného textu výkladu je použito normální písmo, stejné jakým je psán i tento text. Vlastní program je psán "písmem psacího stroje" s barevným odlišením jednotlivých tzv. lexikálních prvků (podobné se slovními druhy v českém jazyce - podstatné jméno, sloveso apod.) programovacího jazyka. Například klíčová slova jazyka (int, class) jsou vytištěna modře, ostatní kód je černý a komentáře jsou světle zelené.
Budu využívat vývojové prostředí Visual C++ 6.0, takže navíc uvedu postup, jak vytvořit příslušný projekt. Pokud nevlastníte VC++, programy je možno psát i v jiných vývojových prostředích neboli IDE (Integrated Development Environment - integrované vývojové prostředí). Stačí vám C++ Builder nebo dokonce starý Borland C++ 1.0 pod DOS, protože jazyk je vždy stejný. Přesto některé možnosti nebudou dostupné a pokud možno, bude na to v textu upozorněno.
U každé lekce bude možno si stáhnout jak zdrojové kódy všech uvedených příkladů, tak i přímo spustitelné programy (spustitelné pouze pod Windows).
Pokud to není nutné,
nejsou použity speciální vlastnosti C++ dostupné např. jen v jednom vývojovém
nástroji. Na místech, kde jsou speciální vlastnosti C++ využity, je to výslovně
uvedeno.
1.2. Historie C++
Jazyk C++ je pokračovatelem jazyka C a obsahuje až na výjimky celý jazyk C. Jazyk C++ je narozdíl od svého předchůdce objektově orientovaný. Co to přesně znamená nás prozatím nemusí zajímat.
V létě roku 1989 společnost AT&T zveřejnila specifikaci pro AT&T C++ Release 2.0. Tato nová verze obsahuje některé významné změny, které řeší některé problémy s dřívějšími verzemi C.
Původní jazyk C byl vyvinut pro operační systém Unix na počítačích DEC PDP-11. Pro svoje vlastnosti si získal značnou oblibu a dnes je C i C++ k dispozici v mnoha operačních systémech na různých počítačích. Jazyk C byl původně určen pro systémové programování Unixu a téměř celý Unix je v něm napsán. Proto je také C samozřejmou součástí každého Unixového OS.
Jazyky C a C++ jsou však dobře použitelné i v mnoha jiných oblastech programování, např. hromadné zpracování dat, práce s textovými informacemi, řešení numerických úloh a mnoho dalších oblastí. Svou oblibu hlavně získal kvůli velké rychlosti vytvořených programů, které jsou dobře přenositelné do jiných operačních systémů.
Dalším důvodem obliby obou jazyků je to, že v nich lze používat obdobné operace a tvary jako při programování v jazyku assembler (jazyk nejnižší úrovně). Velmi silným prostředkem jsou např. široké možnosti práce s adresami dat a funkcí. Při vhodném způsobu programování se pak programy napsané v C/C++ mohou svojí rychlostí téměř rovnat rychlosti stejného programu psaného v assembleru, ale s mnohem menším úsilím.
V současné době je C++ pravděpodobně nejrozšířenějším programovacím jazykem zvláště profesionálů a dalších odborníků. Existuje pro něj spousta vývojových prostředí, jmenujme ty nejznámější: Visual C++ (Windows), C++ Builder, Borland C++, DJGPP (DOS). 1.3. Doporučená literatura
Učebnice jazyka C 1.,
Pavel Herout, Kopp, České Budějovice, 1996
Snad každý programátor se setkal s touto publikací pro úplné začátečníky, která
vás výborným způsobem seznámí s jazykem C. Jedinou nevýhodou této knihy je, že
vám neřekne nic o C++, přesto ji vřele doporučuji i lidem s malou
programátorskou zkušeností jako učebnici a těm zkušenějším jako referenci. Kniha
má i druhý díl, který prohlubuje znalosti C a programování pod DOSem. Dle mého
názoru není toto pokračování nutné ke zvládnutí C++. Cena prvního dílu je 149Kč.
Programujeme v jazyce
Visual C++, Mark
Andrews, Microsoft Press, Praha, 1997
Další krok, který učiníte a vstoupíte tak do objektového světa. Kniha se
převážně zabývá programováním pod Windows ve Visual C++ 1.0, které naleznete na
přiloženém CD. Jsou zde vysvětleny základní pojmy C++ jako jsou třídy a objekty,
dále například dědičnost a polymorfismus. Hlavní výhodou této knihy je, že vás
seznámí s knihovnou MFC (Microsoft Foundation Class), ke které se dostaneme
někdy na začátku příštího roku. Na druhou stranu je to velký skok od knihy pana
Herouta a začátky jsou tudíž značně obtížné. Snad vám pomohou tyto kurzy. Cena
je 295Kč.
Třídy a objekty C++,
Stanislav Racek, Martin Kvoch, Kopp, 1998
Další publikace se opět zabývá jen C++. Tato spíše referenční příručka než
učebnice popisuje všechny rysy C++. Navíc zde najdete stručnou analýzu problému
a návrh projektu, což není špatné si přečíst, i když to ze začátku nevyužijete.
Cena je 149Kč.
Mistrovství ve Visual
C++, David J.
Kruglinski, Microsoft Press, Brno, 1999
Špičková publikace o programování ve Windows ve Visual C++ 5.0, které se od
verze 6.0 téměř neliší, takže vám plně postačí. Popisuje jak vývojové prostředí
VC++, tak MFC, ale je nutné znát alespoň základy C++, takže není vhodná pro
začátečníky. Zabrousí prakticky do všech koutů programování s MFC včetně
takových specialit jako je COM (Component Object Model). Druhé vydání navíc
obsahuje ATL (Advanced Template Library). Následuje krátký výčet z obsahu této
knihy: GDI (Graphics Device Interface), DLL (Dynamic Link Library), ActiveX
prvky, databáze ODBC a DAO, programování s TCP/IP a další. Cena je 850Kč.
1.4. Závěr
Na závěr vám ve zkratce povím, co nás čeká příště. Příště si něco povíme o proměnných a operátorech. Ukážeme si, jak proměnnou vytvořit a jak s ní pracovat. Stihneme hlavní funkci main(). O funkcích ještě nic nevíte, ale příště se dozvíte, jak pracuje funkce main().
Datové typy jsou typy
proměnných, které je možno vytvořit. Proměnná může představovat paměťovou
buňku, ale většinou se jedná o blok paměti, kde je uložena informace jistého
typu. Právě typ proměnné je nejdůležitější atribut proměnné. Jedna proměnná
může uchovat záporné číslo a druhá zase jen kladné. Musíte předem vědět k čemu
proměnnou chcete využít, aby program fungoval správně. Některé chyby odhalí
kompilátor při překladu, ale pozor si musíte dát především na ty skryté, které
se projeví nesprávnou funkcí programu. V průběhu programu se hodnota proměnné
mění, podle druhu operace. Můžeme například sečíst dvě proměnné stejného typu
atd. Lze použít i jisté konverze typů, ale o tom si podrobně povíme v některé
příští lekci.
Přehled nejpoužívanějších jednoduchých typů jazyka C :
|
Deklarace |
Typ informace |
Velikost v bytech |
|
int |
celé číslo |
4 (2) |
|
long |
celé číslo |
4 |
|
short |
celé číslo |
2 |
|
float |
reálné číslo |
4 |
|
double |
reálné číslo |
8 |
|
long double |
reálné číslo |
8 |
|
char |
znak |
1 |
|
BYTE |
byte |
1 |
|
BOOL |
0 nebo 1 |
4 |
|
WORD |
16-bitové slovo |
2 |
|
DWORD |
dvojnásobné slovo |
4 |
|
UINT |
neznamínkový int |
4 |
|
a další |
|
|
Poznámka:
Všimněte si, že některé typy jsou psány velkými písmeny (DWORD, UINT). Tyto typy
jsou deklarované (pojmenované) v hlavičkovém souboru
"Windef.h" a jsou použitelné jen pod
Windows. Když se podíváte do souboru "Windef.h",
najdete tam například řádek:
typedef unsigned int DWORD;
Znamená to, že deklarujeme nové jméno (synonymum) pro již existující typ (v
tomto případě unsigned int).
Typ BOOL je deklarován jako
int,
tzn. že zabírá 4
bajty. C skutečně nemá vlastní typ boolean jako například Pascal.
Některé typy mají dvě
varianty a to sice znaménkové (signed) a neznaménkové (unsigned).
Implicitně jsou tyto typy znamínkové. Pokud chcete vytvořit neznamínkový typ,
stačí před klíčové slovo typu vložit další klíčové slovo
unsigned.
Co ale přesně znamená znamínkový a neznamínkový typ? Je to snadné. Znamínkové
typy mohou nabývat i záporných hodnot, ale mají oproti neznamínkovým typům
poloviční maximální hodnotu, takže například platí:
|
Typ |
Rozsah hodnot |
|
unsigned char |
0 až 255 |
|
char |
-128 až +127 |
Maximální hodnotu proměnné zjistíme takto: 2n-1, kde n je počet bitů, které proměnná zabírá v paměti (viz tabulka nahoře - 1 bajt má 8 bitů). Které typy jsou znaménkové a mohou být neznaménkové a které jsou od přírody pouze neznaménkové? Celkem to vyplývá z názvu proměnné. Další tabulka vám vše osvětlí. V programu můžete použít oba typy deklarace. Znamená to tedy, že například typ int je implicitně znaménkový.
|
Deklarace |
Ekvivalentní deklarace
s |
|
int |
signed int |
|
long |
signed long |
|
short |
signed short |
|
char |
signed char |
|
BYTE |
unsigned char |
|
WORD |
unsigned short |
|
DWORD |
unsigned long |
|
UINT |
unsigned int |
Poznámka: V 16-bitových operačních systémech (DOS) měl typ
int
velikost pouze 2
bajty narozdíl od systému 32-bitových (Windows 9X), kde
int
zabírá 4 bajty. Co z toho plyne? Pokud například pod DOSem zapíšete do souboru
dvě proměnné typu
int
(2x2bajty) a pak je pod Windows přečtete, vznikne chyba, protože původně
dvě hodnoty se přečtou jako jedna a při pokusu o další čtení program vyhodí
nejspíš výjimku (exception). Zabráníte tomu používáním vhodnějších typů
pro zápis do souboru.
Za prvé si musíme
uvědomit, jaký typ informace chceme „skladovat“. Pak můžeme určit datový typ
proměnné.
Tak tedy jak definovat proměnnou v programu? Definovat znamená, že překladač
přidělí jméno a paměť pro naší proměnnou zatímco deklarace pouze přiřadí
jméno proměnné – překladač nealokuje žádnou paměť!!!
Příklad definice proměnné :
int
i;
double d = 1.0;
Takto vytvoříme
celočíselnou a reálnou proměnnou. Reálná proměnná typu
double
je ihned inicializována na hodnotu 1.0. Proměnná totiž po vytvoření není
inicializována, takže v ní může prakticky cokoliv (většinou je to dost obludné
záporné číslo) a tímto se vyvarujeme zbytečných chyb. Překladač nás upozorní
pokud používáme nějakou proměnnou, aniž by jsme jí před tím inicializovali.
Definice může být buď vně nebo uvnitř těla funkce. Proměnným, které jsou mimo
tělo jakékoliv funkce říkáme globální a jsou přístupné ze všech funkcí
daného modulu (souboru
.cpp).
Globální proměnná je zrušena, když je ukončen celý program. Pokud je
proměnná definována uvnitř těla funkce, její platnost se omezuje pouze na dobu
trvání funkce tzn. že když program ukončí funkci zároveň je zrušena i lokální
proměnná. K lokální proměnné můžeme přistupovat pouze z funkce, kde je
tato proměnná definována.
Příklad definice globální a lokální proměnné :
int
i; //globalni celociselna promenna
//viditelna v celem programu
main()
{
double d; //lokalni realna
promenna
// viditelna pouze z funkce main()
}
Poznámka:Obecně
platí, že v souborech s příponou
.cpp
provádíme definici a hlavičkových souborech
.h
provádíme deklaraci proměnných a funkcí. K čemu tedy používáme hlavičkové
soubory? Právě k deklaraci globálních proměnných či deklaraci funkčních
prototypů (vše bude vysvětleno později). Navíc, pokud používáte více modulů
najednou (více na sobě závislých implementačních souborů
.cpp),
můžete použít společný hlavičkový soubor
common.h,
ve kterém budou definovány společné globální proměnné a funkce. Více si o
hlavičkových souborech povíme dál v této lekci.
Nyní máme definované jméno
proměnné v programu a můžeme začít s proměnnou pracovat. Kompilátor nám přiřadil
buňky v paměti, takže můžeme získat adresu naší proměnné. Adresu využijeme až se
budeme zabývat ukazateli (ukazatel = pointer) někdy v příštích lekcích, ale nyní
to pro nás znamená, že proměnná, právě protože má adresu v paměti, je tzv.
l-hodnota (l-value). Hodnotu proměnné přiřadíme operátorem „rovná se“ ( = ).
Takže například, pokud chceme do proměnné i uložit číslo 5, napíšeme :
int
i; // definice promenne
i = 5; // v i je nyní hodnota 5
Důležité ovšem je, aby na levé straně příkazu byla vždy l-hodnota.
L-hodnota představuje adresu, tedy proměnná je l-hodnota, konstanta 5 l-hodnotou
není. Následuje triviální příklad:
int
u = 3; // definice a inicializace promenne u
i = u; // promenna muze byt vlevo i
vpravo, i = 3
i = 2 + u; // i = 5
i = u * 5 + 10; // i = 25, vyraz muze
byt jen na prave strane
i + 1 = u; // CHYBA!!! Prekladac
nastesti
//tyto chyby odhali pri
prekladu...
1 = u; // JESTE VETSI CHYBA!!!
V C je navíc možné
několikanásobné přiřazení. Takto můžeme inicializovat několik proměnných
najednou.
u = i = 5; //promenne i a u budou mit hodnotu 5
Poznámka: Všimněte si, že na konci každého řádku je středník! Každý příkaz v C je ukončen středníkem. Mezi základní slušné mravy programátora patří psát každý příkaz na nový řádek.
Ve starém C musel mít
každý program funkci
main(),
která se použila jako vstupní bod (entry point) do vašeho programu. To
znamená, že když se váš program zkompiloval a spustil, jako první se vždy
zavolala tato funkce. Z této funkce jste pak volali vaše funkce a váš program se
prováděl dokud funkce
main()
neskončila. Takže touto funkcí vše začalo a taky to pěkně skončilo. Toto platilo
až na výjimky (pokud jste třeba pracovali s více vlákny (threads)) na
100%.
V objektovém modelu programování se na tomto až zas tak moc nemění. A proč tedy o tom vůbec mluvím? Ve Visual C++ (VC++), ve kterém se nyní učíte programovat pracují programy zcela jinak! Neplatí to však jen pro VC++. Vývojové nástroje jako Visual Basic nebo Delphi pracují naprosto stejně. Je to dáno tím, že se jedná o nástroje pro Windows. Takže původce těchto změn jsou právě Windows (a jiné multitaskingové OS - znamená to, že může pracovat více programů najednou). Způsob, kterým pracují Windows je na delší povídání a nás zatím nezajímá, ale nebojte se určitě se k němu časem prokousáme.
Nyní vám stačí vědět, že
programy, které budeme vytvářet budou mít "zatím" jen funkci
main(),
kterou začíná a končí program. Funkce je ohraničena složenými závorkami {}, jako
každá funkce, kterou kdy v C napíšete. Funkci
main()
nikdy nebudete muset volat z vašeho kódu. Tuto funkci volá operační systém, ať
už stařičký DOS nebo padavá Windows.
Funkce
main()
může, ale nemusí mít parametry, ale zpravidla vrací nějakou návratovou hodnotu,
většinou typu int. Pokud má funkce
main()
parametry, tak je to řetězec, který napíšete jako parametr, když program
spouštíte z příkazové řádky. Možná se vám to zdá složité a tak uvedu jednoduchý
příklad, jak taková funkce může vypadat.
Jak tedy může funkce
main()
vypadat? V různých vývojových prostředích různě, například takto:
//prvni priklad
void main()
{//zde jsou videt slozene zavorky
//zadne vstupni parametry
//zadna navratova hodnota
//i takovato funkce se muze
vyskytnout
//ale neni to zvykem
}
//jiny priklad fce main(), casteji
viditelny
int main()
{
//zadne vstupni parametry
//ale vraci hodnotu, nejcasteji int
return 0;
}
//tak do tretice priklad fce main()
int main(char args[])
{
//tato fce prijima jako parametr
retezec z prikazove radky
//a vraci hodnotu int jako v
predchozim priklade
//jmeno vstupniho parametru se muze
lisit od ruznych IDE
//dokonce jich muze byt i vic!
return 0;
}
Poznámka: Opět si všimněte dobrého zvyku. Vidíte, že tělo funkce je o kousek odsazeno oproti složeným závorkám a jménu funkce. Toto odsazení samozřejmě nemá vliv na funkci programu, ale výrazně zpřehledňuje kód!
O funkcích si samozřejmě
povíme více, toto je jen úvod. Když program spouštíte z příkazového řádku
(Windows) nebo v DOSu, můžete zapsat nějaký parametr, který je pak předán funkci
main() jako
argument
args[],
což je pole znaků čili řetězec. Tedy například:
C:\WINNT\
C:\WINNT\cd..
C:\cd Programs
C:\Programs\cd Main
C:\Programs\Main\main.exe test
Zde předáváte funkci
main()
řetězec
"test",
to znamená, že po spuštění programu v proměnné
args[]
bude opravdu řetězec
"test"
a vy s ním můžete cokoliv dělat, třeba vypsat na obrazovku. Takto může uživatel
vašeho programu měnit chování programu, aniž by znal, jak vypadá vlastní kód.
My si ukážeme konkrétní
příklad, jak parametry fungují ve Visual C++, pokud vytvoříte konzolovou
aplikaci. Když se podíváte na funkci
main(),
určitě vám přijde složitá, protože vůbec nevypadá tak, jak jsem před chvilkou
říkal. Na první pohled vidíte, že má dva parametry, místo jednoho. První
parametr
argc
určuje počet vámi zadaných parametrů v příkazové řádce. Jeho minimální hodnota
je 1, protože se jako parametr bere i název programu, tedy například
"main.exe"
a i tento řetězec "vniká" do funkce
main().
Druhý parametr
argv[]
je trošičku složitější, ale o to vychytanější. Je to ukazatel (že nevíte co to
je? tím se zatím netrapte) čili adresa nějaké buňky v paměti. Ukazatel na pole
znaků. Zní to nesmírně složitě, ale ve skutečnosti je to velmi jednoduché.
Zkrátka parametr
argv[]
uchovává všechny řetězce, které jste zapsali do příkazové řádky. K jednotlivým
řetězcům přistupujeme pomocí indexů v intervalu 0 až (argc-1).
Následující příklad vypíše všechny parametry v příkazové řádce nezávisle na
jejich počtu.
#include "stdafx.h"
//vlozeni standardniho hlavickoveho
souboru (kvuli fci cout)
#include <iostream.h>
int main(int argc, char* argv[])
{
//smycka typu for bezi od 0 do argc-1
for(int i = 0; i < argc; i++) {
//funce cout vypisuje jednotlive
parametry
//na obrazovku
//vsimnete si formatovaní
jednotlivych radku
//funkci cout a cin si popiseme
pozdeji
cout << "Argument #" << i << "
:" << argv[i] << "\n";
}
return 0;
}
"Funkce"
cout a
cin
jsou deklarovány v hlavičkovém souboru
iostream.h,
který musíte vložit do svého programu, abyste je mohli použít. Vkládání
hlavičkových souborů je vidět na předchozím příkladu. Pokud se jedná o
standardní hlavičkový soubor, tzn. o soubor vytvořený týmem fy Microsoft,
uzavírá se jméno souboru do lomených závorek: <>. Pokud jde o váš hlavičkový
soubor, který jste napsali vy, uzavírá se jméno souboru do uvozovek: "". IDE tak
pozná odkud má vkládat hlavičkové soubory. Standardní hlavičkové soubory jsou
uloženy v adresáři vlastního IDE (např.
C:\Program Files\Microsoft Visual Studio\VC98\Include)
a vaše soubory jsou uloženy v adresáři vašeho programu (např.
C:\Programs\Main\).
Možná se divíte, že za
funkcemi cout a
cin
nepíši závorky jako u funkce
main().
Je to proto, protože cout a
cin vlastně nejsou funkce:-) Jedná se o objekty
jazyka C++, které používají tzv. proudy (streams). Proudy mohou být různé,
například vstup a výstup na tiskárnu, do souboru a na obrazovku (cout a cin). V
C pod DOSem se používali funkce
printf()
a
scanf(), ale
my budeme používat objekty
cin
a
cout, protože
se učíme C++. Pro přístup k proudům se používají speciální operátory: << a >>,
jak jste si jistě všimli v předchozím případě.
Objekt
cout,
který jsme před chvilkou použili, se používá pro výstup na monitor. Použití si
ukážeme na jednoduchém příkladu:
int i = 5;//definice promene i = 5
cout << "Ahoj\n";// vypise na monitor
"Ahoj" a odradkuje
cout << i << "\n";// vypise na monitor
obsah promene i a odradkuje
cout << "Obsah i: " << i << "\n";//vypise
na monitor "Obsah i:", obsah i a odradkuje
Je vidět, že
cout
můžeme použít k zobrazení řetězce i obsahu proměnné. Asi se divíte, copak dělají
znaky "\n". Tento "dvojznak" je ve
skutečnosti jeden znak, který zajistí odřádkování. Odborně se nazývá escape
sekvence, kterých je celá řada:
|
Sekvence |
Hodnota |
Význam |
|
\n |
0x0A |
nová řádka (newline, linefeed - LF) |
|
\r |
0x0D |
návrat na začátek řádky (carrige return - CR) |
|
\f |
0x0C |
nová stránka (formfeed - FF) |
|
\t |
0x09 |
tabulátor (tab - HT) |
|
\b |
0x08 |
posun doleva (backspace - BS) |
|
\a |
0x07 |
písknutí (alert - BELL) |
|
\\ |
0x5C |
zpětné lomítko (backslash) |
|
\' |
0x2C |
apostrof (single quote) |
|
\0 |
0x00 |
nulový znak (null character - NUL) |
Poznámka: Pokud znáte HTML, tak escape sekvence jsou něco podobného jako znaková entita.
Objekt
cin
(nyní víte, že se jedná o objekt) se používá velmi podobně. Následuje opět
jednoduchý příklad:
int i; //definice promene i
cin >> i;//nacteni ciselne hodnoty do
promene i
cout << "Obsah i: " << i << "\n";//vypsani
obsahu i (viz vyse)
Všimněte si, že "šipky"
u
cin jsou na
opačnou stranu než u
cout.
To proto, že se jedná o opačný směr proudu - z klávesnice do proměnné je >>,
zatímco z proměnné na monitor jsou "šipky" opačně <<. A to je celé kouzlo vstupu
a výstupu na obrazovku. Pokud jste to pochopili, pak máte vyhráno a pokud ne,
tak si s tím nelámete hlavu, protože časem se všechno vyjasní :-)
Pomocí operátorů provádíme aritmetické, ale i logické operace s proměnnými. Nyní už víte, jak proměnnou vytvořit (definovat), takže už nezbývá nic jiného, než se naučit s proměnnými pracovat. Uvedu krátký přehled, kde by měly být uvedeny všechny základní operátory. Všechny tyto operátory jsou binární tzn., že potřebují dva operandy a vrací výsledek.
|
Binární operátory |
||
|
Význam operace |
Syntaxe |
Příklad |
|
Sčítání |
+ |
50 + 50 = 100 |
|
Odečítání |
- |
100 - 50 = 50 |
|
Násobení |
* |
10 * 10 = 100 |
|
Dělení |
/ |
100 / 10 = 10 |
|
Dělení modulo |
% |
7 % 3 = 1 (zbytek po dělení) |
|
Bitový posun doleva |
<< |
10 >> 1 = 5 |
|
Bitový posun doprava |
>> |
10 << 1 = 20 |
|
Logický součin |
& |
1 & 2 = 0 nebo 3 & 6 = 2 |
|
Logický součet |
| |
1 | 2 = 3 nebo 3 | 6 = 7 |
Dále rozlišujeme unární operátory, kterým stačí jeden operand.
|
Unární operátory |
||
|
Význam operace |
Syntaxe |
Příklad |
|
Unární plus - kladné číslo |
+ |
+12547 |
|
Unární minus - záporné číslo |
- |
-12547 |
Speciální unární operátory
Význam:
++proměnná
proměnná--
Poznámka:Operátor inkrement a dekrement lze použít pouze na l-hodnotu čili proměnnou. Oba operátory mohou být použity buď jako předpona (prefix) nebo jako přípona (sufix).
C navíc poskytuje celou řadu dalších přiřazovacích operátorů, které oproti binárním operátorům zkracují zápis.
|
Zkrácený zápis |
Normální zápis |
|
l-hodnota += výraz |
l-hodnota = l-hodnota + výraz |
|
l-hodnota -= výraz |
l-hodnota = l-hodnota - výraz |
|
l-hodnota *= výraz |
l-hodnota = l-hodnota * výraz |
|
l-hodnota /= výraz |
l-hodnota = l-hodnota / výraz |
|
l-hodnota %= výraz |
l-hodnota = l-hodnota % výraz |
|
l-hodnota <<= výraz |
l-hodnota = l-hodnota << výraz |
|
l-hodnota >>= výraz |
l-hodnota = l-hodnota >> výraz |
|
l-hodnota &= výraz |
l-hodnota = l-hodnota & výraz |
|
l-hodnota |= výraz |
l-hodnota = l-hodnota | výraz |
Abyste mohli využívat
funkce pro vstup a výstup musíte k vašemu programu připojit hlavičkový soubor
(header file). O vkládání hlavičkových souborů jsem se již zmínil v minulé
lekci. Provede to příkazem
include.
Vložte následující řádek na začátek vašeho programu:
#include <stdio.h>
Nyní můžete používat všechny funkce deklarované právě v souboru
stdio.h
a které souvisí s I/O (input/output - vstup/výstup) operacemi jako je i
výstup na obrazovku a vstup z klávesnice (dále může obsahovat například výstup
na tiskárnu či čtení a zápis do souboru).
Výstup jednoho znaku
zajišťuje funkce
putchar(),
která má jeden parametr kupodivu typu
int
(nikoliv char), který představuje hodnotu znaku v ASCII tabulce.
Druhá funkce
getchar()
přečte jeden znak. Pokud na řádek napíšeme víc znaků a stiskneme Enter,
funkce přečte pouze první znak a ostatní ignoruje.
Program přečte znak, vypíše ho a odřádkuje.
Krátký příklad:
int
c;
c = getchar();
putchar(c);
putchar('\n');
Všimněte si, že jako
parametr funkce
putchar()
můžete použít znakovou konstantu tj. znak uzavřený mezi apostrofy např.
'a', '5'
apod. Hodnota znakové konstanty odpovídá celočíselné hodnotě z ASCII
tabulky např.
'a'
má hodnotu 97. (ASCII tabulka nemusí být na všech systémech stejná).
Zápis
putchar(97);
a
putchar('a');
je ekvivalentní.
Co když ale chceme přečíst více než jeden znak nebo naopak chceme vypsat celou větu? Od toho má C dvě funkce:
scanf()
- načte znaky, řetězce nebo čísla z klávesnice a uloží je v požadovaném
formátu do proměnné.
printf()
- vypíše řetězec v požadovaném formátu na obrazovku.
Způsob formátování
Obě funkce mají proměnný počet parametrů a my jim tedy musíme říct, kolik
parametrů mají zpracovávat. K tomu slouží první parametr, což je řetězcová
konstanta (nebo také řídící řetězec) obsahující jisté znaky, které právě říkají
kolik má mít funkce parametrů a jakého jsou typu.
Řídící řetězec může obsahovat:
printf().
Tabulka nejčastěji používaných formátových specifikací:
|
Znak za % |
Typ parametru |
|
c |
znak - je lepší použít funkci getchar() nebo putchar() |
|
d |
celé číslo typu signed int |
|
ld |
celé číslo typu signed long |
|
u |
celé číslo typu unsigned int |
|
lu |
celé číslo typu unsigned long |
|
f |
reálné číslo typu float |
|
lf |
reálné číslo typu double |
|
Lf |
reálné číslo typu long double (L musí být skutečné velké) |
|
x |
číslo v hexadecimálním tvaru s malými písmeny - 5b8f |
|
X |
číslo v hexadecimálním tvaru s velkými písmeny - 5B8F |
|
o |
číslo v osmičkovém tvaru |
|
s |
řetězec |
Příklady použití
printf():
printf("Je presne %2d:%2d\n", hodiny, minuty);
printf("Cena vyrobku je %6.2f Kc\n", cena);
printf("Cislo %d je hexa %XH\n", cislo, cislo);
Příklady použití
scanf():
scanf("%d", &cele_cislo);
&
před proměnnou
cele_cislo.
Funkci
scanf()
předáváme ukazatel na proměnnou
cele_cislo
a tento operátor vrací právě ukazatel. To je rozdíl od předchozí funkce a
začínající programátor na to často zapomene. Kompilátor žádnou chybu nevypíše,
ale program funguje chybně. Zatím vám stačí vědět, že na tento operátor
nesmíte zapomenout.
scanf("%f", &realne_cislo);Booleovský výraz
je výraz, který vrací buď logickou 1 (TRUE - pravda) nebo logickou 0 (FALSE -
nepravda). Jazyk C nemá přímo typ Boolean a tak místo toho využívá typ
int, kde
hodnota 0 představuje FALSE a nenulová hodnota (nejčastěji 1, ale není to
podmínkou) představuje TRUE. Následující tabulka ukazuje tzv. relační
operátory, které používáme v podmínkách. Vše si ukážeme na příkladu.
|
Operace |
Syntaxe |
|
rovnost |
= = |
|
nerovnost |
!= |
|
logický součin |
&& |
|
logický součet |
|| |
|
negace |
! |
|
větší |
> |
|
menší |
< |
|
větší nebo rovno |
>= |
|
menší nebo rovno |
<= |
Každý booleovský výraz
obsahuje jeden nebo více těchto operátorů.
Příklad:
// Toto je prirazeni, nikoliv porovnani, menime hodnotu promenne i
i = 5
// Zde vidite porovnani hodnoty promenne
i a cisla 5
// Vyraz vraci nenulovou hodnotu (TRUE)
v pripade ze v promenne i je skutecne cislo 5, jinak vraci 0 (FALSE)
i == 5
Zkrácené vyhodnocování
platí pro logické operace tzn. logický součin a logický součet. Znamená to, že
jakmile je možno určit konečný výsledek vyhodnocování skončí.
Příklad:
// Toto je zcela správné a k dělení nulou nikdy nedojde,
// protože y != 0 ukončí vyhodnocování
dříve než by k něčemu takovému došlo
if(y != 0 && x / y < z)
Jak jsme si říkali v
minulé, booleovský výraz vrací někajou logickou hodnotu (0 nebo 1). K
vyhodnocení takovéhoto výrazu použijeme příkaz
if:
if(výraz)
{ // zavorky jsou nutne
příkaz_1;
}
příkaz_2;
Pokud je podmínka
výrazu
splněna (vrací TRUE), program provede
příkaz_1
a poté i
příkaz_2.
V opačném případě (pokud výraz vrátí FALSE) je proveden pouze
příkaz_2.
Příklad:
// Program testuje zdali hodnota promenne i je vetsi nez 5
// V kladnem pripade vypise hlasku
if(i > 5) {
printf("Hodnota promenne i je vetsi
nez 5\n");
}
Všimněte si, že výraz je vždy uzavřen do kulatých závorek () a že příkazy "pod"
if
jsou uzavřeny do závorek složených {}. Pokud je "pod"
if
pouze jeden příkaz, složené závorky nejsou nutné, naproti tomu kulaté jsou
nutné vždy!
Příkaz
if-else
se liší jen málo. Příkazy "pod"
else
jsou prováděny jen když
výraz
vrací FALSE.
Česky řečeno:
"Když (if) něco (výraz), tak udělej tohle (příkaz_1), jinak (else) udělej
tamhleto (příkaz_2)."
Příklad:
// Program testuje zdali hodnota promenne i je vetsi nez 5
// V obou pripadech vypise logickou
hlasku
if(i > 5) {
printf("Hodnota promenne i je vetsi
nez 5\n)";
}
else {
printf("Hodnota promenne i je mensi,
rovna nez 5\n)";
}
Všimněte si, jak jsou příkazy ať už "pod"
if
nebo
else,
mírně odsazeny oproti ostatním příkazům. Je to zdůvodu čitelnosti programu a
silně doporučuji se toho držet.
Příkazy
if-else
jsou často vhnízděné do jiných příkazů
if
nebo
if-else.
Každé další
if
je odsazeno o jeden tabelátor doprava.
Příklad:
// Program nejprve testuje hodnotu promenne a a teprve pak pokracuje
if(a != 0) {
if(i > 5) {
printf("Hodnota promenne i je vetsi
nez 5\n");
}
else {
printf("Hodnota promenne i je mensi
nebo rovno nez 5\n");
}
}
Teď znovu přepíšu předchozí příklad, ale trochu jinak:
// Zde vidite uzity operator logickeho soucinu neboli AND (zaroven)
// Vyraz v zavorkach tedy znamena:
// Pokud a je ruzne od 0 A
ZAROVEN i je vetsi nez 5, pak vypis hlasku
if(a != 0 && i > 5) {
printf("Hodnota promenne i je vetsi
nez 5\n");
}
V následujím příkladu vidíte logický součet:
// Zde vidite uzity operator logickeho soucinu neboli OR (nebo)
// Vyraz v zavorkach tedy znamena:
// Pokud a je ruzne od 0 NEBO i je vetsi
nez 5, pak vypis hlasku
if(a != 0 || i > 5) {
printf("Hodnota promenne i je vetsi
nez 5\n");
}
Poznámka:
Často můžeme vidět příkaz
if
zkrácený.
Místo příkazu:
if(vyraz
!= 0)
píšeme jen
if(vyraz)
a místo příkazu:
if(vyraz
== 0)
píšeme jen
if(!vyraz)
Ternární operátor
má stejný význam jako příkaz
if-else,
jen syntaxe je jiná. V některých případech je lepší použít ternární operátor
kvůli kratšímu zápisu na jeden řádek, ale doporučuji raději používat
if-else.
Ternární operátor má následující syntaxy:
vyraz_podm ? vyraz_1 : vyraz_2;
Příklad:
// Pokud je a ruzne od 0, i bude rovno 5
// V opacnem pripade bude i rovno 10
i = (a != 0) ? 5 : 10;
Stejný příklad s použitím
if-else:
// Funkce je uplna stejne jako v predchozim prikladu
// Vidite ze zapis pomoci if-else je
delsi, ale je prehlednejsi
if(a != 0) {
i = 5;
}
else {
i = 10;
}
Poznámka: Závorky kolem podmínky u ternárního operátoru nejsou nutné, ale vřele je doporučuji kvůli čitelnosti programu.
Tento cyklus použijeme v
případě, když předem známe počet průchodů cyklem.
Syntaxe je následující:
for(vyraz_start;
vyraz_stop; vyraz_iter) {
prikaz;
}
Cyklus
for
nejdříve vyhodnotí vyraz_start, otestuje pravdivost vyrazu_stop
(když výraz vratí FALSE, cyklus skončí) a provede příkazy uvnitř cyklu a nakonec
provede vyraz_iter. Toto celé se opakuje dokud vyraz_stop nevratí
nepravdu (FALSE).
Ukážeme si několik příkladů:
// Program vypise pod sebe 20 cisel
(0-19)
// Promenna i je predem deklarovana jako
int
// Toto je doporucene pouziti cyklu for
for(i = 0; i < 20; i++) {
printf("%d\n", i);
}
// Program vypise pod sebe 20 cisel
(19-0)
// Promenna i je deklarovana primo v
cyklu (toto nemusi vzdy fungovat)
// Toto je caste pouziti cyklu for
for(int i = 19; i >= 0; i--) {
printf("%d\n", i);
}
Můžeme využít nekonečný cyklus:
// Takovy program radeji nezkousejte,
protoze nikdy neskonci
// ale za chvilku si povime jak ukoncit
cyklus explicitne
for( ; ; ) {
printf("Ahoj\n");
}
Dalším velice důležitým
cyklem je cyklus
while.
Testuje podmínku před průchodem cyklu, tzn., že cyklus nemusí proběhnout ani
jednou. Ukončovací podmínka většinou závisí na příkazu v těle cyklu a my předem
nevíme kolikrát cyklus proběhne.
Syntaxe:
while(podm)
{
prikaz;
}
Výraz
podm
se testuje a pokud vrátí 0 (FALSE), cyklus skončí.
Posledním iteračním cyklem
je
do-while
(dělej-dokud). Liší se od předchozáho tím, že testuje podmínku až po průchodu
cyklem tzn., že cyklus proběhne alespoň jednou. Cyklus je ukončen až když výraz
v podmínce vrátí 0 (FALSE).
Syntaxe:
do {
prikaz;
} while(podm);
Příkazy break a continue ovlivňují normální průběh cyklu. Lze je použít na všechny výše uvedené typy cyklů.
C obsahuje jakýsi
přepínač neboli switch, který umožňuje mnohonásobné větvení programu.
Syntaxe:
switch(vyraz)
{
case hodnota_1: // Toto je jedna vetev
prikaz_1; // ...
break; // ...
case hodnota_2: // A zde zacina dalsi
vetev
prikaz_2;
break;
case hodnota_3:
prikaz_3;
break;
default:
prikaz_def;
break;
}
Program porovnává
vyraz
s jednotlivými
hodnotami_X
každé větve a provede tu větev, kde se
vyraz
rovná
hodnote_X.
Pozor!
Není-li větev ukončena příkazem
break,
program začne zpracovávat další větve v pořadí dokud nenarazí na
break.
Proto je třeba na konci každé větve psát
break.
Z tohoto ale vyplývá, že pokud chceme pro více hodnot zpracovat pouze jednu
větev, stačí vynechat příkaz
break
v těchto větvích.
Například:
switch(vyraz)
{
case hodnota_1:
prikaz_1;
break;
case hodnota_2:// Zde neni break
case hodnota_3:
prikaz_23;
break;
}
Prikaz_23 se
provede, když je vyraz roven buď
hodnote_2
nebo
hodnote_3.
V dnešní lekci jsem si pro Vás připravil příklad, který procvičuje veškerou předchozí látku. Příklad si můžete stáhnout zde.
#include
<stdio.h>
int main(int argc, char* argv[])
{
int iVolba, i;
char c;
double f, g;
do {
//
// Informace pro uzivatele
printf("1) Test smycky for\n");
printf("2) Test smycky while\n");
printf("3) Test smycky do-while
\n");
printf("4) Konec programu\n");
printf("Zadejte co chcete
otestovat:");
//
// Ulozime uzivatelovu volbu do
promenne iVolba jako hodnotu typu int
scanf("%d", &iVolba);
//
// Vetveni programu podle toho, co
zvoli uzivatel
switch(iVolba) {
case 1:
//
// Test cyklu for
printf("Zvolil jste prikaz for,
vypisu 10 cisel a skoncim.\n");
//
// Cyklus for od 0 do 9
for(i = 0; i < 10; i++) {
printf("%d\n", i);
}
//
// Nakonci jeste odradkujeme
printf("\n");
break;
case 2:
printf("\nZvolil jste prikaz
while.\n");
printf("Program cte znaky z
klavesnice, tisknutelne znaky opisuje,\n");
printf("neviditelne preskakuje a
zastavi se pri precteni znaku 'z'.\n\n");
//
// Precteni znaku
while((c = getchar()) != 'z') {
if(c >= ' ') { // Vynechavame
neviditelne znaky
putchar(c); // Tisk znaku
printf("\n"); // Odradkuju
}
}
break;
case 3:
printf("Zvolil jste prikaz do-while.
\n");
printf("Program nacte dolni a
horni mez intervalu\n");
printf("a pote vypise vsechny
cela cisla z tohoto intervalu.\n");
//
// Nacteme dve realna cisla
printf("Zadejte dolni mez
intervalu:");
scanf("%lf", &f);
printf("Zadejte horni mez
intervalu:");
scanf("%lf", &g);
//
// Test zdali je horni mez
skutecne vetsi nez dolni mez
if(f < g) {
//
// Zaokrouhleni realneho cislo
na cele cislo (explicitni konverze)
i = (int) f;
do {
// Vypsani celeho cisla
printf("%d\n", i);
// Prejdeme na dalsi cele
cislo
i++;
// A testujeme zdali jsme jiz
dosahli horni hranice intervalu
} while(i <= g);
}
else {
printf("Chyba! Zadali jste
chybne meze.\n");
}
break;
case 4:
//
// Konec
printf("Konec...\n");
break;
default:
printf("Zvolil jste kravinu\n");
break;
}
//
// Testujeme promennou iVolba na
hodnotu 4,
// coz je volba, pri ktere ma program
skoncit
} while(iVolba != 4);
return 0;
}
Poznámka:
Všimněte si odsazení jednotlivých větví příkazu
switch
a odsazení těl
cyklů. Toto je velmi dobré dělat kvůli přehlednosti v programu
Nejdříve se podíváme na
převody proměnných určitého typu na jiný typ (například z
int na
double).
Existují dva druhy typové konverze:
Pravidla:
|
int |
-> |
unsigned int (UINT) |
|
unsigned int (UINT) |
-> |
long |
|
long |
-> |
unsigned long (DWORD) |
|
unsigned long (DWORD) |
-> |
float (FLOAT) |
|
float (FLOAT) |
-> |
double |
|
double |
-> |
long double (nejvyšší priorita) |
3.
V závorkách jsou uvedeny typy, které se používají ve Visual C++.
Příklad:
int i = 5;
double d;
//
// Toto je mozne diky implicitni konverzi
// Typ int (i) se automaticky prevede na
typ float (d)
d = i;
Tuto konverzi plně řídí programátor. Může tak prakticky převést cokoliv na cokoliv, ale nezaručí vždy přesné nebo očekávané výsledky (např. konverze z double na int zaokrouhlí reálné číslo na celé číslo - 3.14 -> 3).
Explicitní typová
konverze má tvar:
(typ) vyraz
a znamená to, že
vyraz je v
čase překladu konvertován na
typ.
Můžeme také použít explicitní konverzi tam, kde mi normálně proběhla implicitní,
ale programátor tak může vyjádřit, že konverzi chtěl.
Seznam často používaných explicitních konverzí:
|
(int) char_vyraz |
- převod znaku na ordinální číslo (pořadí v ASCII tabulce) |
|
(char) int_vyraz |
- převod ordinálního čísla na znak |
|
(int) float_vyraz |
- zaokrouhlení reálného čísla (zaokrouhluje se vždy dolu) |
Pokud se pokusíte o implicitní konverzi, která nějakým způsobem zhoršuje
přesnost čísla, kompilátor vás na to upozorní. Toto varovné hlášení odstraníte
explicitní konverzí.
Příklad:
double d = 3.14;
int i;
//
// Explicitni konverze odrizne desetinnou
cast
// takze promenne i se priradi hodnota 3
i = (int) d;
Preprocesor
zpracovává kód ještě před vlastním překladem. Zatím jsme používali jen příkaz
include,
který nám umožňoval používat nějaké další užitečné funkce v našich programech. V
této lekci si rozšíříme znalosti příkazů preprocesoru. Preprocesor připraví Váš
kód k překladu.
Preprocesor provádí:
Jistě jste si všimli, že
příkaz
include
musí mít před sebou znak
#.
To je spravný postřeh a neplatí jen pro příkaz
include,
ale pro všechny ostatní příkazy preprocesoru. Takže znakem
#
uvozujeme všechny příkazy určené pro preprocesor.
Symbolickým konstantám
se někdy též říká makra bez parametrů. Pomocí těchto konstant zbavíte program
"magických čísel" tzn. konstant, které používáte v programu. Když pak někdo čte
váš program a vidí konstantu
PI
místo čísla
3.141592654,
velmi ho to potěší.
Navíc definováním takových konstant můžete snadno měnit parametry programu. Když
například vypisujete 100 řádek na monitor, ale náhle rozhodnete, že chcete aby
program vypisoval jen 50 řádek, stačí změnit konstantu na začátku programu a
nemusíte přepisovat všechny konstanty v programu.
Syntaxe:
#define
JMENO_KONSTANTY hodnota
Pro symbolické konstanty platí tyto pravidla:
"\".
Příklady:
#define
PI 3.141592654 // Presne Ludolfovo cislo
#define DATA_TXT "DATA.TXT" // Jmeno
souboru
#define EOL '\n' // Odrakovani -
End of Line
#define DLOUHY_RETEZEC "Tohle je strasne
dlouhy retezec, \
takze bacha."
A teď můžete psát
definované konstanty místo konkrétních číselných hodnot a preprocesor je nahradí
správnými hodnotami při překladu.
Poznámka:
Makro se v programu nerozvine (nebude nahrazeno), pokud je uzavřeno v
uvozovkách.
Například:
printf("Ludolfovo cislo je PI\n");
Toto je špatně,
konstanta PI nebude nahrazena. Řešením může být třeba toto:
printf("Ludolfovo cislo je %f\n", PI);
Platnost definice
konstanty
Pokud nadefinujete již definovanou konstantu a přitom změníte její hodnotu,
kompilátor vypíše varovné hlášení. Pokud chcete v průběhu programu konstanty
měnit, musíte ji nejdříve "oddefinovat" a teprve poté ji nadefinovat znovu.
Příklad:
#define MAX_POLE 50 // prvni definice
MAX_POLE
..
..
..
#undef MAX_POLE // Oddefinovani stare
definice
#define MAX_POLE 75 // Definice nove
hodnoty
Toto platí obecně pro všechny makra tzn. symbolické konstanty i makra s
parametrem.
Tyto makra fungují podobně jako funkce. Na začátku programu si nadefinujete určité makra, které pak použijete v programu, preprocesor opět nahradí makro konkrétním kódem v době překladu.
Použití makra je rychlejší než funkce, protože se nic nevolá, jen se v kódu nahrazují kousky kódu makra, ale výsledný program je větší, protože s každým výskytem makra, se kód makra opakuje narozdíl od funkce.
Syntaxe makra:
#define
jmeno_makra(arg1,....,argN) telo_makra
Příklad:
#define je_velke(c) ((c) >= 'A' && (c) <= 'Z')
V programu pak makro voláte takto:
ch
= je_velke(ch) ? ch + ('a' - 'A') : ch;
Těsně před překladem se makro rozvine takto:
ch = ((ch) >= 'A' && (ch) <= 'Z')) ? ch +
('a' - 'A') : ch;
Dobré rady:
Soubor
stdio.h
obsahuje několik maker, které jsme již využívali:
putchar(c)
a
getchar()
My si uvedeme další hlavičkový soubor
ctype.h,
který obsahuje definice dalších užitečných maker. Makra jsou zde rozdělena do
dvou skupin, z nichž první skupina nemění hodnotu parametrů, ale jen zjišťují
vlastnosti parametru:
|
Jméno |
Použití |
|
|
Vrací argument, pokud je argument číslice, malé či velké písmeno, jinak vrátí 0 (FALSE) |
|
|
Vrací argument, pokud je argument malé či velké písmeno, jinak vrátí 0 (FALSE) |
|
|
Vrací 1 (TRUE), pokud je argument z ASCII tabulky, jinak vrátí 0 (FALSE) |
|
|
Vrací znak (číslici), pokud je argument číslice, jinak vrátí 0 (FALSE) |
|
|
Vrací znak, pokud je argument malé písmenko, jinak vrátí 0 (FALSE) |
|
|
Vrací znak, pokud je argument neviditelný znak (mezera, tabulátor), jinak vrátí 0 (FALSE) |
|
|
Vrací znak, pokud je argument velké písmenko, jinak vrátí 0 (FALSE) |
Narozdíl makra druhé skupiny mění hodnotu parametru:
|
Jméno |
Použití |
|
|
Převede argument (velké písmenko) na malé písmenko |
|
|
Převede argument (malé písmenko) na velké písmenko |
Velice užitečnou součástí programovacího jazyka jsou pole. Představte si, ze byste chtěli napsat jednoduchý telefonní seznam. Určitě Vás napadá, že ukládat jména Vašich kamarádů tak, že pro každé jméno budete mít jednou proměnnou, není moc dobrý nápad. To byste museli program zkompilovat znovu pokaždé, když chcete někoho přidat. A to je jen ta nejmenší nevýhoda. Pole Vám dovolí používat více proměnných stejného typu, jedna vedle druhé, pod stejným jménem:
void main(int argc, char *argv[]) {
int pole[10]; // deklarujeme pole 10 proměnných int
pole[5] = 1; // měníme jedno z prvků pole
cout << pole[5]; // a vypisujeme ho
}
V příkladu jsme
deklarovali pole deseti prvků typu
int,
a ukázali jsme si, jak se k jednomu z prvků pole přistupuje, totiž pomocí
stejného operátoru, kterým se pole deklarují - hranatých závorek. Je důležité si
pamatovat, že počítání prvků pole vždy začíná nulou, takže můžeme používat čísla
prvků (správně se jim říká indexy) 0 až 9 (v dalších příkladech budu
vynechávat funkci
main(),
proto ji nezapomínejte pokaždé přidat):
int pole[10];
for (int i = 0; i < 10; i++)
pole[i] = i; // procházení polem cyklem for
for (int i = 0; i < 10; i++)
cout << pole[i] << ' ';
Dalším důležitým faktem je, že kompilátor nekontroluje, zda jsme nepřekročili meze pole, takže když napíšeme něco jako:
int pole[10];
int dalsi;
pole[10] = 1; // 10 není platný index
přepíšeme si tímto jinou
proměnnou, zde zrovna proměnnou
další,
a program nám určitě nebude fungovat správně. Musíme si tedy pamatovat, že
nejvyšší index, který můžeme používat, je o jedničku menší, než deklarovaná
velikost pole. Nejmenší index je 0, ačkoli jestli se pokusíte použít záporné
číslo kompilátor Vám to klidně dovolí.
Jazyky C a C++ obsahují
i prostředek, kterým se zjišťuje velikost pole, je jím operátor
sizeof:
char pole_char[10];
int pole_int[10];
cout << sizeof pole_char; // vypíše se 10
cout << sizeof pole_int; // vypíše se 40
Výsledek druhého řádku
cout
možná překvapí. Je
to tím, že operátor
sizeof
vrací skutečnou
velikost pole v bajtech, ne počet jeho prvků. U pole prvků typu
char je to jedno, ale to jen protože
char je velký jeden byte. U prvků
int to už jedno není. Kdybychom chtěli zjistit
počet prvků, musíme používat jeden z těchto zápisů:
cout << sizeof pole / sizeof int;
cout << sizeof pole / sizeof pole[0];
cout << sizeof pole / sizeof *pole; // tento zápis pochopíte později
Podle prvního způsobu
zjišťujeme počet prvků "napevno" - víme, že se jedná o pole "intů", takže dělíme
přímo velikostí datového typu
int
(zjišťujeme-li velikost datového typu, musí tento být uveden v závorkách, u
proměnných jsou závorky nepovinné).
Podle druhého a třetího
způsobu zjišťujeme přímo velikost prvního prvku pole, a nezáleží na tom, jakého
je typu. Druhému způsobu byste měli rozumět hned, a třetí způsob, který se mi
zdá ze všech nejelegantnější, pochopíte později, až budeme probírat ukazatele,
zatím stačí když budete vědět, že zápis
*pole
vrací první prvek pole, má tedy stejný význam jako
pole[0].
Výhoda druhého a třetího
způsobu spočívá v tom, že kdybyste v budoucnu změnili datový typ prvků pole na
jiný, stačí, když ho upravíte pouze v deklaraci pole, ale nemusíte ho měnit i v
kódu
sizeof.
Doporucuji vždy používat operátor
sizeof
tam, kde to je možné, místo pevně zadaného rozměru pole.
Třetí způsob, který se
mi zdá ze všech nejelegantnější, úplně pochopíte později, až budeme probírat
ukazatele, zatím stačí když budete vědět, že zápis
*pole
vrací první prvek pole, má tedy stejný význam jako
pole[0].
Při deklaraci pole ho můžeme hned inicializovat. Například:
int pole1[4] = { 0, 1, 2, 3 };
int pole2[] = { 0, 1, 3, 3, 4, 5 };
Jak vidíte, v deklaraci
pole2 chybí
počet prvků. To protože z počtu konstant ve složených závorkách (inicializátorů)
kompilátor pozná, jak velké musí alokovat pole. Tento zápis je výhodnější,
protože umožňuje určit velikost pole nepřímo, podle počtu inicializátorů.
Uvedení jak rozměru pole, tak i inicializátorů je jistá redundance, jeden z
těchto údajů je nadbytečný, můžeme ho tedy vynechat. Tady je také vidět
důležitost operátoru
sizeof
- bez něj bychom velikost pole
pole2
nemohli zjistit.
Tato vlastnost se nám také bude hodit při deklarování řetězců, jak uvidíme
později.
Jazyky C a C++ umožňují používat i pole vícerozměrná. Deklarují se obdobně jako jednorozměrná pole:
float matice[3][4];
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
matice[i][j] = i * j; // postupné procházení a naplnění pole
Tady jsme deklarovali
dvourozměrné pole, první rozměr je 3, druhý je 4. Je to vlastně matice o třech
řádcích a čtyřech sloupcích. Na tuto deklaraci je také možno hledět jako na pole
tří polí, z nichž každé má čtyři prvky typu
float.
Možná to zní složitě, ale skutečně nic na tom není a určitě si rychle zvyknete.
Z tohoto pohledu vyplývá také použití operátoru
sizeof:
cout << sizeof matice; // vypisuje se velikost v bajtech, tedy 12 * 4 = 48
//(velikost typu float je 4 byty)
cout << sizeof matice[0]; // vypisuje se velikost "prvního řádku", tedy 4 * 4 = 16
Inicializace vícerozměrných polí se provádí obdobně jako jednorozměrných:
int pole[2][3] = {
{0, 1, 2},
{3, 4, 5}
};
Všimněte si, jak tato deklarace opravu připomíná to, co je výše řekl o "poli polí". Vnější složené závorky jako by začínají inicializaci pole dvou prvků, tyto jsou ale zase pole. Toto uspořádání kódu nemusíte dodržovat a můžete vše napsat na jednom řádku, já si myslím, že je přehledný, protože je v něm vidět ta matice.
Samozřejmě není problém deklarovat pole i více než dvourozměrná, prostě přidáme další pár hranatých závorek s dalším rozměrem.
Pole a řetězce mají v C a C++ k sobě velmi blízko. Tyto jazyky totiž pohlížejí na řetězce jako na pole prvků char:
char retezec[100];
Takto jsme deklarovali řetězec, který může obsahovat maximálně 99 znaků. Proč 99, když jsme deklarovali pole o 100 prvcích? To protože jazyk C++ vyžaduje, aby poslední znak řetězce byl speciální ASCII znak s kódem 0. Tímto pozná, kde řetězec končí, například při jeho vypisování. To znamená, že když deklarujeme řetězec musíme uvést počet znaků o jednu větší než maximální počet znaků, který zamýšlíme do řetězce ukládat.
Výše deklarovaný řetězec nám zatím není k ničemu, zkusíme si tedy ho rovnou inicializovat. Tady se nám bude hodit možnost inicializace bez uvedení počtu znaků. Kdybychom museli ten počet uvést museli bychom znaky řetězce počítat... no, nebylo by to nic pěkného:
char str[] = "Ahoj";
Kompilátor poznal, že
deklarujeme řetězec, a alokoval pro řetězec 5 bytů, což uvidíte, když si necháte
vypsat velikost
str
operátorem sizeof. Existuje ještě jeden způsob,
elegantnější, úplně ho zase pochopíte až budeme brát ukazatele, zatím musíte jen
vědět, že to znamená úplně to samé:
char *str = "Ahoj";
Na druhou stranu se nám ale může hodit i inicializace uvedením počtu prvků, například kdybychom chtěli mít možnost ukládat do proměnné i delší řetězce než je ten uvedený při deklaraci. Musíme ovšem dávat pozor na to, abychom deklarovali alespoň tolik znaků, kolik má inicializátor (samozřejmě plus jedna), jinak kompilace skončí chybou.
char str[20] = "Další řetězec.";
char str[5] = "Toto skončí chybou";
Jazyk C++ nabízí pro
práci s řetězci více funkcí. Všechny jsou uloženy v takzvané run-time
knihovně, což je soubor funkcí, který se přidá k vašemu programu při jeho
sestavení (build). K nejdůležitějším funkcím pro práci s řetězci patří:
strlen,
strcpy,
strcat,
strchr,
strcmp
a jiné (je jich
skutečně spousta, uvedl jsem jen nejpoužívanější):
Funkce
strlen
vrací délku řetězce, volá se
strlen(řetězec):
char str1[30] = "Já jsem krátký řetězec.";
cout << strlen(str1);
Pozor, neplést si funkci
strlen
a operátor
sizeof,
jsou to dvě různé věci:
sizeof
vrací velikost řetězce, tedy kolik znaků se do něj maximálně vejde (včetně
nulového znaku na konci) - podle našeho příkladu by to bylo 30, kdežto
strlen
vrací skutečný počet znaků, který řetězec obsahuje (to poznává podle nulového
ukončovacího znaku, který se v našem případě nachází hned za tečkou).
Funkce
strcpy
kopíruje řetězec do jiného včetně nulového ukončovacího znaku, volá se
strcpy(kam, odkud).
Například:
char str1[] = "Zkopíruj me!";
char str2[20];
strcpy(str2, str1);
cout << str2; // nyní str2 obsahuje stejný text jako str1
Při používaní této
funkce je třeba dávat pozor na to, že nekontroluje velikost cílového řetězce.
Jestliže se pokusíme zkopírovat 100znakový řetězec do řetězce, jehož velikost je
10, funkce
strcpy
si nebude stěžovat, velmi ochotně to provede, ale na funkčnosti programu se toto
asi projeví katastrofálním způsobem, protože nám určitě přepíše část jiných dat.
Teď, když známe funkci
strcpy
můžeme si ukázat další možnost inicializace řetězců, a to řetězcovou konstantou
v programu:
char str1[20];
strcpy(str1, "Konstanta"); // toto je řetězcová konstanta
cout << str1;
Jde o to, že v v
programu můžeme kdykoli používat řetězec, který nemá jméno a který jsme předtím
nedeklarovali. Je to totéž jako když napíšeme
a = 3.
Tu trojku jsme přece nikde nedeklarovali, a s řetězci je to to samé. Nevýhoda
tohoto postupu ale je, že k jednou použitému řetězci se nemůžete vrátit,
nemůžete se k němu znovu odkazovat, prostě ho musíte napsat znovu. A až Váš
program bude slavný a budete ho chtít přeložit do jiného jazyka, budete muset
projít celý kód a hledat kde všude máte řetězcové konstanty. Je to dřina, a tak
Vám tuto praktiku příliš nedoporučuji.
Chceme-li spojit dvě
řetězce do jednoho, použijeme funkci
strcat(kam, odkud).
Například:
char str1[20];
strcpy(str1, "První ");
strcat(str1, "Druhý");
cout << str2; // vypíše se První Druhý
Další užitečnou funkcí
je
strcmp.
Jak jste možná poznali z jejího jména (cmp je zkratka anglického
compare), slouží k porovnávání řetězců. Funkce se volá
strcmp(první_řetězec, druhý_řetězec),
a vrací hodnotu int, která má tento význam:
je-li menší než nula,
první_řetězec
by byl ve slovníku před
druhý_řetězec,
je-li větší než nula bylo by to naopak, a je-li nula řetězce jsou stejné. Tato
funkce má variantu
stricmp,
která porovnává bez ohledu na velikost písmen (vnitřně převádí všechna písmena
na malá).
char str1 = "abcd";
char str2 = "bcde";
int vysledek;
vysledek = strcmp(str1, str2);
cout << vysledek;
Funkce nám umožňují napsat kód, který provádí nějakou operaci, pouze jednou, a pak se na něj odkazovat z jakéhokoli místa v programu (programátorsky se tomu říká "zavolat funkci"). Můžeme tak provádět stejnou činnost na různých místech v programu aniž bychom pokaždé museli napsat stejný kód. Funkce také vnášejí do kódu modularitu a řád a dělají program přehlednějším.
Funkce může získat od volajícího programu nějaké informace (říká se jim "parametry funkce"), a může mu nějakou informaci vrátit (to je "návratová hodnota"). Jak parametry, tak návratová hodnota mohou být jakéhokoli datového typu.
Před použitím funkcí si musíme ujasnit pojmy deklarace a definice. Deklarací říkáme kompilátoru, že někde dále v programu voláme takovou funkci, a dáváme mu k dispozici příslušné údaje o ní (jméno, seznam parametrů, typ návratové hodnoty). Příklad dvou deklarací:
int min(int a, int b); // počítá minimum dvou čísel
void vypisCopyright(); // vypisuje údaje o autorovi
Na prvním místě je typ návratové hodnoty, v našem případě funkce min vrací celé číslo, funkce vypisCopyright nevrací vůbec nic - to je význam klíčového slova void. Dále následuje jméno funkce (min, vypisCopyright) a pak v kulatých závorkách seznam parametrů (uvádí se datový typ a jméno parametru - funkce min má tedy dva celočíselné parametry (a, b), funkce vypisCopyright nemá žádný parametr. Takovému řádku se říká "hlavička funkce".
Poznámka: V některých programech se můžete setkat s deklarací, která vypadá následovně:
void vypisCopyright(void);
tedy místo prázdného seznamu parametrů je klíčové slovo void. Je to naprosto totéž jako příklad vypisCopyright výše (funkce bez parametrů), ale v jazyce C (předchůdci jazyka C++).
Všimněte si, že deklarací ještě neuvádíme, co vlastně bude funkce provádět. To
kompilátoru říkáme až definicí:
int min(int a, int b) { // vrací nejmenší ze dvou čísel
if (a < b)
return a; // a < b, vracíme tedy jako výsledek a
return b; // pokud jsme se dostali az sem,
// tak a >= b, vracime tedy b
}
void vypisCopyright() {
cout << "(c) Andrei Badea, 2001\n";
}
Definice tedy začíná opětovným uvedením hlavičky, ale místo středníku na konci již píšeme kód, který se ve funkci vykonává, uzavřen do složených závorek ("tělo" funkce). V příkladu jsme použili další klíčové slovo jazyka C++, a sice příkaz return. Tímto příkazem říkáme: tady skonči provádění funkce, vrať hodnotu a předej řízení nadřazenému programu (který funkci zavolal). Proběhne-li příkaz return, další příkazy, které za ním mohou následovat, se neprovedou. Návrat do nadřazeného programu také probíhá samovolně na konci funkce. Pokud funkce nic nevrací, return tam ani nemusí být (viz funkci vypisCopyright). V opačném případě je použití return i s nějakou návratovou hodnotu povinné (funkce min).
Použítí funkce v programu vypadá takto:
#include <iostream.h>
// deklarace
int min(int a, int b);
// definice
void vypisCopyright() {
cout << "(c) Andrei Badea, 2001\n";
}
// funkce main - to je také definice
void main() {
int cislo1 = 2, cislo2 = 3;
int vysledek;
vypisCopyright(); // voláme funkci
vysledek = min(cislo1, cislo2); // voláme funkci - parametry uvedeny v hlavičce funkce a
// ty, které předáváme, nemusí mít stejné jméno
cout << vysledek;
}
// definice
int min(int a, int b) {
if (a < b) {
return a;
return b;
}
Proměnné výsledek jsme přiřadili návratovou hodnotu funkce min. Funkci můžeme použít na všech místech, kde kompilátor očekává výraz stejného datového typu, jako je její návratová hodnota, přičemž funguje implicitní i explicitní konverze. Funkci s návratovým typem void můžeme používat jako příkaz, (v jazyce Pascal existuje poměrně výstižný termín "procedura") ale nesmíme zapomenout na závorky. Vlastně jakoukoli funkci můžeme používat jako proceduru, tedy ignorujeme návratovou hodnotu. V minulém díle jsme mluvili o funkci strcpy. Ta je deklarována takto:
char *strcpy(char *str1, char *str2);
Funkce spojí oba řetězce do řetězce str1, který pak vrací. Nás pravděpodobně nezajímá návratová hodnota, protože řetězec str1 máme někde deklarovaný, takže ji můžeme ignorovat a zavolat funkci takto:
strcpy(str1, str2);
Dále si všimněte, že funkci můžeme deklarovat i definovat kdekoli v programu (ale mimo jinou funkci - deklarovat například ve funkci main jinou funkci by nešlo).
Poznámka: z tohoto příkladu je vidět, že veškerý kód programu je uzavřen v nějaké funkci, mimo ni nemůžete psát kód, pouze deklarovat proměnné a konstanty. Takže tvrzením "program volá funkci" jsem se dopustil malé nepřesnosti (z důvodu jednoduchosti výkladu): ve skutečnosti je vždy funkce zavolána jinou funkci (až na funkci main, která je zavolána operačním systémem).
Výše uvedený příklad na používání funkce je možná trochu zarážející, protože chybí deklarace funkce vypisCopyright. Deklarace funkcí jsou ve skutečnosti nepovinné. Pokud funkci definujeme před tím, než ji poprvé zavoláme, tak deklarace je vlastně zbytečná, protože v okamžiku zavoláni funkce kompilátor již má všechny údaje o ní k dispozici. Kdybychom posunuli funkci min před funkci main, mohli bychom vynechat i její deklaraci. Nicméně existují i případy, kdy se bez deklarací neobejdeme. Typickým příkladem jsou dvě funkce, z nichž každá volá tou druhou:
void a(bool stop) {
cout << "Ve funkci a\n";
if (stop == false)
b();
}
void b() {
cout << "Ve funkci b\n";
a(true);
}
void main() {
a(false);
}
Pokusíte-li se zkompilovat tento program, kompilátor ohlásí chybu ve funkci a na řádku b(), protože o funkci b zatím neví vůbec nic (postupuje shora dolů).
Poznámka: pokud Vám není jasný význam parametru stop, ten tam je pouze kvůli tomu, aby vzájemné volání funkcí a a b vůbec skončilo. Jinak by a stále volala b, následně b volala a a nikdy by to neskončilo (tedy ano: "program způsobil neplatnou operaci a bude ukončen", ale to asi nechcete).
Aby nám příklad výše fungoval, musíme ho vylepšit o deklaraci funkce b před funkcí a:
void b();
void a(bool stop) {
cout << "Ve funkci a\n";
if (stop == false)
b();
}
void b() {
cout << "Ve funkci b\n";
a(true);
}
void main() {
a(false);
}
Takové případy, kdy budete potřebovat deklarace, ovšem nejsou příliš časté, takže se deklaracemi nemusíte obtěžovat. Stačí, když budete vědět, že existují.
Představte si, že byste potřebovali funkci počítající aritmetický a geometrický průměr (obě najednou). To by znamenalo, že funkce bude muset vracet dvě hodnoty, a přitom máme k dispozici pouze jednu návratovou hodnotu. Nabízí se možnost použít k vracení výsledků parametry funkce. Deklarace funkce by vypadala takto:
void prumery(double a, double b, double aritm, double geom);
Má tedy 2 vstupní parametry (čísla a, b), a dva výstupní (do parametru aritm budeme ukládat spočtený aritmetický průměr, do geom geometrický):
void prumery(double a, double b, double aritm, double geom) {
aritm = (a + b) / 2;
geom = sqrt(a * b); // sqrt počítá druhou odmocninu
}
Pokusíte-li se funkci použít, zjistíte, že se vypisují nějaké nesmyslné hodnoty (a také dostanete při překladu upozornění že používáte neinicializované proměnné aritm a geom):
#include <math.h>
#include <iostream.h>
void prumery(double a, double b, double aritm, double geom) {
aritm = (a + b) / 2;
geom = sqrt(a * b); // sqrt počítá druhou odmocninu
}
void main() {
double a = 2, b = 3;
double aritm, geom;
prumery(a, b, aritm, geom);
cout << aritm << "\n";
cout << geom << "\n";
}
Je to tím, že existují dva způsoby předávání parametrů: hodnotou a odkazem. Předávání odkazem se používá standardně, a znamená to, že hodnoty parametrů se zkopírují a funkci se předají právě kopie. Tím pádem jakékoli změny, které funkce provede, že ve skutečnosti provedou na kopiích, které se při návratu zahazují. Další možností je předávání odkazem. V tomto případě se funkci předají odkazy na parametry, takže se veškeré změny do hodnot parametrů promítnou. U parametrů aritm a geom potřebujeme tedy předávání odkazem, to se udává operátorem & (nazývá se operátor reference) u těchto dvou parametrů:
void prumery(double a, double b, double &aritm, double &geom) {
aritm = (a + b) / 2;
geom = sqrt(a * b); // sqrt počítá druhou odmocninu
}
Funkce min, kterou jsme si napsali, je poměrně užitečná a možná se Vám bude hodit i v dalším programování. Má ale jednu nevýhodu: umí počítat nejmenší hodnotu jen pro celá čísla. Chceme-li funkce min i pro datový typ double, char atd., musíme si je napsat. Ve starém C muselo jméno funkce být unikátní, z čehož vyplývá, že např. funkce min pro double by se vlastně nemohla jmenovat min, ale třeba min_double. C++ je vyspělejší a tak v něm existuje mechanismus, který umožňuje deklarovat více funkcí se stejným jménem - je to přetěžování funkcí (angl. function overloading), například:
int min(int a, int b) {
if (a < b)
return a;
return b;
}
double min(double a, double b) {
if (a < b)
return a;
return b;
}
void main() {
double dbl_a = 1.0, dbl_b = 1.5;
int int_a = 1, int_b = 2;
cout << min(int_a, int_b) << "\n";
cout << min(dbl_a, dbl_b) << "\n";
}
Spustíte-li tento program, vypíše se nejdřív 1, pak 1.5. Kompilátor poznal jakou funkci chceme volat podle typů parametrů. Pravě takhle to funguje - dvě funkce se stejným jménem se musí lišit alespoň typem jednoho parametru. Není možné přetěžovat funkce pouze na základě rozdílného typu návratové hodnoty. Představte si tento příklad:
void min(int a, int b);
int min(int a, int b);
// definici těchto funkcí vynechávám, nejsou pro tento příklad důležité
void main() {
min();
}
Když voláte funkci min jako proceduru, kompilátor by nevěděl, kterou funkci zavolat: tu bez parametru, nebo tu vracející int.
Každé volání funkce spotřebuje nějaký čas procesoru. Není to moc, ale voláte-li funkci často (například v nějakém cyklu) a záleží-li na každém taktu, může to být poznat. Právě u takových funkcí může být výhodné je definovat jako inline. To znamená, že funkce se nezavolá, ale celé její tělo se vloží na místo, odkud ji voláme (inline funkce fungují prakticky stejně jako makra). Režie spojená se zavoláním odpadne a program tak poběží rychleji:
inline int main(int a, int b) {
if (a < b)
return a;
return b;
}
Na závěr tohoto dílu si
ukážeme rozsáhlejší příklad. Bude se jednat o program počítající, kolika způsoby
je možno vybrat ze skupiny n prvků skupinu k prkvů, bez opakování (počet
k-členných variací bez opakování z n prvků). Například je-li n = 4 a k = 2 jedná
se o počet možných výběrů dvou prvků ze ctyř, přičemž záleží na pořadí - (a,b)
je něco jiného než (b,a):
|
Pro výpočet variací existuje matematický vzorec:
kde
(faktoriál
z čísla n). Také platí 0! = 1.
Vybaveni těmito znalostmi se můžeme pustit do práce: nejdřív budeme potřebovat přečíst parametry z příkazové řádky a připravit je pro výpočet:
#include <stdlib.h>
#include <iostream.h>
// deklarace řetězců s chybovými hlášeními
char *szMaloParametru = "Parametry programu musí být dvě čísla.";
char *szNeplatne = "Parametry nejsou platné.";
// další řetězce použité v programu
char *szVysledek = "Výsledek: ";
void main(int argc, char **argv) {
int n, k;
// program musí mít alespoň 3 parametry (cesta k programu (ta tam je vzdy), n a k)
if (argc < 3) {
cout << szMaloParametru;
return;
}
// převod parametru uložených jako řetězce na čísla funkcí atoi (v hlavičkovém souboru stdlib.h)
// argv[1] je k (první parametr)
// argv[2] je n (druhý parametr)
k = atoi(argv[1]);
n = atoi(argv[2]);
// kontrola správnosti parametrů
if (n <= 0 || k <= 0 || n < k) {
cout << szNeplatne;
return;
}
// výpis výsledku
cout << szVysledek << variace(k, n);
}
Na konci programu vypisujeme výsledek funkcí variace, která spočte požadovaný počet variací na základě parametrů n a k. Tuto funkci si musíme definovat (před funkci main, abychom nemuseli psát i deklaraci), podle matematického vzorce:
unsigned int variace(unsigned int k, unsigned int n) {
// výsledek počítáme rovnou, nemusíme pro něj deklarovat proměnnou
return faktorial(n) / faktorial(n - k);
}
Ve funkci variace používáme funkci faktorial, takže ji přidáme:
unsigned int faktorial(unsigned int n) {
unsigned int i, vysledek = 1;
if (n == 0)
return 1;
for (i = 2; i <= n; i++)
vysledek *= i; // vysledek = vysledek * i6
return vysledek;
}
A to je o funkcích všechno (skoro). Zajímavé je snad jen to, že funkce variace a faktoriál dostávají parametry typu unsigned int (celé číslo bez znaménka) a také takovou hodnotu vracejí. To je v pořádku, variaci ani faktoriál ze záporných čísel počítat nelze. Ale při čtení parametrů z příkazové řádky ve funkci main používáme proměnné typu int, abychom mohli zachytit pokus uživatele o zadání záporných čísel (kdybychom místo int použili i tady unsigned int došlo by při zadání záporného čísla k přetečení a např. místo čísla -1 by se do proměnné uložilo 4294967295).
Každá proměnná, kterou v programu deklarujeme, se nachází v paměti počítače, která je rozdělena na buňky o velikosti jednoho bytu, tedy 8 bitů. Každá buňka má nějaké číslo, pomocí kterého ji lze jednoznačně určit. Tomuto číslu se říká adresa.
Ukazatel (angl. pointer) je proměnná nebo konstanta, která v sobě udržuje adresu nějaké jiné proměnné (říká se, že ukazatel "ukazuje na proměnnou"), nebo i adresu nějaké libovolné buňky v paměti. Ukazatelem je možné číst nebo měnit hodnotu na adrese, kam ukazuje. Jako vždy, ukážeme si příklad:
int a;
int *pInt;
//
pInt je ukazatel na proměnnou typu int
pInt = &a;
//
ukazateli přiřadíme adresu proměnné a
*pInt = 2;
//
měníme hodnotu na adrese, kam ukazatel ukazuje
cout << *pInt;
// vypíše se hodnota 2
Nejdříve jsme
deklarovali ukazatel
pInt,
což se provádí operátorem * s uvedením "na jaký datový typ má ukazatel
ukazovat". Situace v paměti vypadá asi takto:
Dále jsme ukazatel
nastavili na adresu proměnné
a,
kterou jsme získali operátorem &:
Na dalším řádku jsme
nepřímo přiřadili proměnné
a
hodnotu 2. Zápis
*pInt = 2
znamená: obsah adresy, na kterou ukazuje pInt,
interpretuj jako proměnnou typu int a proveď
vyžádanou operaci (v našem případě přiřazení čísla 2). Protože ukazatel ukazuje
na adresu proměnné
a,
dojde k přiřazení čísla 2 této proměnné. Operace kterou získáme obsah adresy, na
kterou ukazuje ukazatel,se nazývá
dereference.
Ukazatel může být pevného datového typu, to znamená, že ukazatel "ví", na jaký datový typ ukazuje, a při dereferenci vrátí přesně ten datový typ. Existuje ještě jeden typ ukazatele, tzv. obecný, který může ukazovat na proměnnou jakéhokoli datového typu, ale datový typ "si nepamatuje", a z tohoto důvodu nemůže být na něm provedena dereference. Obecný ukazatel se deklaruje klíčovým slovem void:
int a = 2;
void *p;
//
toto je obecný ukazatel
p = &a;
//
přiřadit adresu můžeme
cout << *p;
//
kompilátor ohlásí chybu
Kompilátor ohlásí chybu
na řádku
cout << *p,
protože obecný ukazatel je beztypový a neví se jakého datového typu je proměnná,
na kterou ukazuje. To ovšem znamená, že to víme my, programátoři, takže obsah
adresy, na kterou ukazuje obecný ukazatel, přece jen lze získat, a to
přetypováním:
int a = 2;
void *p1;
int *p2;
p1 = &a;
p2 = (int*)p1;
// přetypujeme obecný ukazatel (p1) na ukazatel na proměnnou typu int (p2)
cout << *p2;
// dereference ukazatele p2 již možná je
Poslední dva řádky můžeme spojit do jednoho, je to rozumnější zápis, a určitě
elegantnější, protože se vyhneme deklaraci zbytečného ukazatele p2:
cout << *(int*)p1;
Pozor, obecný ukazatel můžeme přetypovat na ukazatel na libovolný datový typ,
ale výsledek dereference nebude správný.
Ukazatel může být i
prázdný, tedy neukazuje na žádnou adresu. K tomu se používá zvláštní hodnota
NULL, v C++ lze používat i 0.
*pInt
= NULL; // v C
*pInt = 0;
//
v C++
Možná se ptáte, k čemu vlastně ukazatele jsou. Je pravda, že jsme si neukázali žádné jejich smysluplné použití. Dále vysvětlím souvislost mezi ukazateli a poli. Až si vysvětlíme záznamy ukážeme si další použití významné ukazatelů a záznamů. Dále v kurzu probereme třídy a tam také uvidíte, jak jsou ukazatele důležité.