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

2. Deklarace proměných

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
vyjádřením znamínka

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.

 

3.1. Definice proměnné v programu

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.


 

3.2. Přiřazení

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.


 

4. Funkce main()

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;
   }

 

5. Funkce cout a cin

"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í :-)

 

6. Operátory

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

 

7. Terminálový vstup a výstup

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

7.1. Vstup a výstup jednoho znaku

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

7.2. Formátovaný vstup a výstup

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:

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:

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():

  1. printf("Je presne %2d:%2d\n", hodiny, minuty);
    Vypíše například "Je presne 21:27".
    Číslem mezi % a formátovým znakem je počet cifer, které se vypíší. U reálného čísla lze určit počet cifer před a za desetinou tečkou následovně:
  2. printf("Cena vyrobku je %6.2f Kc\n", cena);
    Vypíše například "Cena vyrobku je 10.40 Kc".
    Bude vytištěno 6 cifer z toho 2 budou za desetinou tečkou.
  3. printf("Cislo %d je hexa %XH\n", cislo, cislo);
    Vypíše například "Cislo 85 je v hexa 55H".
    Všimněte si konverze dekadického čísla na hexadecimální číslo.

Příklady použití scanf():

  1. scanf("%d", &cele_cislo);
    Tento příkaz přečte celé číslo z klávesnice a uloží ho do proměnné cele_cislo.
    Pozor! Všimněte znaku
    & 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.
  2. scanf("%f", &realne_cislo);
    Tento příkaz přečte reálné číslo z klávesnice a uloží ho do proměnné realne_cislo.

 

8. Booleovské výrazy

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  

8.1. Zkrácene vyhodnocování logických výrazů

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)

 

5.2. Příkaz if a if-else

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)


 

10. Ternární operátor

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.


11. Příkaz for

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");
 }

 

12.1. Příkaz while

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

12.2. Příkaz do-while

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);

12.3. Příkazy break a continue

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

 

 

13. Příkaz switch

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.

 

14. Shrnutí - příklad

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

 

15. Typová konverze

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:

15.1. Implicitní typová konverze

Pravidla:
 

  1. Před vykonáním operace se typy char a short konvertují na int. Zde není žádný problém, protože char i short jsou celočíselné hodnoty a maximální hodnoty jsou menší než u typu int.
    Typy
    unsigned char (BYTE) a unsigned short (WORD) se automaticky konvertují na int jen pokud se hodnota proměnné vejde do maximální hodnoty int jinak se konvertuje na unsigned int (UINT).
  2. U operací s různými typy operandů se konvertuje podle priority typu. Implicitní konverze vždy probíhá jen na typy s vyšší prioritou. Hierarchie priorit jednotlivých typů (typ int má nejnižší prioritu):

     

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;

 

  1. V uvedeném příkladu vidíte přiřazení. V přiřazení je pravý operand konvertován na typ levého operandu čili výsledek má typ levého operandu.
15.2. Explicitní typová konverze

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;
 

 

16. Preprocesor jazyka C

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.

16.1. Symbolické konstanty

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.

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

16.3. Předdefinované makra

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í

isalnum

Vrací argument, pokud je argument číslice, malé či velké písmeno, jinak vrátí 0 (FALSE)

isalpha

Vrací argument, pokud je argument malé či velké písmeno, jinak vrátí 0 (FALSE)

isascii

Vrací 1 (TRUE), pokud je argument z ASCII tabulky, jinak vrátí 0 (FALSE)

isdigit

Vrací znak (číslici), pokud je argument číslice, jinak vrátí 0 (FALSE)

islower

Vrací znak, pokud je argument malé písmenko, jinak vrátí 0 (FALSE)

isspace

Vrací znak, pokud je argument neviditelný znak (mezera, tabulátor), jinak vrátí 0 (FALSE)

isupper

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í

tolower

Převede argument (velké písmenko) na malé písmenko

toupper

Převede argument (malé písmenko) na velké písmenko

 

 

 

17. Pole

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.

 

18. Vícerozměrná pole

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.

 

 

19. Řetězce

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;

 

20. Funkce (1.) - Základy

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

 

21. Funkce (2.) - Předávání parametrů

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
}

 

22. Funkce (3.) - 22.1. Přetěžování funkcí

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.

22.2. Inline funkce

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;
}

 

23. Funkce (4.) - Příklad

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):

 

a,b

a,c

a,d

b,a

b,c

b,d

c,a

c,b

c,d

d,a

d,b

d,c

 


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

Ukazatele

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