ČVUT

České vysoké učení technické

 

Fakulta strojní

Katedra technické matematiky

Programovací jazyk C++

Programovací jazyk C++

Jiří Vogel

1994

Úvodní poznámka

Tato skripta vznikla po odpřednášení několika jednosemestrálních doporučených kurzů: Programovací jazyk C++. Při sestavování těchto přednášek jsem vycházel hlavně z publikace [3], jejíž autoři jsou zároveň tvůrci jazyka C. Jsou to zřejmě ty výjimečné typy lidí, kteří jsou skvělými programátory a zároveň výbornými pedagogy. Takže v prakticky celé 1. a 2. kapitole jsem se nechal vést jejich vzorem. Další publikace, do kterých jsem byť jen nahlédl, jsou uvedeny v seznamu literatury. Publikace [2] a [6] pojednávají o standardu ANSI C. Publikace [1] a [3] o původní K&R verzi jazyka C, skriptum [4] popisuje jazyk ANSI C a zároveň seznamuje čtenáře s verzí K&R. Jazyka C++ se týkají knihy [5], [7] a kniha [8], která je navíc věnována hlavně objektově orientovanému programování, protože C++ je jedním z mnoha jazyků, které tento programátorský styl práce umožňují.

Skripta jsem se snažil psát maximálně stručně a důraz jsem kladl na příklady, které jsou všechny odladěny (většinou jsem použil produktu firmy Borland Turbo C++), takže syntaktické chyby ani chyby v algoritmizaci (snad s výjimkou volby optimálního algoritmu a chyb, které vznikly při přenosu programů do skript) by se v nich neměly vyskytovat.

V prvé kapitole jsou základní struktury jazyka C++ vysvětleny pouze na příkladech. Tuto kapitolu by si měl každý čtenář důkladně promyslet, a odzkoušet jak odladěné příklady, tak zadaná cvičení. Ve druhé kapitole jsou uvedeny podrobnosti typu: Jak se zadávají konstanty? Kolik a jakých znaků smí mít jméno nějakého objektu? Jaké jsou základní typy hodnot, jaké jsou standardní operace v jazyku atd. Ve třetí kapitole se čtenář dozví další důležité podrobnosti o jazyku C++, kterými jsem nechtěl z pedagogických důvodů zatěžovat v prvních dvou kapitolách. Čtvrtá kapitola pojednává podrobně o polích a funkcích, o kterých čtenář skript, který došel až sem, již dost ví, a o ukazatelích (jiný český termín je spoj), které byly čtenáři prakticky zamlčeny. Pátá kapitola pojednává o strukturách, třídách (i když s nejdůležitějšími vlastnostmi tříd se čtenář seznámí až v kapitole šesté), unionech a takových podrobnostech jako jsou tzv. bitová pole. Šestá kapitola je věnována objektově orientovanému programování, které je vysvětleno na různě složitých příkladech, kdy jsem se vždy snažil vycházet od jednoduchých příkladů, ale závěrem jsem se nevyhnul ani příkladům dosti složitým, aby vynikl důvod, pro který tento programátorský styl vznikl. Sedmá kapitola je věnována předpřekladači (nebo, chcete-li, preprocesoru), který usnadňuje programování jak v jazyku C, tak v jazyku C++.

Závěrem bych chtěl upozornit, že ten, kdo se chce seznámit se speciálními možnostmi konkrétní implementace jazyka C++, tj. např. grafickými a matematickými knihovnami, možnostmi optimalizace programu apod., ten nechť tato skripta odloží. Ten kdo se chce obecně, ale relativně podrobně seznámit s jazykem C++, ten by snad mohl toto skriptum použít jako užitečnou pomůcku.

Závěrem bych chtěl poděkovat studentu Jiřímu Vávrovi ze Strojní fakulty ČVUT a doktorandu Ing. Luďku Šárovi z Ústavu termomechaniky AV ČR za pečlivé přečtení rukopisu a oponentu Ing. Svenu Ubikovi, systémovému programátorovi z Informačního centra FS ČVUT za cennépřipomínky k úpravě textu.



Autor

Poznámka k "elektronické" podobě skript

Skripta jsem přepsal do jazyka HTML, aby se studentům usnadnil přístup a zároveň aby byla demonstrována užitecnost práce s tímto tvarem informací. Dnešním dnem rušim informaci: Contents under Construction , ale za jakékoliv připomínky na své e-mailové adrese:

vogel@marian.fsik.cvut.cz
nebo
vogel@fsid.cvut.cz

předem děkuji



                                                                                                                                                                                                           Autor

Praha 11. července 1998

Obsah

  1. Úvod do jazyka C++

    1. Vstup a výstup
    2. Příkazy cyklu for a do-while
    3. Několik jednoduchých programů
      1. Vstup a výstup po znacích
      2. Kopírování souboru
      3. Počítání znaků
      4. Počítání řádků
      5. Počítání slov
    4. Pole
    5. Funkce
      1. Automatické a externí proměnné
      2. Rekurzívní funkce
    6. Závěrečné poznámky k 1. Kapitole
    7. Cvičení

  2. Typy, operátory, výrazy

    1. Jména, typy, konstanty
      1. Jména
      2. Typy
      3. Konstanty
    2. Operátory
      1. Aritmetické operátory
      2. Přiřazovací operátory
      3. Podmíněný výraz
      4. Relační a logické operátory
    3. Typová konverze
      1. Priority operací

  3. Podrobnosti k předchozím kapitolám

    1. Příkazy a bloky
    2. Operátor ::
    3. Operátor čárka
    4. Příkaz break
    5. Příkaz continue
    6. Registrové proměnné
    7. Cvičení

  4. Ukazatelé, pole, funkce

    1. Ukazatelé a adresy
      1. Ukazatelé a argumenty funkcí
    2. Ukazatelé a pole
    3. Ukazatelé a konstanty
    4. Argumenty povelového řádku
    5. Inicializace polí a polí ukazatelů
    6. Ukazatelé a vícerozměrná pole
    7. Ještě několik podrobností o funkcích
      1. Vnitřní funkce
      2. Dosazené parametry
      3. Přetížené funkce
      4. Funkce s nespecifikovanýn počtem parametrů
    8. Cvičení

  5. Struktury, třídy, uniony, bitová pole

    1. Struktury a třídy
      1. Pole struktur
    2. Typ union
      1. Anonymní typ union
    3. Příklady dynamických objektů
    4. Bitová pole
    5. Cvičení

  6. Základy objektově orientovaného programování

    1. Úvod
    2. Příklady objektově orientovaného programování v C++
    3. Spřátelené funkce a třídy
    4. Přetížené operátory
    5. Závěrečná poznámka k OOP
    6. Cvičení

  7. Poznámky k předpřekladači

    1. Direktiva #define
      1. Direktiva #define bez parametrů
      2. Direktiva #define s parametry
    2. Operátory # a ##
    3. Podmíněný překlad
Literaratura

Obsah

Kapitola 1
Úvod do jazyka C++

V této kapitole se seznámíme s podstatnými prvky jazyka, aniž bychom zabíhali do podrobností. Jazyk budeme vykládat na příkladech. Tento způsob výkladu má svá úskalí, zejména pokud se jedná o přesnosti popisu jazyka, ale jediným způsobem jak se naučit programovací jazyk, je programovat, a proto zvolíme tuto strategii. Budeme postupovat tak, že nejprve zadáme úlohu, napíšeme program v jazyku C++ a program si vysvětlíme.

Obsah

1.  Vstup a výstup

Příklad 1.1. Sestavme program, který vytiskne text
Vitejte v kurzu jazyka C++



Řešení:

  1. varianta:
    #include <iostream.h>
    main()
    {
     cout << "Vitejte v kurzu jazyka C++" << '\n';
    }
    

    Vlastní program začíná slovem main(), které v tomto případě dává kompilátoru jazyka informaci, že se jedná o tzv. hlavní program. Program v jazyku C++ může sestávat z jednotlivých programových jednotek, kterým říkáme funkce. Každá funkce, může mít argumenty uvedené v okrouhlých závorkách za jménem funkce. Protože hlavní program je považován také za funkci, jsou za jménem main okrouhlé závorky. Později uvidíme, že i hlavní program smí mít argumenty. Složené závorky označují začátek a konec hlavního programu a příkaz cout je tzv. proudová funkce pro výstup. Zajistí (s pomocí operátorů proudového výstupu <<) výstup řetězce znaků, které jsou zapsány mezi uvozovkami a znaku \n , který je zapsán mezi apostrofy. Tento znak znamená přechod na nový řádek. Každý příkaz v jazyku C++ končí středníkem. Přesvědčte se, že lze též napsat:

    cout << "Vitejte v kurzu jazyka C++ \n";
    

    První řádek programu je příkazem pro tzv. předpřekladač, který před překladem programu v tomto případě zajistí, aby byl zahrnut (anglický termín include) tzv. hlavičkový soubor iostream.h, který v jazyku C++ (obvykle) umožňuje proudové funkce pro vstup a výstup používat.

  2. varianta:
    #include <stdio.h>
    main()
    {
     printf("Vitejte v kurzu jazyka C++\n");
    }
    

    Tento program využívá hlavičkového souboru stdio.h (standardní input/output), který se používá i v jazyku C a jednu jeho funkci pro výstup ( printf) na standardní výstupní zařízení.

Příklad 1.2. Zapišme tentýž text jako v příkladu do souboru, který např. pojmenujeme out.dat.



Řešení:

  1. varianta:
    #include<fstream.h>
    main() // Zapis do souboru out.dat
    {
     // Nejprve otevreni souboru out.dat pro zapis
     ofstream fout("out.dat");
     // a vlastni zápis
     fout << "Vitejte v kurzu jazyka C++\n";
     // Uzavreni souboru
     fout.close();
    }
    

    První řádek informuje předpřekladač, že má zavést soubor fstream.h, který umožňuje (obvykle) definovat libovolně pojmenované proudové funkce. V našem případě fout. Text za znaky // je v jazyku C++ považován až do konce řádku za poznámku.

    Je vhodné nestandardní soubory uzavřít (i když v tomto případě po ukončení programu se uzavře automaticky). Chceme-li soubor z nějakého důvodu uzavřít před ukončením programu, je tento příkaz povinný.

  2. varianta:
    #include <stdio.h>
    main()
     /* Tentyz priklad prostredky, ktere jsou obvykle v jazyku C i C++
     */
    {
     /* Definice ukazatele typu FILE*/
     FILE *fp;
     /* Otevreni souboru out.dat pro zapis*/
     fp=fopen("out.dat","w");
     /* Zapis do souboru funkci fprint
        ze souboru stdio.h*/
     fprintf(fp,"Vitejte v kurzu jazyka C++\n");
     /* Uzavrení souboru*/
     fclose(fp);
    }
    

    V této variantě zápisu jsme použili poznámek uzavřených mezi znaky /* a */ .

    Oba typy poznámek jsou v  C++ použitelné a mohou se libovolně střídat. Je vhodné jednořádkové poznámky uvozovat znaky // a víceřádkové poznámky mezi znaky /* a */. Ukazatel zmíněný v poznámce budeme probírat podrobně v  kapitole 4. Důležité upozornění: jazyk C i C++ rozlišují mezi malými a velkými písmeny: tedy FILE, File a file jsou tři různá jména.

Příklad 1.3. Sestavme program pro přečtení dvou hodnot ze standardního vstupního zařízení a jejich přiřazení proměnným x a i.



Řešení:

  1. varianta:
    #include <iostream.h>
    main()
    {
     float x; // Definice realne promenne x
     int i;   // Definice celociselne promenne i
    
     cout << "Cti x, i\n";
     // Cteni hodnoty a x a i   (zleva doprava)
     cin >> x >> i;
     // Tisk hodnoty promenne x a i (zleva doprava)
     cout << "\nx = " << x << " i = " << i << '\n';
    }
    

    Tento zápis je dostatečně jasný z poznámek.

  2. varianta:
    #include <stdio.h>
    main()
    {
     float x;int i;
    
     printf("Cti x, i\n");
     // Ctení hodnoty x a i
     scanf("%f %d",&x,&i);
     // Tisk hodnoty x a i
     printf("x = %f i = %d \n",x,i);
    }
    

    Zde je nová funkce scanf, která zajišťuje vstup hodnot, zdánlivě složitějším způsobem než cin. Právě tak jako funkce printf využívá tzv. formátu. Zde konverze f zajistí vstup reálné hodnoty a konverze i zajistí vstup celočíselné hodnoty. U proměnných x a i je povinný symbol &. Pamatujme si prozatím, že tento znak ve funkci scanf musí být u jednoduchých proměnných a indexovaných proměnných. Vysvětlení bude možné, až se naučíme pracovat s ukazateli.

Příklad 1.4. Sestavme program pro přepočet palců na centimetry a obráceně. Budeme číst číselnou hodnotu a znak: p (převod palců na centimetry), c (převod centimetrů na palce). V případě chybného zadání znaku se vytiskne výstražná zpráva.



Řešení:

// Prepocet palcu na centimetry a obracene
#include <iostream.h>
main()
{
 typedef float real;
 /*Definice noveho typu (zde v C++ jsme standardní float
   zamenili nestandardnim, ale v jinych jazycich obvyklym slovnim
   symbolem real*/
 const real PREPOCET=2.54; // Definice konstanty
 real x,palec,cm;
 char ch=0; //Definice promenne typu char s pocatecnim prirazenim
 cout << "\nCti délku a co je zadano: p -- palce   c -- cm\n";
 cin >> x >> ch;

 if(ch=='p') // Prepocet palcu na cm
 {
  palec=x;cm=PREPOCET*x;
 }
 else
  if(ch=='c') // Prepocet centimetru
   {
    cm=x;palec=x/PREPOCET;
   }
 if(ch=='p'|| ch=='c')
  cout << palec << "\" = " << cm << "cm\n";
 else
  cout << "\n Chybne zadani!!!\n";
}

V programu jsme použili definici nového typu, počáteční přiřazení (které se okamžitě realizuje při překladu zdrojového programu), definici symbolické konstanty (na rozdíl od proměnné se symbolické konstantě nesmí v průběhu plnění přiřadit nová hodnota), přiřazovacího příkazu (kdy v nejjednodušším případě se proměnné na levé straně přiřadí hodnota výrazu na straně pravé) a podmíněného příkazu:

if (pravda) příkaz; else jiný_příkaz;

Je-li hodnota pravda různá od nuly, provede se příkaz, je-li rovna nule provede se jiný_příkaz. Příkaz i jiný_příkaz musí končit středníkem a příkazjiný_příkaz může být jeden příkaz, nebo více příkazů - tzv. složený příkaz - uzavřených do složených závorek. (Ty v jazyku C a C++ nahrazují slovní symboly begin a end známé z jiných programovacích jazyků.) V podmínce if(ch=='p' || ch=='c') se zjišťuje, je-li ch rovno () znaku p, nebo ( || disjunkce nebo-li logický součet v jazyku C i C++) je-li rovno znaku c. Pozor na operátor rovná se () a operátor přiřaď () začátečníkům (a nejenom jim) se někdy pletou. Je-li větev za else prázdná, může se vynechat (včetně slovního symbolu else).

V příkazu cout << palec << "\" =" << cm << "cm\n"; jsme potřebovali zapsat v řetězci znak uvozovka ( "). To jazyk C a C++ umožní předřazením znaku zpětné lomítko (\) před znak uvozovka.

Příklad 1.5. Sestavme program pro tutéž úlohu, ale použijme tzv. přepínače.



Řešení:

 // Piseme jen vlastni algoritmus bez deklaraci, ktere se nemeni
//...
 switch(ch)
{
 case 'p':
  palec=x;
  cm=PREPOCET*x;
  cout << palec << "\" = " << cm << "cm\n";
  break;
 case 'c':
  cm=x;
  palec=x/PREPOCET;
  cout << palec << "\" = " << cm << "cm\n";
  break;
 default:
  cout << "\n Chybne zadani!!!\n";
  break;
}
// ...

Přepínač pracuje tak, že se procházejí jednotlivé alternativy case a nalezne-li se shoda mezi výrazem za slovním symbolem switch a výrazem za case, provedou se příkazy za tímto case. Pak by se prováděly další příkazy bez ohledu na další case; abychom tomuto nežádoucímu jevu zabránili, musíme na konci každé alternativy přepínače umístit příkaz break, který přepínač ukončí. Alternativa uvedená slovním symbolem default se provede v případě, že nevyhovuje žádná jiná alternativa. Alternativa default je nepovinná a příkaz break je v ní zbytečný, ale je zvykem jej na toto místo psát..

Příklad 1.6. Sestavme program pro přepočet palců na centimetry a obráceně. Budeme číst číselnou hodnotu a znak ( p - převod palců na centimetry, c - převod centimetrů na palce, k - konec čtení) a budeme je postupně zapisovat do souboru muj_s.dat. V případě, že čteme chybný znak, program bude požadovat opravu. Po přečtení hodnot soubor uzavřeme pro zápis, otevřeme pro čtení, budeme z něj číst a výsledky tisknout na standardním výstupním zařízení.



Řešení:

// Vstup a vystup do souboru v C++
/* Program ocekava vstup do promenne ch a x, zapise vysledky do souboru
   muj_s.dat, ktery predtim otevre. Po ukonceni zapisu (predpoklad cte
   se pismeno k a x je libovolne) se soubor zavre pro vystup a otevre
   pro vstup. Vypoctene hodnoty se prectou a vypisi na obrazovce.
   Prepocitavaji se palce na cm a obracene */
#include <fstream.h>
#include <iomanip.h>
main()
{
 const float prepocet=2.54;
 float x=1,palec,cm; char ch=0;
 // Vstup a zapis do souboru muj_s.dat
 ofstream fout("muj_s.dat");
 while(1)
 {
  ZNOVA:
  cout << "\nZadejte, prosim, co se ma provadet:\n"
       << "p - zadana hodnota v palcich\n"
       << "c - zadana hodnota v cm\n"
       << "k - konec vypoctu\n"
       << "    zadana hodnota\n";
  cin >> ch >> x;
  switch (ch){
   case 'p':
    palec=x; cm=x*prepocet; break;
   case 'c':
    cm=x; palec=x/prepocet; break;
   case 'k':
    goto KONEC;
   default:
   cout << "\n Chybny udaj zadava se c, p nebo k. Opakujte cteni\n";
   x=0; ch='\0';
   goto ZNOVA;
  }
// Zapis do souboru
  fout << palec << '\n';
  fout << cm << '\n';
 }
 // Uzavreni souboru muj_s.dat pro zapis
 KONEC: fout.close();
 // Ctou se data ze souboru muj_s.dat
 cout << "\n A nyni hodnoty: palec, cm vytisknu\n";
 ifstream fin("muj_s.dat");
 while(!fin.eof()) // ! je negace logickeho vyrazu
 {
  fin >> palec;
  if(!fin.eof()){
   fin >> cm;
   cout << setw(6) <<  setprecision(2) << palec << "\" = " << cm
        << " cm\n";
  }
 }
 // Uzavreni souboru muj_s.dat
 fin.close();
 cout << "Konec programu.\n";
}

U příkazu cout jsme použili tzv. manipulátorů (v systému, kde byly laděny programy pro tato skripta, jsou manipulátory definovány v souboru iomanip.h. Např. manipulátor setw(6) použitý v cout nastaví šířku slova na výstupu na hodnotu 6. Manipulátor setprecision(2) nastaví přesnost pro reálné hodnoty na 2 desetinná místa. Hlavičkový soubor iostream.h je součástí souboru fstream.h, takže nemusí být uveden. V programu jsme použili cyklus while. Jeho význam je jednoduchý. Příkaz uvedený za příkazem (eventuálně příkazy uzavřené v příkazových závorkách) while se provádí tak dlouho, dokud je výraz v závorkách za slovem while různý od nuly. V našem případě jsme použili tedy nekonečný cyklus a k ukončení jsme použili příkaz skoku a návěští, které se odděluje od příkazu, ke kterému se má provést skok, dvojtečkou. Je dobrou praxí, používat příkazů skoku co nejméně, protože někdy znepřehledňují program. V našem případě je tento příkaz vhodný v místě, kde kontrolujeme správnost vstupu. V druhém případě, kdy pomocí tohoto příkazu ukončujeme program, je lepší využít příkazu while.

K odkazu

Příklad 1.7. Sestavme program pro tisk tabulky Fahrenheit - Celsius. Pro přepočet použijte vzorce

C = 5
9
(F-32)
. Tabulku tiskněme ve tvaru:

     Stupne Fahrenheita          Stupne Celsia
          dddd                         ddd.dd
          dddd                         ddd.dd
          ...                           ...
kde d je číslice, znaménko minus nebo prázdný znak.



Řešení:

/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius
  pro  fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
main()
{
 // Definice konstant pro dolni a horni mez a krok zmeny
 const int DOLNI=0,HORNI=300,KROK=20;
 float fahr=DOLNI; // Definice promenne fahr s pocatecnim prirazenim;
 float cels; /* Definice promenne, ktera bude nabyvat hodnot stupnu
                Celsia */
 // Tisk zahlavi tabulky
 cout << "    Stupne Fahrenheita          Stupne Celsia\n";
 /*Prikaz while, ktery se bude provadet dokud fahr bude mensi nebo
   rovno 300*/
 while(fahr<=HORNI)
 {
  cels=5./9.*(fahr-32);
  cout << "         " << setw(4) << fahr << "                         "
       << setw(5) << setprecision(2) << cels << '\n';
  fahr=fahr+KROK;
 }
}

Program je snad opět z poznámek srozumitelný. V příkazu while se provádí více příkazů, a proto jsou uzavřeny do složených (příkazových) závorek. Je nutno upozornit na zlomek 5/9, který jsme zapsali v přiřazovacím příkazu 5./9. a mohli jsme ho zapsat i ve tvarech: 5.0/9.0 nebo 5./9 popř. 5/9. apod.

Nesmíme jej však zapsat ve tvaru: 5/9

To je v jazyku C i C++ tzv. celočíselné dělení. Výsledkem 5/9 je celočíselný výsledek dělení (po odříznutí zlomkové části) a to je nula. Právě tak výsledek dělení 3/2 je hodnota 1, výsledkem 11/5 je hodnota 2 atd. Fortranisté jsou na tuto skutečnost zvyklí a vědí, že se celočíselné dělení dá v mnoha případech s výhodou použít, ale v mnoha úlohách může být jeho přehlédnutí zdrojem těžko objevitelných chyb.

Na tomto místě znovu opakujeme, že v jazycích C++ a  C je pravdivostní hodnota pravda realizována hodnotou různou od nuly a hodnota nepravda hodnotou nula. Hodnoty truefalse známé z jiných programovacích jazyků tedy v C++ i  C explicitně neexistují.

Obsah

2.  Příkazy cyklu for a do-while

Příkazy cyklu jsou velmi důležitou a naštěstí nijak komplikovanou částí každého programovacího jazyka. V minulém odstavci jsme již probrali příkaz while a nyní se zmíníme o příkazu for. Význam ukážeme opět na příkladu. Příklad 1.7 naprogramujeme pomocí tohoto příkazu.

/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius pro
  fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
main()
{
 // Definice konstant pro dolni a horni mez a krok zmeny
 const int DOLNI=0,HORNI=300,KROK=20;
 // Tisk zahlavi tabulky
 cout << "    Stupne Fahrenheita          Stupne Celsia\n";
 // Prikaz for
 for(int fahr=DOLNI;fahr<=HORNI;fahr=fahr+KROK)
 {
  cout << "         " << setw(4) << fahr << "                         "
       << setw(6) << setprecision(2) << 5./9.*(fahr-32) << '\n';
 }
}

Význam příkazu for lze popsat pomocí příkazu while následovně:

//...
fahr=DOLNI;
while(fahr<=HORNI)
{
 cout ...;
 fahr=fahr+KROK;
}
//...

V příkazu jsme využili možnosti definice typu parametru cyklu přímo v příkazu for; to je v jazyku C++ možné. Definice proměnné pak platí až do konce funkce nebo bloku ve funkci (podrobněji v kap. 3 v čl. 3.1 a 3.2). Samozřejmě, že jsme mohli psát

//...
int fahr;
//...
for(fahr=DOLNI;fahr<=HORNI;fahr=fahr+KROK){//...

ale i

//...
int fahr=DOLNI;
//...
for(;fahr<=HORNI;fahr=fahr+KROK)

Z ukázek je vidět, že v příkazu for lze jednotlivé složky příkazu vynechávat, podle toho, jak se nám hodí.

Dalším příkazem cyklu v jazyku C++ je cyklus do-while.

Syntaxe příkazu je

do

příkaz

while(výraz)

Nejprve se provede příkaz a pak se vyhodnotí výraz. Je-li pravdivý (tedy přesně řečeno různý od nuly) příkaz se provede znovu atd. Je-li nepravdivý (roven nule), cyklus se ukončí. Program z příkladu 1.7 můžeme pomocí příkazu do-while napsat takto (Je samozřejmé, že příkaz může být složený):

/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius pro
  fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
main()
{
 // Definice konstant pro dolni a horni mez a krok zmeny
 const int DOLNI=0,HORNI=300,KROK=20;
 int fahr=DOLNI; //Definice promenne fahr s pocatecnim prirazenim
 // Tisk zahlavi tabulky
 cout << "    Stupne Fahrenheita          Stupne Celsia\n";
 // Prikaz do-while
 do
 {
  cout << "        " << setw(4) << fahr << "                         "
       << setw(6) << setprecision(2) << 5./9.*(fahr-32) << '\n';
  fahr=fahr+KROK;
 }while(fahr<=HORNI);
}

V příkazu do-while se provede příkaz alespoň jednou, protože tzv. zkouška konce se provádí po provedení příkazu.

Obsah

3.  Několik jednoduchých programů

V této části si ukážeme několik jednoduchých programů pro práci se znaky.

Obsah

3.1  Vstup a výstup po znacích

V knihovnách jazyka C++ existují funkce, které po vyvolání přečtou nebo zapíší jeden znak. Uveďme některé:

Řekli jsme si, že funkce getchar čte z klávesnice (přesněji ze standardního vstupu) a putchar vypisuje znakové hodnoty na obrazovku (přesněji na standardní výstup). To ovšem můžeme snadno v moderních operačních systémech změnit tzv. přesměrováním, kdy vyvoláním pracovního programu, který se např. jmenuje mujprog, ve tvaru

mujprog < mujvst > mujvys

zajistíme, že standardním vstupem pro něj bude soubor mujvst a výstupem mujvys. Totéž samozřejmě docílíme, budeme-li ve zdrojovém programu mujprog.cpp psát příkazy getc(mujvst) a putc(nějaká znaková veličina, mujvys).

Obsah

3.2  Kopírování souboru

Příklad 1.8. Napišme program který znak po znaku kopíruje vstup na výstup.



Řešení:

  1. varianta:
    #include<stdio.h>
    /* Kopie ze standardniho vstupniho souboru na standardni vystupni
    soubor*/
    main()
    {
     int c;
     c=getchar();
     while(c!=EOF)
     {
      putchar(c);
      c=getchar();
     }
    }
    

    Relační operátor != má význam . Cyklus while tedy probíhá, dokud se nepřečte znak konec souboru, pak se cyklus ukončí. Znak konec souboru pro konkrétní systém je uložen v souboru stdio.h. Je označen jménem EOF. Funkce getchar vrací celočíselnou hodnotu, a proto i proměnnou c jsme definovali jako celočíselnou ( typ int) a ne typu char. Je to stručně řečeno proto, že některé znaky nejsou typu char (např. znak konce souboru EOF).

  2. varianta:

    Programátor v jazyku C nebo C++ ovšem napíše program stručněji:

    #include <stdio.h>
    main() // Kopirovani vstupu na vystup strucneji
    {
     int c;
     while((c=getchar())!=EOF)
     {
      putchar(c);
     }
    }
    
    Program nejprve v příkazu while přiřadí proměnné c hodnotu a pak se testuje, je-li tato hodnota různá od EOF. Je-li různá, provede se příkaz putchar, není-li různá, cyklus a program se ukončí. Protože operátor přiřazení (=) má nižší prioritu než operátor (!=), musí být přiřazovací příkaz uzávorkován. V jazyku C a  C++ je mnoho možností, jak psát stručné programy. Necháme-li se však touto vlastností , může se nám stát, že budeme psát zcela nesrozumitelné programy, kterým, po čase, nebudeme rozumět ani my sami.

Obsah

3.3  Počítání znaků

Příklad 1.9. Napišme program, který spočítá znaky ve vstupním textu.



Řešení:

#include<stdio.h>
main()// Pocitani znaku ve vstupnim textu
{
  long nc=0;
  while(getchar()!=EOF) ++nc;
  printf("Pocet znaku ve vstupnim textu = %ld\n",nc);
}

V programu se objevil nový operátor ++, který znamená zvětšení o jednotku. Je možné psát nc=nc+1, ale ++nc je stručnější a mnohdy efektivnější. Operátory inkrementace mohou být prefixové ++nc nebo postfixové nc++.Rozdíl si vysvětlíme později. Vedle operátorů inkrementace máme i operátory dekrementace -, které se obdobně využívají při odečítání jedničky.

V programu jsem využili nového typu long, který je stručnějším zápisem long int; u typu long je zaručeno, že že maximální hodnota tohoto typu nebude menší než maximální hodnota typu int. Konverze v printf a scanf pro tento typ je %ld .

Obsah

3.4  Počítání řádků

Příklad 1.10. Sestavme program, který spočítá řádky zapsané v souboru muj_sou . Předpokládáme, že každý řádek je ukončený znakem nový řádek ( \n ).



Řešení:

#include <stdio.h>
main() // Pocitani radku
{
 int zn,nr=0; // zn - cteny znak, nr - pocet radku
 FILE *ms; // Definice ukazatele typu FILE
 ms=fopen("muj_sou","r"); // Otevreni souboru
 while((zn=getc(ms))!=EOF)
  if(zn=='\n') ++nr;
  printf("Pocet radku= %d\n",nr);
  fclose(ms); /* Zavreni souboru. V tomto pripade nepovinne. Po ukonceni
                programu se vsechny otevrene soubory automaticky uzavrou
                */
}

Obsah

3.5  Počítání slov

Příklad 1.11. Sestavme program, který spočítá slova, řádky a znaky zapsané v souboru msbr. Předpokládáme, že slovo je posloupnost znaků, která neobsahuje znaky mezera, tabelátor (\t), nebo nový řádek (\n).


Řešení:

#include<stdio.h>
main() // Pocitani slov, radek a znaku v souboru msbr
{
 typedef int BOOL;
 FILE *sb=fopen("msbr","r"); // Otevreni souboru msbr
 const BOOL ANO=1,NE=0; // Definice logickych konstant
 int zn,nz,nr,ns; /* zn - cteny znak, nz - pocet znaku, nr - pocet
                     radku, ns - pocet slov */
 BOOL ve_slove; /* ve_slove je logicka (Booleovska) promenna, nabyvajici
                  logickych hodnot ANO a NE */
 ve_slove=NE; // A priorni predpoklad: nejsem uvnitr slova;
 nz=nr=ns=0; // Vynulovani hodnot promennych
 while((zn=getc(sb))!=EOF)
 {
  ++nz; // Opakovani: ++nz je totozne s nz=nz+1;
  if(zn=='\n')++nr;
  if(zn==' ' || zn=='\t' || zn=='\n')
   ve_slove=NE;
  else
   if(ve_slove==NE)
   {
    ve_slove=ANO; ++ns;
   }
  }
  printf("\nV souboru je:\n \n pocet znaku = %d,\n pocet radku = %d,\n",
          " pocet slov = %d\n",nz,nr,ns);
 }

Čtenář při troše přemýšlení jistě porozumí popsanému algoritmu. Všimněme si snad jen pro úplnost zřetězeného příkazu

 nz = nr = ns = 0;

který se realizuje takto

 nz = (nr = (ns = 0));

a pro zopakování toho, že v jazycích C a C++ neexistují tzv. logické (Booleovské) proměnné: chceme-li je explicitně použít, musíme to realizovat např. tak, jako v tomto programu.

Obsah

4.  Pole

Příklad 1.12. Sestavme program, který spočítá výskyty jednotlivých číslic, mezer, tabelátorů, nových řádek a ostatních znaků.


Řešení:

#include<stdio.h>
#include<conio.h>// Soubor pro ovládání obrazovky v systému TURBO C++
main() // Pocitani cislic a ostatnich znaku
{
 int cis[10],bily_zn=0,ost_zn=0,zn;
 FILE *ms;
 ms=fopen("vzorek.dat","r");
 clrscr(); // Mazani obrazovky v systému TURBO C++
 for(int i=0;i<10;++i) cis[i]=0; // Nulovani prvku pole
 // A vlastni realizace algoritmu
 while((zn=getc(ms))!=EOF)
  /* Je-li zn >= '0'a zaroven jeli zn <= '9' (operator &&),
  vypocte se hodnota indexu:  c - '0' a k prislusnemu prvku
  se pricte jednicka  */
  if(zn>='0' && zn<='9') ++cis[zn-'0'];
   else
    if(zn==' ' || zn=='\t' || zn=='\n') ++bily_zn;
     else ++ost_zn;
 // Tisk vysledku
 printf("\nCislice          Pocet cislic\n\n");
 for(i=0;i<10;++i) printf("      %d                  %d\n",i,
                          cis[i]);
 printf("\n\nPocet: bilych znaku: %d  a ostatnich %d",bily_zn,
        ost_zn);
}

V programu je použito jednorozměrné pole tj. vektor cis, jež má deset prvků o indexech měnících se od nuly do devíti. Typické pro pole v jazyku C i C++ je právě indexování od nuly. Mez udaná v definici tedy znamená pocet prvků pole a ne horní mez indexu. To umožňuje v cyklu for psát ostrou nerovnost. V programu jsme použili nový hlavičkový soubor conio.h, který v systému, ve kterém byly programy laděny slouží k ovládání obrazovky; např. funkce clrscr() maže obrazovku.

Příklad 1.13. Sestavme program, který vypočítá součet matic typu (m,n), kde m ú 5 a n ú 5 podle vzorce cij = aij+bij, i = 1,2,...m; j = 1,2,...,n. Matice budeme číst ze souboru vzorek.dat a výsledek bude zapsán do téhož souboru.



Řešení:

#include<stdio.h>
main()
{
 FILE *vz=fopen("vzorek.dat","r"); //Otevreni souboru pro cteni
 const int m=5,n=5;
 int a[m][n],b[m][n],c[m][n];// Definice matic typu(m,n)
 int ms,ns; // Definice skutecneho poctu radku (ms) a sloupcu (ns)
 fscanf(vz," %d %d",&ms,&ns); // Cteni hodnot ms, ns
 for(int i=0;i<ms;++i) // Cyklus i s definici parametru cyklu i
  for(int j=0;j<ns;++j) // Cyklus j s definici parametru cyklu j
   fscanf(vz,"%d ",&a[i][j]); // Cteni prvku matice A;
 for(i=0;i<ms;++i)
  for(j=0;j<ns;++j)
   fscanf(vz,"%d ",&b[i][j]); // Cteni prvku matice B
 for(i=0;i<ms;++i)
  for(j=0;j<ns;++j)
   c[i][j]=a[i][j]+b[i][j]; // Vypocet souctu (matice C)
 fclose(vz); vz=fopen("vzorek.dat","w");
 fprintf(vz,"Tisk souctu matic: C = A  + B\n\nMatice A:"
            "\n=========\n");
 for(i=0;i<ms;++i) // Tisk matice A
 {
  fprintf(vz,"\n");
  for(j=0;j<ns;++j)
   fprintf(vz,"%d ",a[i][j]);
 }// Konec Tisku matice A
  fprintf(vz,"\n\nMatice B:\n=========\n");
 for(i=0;i<ms;++i) // Tisk matice B
 {
  fprintf(vz,"\n");
  for(j=0;j<ns;++j)
   fprintf(vz,"%d ",b[i][j]);
 } // Konec tisku matice B
 fprintf(vz,"\n\nMatice C:\n=========\n");
 for(i=0;i<ms;++i) // Tisk matice C
 {
  fprintf(vz,"\n");
  for(j=0;j<ns;++j)
   fprintf(vz,"%d ",c[i][j]);
 } // Konec tisku matice C
}

Program doplněný poznámkami je snad dostatečně čitelný. Upozorňujeme jen na zápis dvojrozměrných polí, tj. matic, který je oproti většině programovacích jazyků poněkud atypický, ale později uvidíme, že má své výhody. Též je nutno dát pozor na meze indexů. Parametry cyklů ij jsme definovali při prvém použití způsobem povoleným v  C++; standardní způsob zápisu je samozřejmě přípustný.

Obsah

5.  Funkce

Funkce v jazyku C i  C++ tvoří samostatné programové jednotky, pravě tak jako v jazyku Fortran.

Příklad 1.14. Zapišme funkci, která umo'ní celočíselné umocňování celočíselné hodnoty. Umocňování není standardní operací jazyka a bývá součástí matematické knihovny příslušných systémů. Obvykle se jmenuje pow.



Řešení:

#include<stdio.h>
main() //Testovani funkce umocneni
{
 int umoc(int,int); /* Toto je tzv. prototyp, ktery je v C++
  povinny nepredchazi-li nahodou popis funkce jednotce, ve kte-
  re je pouzivana. V prototypu je receno, jakeho je funkce typu
  a kolik a jakeho typu jsou parametry. */
  for(int i=0;i<=10;++i)
  printf("%d na %d = %d\n",2,i,umoc(2,i));
}
int umoc(int m,int n) // Funkce umocnovani m na n
{
 int p=1; //Pocatecni prirazeni hodnoty pomocne (!!!) promenne p
 for(int i=1;i<=n;++i)p=p*m; //Primitivni zpusob vypoctu mocniny
 return p; // Vraceni hodnoty funkce (mozny je i zapis return(p))
}

Funkci nebo skupinu funkcí lze překládat samostatně a propojení závisí na konkrétním systému. Program z příkladu 1.14 jsme zapsali jako jeden soubor. Pracujeme-li v operačním systému UNIX a jmenuje-li se soubor ukazka.cc, lze obvykle přelo'it program příkazem

c++ ukazka.cc -o uk

kdy pracovní (spustitelný) program se bude jmenovat uk. Budeme-li program z příkladu 1.14 psát jako dva soubory  - hlavni.cc a funkce.cc, pak budeme postupovat např. takto

c++ -c hlavni.cc funkce.cc

Příkaz zajistí překlad souborů do sestavitelných modulů (majících v unixu postfixovou koncovku o a příkazem

c++ hlavni.o funkce.o -o uk

se realizuje sestavení obou modulů do spustitelné verze uk. Kombinací je samozřejmě mnoho, ale práce se soubory a funkcemi je přehledná a praktická.

Překlad a sestavení mů'eme však provést najednou zápisem

c++ hlavni.cc funkce.cc -o uk

Napíšeme-li jen c++ program.cc funkce.cc bude mít výsledný produkt standardní jméno a.out.

V produktech firmy Borland je první případ (překlad souboru

ukazka.cpp - koncovka cpp povinná) jasný z menu borlandského prostředí. Druhý případ se realizuje v okně project takto:

Parametry funkce, tzv. formální parametry, které stojí na místě jednoduché proměnné nebo indexované proměnné (prvku pole), jsou, není-li řečeno jinak,(viz) volány hodnotou; to znamená, 'e se jim po zavolání funkce přiřadí hodnota skutečných parametrů. Hodnota formálních parametrů se uvnitř funkce mů'e jakkoliv měnit, ale hodnota skutečných parametrů zůstane po ukončení funkce nezměněna. Ve funkci mocnina jsme toho vyu'ili a ušetřili jsme jednu proměnnou.

// Vyuziti vlastnosti volani hodnotou
float mocnina (float x,int n)
{
 for (float p=1;n>0;n--) /* parametr n se meni, ale jemu
  odpovidajici skutecny parametr zůstane beze změny */
  p=p*x;
 return p;
}

Funkce v tomto případě vrací reálnou hodnotu, a proto jsme ji museli definovat jako reálnou (float).

Příklad 1.15. Sestavme program pro čtení řádků znakového souboru a tisku nejdelšího z nich.



Řešení:

Algoritmus mů'eme stručně slovně popsat takto:


pokud (existuje další řádek)

    Jestli'e (je delší ne' poslední nejdelší )

       ulo' řádek a jeho délku;

Tiskni nejdelší řádek;


 
/* hrad.cpp - Cteni radku ze souboru ms a tisk nejdelsiho z  nich.
              Program testuje soubor funkci frad.cpp */
#include <stdio.h>;
int getline(char[],int); /* Prototyp funkce getline. Informace
 v nem obsazena rika, ze funkce vraci hodnotu typu int a ma dva
 parametry: jeden parametr je vektor - do nej se ukladaji znaky
 cteneho radku, druhy parametr je celociselna hodnota urcujici
 maximalni pripustnou delku retezce. Funkce mohla byt tez zapsa-
 na:   getline(char[],int); protoze v takovem pripade se
 automaticky rozumi, ze funkce vraci hodnotu typu int. Jsme-li
 priznivci uplne informace, lze vsak maximalisticky psat
 int getline(char s[],int m_del_r) */

void copy(char s1[],char s2[]); /* Prototyp funkce copy. Funkce kopiruje
 hodnoty prvku vektoru s1 do prislusnych prvku vektoru s2. Mohli jsme
 tez psat  void copy(chr[],char[]).Slovni symbol void sdeluje kom-
 pilatoru, ze se jedna o  funkci, ktera nevraci hodnotu. V jinych pro-
 gramovacich jazycich se takove programove jednotce rika podprogram
 nebo vlastni procedura. Slovni symbol je povinny, nenapiseme-li ho,
 jsme kompilatorem varovani, ze funkce ma vratit hodnotu typu int a
 nevraci nic */

FILE *ms; /* Definice tzv. externi promenne, ktera se uziva v ruznych
             funkcich jednoho nebo vice programovych souboru */

main()
{
 const int MAXDEL=100;// Maximalni delka radku
 int delka, // bezneho radku
     max; // delka nejdelsiho radku
 char radek[MAXDEL], // bezny radek
      mradek[MAXDEL]; // nejdelsi radek;
 ms=fopen("ms","r"); // otevreni souboru ms pro cteni a zapis
 max=0;
 while ((delka=getline(radek,MAXDEL))>0) /* getline vraci delku radku
                       a vektoru radek  priradi retezec znaku radku */
   if(delka>max) // Je-li cteni radek zatim nejdelsi z ctenych, pak
   max=delka;
   copy(radek,mradek); // Zapis radek do mradek
  
  fclose(ms); // Uzavreni souboru pro cteni
  ms=fopen("ms","w"); // Otevreni souboru pro zapis
  if(max>0) //Cetlo se vubec neco?
   // Tisk radku do souboru ms
   fprintf(ms,"Radek %s\nje nejdelsi a ma delku %d\n",mradek,max);
} 

// Soubor frad.cpp
#include<stdio.h>;
int i; // Externi promenna, ktera se smi uzivat v celem souboru frad.cpp
getline(char s[],int lim) // Cteni radku s maximalni delkou lim
{
 extern FILE *ms; /* Deklarace (ne definice) externi promenne definovane
                  v souboru hrad.cpp (viz pozn. na konci odst. 1.5.1) */
 char c;
 /* V prikazu for je uzita operace konjunkce (tzv. logickeho
    soucinu) znama z vyrokove logiky. (V C++ se znaci &&) */
 for(i=0;i<lim-1&&(c=getc(ms))!=EOF&&c!='\n';i++)
  s[i]=c;
 if(c=='\n'){
  s[i]='\n';
  ++i;
 }
 s[i]='\0'; //Oznaceni konce retezce v C++
 return i;
}
void copy(char s1[],char s2[]) // Kopirovani s1 do s2
{
 i=0;
 while((s2[i]=s1[i])!='\0')
  i++;
}
Poznámky: Pro úsporu místa bylo snahou vše vysvětlit poznámkami přímo v programu. Zde jen tři vysvětlení:

Zvláštní zmínku si ovšem zaslou'í tzv. externí proměnné, pou'ité v příkladu 1.15.

Obsah

5.1  Automatické a externí proměnné

K odkazu Proměnné ve funkci main, tj. např. delka, max, radek, mradek jsou lokální v  main. Žádná funkce k nim nemá přístup. Totéž platí o jakékoliv programové jednotce v níž je nějaká lokální proměnná definovaná. Lokální proměnná začne existovat až po vyvolání funkce a její hodnota se ztrácí po ukončení činnosti příslušné funkce. V jazycích C a C++ se těmto proměnným říká automatické proměnné. Při vstupu do programové jednotky jim musí být vždy znovu přiřazena hodnota, protože jinak by obsahovaly hodnotu náhodnou. Mohou se inicializovat, tj. může se jim přiřadit počáteční hodnota ihned při definici. Pak se při každém vstupu do funkce inicializují znovu. V jazyku C se automatická pole inicializovat nesmějí (mají při vstupu do funkce vždy hodnotu nula), avšak v  jazyku C++ toto omezení neplatí. Proměnné, které chceme používat ve více funkcích, a nechceme-li nebo nemůžeme-li použít mechanismu formální - skutečný parametr, zavedeme jako tzv. externí proměnné. Tyto proměnné jsou definovány globálně pro celý soubor funkcí a jsou dostupné pro všechny funkce souboru, . Externí proměnné se musí definovat mimo funkce a deklarovat buď explicitně deklarací extern, nebo implicitně svým použitím v příslušné funkci.

Externí proměnné a pole se inicializují jen jednou v procesu plnění programu. Neprovede-li se inicializace mají napočátku externí proměnné hodnotu nulovou.

Sestává-li program z více souborů, pak je externí proměnná definována v jednom souboru a lze ji tam použít shora popsaným způsobem; ve druhém souboru se externí proměnná musí deklarovat ve funkci, deklarací extern.

V našem programu jsme museli definovat proměnnou ms jako externí. V souboru hrad jsme s ní pracovali bez jakékoliv deklarace, v souboru frad jsme ji museli ve funkci getline explicitně deklarovat. Proměnnou i v souboru frad jsme definovali na začátku souboru a implicitně použili v obou funkcích getlinecopy. V druhém případě by bylo vhodnější definovat proměnnou v obou funkcích zvlášť, náš záměr zde byl čistě pedagogický. Na tomto místě je třeba upozornit, že extern i a extern int i znamená totéž. Externí proměnné je vhodné užívat uvážlivě, protože mohou být (právě tak jako proměnné v popisu common v jazyku fortran) zdrojem nepříjemných chyb a mohou znepřehledňovat program.

Závěrem si uveďme schéma na vysvětlenou:

// soubor1.cpp
int sp=0; // Definice externi promenne s inicializaci
float val[1000]; // Definice externiho pole bez inicializace
void main()
{
 extern sp; // Nepovinna deklarace
 // ...
}
funkce()
{
 extern float val[]; // Nepovinna deklarace

// soubor2.cpp
extern sp; // Povinna deklarace
extern float val[]; //Povinna deklarace
/* Toto byly povinne deklarace externich promennych. Pak se mohou
   pouzivat ve vsech funkcich souboru. Mohli jsem je ale tez
   deklarovat jen ve funkcich, ktere dane promenne pouzivaji */
// ...

Poznámka: Pozorný čtenář si jistě všiml, že používáme dva pojmy: definice a deklarace proměnné nebo pole. V jazyku C a C++ se těchto pojmů užívá takto:

K odkazu
Obsah

5.2  Rekurzívní funkce

O funkci říkáme, že je rekurzívní, volá-li sama sebe ještě před dokončením svého algoritmu. Známým a jednoduchým příkladem rekurzívní funkce je výpočet faktoriálu.
long fakt(int n)
{
 if(n==0||n==1) return(1);
 else return (n*fakt(n-1));
}

nebo ještě stručněji

long fakt(int n)
{
 return (n==0||n==1)?1:n*fakt(n-1);//viz
}

Na tomto velmi jednoduchém příkladu rekurzívní funkce si můžeme snadno vysvětlit princip výpočtu. Představme si, že funkci fakt zavoláme s parametrem 4. Formálnímu parametru se tedy přiřadí hodnota 4. Provede se zkouška, zda se n rovná nule nebo jedné. To se ale nerovná, protože se rovná čtyřem. Vyvolá se tedy znovu funkce fakt, ale tentokrát s novým parametrem, který má hodnotu n-1, tedy hodnotou tři. V  tomto dalším bloku se provede další zkouška, a protože stále není hodnota parametru rovna nule nebo jedničce vyvolá se opět funkce fakt s hodnotou dvě, a pak ještě jednou s hodnotou jedna. Teprve nyní se vrací hodnota jedna, ta se násobí v funkci (podle algoritmu) dvěma, výsledek se opět v funkci násobí třemi a v původně volané funkci čtyřmi. Konečný výsledek je tedy 24. Z tohoto příkladu vidíme, že jsme sice rekurzívním zápisem zápis zjednodušili, ale místo jednoho parametru jsme potřebovali (aniž bychom to explicitně zadali) parametry čtyři. Obecně platí, že rekurzívní zápisy spotřebují více paměti než zápisy nerekurzívní a také více strojového času. Přesto mnohdy je zjednodušení a zpřehlednění zápisu tak významné, že rekurzivitu využíváme.

Obsah

6.  Závěrečné poznámky k 1. kapitole

Po ukončení 1. kapitoly jsme schopni psát i dosti komplikované programy. Nejdůležitější skutečnosti, které vyžaduje zápis programu v  C++, byly v této kapitole uvedeny; zde uvedeme jeden nový poznatek, jeden upřesňující poznatek a jedno důležité opakování.

  1. Proměnné v jazyku C++ se nemusí definovat na začátku funkce, ale před prvním použitím ve funkci. Definice veličin mohou být tedy rozptýleny v celé operační části funkce nebo chcete-li v celém funkce.
  2. V příkazu printf, fprintf, scanf a fscanf se používá tzv. formátová specifikace. Částečně jsme se s ní seznámili. Nyní se zmíníme o konverzích, o kterých jsme ještě nemluvili, popř. mluvili, ale příliš stručně.

    Písmeno l uvedené před d, o a x označuje, že v seznamu argumentů funkcí odpovídá této konverzi forma (long) a u konverzi f nebo e označuje, že odpovídající argument není typu float, ale double, tj., že nabývá reálné hodnoty s dvojnásobnou přesností. Písmeno L označuje v tomto případě, že odpovídající argument je typu long double.

    Zastavme se ještě u tisku řetězce. Uvedeme nejzajímavější příklady aplikace konverze %s pro tisk řetězce:

    Nejneobhospodarovavatelnejsi

  3. Každá funkce volaná v jiné funkci musí mít před místem volání tzv. prototyp. Lze obejít hlavičkovým souborem, ve kterém uvedeme všechny prototypy funkcí a k souboru připojíme příkazem #include

Obsah

7.  Cvičení

  1. [1.] Sestavte programy z příkladů 1.4. a 1.5. tak, že příkazy (funkce) cin a cout nahradíte voláním funkcí scanf a printf.
  2. [2.] Pokuste se u všech u programů v příkladech 1.3 až 1.5 definovat konverzi pro výstup hodnot na výstupu (nevyužívat tedy standardní výstupní konverze).

    Návod:

  3. [3.] Sestavte program podle příkladu 1.6 tak, že použijete soubor stdio.h, funkci fprintf, fscanf a místo návěští KONEC a příslušného příkazu skoku zajistěte ukončení programu příkazem while.
  4. [4.] Sestavte program pro převod teplot tak, aby tiskl tabulku v opačném pořadí, tj. od 300 do 0 stupňů Fahrenheita.
  5. [5.] Sestavte program pro výpočet částečného součtu řady

    Sn = 1+x+[(x2)/ 2!]+ź+[(xn)/( n!)],

    kde n je nejmenší číslo, pro které platí |[(xn)/( n!)]| < e

    Čísla x a e jsou dána vstupními daty. Vypočet realizujte:

    Návod: Pro sestavení algoritmu je nutné si uvědomit, že každý příspěvek k částečném součtu řady lze vypočítat z předchozího příspěvku ze vztahu

    členi = x/ičleni-1
    a není tedy nutné počítat ani mocninu ani faktoriál.
  6. [6.] Sestavte program pro realizaci Euklidova algoritmu výpočtu největšího společného dělitele (NSD) dvou přirozených čísel. Výpočet realizujte:

    Návod: Vyjděte z vlastností NSD:

    pro x > y     platí     NSD(x,y) = NSD(x-y,y),

    pro x < y     platí     NSD(x,y) = NSD(x,y-x),

    pro x = y     platí     NSD(x,y) = x = y.

  7. [7.] Sestavte program na počítání znaků mezera, tabelátor a nový řádek v souboru m_sbr
  8. [8.] Sestavte program na kopírování souboru m_vst do souboru m_vys tak, že každý řetězec znaků mezera v souboru m_vst zamění za jediný znak mezera a znak tabelátor zamění šesti mezerami.
  9. [9.] Sestavte program, který na každý řádek výstupního souboru zapíše maximálně deset slov ze vstupního souboru.
  10. [10.] Upravte program z příkladu 1.11 tak, aby slovo zpracovával podle definice: Slovo je posloupnost písmen, číslic, znaků '_' a '.' začínající písmenem.
  11. [11.] Sestavte program pro transponování čtvercové matice A stupně n,

    kde n  ú  10.

  12. [12.] Sestavte program pro výpočet stopy matice stupně n, kde n ú 20.

    Návod: Použijte vzorce s = ĺi = 1naii.

  13. [13.] Sestavte program pro násobení matic C(m,n) = A(m,l) ·B(l,n).

    Návod: cij = cij+ĺk=1laikbkji = 1,2,.ź,mj = 1,2,ź,n.

  14. [14] Sestavte program, který zamění znaky tabelátor (\t) ze vstupu za správný počet mezer tak, aby vyplnil prostor po následující zarážku tabelátoru. Předpokládejte, že zarážky tabelátoru jsou pevné, např. na každé šesté pozici.
  15. [15.] Sestavte program, který zamění řetězce znaků mezera za nejmenší počet znaků tabelátor a mezera tak, aby se zachovala grafická úprava textu.
  16. [16.] Sestavte program, který zalomí dlouhé vstupní řádky za posledním znakem, který se vyskytne před n-tou pozicí vstupního řádku. n je parametr.
  17. [17.] Napište předchozí program tak, aby řádky byly zarovnány.
  18. [18.] Sestavte program, který vynechá z  C++ programu všechny poznámky.
  19. [19.] Sestavte funkci malep(chr z), která vrátí hodnotu z, není-li z písmeno, a malé písmeno, je-li z písmeno. Použijte funkci malep v programu, který konvertuje text na malá písmena.

    Návod: Využijte vlastnosti kódu ascii, kde číselné kódy malých a velkých písmen mají konstantní rozdíl; obě abecedy jsou spojité.

  20. [20.] Sestavte funkci obrat(char s[]), která obrátí znakový řetězec s. Funkci použijte v programu.
  21. [21.] Sestavte funkci sks(int x[],int y[],n) a void sks1(int x[],int y[],int s,n) pro výpočet skalárního součinu ĺi = 1nxiyi a použijte je v programu.
  22. [22.] Sestavte rekurzívní funkci pro operaci umocňování podle následujícího efektivního rekurzívního algoritmu.
    mocnina(int x,int n)
    {
     int v=1;
     while (n)
     {
      while(n/2*2==n)
      {
       n /= 2; x *= x;
      }
      n--;v *= x;
     }
     return v;
    }
    

    Obsah

Kapitola 2
Typy, operátory, výrazy

V této kapitole nebudeme opakovat již probrané objekty, jen si řekneme některé podrobnosti.

Obsah

1.  Jména, typy, konstanty

Obsah

1.1  Jména

Jméno, nebo v odborné terminologii synonymum identifikátor, je v jazyku C++ posloupnost maximálně 31 písmen, číslic a znaků podtržení ( _) začínající písmenem. Znak _ se považuje za písmeno. Lépe je používat jména kratší než delší (maximálně osm, spíše šest). Slovní symboly (např. for, else, int, while apod. jsou tzv. vyhrazená slova, která se nesmějí používat jako jména. Malá a velká písmena se v  C++ rozlišují: haha, Haha a HaHa jsou tři různá jména a For není slovní symbol.

Obsah

1.2  Typy

V jazyku C++ jsou následující základní standardní typy: char, int, unsigned, floatdouble, kterými definujeme proměnné, které mohou po řadě nabývat hodnoty: znak, celočíselná hodnota, celočíselná hodnota bez znaménka, reálná hodnota a reálná hodnota s dvojnásobnou přesností. Celočíselná proměnná může být označena kvalifikátorem short nebo long. Definice nebo deklarace: short int ishort i jsou totožné; právě tak long jlong int j. Dlouhé a krátké hodnoty můžeme definovat i pro typ unsigned: např. short unsigned u a long unsigned u (nebo unsigned short, unsigned long). Je možné také psát unsigned char nebo char unsigned. Dlouhá forma je možná i u proměnných typu double: např. long double ld. Můžeme odvozovat další typy např. struktury (viz), uniony (viz)a třídy (viz).

Na tomto místě se zmíníme o tzv. výčtovém typu. Dovoluje zpřehlednit program. Výčtový typ lze definovat několika způsoby. Uvedeme ten, který je v  C++ nejjednodušší.

Příklad 2.1. Sestavme program , který přečte hodnotu proměnné barva a bude-li barva červená, resp. žlutá, resp. zelená, bude se tisknout text STUJ, resp. PRIPRAV SE, resp. VOLNO.


Řešení:

#include<iostream.h>
 main()
{
 enum BARVA{cervena,zluta,zelena}; // Deklarace typu BARVA
 BARVA semafor; // Definice promenne semafor typu BARVA
 cin >> semafor;/* Cteni hodnoty. Neni-li receno jinak,
                        plati cervena == 0, zluta == 1 a zelena == 2 */
 switch(semafor)
 {
  case cervena: cout << "STUJ\n";break;
  case zluta:   cout << "PRIPRAV SE\n";break;
  case zelena:  cout << "VOLNO\n";break;
 }
}

Mohli bychom samozřejmě pracovat s čísly 0, 1, 2, ale shora uvedeným způsobem se zápis algoritmu zpřehlední. Implicitně zadané hodnoty položkám typu se mohou změnit, např. zápis

enum DEN{po=1,ut,st,ct,pa,so,ne}
zajistí, že položka po == 1, ut == 2, st == 3 atd. Zápis

enum D{po,ut,st,ct,pa=1,so=12,ne}
zajistí, že ut == pa == 1, so == 12, ne == 13
.

Obsah

1.3  Konstanty

4444444444444                                          ¯ 4444444444444 Matematický zápis  Zápis v  C++  Matematický zápis   Zápis v  C++
-123  -123  10-4  1e-4
2,345  2.345  x  'x'
-0,5  -.5  (77)8  077
1,25  1.2e5  (1A)16  0x1a

Zápisy číselných konstant jsou jasné z předchozí tabulky. Celočíselné konstanty se píší tak jako v matematice. U reálných konstant píšeme desetinnou tečku a exponent označujeme písmenem e nebo E (jako v jiných programovacích jazycích) a znaky píšeme mezi apostrofy. V  C++ můžeme zapisovat i osmičkové nebo hexadecimální konstanty, tak jak je v tabulce uvedeno. Některé nezobrazitelné znaky je možné zapsat pomocí změnových posloupností, např. \n (nový řádek), \t (tabelátor), \0 (prázdný znak),\\ (zpětné lomítko), \' (apostrof) apod. Mimoto se může zápisem '\ddd' realizovat libovolný bitový řetězec, kde (d je osmičková číslice), popř.'\xdd', kde d je hexadecimální číslice. Napíšeme-li v programu konstantu desítkovou konstantu 48 nebo '\60' nebo '\x30', znamená to v kódu ascii znak nula; napíšeme-li desítkově 95 nebo '\137' nebo '\x5f', je to v kódu ascii znak _. Definujeme-li např. v programu symbolickou konstantu:

const char nova_str='\14';
definovali jsme novou stránku (pracujeme-li v kódu ASCII).

Obsah

2.  Operátory

V tomto odstavci probereme podrobnosti o aritmetických, logických a  dalších operátorech a zmíníme se o prioritách operací v C++

K odkazu
Obsah

2.1  Aritmetické operátory

1. Operátor o kterém jsme ještě nehovořili je operátor operace . Např. 5%4 - > 1, 3%4 - > 3, 10%2 - > 0.

Příklad 2.2. Sestavme část programu, která zjistí, je-li rok přestupný.



Řešení:
Rok je přestupný, je-li letopočet dělitelný čtyřmi a zároveň není dělitelný stem. Rok je však vždy přestupný, je-li letopočet dělitelný čtyřmi sty.

//...
  if(rok%4 == 0 && rok%100 != 0 || rok%400 == 0)
   cout << "Rok je prestupny \n";
  else
   cout << "Rok neni prestupny";
//...
\

V programu jsme použili operace konjunkce a disjunkce, známých z výrokové logiky.


2.Operátory inkrementace a dekrementace

Operátory jsme již používali v 1. kapitole a nyní naše znalosti doplníme o velmi důležitou podrobnost.

Příklad 2.3. Napišme funkci, která z řetězce r vymaže znaky zn.



Řešení:

// Funkce vymaze znaky zn z retezce r
void vymaz(char r[],char zn)
{
 int i,j;
 for(i=j=0;r[i]!='\0';i++)
  if(r[i]!=zn)
   r[j++]=r[i]; // !!!!
 r[j]='\0';
}

V programu je naznačena velká modifikovatelnost příkazu for. Prosím čtenáře, aby se zamyslel, proč není možné v tomto případě napsat r[++j]=r[i];

Obsah

2.2  Přiřazovací operátory

V jazyku C++ samozřejmě můžeme, právě tak jako v jiných programovacích jazycích psát přiřazovací příkazy typu:


i = i + 2; x = x * 2; z = z / y;



tyto příkazy však v  C++ můžeme psát stručněji, takto:


i += 2; x *= 2; z /= y;


Těchto operátorů je celá řada, kromě uvedených např. ještě %=. Zbylé najde čtenář v článku , který pojednává o prioritách operací v  C++. Důležité je ovšem vědět, že tyto operace mají velmi nízkou prioritu (stejnou jako přiřazovací příkaz), a proto např.


x *= y + 1;



znamená totéž jako příkaz


x = x * (y + 1)


Těmito příkazy nejen zkracujeme zápis, ale můžeme i optimalizovat výpočet, protože


výraz_1 výraz_1 op výraz_2;



je totožný se zápisem


výraz_1 op= výraz_2;



ale výraz_1 se vyhodnocuje jen jednou. Pak ovšem příkaz


yypro[yypv[p3 + p4]+yypg[p1 + p2]] += 2;



vyskytuje-li se v cyklu, je nejen stručný, ale též šetří strojový čas.

Obsah

2.3  Podmíněný výraz

V jazycích jako Algol 60 nebo Algol 68 existuje konstrukce zvaná podmíněný výraz. Taková konstrukce existuje i v  C++. Příkaz


if(a > b) z = a; else z = b;



můžeme zapsat stručněji


z = a > b ? a : b;



nebo (jak je zvykem) méně stručně, ale přehledněji


z = (a > b) ? a : b;


Příklad 2.4. Sestavme program, který tiskne n prvků pole tak, že na řádku je 10 prvků. Každý prvek je oddělen od druhého mezerou a každý řádek (i poslední) je ukončen právě jedním znakem . Pole a jeho velikost definujme jako externí proměnné.



Řešení:

#include<stdio.h>
int a[15]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},n=15;
main()
{
for (int i=0;i<n;i++)
 printf("%d%c",a[i],(i%10==9 || i==n-1) ? '\n' : ' ');
}

nebo

#include<iostream.h>
int a[15]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},n=15;
main()
{
 for (int i=0;i<n;i++)
 cout << a[i] << ((i%10==9 || i==n-1) ? '\n' : ' ');
}

Napsané varianty programu jsou trochu školními případy. Chtěl jsem na nich opět nejen zopakovat použití externích proměnných a to, že polím je možné přiřadit počáteční hodnoty, ale i jak se to dělá.(Podrobněji v čl. .) Proměnná n je ovšem definována jako externí celkem zbytečně. V druhém případě musel být výraz


(i%10==9||i==n-1) ? '\n' : ' '



uzavřen do okrouhlých závorek, protože jinak systém reaguje chybně (viz).

K odkazu
Obsah

2.4  Relační a logické operátory

Relační operátory jsou


> >= < <=


Všechny  mají tutéž prioritu (viz).  O stupeň nižší prioritu mají operátory rovnosti a nerovnosti


== !=jejichž priority se také rovnají.

Výrazy spojené logickými operátory konjunkce &&

a disjunkce || se vyhodnocují zleva doprava a  vyhodnocování se zastaví v okamžiku, kdy je známa pravdivost nebo nepravdivost výsledku. Priorita operátoru && je vyšší než priorita operátoru || a oba mají nižší prioritu než operátory rovnosti. Operátor negace ! jako unární operátor má jednu z nejvyšších priorit.

Logické operátory po bitech není možné aplikovat na typy float nebo double. Mají vyšší prioritu než operace disjunkce a konjunkce. Jsou to operátory:


&         operátor logického součinu po bitech

|         operátor logického součtu po bitech

^         operátor nonekvivalence po bitech

<<        operátor posunu vlevo

>>        operátor posunu vpravo

~         unární operátor jednotkového doplňku


Obsah

3.  Typová konverze

Pod pojmem typová konverze se míní převod proměnné určitého typu na typ jiný např. int na float. Většina typových konverzí probíhá implicitně, bez potřeby zásahu programátora. Platí:

  1. [a)]Před vykonáním operace se samotné operandy konvertují takto:

  2. [b)] Mají-li dva operandy jedné operace různý typ, pak je typ s nižší mírou informace konvertován na typ s vyšší mírou informace takto:

    unsigned int     ¯ na     ¯ unsigned int  na  unsigned int
    unsigned int  na  long
    long  na  unsigned long
    unsigned long  na  float
    float  na  double
    double  na  long double

    Toto pravidlo platí i pro podmíněný výraz. Je-li např. proměnná i typu int a proměnná f typu float, je výraz (n > 0) ? i : f typu float.

  3. V přiřazovacím příkazu je typ na pravé straně konvertován na typ z levé strany; tato skutečnost, právě tak jako ve Fortranu, může vést ke ztrátě přesnosti a nepříjemným chybám.


Příklady:

Předchozí popsané případy se provádějí automaticky bez našeho přispění. Můžeme však provádět typové konverze explicitně, které se říká přetypování. Má tvar:


( typ)výraz a význam: výraz je konvertován na typ


Příklady a význam některých používaných konverzí:

(double) float_výraz     převod celého čísla na jeho znakový ekvivalent  převod znaku na jeho celočíselný ekvivalent
(char) int_výraz  převod celého čísla na jeho znakový ekvivalent
(int) float_výraz  odříznutí desetinné části

(double) int_výraz  převod celého čísla na reálné
(double) float_výraz  zvětšení přesnosti

Přetypování je nutné v různých případech, např. při použití ukazatelů (viz). V původně definovaném jazyku C (viz např. [3]), bylo nutné při nesouladu formálních a skutečných parametrů provést přetypování skutečného parametru (tak je tomu ostatně ve většině programovacích jazyků). Tedy např.

//...
int i; double d;
//...
d=exp((double)i);
//...

V jazyku C++ to není nutné, protože jsou zavedeny povinné prototypy, ale přesto je vhodné přetypování uvést, protože zpřehledňuje program. V jazyku C++ se více než přetypování používá tzv. explicitní typová konverze. Spíše než dříve uvedený zápis píšeme v C++:

//...
int i; double d;
//...
d=exp(double(i)); // double (i) je explicitni typova konverze
//...

nemáme-li ovšem odvahu napsat

//...
int i; double d;
//...
d=exp(i);
//...

Obsah

3.1  Priority operací

Závěrem této kapitoly si uvedeme priority operací. Uvedeme všechny operátory s tím, že nám ještě neznámé probereme později. Tabulka priorit operací pro jazyk C++:



Operátor
::
() [ ] -> . sizeof
! ~ ++ -- + - ( typ) * & new delete
* / %
+ -
<< >>
< <= >= >
= = ! =
&
^
|
&&
||
? :
= + = - = * = / = % = >>= <<= & = / = ^=
,



Poznamky:

K odkazu 1
K odkazu 2
Obsah

Kapitola 3
Podrobnosti k předchozím kapitolám

Nyní již dokážeme realizovat i dosti složité programy v jazyku C++. Budeme muset probrat ještě velmi důležité pojmy jako struktury a uniony a hlavně ukazatele a třídy, ale v této kapitole zatím probereme jen některé podrobnosti, o kterých jsme z pedagogických důvodů nechtěli hovořit v kapitolách předchozích, ale které nemůžeme opominout.

Obsah

1.  Příkazy a bloky

Výraz jako x = 0 nebo i++ nebo printf(...) se stane příkazem, následuje-li za ním středník: x = 0; nebo i++; nebo printf(...);

V jazyku C a C++ je středník koncovým znakem příkazu a ne oddělovačem, jako v jazycích typu Algol nebo Pascal. Proto se také musí např. psát

...;  if(a>b) max=a; else max=b;

i když středník před slovním symbolem else je v programovacích jazycích obvykle chybný.

Složené závorky se používají pro sdružení příkazů v tzv. složený příkaz, který jsme již mnohokrát použili. Jsou-li ve složeném příkazu uvedeny definice proměnných, jedná se o tzv. blok. Bloky mohou být do sebe vřazeny.

Příklad 3.1. Sestavme školní program, kterým si osvětlíme princip blokové struktury.



Řešení:

#include<stdio.h>
main()
{
 int a,b,c; // Lokalizovane promenne v programu
    a=10;
    b1:{int i,j; // Lokalizovane promenne v bloku b1
        i=j=5;
        c=i*j*a;
        printf("Tisk v b1: c = %3d\n",c);
       }
    b=c*a;
    b2:{float b,c,d; // Lokalizovane promenne v bloku b2
        d=c=2.5;
        b=c+4;
        b3:{int p; // Lokalizovana promenna v bloku b3
            p=5; d=p+b;
            b=10*p+b;
            printf("Tisk v b3:  d = %4.1f b = %4.1f\n",d,b);
           }
        a=100+a; printf("Tisk v b2: a = %3d\n",a);
       }
    printf("Tisk v programu: a = %3d b = %4d c = %3d\n",a,b,c);
}
Bloky b1, b2, b3 jsou podřazené hlavnímu programu. Hlavní program a blok b2 jsou nadřazené bloku b3.

Definice, které jsou v bloku uvedeny, platí pouze uvnitř tohoto bloku. Vně bloku může být každý z definovaných identifikátorů užíván pro jiné účely. Tak každý blok zavádí novou úroveň označování. Každý identifikátor, který je v bloku definován, je v něm lokální, tj. má tyto vlastnosti:

  1. Objekt označený tímto identifikátorem v bloku neexistuje vně tohoto bloku.
  2. Každý objekt označený tímto identifikátorem vně bloku je uvnitř tohoto bloku zcela nepřístupný.

V příkladu 3.1 jsou automatické proměnné a, b, c z hlavního programu přístupné v bloku b1. V bloku b2b3 je přístupná pouze proměnná a, protože identifikátory b, c jsou užity jinak. Proměnné i, j z bloku b1 jsou přístupné jen v tomto bloku a po jeho ukončení jejich hodnoty přestávají být definovány. Proměnné b, c, d definované v b2 jsou přístupné i v bloku b3 a přestávají být definovány po ukončení bloku b2. Pro pečlivého čtenáře je třeba uvést, že v příkladu 3.1 se budou tisknout postupně hodnoty: Tisk v b1: c = 250 Tisk v b3: d = 11.5 b = 56.5 Tisk v b2: a = 110 Tisk v programu: a = 110 b = 2500 c = 250 Za koncovými složenými závorkami složených příkazů a bloků se nemusí dělat středníky.

Závěrem tohoto odstavce se letmo zmíníme o tom, že definice a deklarace nemusí být umístěny na začátku programové jednotky nebo bloku, ale mohou být zapsány na kterémkoli místě před prvým použitím definovaného nebo deklarovaného objektu. Původně definovaný jazyk C toto zakazoval (viz např. [3]).

Obsah

2.  Operátor ::

Operátor :: se v jazyku C++ užívá k řešení konfliktů v přístupových právech k objektům. Máme-li např. automatickou (lokální) proměnnou vekt_sum v nějaké funkci a potřebujeme-li užít externí (globální) proměnnou, která se jmenuje též vekt_sum, zapíšeme: ::vekt_sum, čímž jsme programu umožnili tuto externí proměnnou použít.

K odkazu Napíšeme-li např. program z příkladu 3.1 takto:

#include<stdio.h>
int c; // Externi promenna
main()
{
 int a,b,c; // Lokalizovane promenne v programu
    a=10;
    ::c=1000; // Pracuje se s externi promennou
    b1:{int i,j; // Lokalizovane promenne v bloku b1
        i=j=5;
        c=i*j*a; // Pracuje se s automatickou promennou
        printf("Tisk v b1: c = %3d\n",c);
       }
    b=c*a;
    b2:{float b,c,d; // Lokalizovane promenne v bloku b2
        d=c=2.5;
        b= c+4;
        b3:{int p; // Lokalizovana promenna v bloku b3
            p=5; d=p+b;
            b=10*p+b;
            printf("Tisk v b3:  d = %4.1f b = %4.1f\n",d,b);
            printf("Tisk zdanlive nepristupne promenne c = %d\n",::c);
           }
        a=100+a; printf("Tisk v b2: a = %3d\n",a);
       }
    printf("Tisk v programu: a = %3d b = %4d c = %3d\n",a,b,c);
}

bude se v bloku tisknout: Tisk v b1: c = 250 Tisk v b3: d = 11.5 b = 56.5 Tisk zdanlive nepristupne promenne c = 1000 Tisk v b2: a = 110 Tisk v programu: a = 110 b = 2500 c = 250 Je nutno upozornit, že operátorem :: lze zpřístupnit jen globální proměnné, které jsou externí a statické (viz ), nikoliv automatické proměnné, které jsou globální z hlediska blokové struktury. Nedůvěřivý čtenář se může v původním programu z příkladu 3.1 pokusit zpřístupnit proměnnou c ze záhlaví hlavního programu v  bloku b3. Uvidí, že neuspěje. Toto nebývá v učebnicích jazyka C++ příliš zdůrazňováno.

Operátor :: se užívá i ve spojitosti s tzv. třídami (viz ).

Obsah

3.  Operátor čárka

Dvojice výrazů oddělených čárkou se vyhodnocuje zleva doprava a výsledek má hodnotu pravého operandu. Čárky mezi parametry nemají význam operátoru.

Příklad 3.2. Sestavme funkci pro obracení řetězce znaků.



Řešení:

// Funkce obrati retezec: tedy napr. "abcd" zmeni na "dcba"
void obrret(char s[])
{
 int c,i,j;
 for(i=0,j=strlen(s)-1;i<j;i++,j--)
 { c=s[i];s[i]=s[j];s[j]=c;}
}

Funkce strlen je obvykle standardní funkcí jazyka C++ a bývá součástí souboru string.h. Zjišťuje délku řetězce bez koncového znaku '\0'. Protože indexace v jazyku C++ začíná od nuly a znak '\0' nepřehazujeme, museli jsme napsat strlen(s)-1. Příklad je pro tuto operaci typický, protože se operátor čárka v jazycích C a C++ užívá hlavně v příkazech for a while.

Obsah

4.  Příkaz break

Příkaz break jsme již použili ve spojitosti s příkazem switch (přepínač), kdy jsme jednotlivé alternativy ukončovali právě příkazem break. Další využití tohoto příkazu je v ukončení příkazu cyklu, ve kterém je zapsán.

Příklad 3.3. Sestavme program, který vynechá v textu koncové mezery, tabelátory a nové řádky.



Řešení:

V programu využijeme funkci getline, kterou jsme již sestavili v příkladu 1.15.

main() // Vynechani koncovych mezer, tabelatoru a novych radku
{
 const int MAXLINE = 1000;
 int n; char radka[MAXLINE];
 while((n=getline(radka,MAXLINE))>0)
 {
  while(--n > 0)
   if (radka[n] != ' ' && radka[n] != '\t' && radka[n] != '\n')
      break;
  radka[n+1]='\0';
  cout << radka;
 }
}

Příkaz break v tomto případě ukončí práci vnitřního cyklu, nerovná-li se poslední kontrolovaný znak ani mezeře, ani tabelátoru a ani znaku nový řádek.

Obsah

5.  Příkaz continue

Tento příkaz zajistí ukončení jednoho průchodu cyklem a narozdíl od příkazu break se používá pouze v cyklech.

//... tato část programu vynechá záporné prvky
 for(i=0;i<n;i++)
 {
  if(a[i]<0) continue // Vynechaji se vsechny prikazy do konce cyklu
  //Zpracovani kladnych prvku
  //...
 }
V příkladu se zpracovávají v cyklu i pouze kladné prvky podle nějakého algoritmu. Narazí-li na záporný prvek příkaz continue zajistí, že se algoritmus neprovede i se (v tomto příkladu) zvětší o jedničku a provádí se další krok cyklu.

Obsah

6.  Registrové proměnné

Jazyk C i jazyk C++ umožňuje pracovat s tzv. registrovými proměnnými. Počítač je umístí v registrech počítače, takže přístup k nim se značně zrychlí.

V následujícím naznačení funkce f jsou jako registrové proměnné použity oba formální parametry a automatická proměnná m.

f(register int c, register int n}
{
 register int m;
// ...
}

Příklad 3.4. Sestavme funkci pro výpis násobků libovolného čísla hodnotami 1120.



Řešení:

#include <stdio.h>
void nasob(register float k)
{
 for (register j=11;j<=20;j++)
  printf("%d * %f = %f\n",j,k,j*k);
}
V příkladu je použit formální parametr typu register float a proměnná j, která je typu register int. Pro celočíselnou proměnnou je možné slovní symbol int vynechat.

V praxi se na registrové proměnné kladou jistá omezení, které odrážejí možnosti použitého počítače. Registrové proměnné musí být vždy automatické (lokální) . V každé funkci můžeme v registrech držet jen několik proměnných a tyto mohou být jen určitých typů. Pro nadbytečné nebo nepřípustné definice se označení register ignoruje.

Obsah

7.  Cvičení

  1. [23.] Sestavte funkci, která připojí řetězec t na konec řetězce s. Funkci použijte v programu.
  2. [24.] Sestavte funkci, která z řetězce s1 odstraní všechny znaky nacházející se v řetězci s2. Funkci použijte v programu.
  3. [25.] Napište funkci, která vrátí pozici prvního výskytu libovolného znaku z řetězce s2 v řetězci s1. Když s1 neobsahuje žádný znak z řetězce s2, vrátí funkce hodnotu  -1.
  4. [26.] Sestavte funkci poc_bit(long unsigned n), která zjistí počet bitů v proměnné typu long usigned. Zkuste modifikovat na int, long int a short int.
  5. [27.] Sestavte funkci itoa(int n,char s[]), která konvertuje číslo na znakový řetězec. Použijte příkaz do-while, modulo a \= (jako celočíselné dělení).

    Návod: Seřaďte číslice odzadu a pak použijte funkci obrat ze cvičení č. 20.

Obsah

Kapitola 4
Ukazatelé, pole, funkce

Ukazatel (též se užívá termínu spoj) je proměnná (nebo symbolická konstanta) jejíž hodnotou je adresa jiného objektu (proměnné, pole apod.) Pro zavedení těchto lze např. uvést tyto důvody:

Obsah
K odkazu

1.  Ukazatelé a adresy

Předpokládejme, že v programu jsou zapsány následující definice a příkazy
//...
int *px,x,y,z,*py;
//...
px=&x;
//...
y=*px;
z=*px+1;
printf("x = %d odmocnina z x = %lf",sqrt((double)*px);
py=px;
//...

V této ne příliš smysluplné části programu jsme demonstrovali základní práci s ukazateli. Ukazatel se v deklaraci nebo definici pozná podle znaku *, který předchází jménu ukazatele. V našem příkladu je ukazatelem veličina px. Příkaz px=&x; zajistí, že se ukazateli px přiřadí hodnota adresy proměnné x. Operátor & se nazývá operátor adresy. Od tohoto okamžiku je v programu ekvivalentní zápis *px a x do doby, než je ukazateli px přiřazena jiná hodnota. Tedy příkazy y=*px; z=*px+1; jsou ekvivalentní příkazům y=x; z=x+1;. Též v příkazu printf jsme školně napsali *px, i když jsme mohli psát x. Příkazem px=py jsme zajistili, že px a py ukazují na totéž místo v paměti, tedy v našem případě na místo, které je přiděleno proměnné x.

Závěrem tohoto odstavce by bylo vhodné, aby se čtenář laskavě přesvědčil, že zápis *px++ není totožný se zápisem (*px)++, kdežto zápis *++px je totožný se zápisem *(++px). V těchto případech je vhodné, aby se priorita operací vždy vyznačila závorkami. Vyhneme se tak nepříjemným omylům a program zpřehledníme.

K odkazu
Obsah

1.1  Ukazatelé a argumenty funkcí

Jednoduché proměnné, jak jsme si řekli v čl. 5, jsou volány hodnotou, není-li řečeno jinak. Je-li řečeno jinak, volají se odkazem. Jinak může být v jazyku C++ řečeno dvěma způsoby. První způsob je obvyklý i v jazyku C a provádí se takto:

  1. Formální parametry se deklarují jako ukazatele.
  2. Odpovídající skutečné parametry se zapisují s operátorem adresy.

Druhý způsob je typický pro jazyk C++ a provádí se takto:

  1. Formální parametry se označí operátorem adresy.
  2. Odpovídající skutečné parametry se zapisují bez jakéhokoliv označení.

Čtenář se může přesvědčit, že následující dva úseky programu jsou algoritmicky správně; kdežto třetí je algoritmicky (nikoliv ovšem syntakticky) chybný.

K odkazu
Obsah

2.  Ukazatelé a pole

Z čl. 4 víme, že např. při definici int a[10] se v paměti vyhradí deset paměťových míst pro prvky pole indexované od 0 do 9. Napíšeme-li v programu int *pa = &a[0], pak pa ukazuje na místo, kde je umístěno a[0]. (Pozor! Toto počáteční přiřazení je poněkud matoucí; srovnejte se zápisem pa = &a[0], které má tentýž význam a které je v intencích mnemotechniky: bez hvězdičky adresa a s hvězdičkou hodnota, která je na příslušné adrese uložena. Tato mnemotechnika většinou platí.)

Platí-li že pa ukazuje na a[0], pak pa+1 ukazuje na a[1], pa+i na a[i]. Napíšeme-li *(pa+i), pak je to totéž jako bychom napsali a[i]. Místo pa = &a[0] smíme zapsat pa = a.

Tyto zápisy vypadají zpočátku velmi nezvykle, ale v programu se často využívají.

Příklady:

Obsah

3.  Ukazatelé a konstanty

Použije-li se slovní symbol const s ukazatelem, jsou konstantní data spojená s ukazatelem, kdežto ukazatel konstantní není.

Při deklaraci const char *name ="PROKOP"; Je přípustný příkaz name="BUBEN";

ale nikoliv příkaz name[0]='B';

Chceme-li konstantní ukazatel a nikoliv údaj, na který ukazuje, píšeme char *const JMENO="hana";

Pak JMENO[0]='d';

je správně, kdežto JMENO="dana";

je chybně. Chceme-li mít konstantní ukazatel i údaj, na který ukazuje, píšeme const char *const KONST="NAZDAR";

Tato poněkud méně přehledná pravidla jsou zachycena v ukázkách v příkladu 4.1.

Příklad 4.1.

#include<stdio.h>
main()
{
// Definice ukazatele, ktery ukazuje na konstantu
 const char *name ="PROKOP";
// name[0]='B'; // Chybny prikaz
 name="BUBEN"; // Spravny prikaz
// Definice konstantniho ukazatele
   char *const JMENO="hana";
   JMENO[0]='d'; // Spravny prikaz
//   JMENO=dana; //Chybny prikaz
// Definice konstantniho ukazatele ukazujiciho na konstantu
   const char *const KONST="NAZDAR";
//   KONST[0]='A'; // Chybne
//   KONST="AHOJ; // Take chybne
     printf("%s %s %s\n",name,JMENO,KONST);
}

Obsah

4.  Argumenty povelového řádku

Funkce main se může volat se dvěma argumenty. Prvý - označme ho argc, což bývá zvykem, ale není to podmínkou - obsahuje počet argumentů tzv. povelového řádku, tj. řádku, kterým vyvoláváme příslušný program. Druhý - označme jej, jak je zvykem, ale nikoliv povinností, argv je ukazatel na pole znakových řetězců, z nichž každý označuje jeden argument.

Příklad 4.2. Sestavme program, který bude realizovat známý příkaz echo (ozvěna) operačního systému UNIX.


Řešení:

Příklad 4.3. Sestavme program pro výpočet integrálu Simpsonovou metodou. Simpsonovu metodu i integrovanou funkci zapišme jako samostatné funkce. Integraci proveďme tak, že zadáme v příkazovém řádku vedle jména programu ještě počet výpočtů a  počáteční dělení. V případě, že zadáme chybný počet argumentů, program nás bude instruovat, jak povelový řádek zadat. V tomto příkladu integrujeme funkci ň01,5[(sinx)/( x)] dx



Řešení:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "integral.cpp"
#include "int.cpp"
// Jméno programu hlavint.cpp
// Program pracuje s externi funkci integral.cpp
// (Simpsonova metoda) a integrovanou funkci int.cpp
main(int pocet_parametru, char *pole_ukazatelu_na_parametry[])
{
 int i,m,j;double g(double),integral(double,double,int,FUK);
 if(pocet_parametru==3){
  j=atoi(*++pole_ukazatelu_na_parametry); /* Funkce prevadejici retezec
  znaku ulozenych v pameti na celociselnou hodnotu (soubor stdlib.h)*/
   m=atoi(*++pole_ukazatelu_na_parametry);
  for(i=1;i<=j;i++)
   printf("Deleni intervalu: %d     integral = %f\n",m*i,
                                   integral(0.,1.5,m*i,g));
 }
  else
   printf("Zadej: hlavint pocet_vypoctu zakladni_deleni");
  return 0;
}
// Integrovaná funkce
double g(double x)
{
 if (x==0)
    return(1);
 else
   return(sin(x)/x);
}
//  Simpsonova metoda
typedef double(*FUK)(double);
double integral(double a, double b, int n, FUK f)
// FUK f je totez jako bychom psali: double(*f)(double))
{
 double s; double h=(b-a)/n; int k,i;
 k=1;s=f(a)+f(b);
/*Toto je strucnejsi,hezci a rozumnejsi zapis nez
                      s=(*f)(a)+(*f)(b)*/
 for(i=1;i<n;i++){
  s+=(k+3)*f(a+i*h);
  k=-k;
 }
 return(h/3*s);
}

V příkladu jsme si ukázali vedle použití povelového řádku (kdy zbytečně dlouhé názvy argumentů jsem zvolil proto, abych explicitně upozornil na možnost volby jmen argumentů; argc*argv[] se používají proto, že to tak zavedli tvůrci jazyka C pánové Kerninghan a Ritchie [3], není to však dogma a lze využívat různých mnemotechnických názvů) i využití jazyka C++ v numerické matematice, kdy je často vhodné použít jako skutečný parametr funkci. Tento způsob jsme si mohli ukázat až v okamžiku, kdy jsme si vysvětlili mechanismus ukazatele. Formální parametr, který stojí na místě funkce, musí být totiž deklarován jako ukazatel na funkci vracející hodnotu. Jak jsme již napsali v poznámce v proceduře, mohli jsme zapsat tento formální parametr jako double(*f)(double), my jsme však použili příkazu typedef v zápisu typedef double(*FUK)(double) čímž jsme definovali typ FUK jako ukazatel na funkci mající jeden argument typu double a vracející hodnotu typu double. Zápis formálního parametru má pak jednodušší formu: FUK f. Funkci f můžeme zapisovat běžným způsobem jako v jiných programovacích jazycích a ne, jak se traduje (a je ovšem také správné) a je uvedeno v poznámce ve funkci integral.

Doplňme si nyní naše znalosti o příkazu typedef, přestože jsme se o něm zmínili již např. v čl. 1. Příkaz

typedef int CEL, *CELUK; zajistí, že místo

int ahoj, *metr; lze psát

CEL ahoj; CELUK metr;což se může jevit jako zbytečné ale v případech jako

(kdy v případě a je deklarováno pole tří ukazatelů na celá čísla; v případě b je deklarován ukazatel na pole tří celých čísel; v případě c je deklarován identifikátor funkce vracející ukazatel na celé číslo a mající jeden argument typu int; a konečně v případě d je deklarován ukazatel na funkci vracející celé číslo a mající jeden celočíselný argument) nám příkaz typedef pomůže zpřehlednit zápis programu, protože shora uvedené zápisy přehledností nevynikají. Napíšeme-li ale v  příkazu typedef některou z výše uvedených konstrukci, pak jméno stojící na místě id1id4 označuje nový typ. Takže

typedef int (*UNP3C)[3]; UNP3C ukp;//...

zajistí, že proměnná ukp je ukazatel na pole tří celých čísel. Zápis

typedef int *FVUNC(int);FVUNC f1;

sděluje kompilátoru, že f1 je identifikátor funkce s jedním celočíselným argumentem, vracející ukazatel na celé číslo atd. Zápis se zjednodušší a toho jsme právě využili v příkl. 4.3.

Obsah

5.  Inicializace polí a polí ukazatelů

Jak jsme si již řekli v 5.1 v jazyku C++ můžeme inicializovat i automatická pole, což je v jazyku C zakázáno. Platí, právě tak jako u jednoduchých proměnných, že externím polím se přiřazuje hodnota právě jednou při plnění programu, kdežto automatickým polím se přiřazuje hodnota vždy při vstupu do funkce nebo bloku. Zde si proto na příkladech ukážeme techniku počátečního přiřazení, aniž bychom rozlišovali mezi externími a automatickými poli.


Příklady:

K odkazu
Obsah

6.  Ukazatelé a vícerozměrná pole

Vysvětleme si rozdíl mezi dvojrozměrným polem a polem ukazatelů. Budeme-li mít definice
int a[20][20], *b[20];

pak použití a i b může být podobné tím, že a[10][10] i b[10][10] je správně zapsaný odkaz na celé číslo. a je však skutečné pole (přidělí se mu 400 paměťových míst) a při výpočtu se používá obvyklý výpočet indexů pravoúhlého pole (matice) pomocí mapovací funkce. Pro b se však přidělí 20 paměťových míst pro ukazatele. Každý z těchto ukazatelů je třeba nejprve inicializovat, má-li ukazovat na nějaké pole celých čísel. Budeme-li předpokládat, že každý z těchto ukazatelů bude ukazovat na pole o dvaceti prvcích, budeme v takovém případě potřebovat 420 paměťových míst. Pole ukazatelů tedy potřebuje více místa v paměti a vyžaduje počáteční inicializaci. Má to však dvě výhody:

V následující ukázce programu se tiskne vždy totéž a sice hodnota prvku a[5][9] tj. v tomto případě hodnota 45.

#include <iostream.h>
#include<conio.h>
main()
{
 int a[10][10],*b[10],i,j;
 clrscr();
 for(i=0;i<10;i++)
  for(j=0;j<10;j++)
   a[i][j]=i*j;

 /* Ted se muze cyklem priradit &a[i][0] nebo (a+i)[0] popr.
    strucneji a mene prehledne a[i] nebo *(a+i) */

 for(i=0;i<10;i++)b[i]=&a[i][0]

          // nebo b[i]=(a+i)[0] protoze obe je pripustne

 cout<< " Tisk a[5,9] = "<< a[5][9] << " Tisk b[5,9] = "
     << b[5][9] << '\n';
 cout<< " Tisk a[5,9] = "<< a[5][9] << " Tisk b[5,9] = "
     << (*(b+5))[9] << '\n';
}

Příklad 4.4. Sestavme funkce, které umožní přečíst, vytisknout a sečíst matice libovolného typu. Využijme ukazatelů na vícerozměrná pole. Funkce použijme v programu.



Řešení:

/***********************************************************************
* Ukazka prace s maticemi v jazyku C++, bez uvazovani dynamickych ob-  *
* jektu, ale prakticky s dynamickou alokaci dvojrozmernych poli na uro-*
* vni (zhruba) jazyka Fortran                                          *
***********************************************************************/
#include<stdio.h>
void scmat(int *a[],int *b[],int *c[],int m,int n)
{
  for(int i=0;i<m;i++)
   for (int j=0;j<n;j++)
   c[i][j]=a[i][j]+b[i][j];
}
void tisk(int *b[],int m,int n)
{
 for(int i=0;i<m;i++)
  for(int j=0;j<n;j++)
   printf((j==n-1||j==9)?"%d\n"
                        : "%d ",b[i][j]);
}
void cti(int *b[],int m,int n)
{
 for(int i=0;i<m;i++)
  for(int j=0;j<n;j++)
   scanf("%d",&b[i][j]);
}
main()
{
 const m=10,n=20;
 int a[m][n],b[m][n],c[m][n]; int ns,ms;
 int *pa[m],*pb[m],*pc[m];

// Prirazeni adres prvku prvniho sloupce matic ukazatelum pa, pb, pc

 for(int i=0;i<m;i++){pa[i]=&a[i][0];pb[i]=&b[i][0];pc[i]=&c[i][0];}
 printf("\nCti pocet radku matice: ");scanf("%d",&ms);
 printf("\nCti pocet sloupcu matice: ");scanf("%d",&ns);
 cti(pa,ms,ns);
 tisk(pa,ms,ns);
 cti(pb,ms,ns);
 tisk(pb,ms,ns);
 scmat(pa,pb,pc,ms,ns);
 tisk(pc,ms,ns);
}

Obsah

7.  Ještě několik podrobností o funkcích

Obsah

7.1  Vnitřní funkce

Před funkcí v jazyku C++ smí být uveden slovní symbol inline. Takovéto funkce jsou tzv. vnitřní funkce, jejichž operační část se zapisuje přímo na místo, kde je funkce volána. Tím, že odpadá odskok na podprogram, který běžnou funkci realizuje, zrychluje se výpočet, ale prodlužuje se strojový kód, zvláště, opakuje-li se volání funkce často na různých místech a operační část funkce je delší. Doporučujeme proto užívat jako vnitřní funkce jen funkce krátké. Navíc příkazy (a slovní symboly) do, for, while, goto, switch, break, continue a case se ve vnitřních funkcích vyskytovat nesmějí. Použijí-li se, není to chyba, ale funkce se implementuje jako normální funkce a na slovní symbol inline se nebere zřetel. Typická vnitřní funkce je uvedena v následujícím příkladu

verbatim #include<stdio.h> inline void pr_int(int a,int b) printf("=

Obsah

7.2  Dosazené parametry

V jazyku C++ se smějí používat tzv. dosazené parametry. Kdekoliv se tak učiní, musí být dosazené parametry uvedeny jako poslední. Zápis

void default_f(int prvni,float druhy=2.345,char treti='t',
               char *ctvrty="naprava")


kdežto zápis

void default_f(int prvni=1,float druhy,char treti='t',
               char *ctvrty="naprava")


je chybný.
Aplikace dosazených parametrů je v příkladu 4.5.

Příklad 4.5.

// Priklad na dosazene parametry
#include<stdio.h>
void default_f(int prvni,float druhy=2.345,char treti='t',
               char *ctvrty="naprava")
{
 printf("prvni = %d  , druhy = %f , treti = %c , ctvrty = %s\n",prvni,
         druhy,treti,ctvrty);
}
main()
{
  default_f(32123);
}

Z uvedeného příkladu vidíme, že musíme uvádět jen nedosazené parametry. Neuvedeme-li dosazené parametry, dosadí se za tyto parametry hodnota uvedená v záhlaví funkce nebo prototypu.

Obsah

7.3  Přetížené funkce

K odkazu

Přetížené funkce v jazyku C++ jsou takové funkce, které mají stejné jméno v daném programu, ale různou operační část (tj. rozdílný algoritmus). Systém rozlišuje takové funkce při volání podle typu skutečných parametrů, které se musí u různých stejně pojmenovaných funkcí lišit. Dříve než jsou přetížené funkce definovány, musí být uvedeny slovním symbolem overload (viz příklad 4.6).

Příklad 4.6.

// Pretizene funkce
#include<stdio.h>
overload pr; /* Musi byt podle normy uvedeno, nektere systemy vsak
                tuto deklaraci nevyzaduji*/
void pr(int ); //Prototyp
void pr(char *); //Prototyp
void pr(int *); //Prototyp
int cislice[10]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
main()
{
 // a jeste jeden prototyp
 void zobraz(const char *jmeno1,const char *jmeno2=" dosazen");
 zobraz("Parametr1"); zobraz("Parametr2"," na obrazovce");
 pr(7);pr("Ma ucta");pr(cislice);
}
void pr(int i)
{
 printf("\nHodnota cisla je %d",i);
}
void zobraz(const char *jmeno1,const char *jmeno2)
{
 printf("\n%s%s",jmeno1,jmeno2);
}
void pr(char *retez)
{
 printf("\nHodnota retezce je %s",retez);
}
void pr(int *c)
{
 printf("\n");
 for(int i;i<10;i++) printf("%d",c[i]);
}
Čtenář se může přesvědčit, že program vytiskne:

Parametr1 dosazen
Prametr2 na obrazovce
Hodnota cisla je 7
Hodnota retezce je Ma ucta
0123456789

Obsah

7.4 Funkce s nespecifikovaným počtem parametrů U'itím tří teček v závorkách v záhlaví funkce deklarujeme v  C++, 'e funkce je definována pro nespecifikovaný počet parametrů. Výklad této vlastnosti, která umo'ní potlačit kontrolu typu parametru, ale hlavně umo'ní větší flexibilitu při pou'ití funkce se vymyká těmto stručným skriptům. Podrobnější výklad lze nalézt např. ve [8] na str. 242, 246 a 247 nebo ve [4]

Obsah

8.  Cvičení

  1. [28.] Sestavte funkci getin, která čte celé číslo ze vstupu. Vrací EOF, narazí-li na konec souboru, jinak vrací první neprázdný nečíselný znak. Číselná hodnota je vrácena ukazatelem (např. formální parametr int *p).

    Návod: Cyklem se přeskočí mezery, nové řádky a tabelátory. Znaky čteme do automatické (lokalizované) proměnné (např. int c). Je-li c == '+', resp. c == '-', přiřadí se např. sgn = 1, resp. sgn = -1. Je-li c Î < '0','9' > připočte se k *p řádná hodnota. Při prvé neprázdné a nenulové hodnotě se provede *p *= sgn a je-li c != EOF, vrátí se c na vstup funkcí ungetch. Návrat: return c.

  2. [29.] Sestavte funkci getfloat, která čte ze vstupu reálné číslo.
  3. [30.] Napište funkci strcat(char *s,char *t), pro kopírování řetězce t na konec řetězce s.

Obsah

Kapitola 5
Struktury, třídy, uniony, bitová pole

V této kapitole se budeme zabývat strukturami, což jsou množiny více prvků i různého typu (narozdíl od polí, kde pole bylo množina více prvků téhož typu), dále třídami, což je zobecněný pojem struktury v jazyku C++, které vedle toho, že mohou být množinami prvků různých typů, mohou k sobě vázat různé algoritmy, kterým se říká metody, jež pracují s prvky třídy, z nichž některé jsou přístupné jen těmto metodám. To je velice zjednodušeně popsaná podstata pojmu třída, jejich důležitá role je v tzv. objektově orientovaném programování, které sice přesahuje rozsah těchto stručných skript, ale kterému věnujeme poslední kapitolu, abychom se alespoň rámcově s tímto způsobem programování seznámili. Typ union byl zřejmě převzat do jazyka C z jazyka Algol 68, kde se poprvé objevil.

Proměnné typu union mohou nejen nabývat různých hodnot (což není překvapivé), ale mohou nabývat i hodnot různých typů (což již překvapivé je). Při nedostatku paměti může být někdy výhodné uložit několik objektů do jednoho paměťového místa, obvykle je to množina jednobitových příznaků v aplikacích jako jsou tabulky symbolů v kompilátorech. Vzhledem k tomu, že jazyk C byl vytvořen zkušenými systémovými programátory pro vlastní ulehčení práce při tvorbě tak složitého operačního systému jako je UNIX, je jasné, že takovouto konstrukci museli zavést. V kapitole se také zmíníme o práci s tzv. dynamickými objekty, které jsou definovány a je jim vytvářeno místo v paměti (v tzv. hromadě) v průběhu procesu plnění algoritmu. Budeme využívat ukazatelů, tříd a unionů. Vynechal jsem záměrně struktury, abych naznačil, že se stavbou struktury se seznámíme pouze na začátku a pak místo se strukturami, budeme pracovat s třídami. Je to sice poněkud atypické, ale třída může strukturu plně nahradit. Tím ušetříme místo na důležitější konstrukce.

Obsah

1.  Struktury a třídy

K odkazu 1
K odkazu 2 Jak jsme se již zmínili, je struktura množinou prvků i různého typu, které jsou z důvodu jednodušší manipulace sdružené pod jedním jménem. Strukturám se někdy říká strukturované proměnné, nebo, což je obvyklejší, záznamy, popř. věty. Prvkům struktury se často říká položky.

Příklad 5.1. Zapišme deklaraci typu datum, který bude zahrnovat položky den, mesic, rok, cislo_dne_roce typu int a jméno_měsíce typu char[10]. Daný typ použijme v programu tak, že definujeme proměnnou typu datum, které přiřadíme počáteční hodnoty a pak tyto hodnoty vytiskneme.



Řešení:

Typ datum jsme použili v hlavním programu k popisu proměnné d1, které jsme hned přiřadili počáteční hodnoty. Po vymazání obrazovky (příkazem clrscr() v systému, kde byl program laděn) tiskneme jednotlivé položky tak, že píšeme

jméno_struktury.položka


Tečka v konstrukci je v tomto případě tzv. operátor kvalifikace.

// Priklad na strukturu
#include<iostream.h>
#include<conio.h>
struct datum
{
 int den;
 int mesic;
 int rok;
 int cis_dne_v_roce;
 char jmeno_mesice[10];
};

main()
{
 datum d1={4,7,1621,185,"cervenec"};
 clrscr();
 cout << "Anno Domini " << d1.den << ". " << d1.mesic << ". "<< d1.rok
      <<" byl 4. den v " << d1.jmeno_mesice << " a " << d1.cis_dne_v_roce
      << ". den v roce";
}

Existují další způsoby jak zajistit deklaraci shora uvedené struktury např.

struct
{
 int den;
 int mesic;
 int rok;
 int cis_dne_v_roce;
 char jmeno_mesice[10];
}d1;

kdy můžeme definovat přímo proměnnou (nebo proměnné) požadované struktury. Zápis uvedený v programu se mi však zdá pro C++ typický.

Nyní si zapíšeme tentýž příklad s tím, že místo struktury použijeme třídu. Odlišuje se od struktury na první pohled tím že místo slovního symbolu struct je zapsán slovní symbol class a u položek uvedených ve třídě musíme zapsat slovní symbol public, kterým dáváme kompilátoru informaci, že položky jsou přístupné i příkazům mimo metody (procedury) svázané s danou třídou.

// Priklad na trídu
#include<iostream.h>
#include<conio.h>
class datum
{

 public:
 int den;
 int mesic;
 int rok;
 int cis_dne_v_roce;
 char jmeno_mesice[10];
};

main()
{
 datum d1={4,7,1621,185,"cervenec"};
 clrscr();
 cout << "Anno Domini " << d1.den << ". " << d1.mesic << ". "<< d1.rok
      <<" byl 4. den v " << d1.jmeno_mesice << " a "
      << d1.cis_dne_v_roce << ". den v roce";
}

Od tohoto okamžiku budeme používat pouze objekty typu class (viz. kap. 6) s tím, že čtenář si může vždy za třídu dosadit strukturu a dostane tentýž výsledek. Na okamžik, kdy objekty typu class začneme používat v širším smyslu než objekty typu struct, upozorníme.

Ve strukturách a třídách mohou být položky, které jsou opět strukturami nebo třídami. Následující příklad to demonstruje snad dostatečně jasně. Jen je třeba si všimnout násobného použití operátoru kvalifikace.

Příklad 5.2. Definujme typ osoba, která bude obsahovat položky jmeno, adresa typu char[], PSC, cislo_pojistky typu long int, mzda typu double a narozeni, zamestnan typu datum. V hlavním programu definujme proměnnou zaměstnanec typu osoba, přiřaďme jí hodnoty a ty ihned vytiskněme.



Řešení:

// Priklad na vnorene tridy (struktury)
#include<iostream.h>
#include<conio.h>
const int DELKA_JMENA=20, DELKA_ADRESY=20;
class datum  // deklarace typu datum
{
 public:
 int den, mesic, rok;
};
class osoba  // deklarace typu osoba (vyuziva vnorene tridy)
{
 public:
 char jmeno[DELKA_JMENA];
 char adresa[DELKA_ADRESY];
 long PSC;
 long cislo_pojistky;
 double mzda;
 datum narozeni, zamestnan; // Vnorene tridy !!
};
main()
{
 osoba zamestnanec={"Vorel Karel",
                     "Jecna 2, Praha 2",
                     12000, 924856787659, 75848, {26,11,1936},
                   {2,1,1962}};
 cout << "Jmeno zamestnance:"<< zamestnanec.jmeno << '\n'
      << "Zamestnan od:     " << zamestnanec.zamestnan.den << ". "
      << zamestnanec.zamestnan.mesic << ". " << zamestnanec.zamestnan.rok
      << '\n';
}

Příklad 5.3. Vypočtěme z údaje, který získáme z konkrétního data pořadové číslo dne v roce.



Řešení:
Úloha je příkladem použití proměnné typu class nebo struct jako parametru ve funkci. V C++ lze skutečné a formální parametry propojovat třemi způsoby, které jsme si již ukázali dříve. V této úloze bychom mohli použít volání hodnotou a výsledek (tj. ze zadaného data vypočítat pořadové číslo dne v roce) by byl správný. Vhodnější je však použít volání odkazem (pomocí operátoru * nebo &), protože se vyhneme kopírování objektu, který může být rozsáhlý a složitě členěný.

#include <iostream.h>
#include <stdio.h>
static int tab_dnu[2][13]=
{
 {0,31,28,31,30,31,30,31,31,30,31,30,31},
 {0,31,29,31,30,31,30,31,31,30,31,30,31}
};
class  datum{
        public:
        int den;
        int mesic;
        int rok;
       } ;
den_v_roce(datum *d)
{
 int i, den, prestupny;
 den=d->den;
 prestupny=d->rok%4 == 0 && d->rok%100 !=0 || d->rok%400 == 0;
 for(i=1;i<d->mesic;i++)
  den+=tab_dnu[prestupny][i];
 return(den);
}
main()
{
  datum k;
  cout << "Cti den mesic rok\n";
  scanf("%d %d %d",&k.den,&k.mesic,&k.rok);
  cout << '\n' << k.den <<' ' << k.mesic << ' ' << k.rok << '\n' ;
  cout << '\n';
  cout << "Poradove cislo dne v roce: " << den_v_roce(&k) << '\n';
}

Zde jsme v programu použili operátoru -> (spojením znaků minus a větší než); např. zápis d->den je ekvivalentní zápisu (*d).den (jsou-li čtenáři nejasné závorky, nechť se laskavě podívá na priority operací dříve uvedené), ale je stručnější.

Jsou-li dva operátory -> zapsány "vedle sebe", vyhodnocují se zleva doprava. Tedy zápis p->q->r je totožný se zápisem (p->q)->r právě tak, jako zápis

zamestnanec.zamestnan.mesic


je totožný se zápisem

(zamestnanec.zamestnan).mesic.

Máme-li definovánu třídu T{int x, *int y} a definujeme-li ukazatel na objekt typu T definicí T *p, (ukazatel musíme v průběhu procesu samozřejmě inicializovat) pak zápis ++p->x; zvětší o jedničku x a ne p, protože implicitní závorky jsou v tomto případě ++(p->x);. Chceme-li zvětšit hodnotu p ještě před přístupem k položce x, musíme psát (++p)->x;. Chceme-li však zvětšit hodnotu p po přístupu k x, píšeme (p++)->x;. Zde jsou však závorky zbytečné. Hloubavý čtenář jistě zjistí proč. Přestože autor těchto skript není přívržencem zbytečných závorek, v podobných konstrukcích jazyka C a C++ je jejich horlivým zastáncem.

Z dřívějších kapitol víme, že v jazyku C++ je možné volání formálních parametrů adresou. Pak shora uvedený program má tvar

#include <iostream.h>
static int tab_dnu[2][13]=
{
 {0,31,28,31,30,31,30,31,31,30,31,30,31},
 {0,31,29,31,30,31,30,31,31,30,31,30,31}
};
class  datum{
        public:
        int den;
        int mesic;
        int rok;
       } ;
den_v_roce(datum &d)
{
 int i, den, prestupny;
 den=d.den;
 prestupny=d.rok%4 == 0 && d.rok%100 !=0 || d.rok%400 == 0;
 for(i=1;i<d.mesic;i++)
  den+=tab_dnu[prestupny][i];
 return(den);
}
main()
{
  datum k;
  cout << "Cti den mesic rok\n";
  cin >> k.den >> k.mesic >> k.rok;
  cout << '\n' << k.den <<' ' << k.mesic << ' ' << k.rok << '\n' ;
  cout << '\n';
  cout << "Poradove cislo dne v roce: " << den_v_roce(k) << '\n';
}
K odkazu 1
K odkazu 2
Obsah

1.1  Pole struktur

Struktury jsou zvláště vhodné, pracujeme-li s poli proměnných, které jsou svázány určitým vztahem. Uva'ujme např. program, který počítá výskyt slovních symbolů jazyka C++ v nějakém programu. Potřebujeme pole znakových řetězců jako vzory slovních symbolů a pole celých čísel jako počítadla výskytu. Ke konkrétnímu slovnímu symbolu musí patřit konkrétní počítadlo. Taková konstrukce by šla realizovat dvěma poli, ale máme-li v jazyku k dispozici objekty jako jsou struktury (a třídy), mů'eme obě pole realizovat např. typem K odkazu
class sl_symb{
       public:
       char *slovo;
       int poc;
      };

a vlastní proměnnou mů'eme definovat (přesně viz program) jako

static sl_symb tab[]={
           {"auto",0},{"break",0},{"case",0},{"cdecl",0},{"char",0},
           {"continue",0},{"default",0},
           // ...
           {"unsigned",0},{"void",0},{"volatile",0},{"while",0}
          };

Příklad 5.4. Sestavme program, který vypočítá četnost výskytu jednotlivých slovních symbolů v libovolném zdrojovém programu psaném v jazyku C++.



Řešení:

Program by zasluhoval podrobnější vysvětlení, které by v těchto stručných skriptech zabralo hodně místa. Předpokládám, 'e čtenář, který aktivně přečetl skriptum a' do těchto míst, algoritmu porozumí. Upozorním jen na několik skutečností:

  1. Později uvidíme, 'e procedury zde pou'ité mů'eme zahrnout do třídy, která je daleko robustnější konstrukcí ne' struktura.
  2. funkce getword vrátí ze vstupu, přičem' rozumíme řetězec písmen a číslic nebo samotný znak. Hodnota, kterou funkce vrací, charakterizuje typ přečteného objektu: mů'e to být LETTER, je-li objektem slovo (slovní symbol, jak napovídá jeho název, je pova'ován v kompilátorech za jeden nedílný znak), EOF pro konec souboru, nebo samotný znak, není-li písmenem. Konstanty LETTER a DIGIT mohou být libovolné hodnoty, zvolili jsme a a 0
  3. Funkce typ určuje typ jednotlivých vstupních znaků. Pou'itá verze je vhodná jen pro abecedu v kódu ASCII
  4. Funkce binary zajišťuje efektivní vyhledávání. Prvky souboru, v kterém se prohledávání realizuje, musí být setříděny (v tomto případě podle abecedy). Algoritmus je obdobný numerické metodě, které se říká půlení intervalu. Funkce strcmp je funkce pro zjišťování, jsou-li dva řetězce identické. V systému, v kterém jsme program ladili, byla tato funkce v souboru string.h.
  5. Konstantu NSLS, která udává počet slovních symbolů jsme mohli vypočítat ručně, ale vhodnější je svěřit výpočet počítači. Počet slovních symbolů se dá vypočíst ze vzorce

    velikost tab / velikost sl_symb

    S výhodou se vyu'ije operátor sizeof, který při kompilaci určí velikost jakéhokoliv objektu.

// Jmeno programu: poc_sl_s.cpp
const int LETTER = 'a';
const int DIGIT = '0';
#include <stdio.h>
#include <string.h>
#include <fstream.h>
#include <conio.h>
const int MAXWORD = 10;
class sl_symb{
       public:
       char *slovo;
       int poc;
      };
static sl_symb tab[]={
           {"auto",0},{"break",0},{"case",0},{"cdecl",0},{"char",0},
           {"continue",0},{"default",0},
           {"do",0},{"double",0},{"else",0},{"enum",0},{"entry",0},
           {"extern",0},{"far",0},
           {"float",0},{"for",0},{"fortran",0},{"goto",0},
           {"huge",0},{"if",0},{"int",0},{"long",0},{"near",0},
           {"pascal",0},{"register",0},{"return",0},{"short",0},
           {"signed",0},{"sizeof",0},
           {"static",0},{"struct",},{"switch",0},{"typedef",0},
           {"union",0},
           {"unsigned",0},{"void",0},{"volatile",0},{"while",0}
          };
const int NSLS = (sizeof(tab) / sizeof(sl_symb));
char getword(char*,int);int binary(char *, sl_symb [],int);
int type(int);
FILE *fd;
void main(int fg,char *nz[])
{
  int a,b,pv=0;
  char slovo[MAXWORD],ch[];
  if(fg!=2)
  {
   cout << "\n Zadej: poc_sl_s jmeno_zkoumaneho_souboru\n";
   return;
  }
  fd=fopen(*(++nz),"r");
  clrscr();
  if (fd==NULL)
  cout<<"\n soubor "<<*nz<<" nelze otevrit";
  else
  {
  cout<<"\n Zacinam pracovat"<<'\n';
  cout<<'\n'<<'\t'<<"SL.SYMBOL"<< "    "<<"CETNOST VYSKYTU"<<'\n';
  while ((a=getword(slovo,MAXWORD))!=EOF)
       if(a==LETTER){
        if((b=binary(slovo,tab,NSLS))>=0)
            tab[b].poc++;
       }
          for(b=0;b<NSLS;b++){
           if(tab[b].poc>0){
             if(++pv==20){
                  cout<<'\n'<<"\n ----- pokracovani -----";
                  gets( ch );
                  pv=0;
              }
              cout<<'\n'<<'\t'<<tab[b].slovo<<"      "<<'\t'
                  <<tab[b].poc;
            }
          }
            cout<<"\n Konec cinnosti ";
   }
}
binary (char *slovo, sl_symb tab[],int n)  //hledani v tabulce
  {
     int dolni,stred,horni,shoda;
     dolni=0;horni=n-1;
     while (dolni<=horni){
           stred=(dolni+horni)/2;
           if((shoda=strcmp(slovo,tab[stred].slovo))<0)
              horni=stred-1;
           else if (shoda>0)
              dolni=stred+1;
           else return(stred);
      }
      return(-1);
   }
char getword (char *w,int lim)  /*cteni dalsiho slova*/
  {
     int c,t;
     if (type(c=*w++=getc(fd))!=LETTER){
          *w='\0';
           return (c);
     }
     while(--lim>0){
           t=type(c=*w++=getc(fd));
           if(t!=LETTER && t!=DIGIT){
                ungetch(c);
                break ;
           }
      }
      *(w-1)='\0';
      return (LETTER);
  }
  type (int c)    // vraceni typu znaku v ASCII
  {
  int q;
   if(c>='a' && c<='z'|| c>='A'&& c<='Z'){
   return (LETTER);
   }
   else if(c>='0' && c<='9')
   return (DIGIT);
   else
   return (c);
  }
K odkazu 1
K odkazu 2
Obsah

2.  Typ union

K odkazu Proměnná typu union mů'e v různých okam'icích nabývat různých typů hodnot.




Příklad:

    union{                                union r_tri{
          int ihod;                              int ihod;
          float fhod;                            float fhod;
          char *phod;                            char *phod;
         }r_hod                                 };
                                           r_tri r_hod;

V příkladu jsme pro úplnost uvedli opět dva způsoby definic. V levé části jsme definovali proměnou typu union, která mů'e nabývat hodnot typu int, float a char *, kde'to v pravé části jsme nejprve deklarovali nový typ r_tri a pak jsme definovali proměnnou r_hod typu r_tri, která mů'e nabývat hodnot typu int, float a char*. Nic nám nebrání v jazyku C++, abychom napsali

    union r_tri{
          int ihod;
          float fhod;
          char *phod;
         }r_hod;

pakli'e se nám to z nějakého důvodu hodí. Toté' platí pro typy struct a class, u kterých jsme to pro stručnost nezdůraznili.

Přístup ke členům (polo'kám) je obdobný jako u typů struct a class, tedy



jméno_proměnné_typu_union . polo'ka



Příklad 5.5. Definujme typ proměnné, která mů'e nabývat hodnoty typu int nebo float nebo typu ukazatel na char. Určeme, jaký typ hodnoty chceme přečíst, přečtěme ji a vytiskněme ji.



Řešení:

Po zavedení souborů pro proudový vstup (iostream.h), standardní vstup (stdio.h) a ovládání obrazovky ( conio.h), které vyu'ívá systém, pod kterým byl program laděn, deklarujeme typ r_tri pro tři různé typy, proměnnou r_hod typu r_tri a definujeme tři konstanty pro rozpoznání čteného typu. V hlavním programu definujeme ukazatel typu char, kterému, co' je zcela nové, pomocí mechanismu (jen' je typický pro C++) new, umo'níme, aby ukazoval na místo pro jeden znak v tzv. hromadě pro dynamické objekty. Zatím nám stačí vědět, 'e objekty v hromadě jsou přístupné jen prostřednictvím nějakého ukazatele. Podrobněji se těmito objekty budeme zabývat v čl. . Pak ma'eme obrazovku funkcí ze souboru conio.h, čteme typ proměnné a podle přečteného typu čteme hodnotu proměnné. Čtení hodnoty typu int a float je snad zcela jasné a při troše přemýšlení čtenář rozkóduje i čtení řetězce. Výstup hodnoty ji' neřeší nic nového. Programátorsky čistší by snad bylo pro tisk řetězce pou'ít funkce putchar, ale proudová funkce cout vykoná v tomto případě stejnou slu'bu. Toté' nelze říci o nahrazení funkce getchar funkcí scanf nebo cin. Čtenář se mů'e přesvědčit sám.

#include<iostream.h> // Proudovy vstup
#include<conio.h>    // Ovladani obrazovky
#include<stdio.h>    // Standardni vstup
    union r_tri{     // Deklarace typu promenne tri hodnot
          int ihod;
          float fhod;
          char *phod;
         };
    r_tri r_hod; // Definice promenne pro tri typy hodnot
    int const INT=1;
    int const FLOAT=2;
    int const STRING=3;
main()
{
 int r_typ;
 char *zr=new  char[1];
 clrscr();
 cout << "Cti typ promenne (int=1 nebo float=2 nebo string=3)\n";
 cin>>r_typ;

 cout << "Cti hodnotu\n";  // Cteni jedne z hodnot
 switch(r_typ){   // Cteni jedne z hodnot
       case INT: cin >> r_hod.ihod;break;
       case FLOAT: cin >> r_hod.fhod;break;
       case STRING:r_hod.phod=zr;
                   while((*r_hod.phod=getchar())!='\n')
                       r_hod.phod++;
                   *r_hod.phod='\0';
                   r_hod.phod=zr;break;
       default: cout << "Chybne zadani";return(0);
      }
 switch(r_typ){    // Tisk jedne z hodnot
       case INT: cout << "celociselna hodnota: " << r_hod.ihod;break;
       case FLOAT: cout <<"realna hodnota: " << r_hod.fhod;break;
       case STRING: cout << "retezec: ";
                    for(;*r_hod.phod!='\0';++r_hod.phod)
                     cout << *r_hod.phod;
      }
}
K odkazu
Obsah

2.1  Anonymní typ union

Položku typu union můžeme použít ve struktuře běžným způsobem např. takto:
//...
  struct ruzne{          // Deklarace typu ruzne
         char *jmeno;
         int *praporky;
         int c_typ;
         union{
            int c_hod;
            float r_hod;
            char *u_hod;
           }r_hodnota;
        };
   ruzne syn_tab[NSYN];  // Definice promenne typu ruzne
//...
// Polozka c_hod promenne tab_syn[i] je dostupna zapisem
// syn_tab[i].r_hodnota.c_hod
// ...

Využijeme-li však tzv. anonymního typu union, lze shora uvedené zápisy psát takto:

//...
  struct ruzne{          // Deklarace typu ruzne
         char *jmeno;
         int *praporky;
         int c_typ;
         union{
            int c_hod;
            float r_hod;
            char *u_hod;
           };     // !!!!!!!!!!!!!
        };
   ruzne syn_tab[NSYN];  // Definice promenne typu ruzne
//...
/* Polozka c_hod promenne tab_syn[i] je pak dostupna jednodussím
  zapisem: */
// syn_tab[i].c_hod

Obsah

3.  Příklady dynamických objektů

V jazyku C a C++ lze pracovat s tzv. dynamickými objekty. Jsou to objekty, je' se vytvářejí v průběhu procesu. Jsou umísťovány v paměti, která se nazývá hromada a ka'dý objekt je definován tak, 'e na něj ukazuje nějaký ukazatel. Pomocí dynamických objektů, lze realizovat slo'ité algoritmy prací se symboly, symbolické derivování, integrování apod. My zde uká'eme jen několik jednoduchých programů pro pochopení principu. K odkazu

Příklad 5.6. Napišme program, který uvolní v hromadě místo pro pole padesáti celočíselných prvků, pole naplníme a vytiskneme několik vybraných prvků.



Řešení:

#include<stdio.h>
void main()
{
 int *data;// int index;
 data = new int[50];  // Vyhrazeno 50 celociselnych mist v hromade
 /* Tim jsme definovali pole a s ukazatelem muzeme pracovat jako
    s polem (coz bychom mohli - ctenar se muze presvedcit - i
    kdybychom definovali misto jen pro jeden prvek. To ale neni
    vhodne a ve slozitejsich pripadech i chybne). Kazdemu prvku
    o indexu i priradime hodnotu 10*i */
 for (int index=0;index<50;index++)
  data[index]=10*index;
// Tisk vybranych dat
  printf("\n++*data = %d",++*data);
  printf("\n*++data = %d",*++data);
  printf("\n*data++ = %d",*data++);
  printf("\n(*data)++ = %d",(*data)++);
  printf("\n*data = %d",*data);
  delete(data); // Zruseni objektu
}

Jako dobré cvičení doporučuji čtenáři, aby zpaměti určil, co se bude tisknout postupně v posledních čtyřech příkazech tisku a přesvědčil se o správnosti svého úsudku na počítači. Té' se mů'e mů'e pokusit vytisknout toté', pou'ije-li indexované proměnné.

Příklad 5.7. Napišme program, který utvoří v hromadě posloupnost objektů, které se říká seznam, ka'dý objekt bude obsahovat celočíselnou hodnotu a ukazatel na další objekt. Poslední objekt seznamu bude ukazovat na nulovou adresu.



Řešení:

Vytvoříme typ objekt, definujeme dva ukazatele na objekt typu objekt. První bude ukazovat na začátek seznamu a druhý v'dy na nový objekt, který se do seznamu zařazuje. Vytvoříme seznam deseti objektů, které budou nabývat celočíselných hodnot jedna a' deset. Seznam v tomto případě vytváříme od konce. Postup by měl být jasný z programu.

#include <stdio.h>
main()
/* Vytvoreni dynamicke struktury seznam, jeji naplneni,vytisteni
   a zruseni */
{
 struct objekt
 {
  objekt *dalsi;
  int hodnota;
 };
  objekt *seznam, *novy_objekt;
  seznam=NULL; // nebo seznam=0;
  novy_objekt= new objekt;
 // Vytvareni seznamu
  for(int i=10;i>=1;i--)
  {
   novy_objekt->hodnota=i;novy_objekt->dalsi=seznam;
   seznam=novy_objekt;
   novy_objekt=new objekt;
  }
  // Tisk seznamu
  novy_objekt=seznam;
  for(i=1;i<=10;i++)
  {
   printf("Hodnota %d. objektu = %d adresa objektu %p\n",
           i,novy_objekt->hodnota,novy_objekt);
   novy_objekt=novy_objekt->dalsi;
  }
  // Uvolneni seznamu z dynamicke pameti
  objekt *pred=seznam;delete (seznam,novy_objekt);
  do
  {objekt *predpred=pred;
   pred=pred->dalsi;
   delete predpred;
  }while(pred!=NULL); // nebo while(pred=!=0);
}
K popisu typu jsme pou'ili standardní typ struct. Je samozřejmé, 'e bychom mohli pou'ít i typu class. V tomto odstavci budeme však pou'ívat typ struct, proto'e příklady zde uvedené naprogramujeme v další kapitole s vyu'itím typu class tak, 'e tento typ pou'ijeme v plné šíři pro tzv. objektově orientované programování.

Příklad 5.8. Napišme program, který bude vytvářet tentý' seznam, ale sudé prvky se budou plnit hodnotami typu float a liché hodnotami typu int.



Řešení:

V typu objekt přidáme anonymní union pro hodnotu, která mů'e být typu float nebo int.

#include <stdio.h>
void main()
/* Vytvoreni dynamicke struktury seznam, jeji naplneni a vytisteni.
   Naplni stridave celociselnymi hodnotami a hodnotami typu float.
   Cviceni na  ukazatele, struktury a uniony*/

{
 struct objekt{
          objekt *dalsi;
          union{
          int c_hodnota;
          float f_hodnota;
          };
        };
  objekt *seznam, *novy_objekt;
  seznam=NULL;
  // Vytvoreni seznamu
  for(int i=10;i>=1;i--){
   novy_objekt= new objekt;
   if (i/2*2==i)
    novy_objekt->f_hodnota=i;
   else
    novy_objekt->c_hodnota=i;
   novy_objekt->dalsi=seznam;seznam=novy_objekt;
  }
  // Tisk seznamu
  novy_objekt=seznam;
  for(i=1;i<=10;i++){
   i/2*2==i?printf("Hodnota %d. objektu =%f\n",i,novy_objekt->f_hodnota):
         printf("Hodnota %d. objektu =%d\n",i,novy_objekt->c_hodnota);
   novy_objekt=novy_objekt->dalsi;
  }
  // Uvolneni seznamu z dynamicke pameti
  objekt *pred=seznam;delete (seznam,novy_objekt);
   do
    {objekt *predpred=pred;
     pred=pred->dalsi;
     delete predpred;
    }while(pred!=NULL);
}

Na tomto místě je vhodné se zmínit o dynamicky definovaných vícerozměrných polí. (Matice a kubické matice.) Podrobnější příklady jsou uvedeny ve [4]. Pro čtenáře by bylo dobrým cvičením, kdyby tam uvedené příklady přepsal do jazyka C++, což je celkem mechanická práce. Jen je třeba si uvědomit rozdíly mezi funkcí malloc a operátorem new. Píše-li programátor v jazyku C např.

/*...*/
double **a=(double**)malloc(m*sizeof (double*));
/*...*/
for(i=0;i<m;i++)a[i]=(double*)malloc(n*sizeof(double));
/*...*/
pak v C++ píše
//...
double **a=new double*[m];
//...
for(i=0;i<m;i++)a[i]=new double[n];
//...

K odkazu
Obsah

4.  Bitová pole

Bitové pole si mů'eme představit jako strukturu, její' velikost je ale pevně omezena velikostí typu int. Nejmenší délka jedné polo'ky v bitovém poli je jeden bit. Bitové pole se definuje jako struktura, ale ka'dá polo'ka je určena svým jménem a délkou v bitech. Proto'e máme mo'nost definice jak unsigned int, tak signed int, je vhodné v'dy uvést, je-li polo'ka se znaménkem či bez znaménka.

Bitové pole má dvě základní oblasti pou'ití

  1. Ulo'ení několika celých čísel v jednom slově, co' se pou'ívá pro šetření pamětí. Toto pou'ití není příliš časté.
  2. Pro přístup k jednotlivým bitům slova pomocí identifikátorů, co' je pou'ití mnohem častější, proto'e se pak operace s jednotlivými bity provádějí elegantně a přehledně.

Příklad 5.9. Napišme datum tak, aby se vešlo do jednoho slova, do kterého lze zapsat celočíselná hodnota v systému, ve kterém předpokládáme, 'e sizeof(int) dává hodnotu 2.



Řešení:

#include<stdio.h>
main()
{
 struct datum{
        unsigned den:   5; // obsazuje bity 0 -  4
        unsigned mesic: 4; // obsazuje bity 5 -  8
        unsigned rok:   7; // obsazuje bity 9 - 15
       };
 datum dnes={26,11,2010-1950},zitra,pozitri;
 zitra.den= dnes.den+1; pozitri.den=dnes.den+2;
 printf("\nDnesni datum: %d. %d. %d, zitra je  %d. "
           "a pozitri je %d.\n",dnes.den,dnes.mesic,dnes.rok+1950,
            zitra.den,pozitri.den);
}

Polo'ka rok zabírá 7 bitů, tzn. 'e největší hodnotou, kterou by bylo mo'né zobrazit je 27 - 1 = 127. Abychom mohli zobrazit letopočty naší doby, odečetli jsme při inicializaci hodnotu 1950. Tu pak musíme při pou'ití data v'dy přičíst.

Příklad 5.10. Napišme fragment kompilátoru, který manipuluje s tabulkou symbolů.



Řešení:

S ka'dým identifikátorem v programu se spojuje určitá informace (jedná-li se o objekt externí, statický nebo slovní symbol apod.) Nejlépe se takováto informace zakóduje mno'inou jednobitových příznaků v objektu typu int nebo char. Bez pou'ití bitových polí, mů'eme postupovat takto:

// ...
 const SLOVSYMB = 01, EXTERNAL = 02, STATIC = 04; unsigned flags=0;
 /* Cisla jsou mocninami dvou: 01 = 001;
                               02 = 010;
                               04 = 100.
    Pak se pristup k bitum stava zalezitosti operaci posuvu, maskovani
    a jednotkoveho doplnku, napriklad*/
    flags |= EXTERNAL | STATIC;
 // nastavi bity EXTERNAL a STATIC ve flags, zatim co
    printf("\nflags = %d",flags);
    flags &= ~(EXTERNAL | STATIC);
 // je vynuluje a podminka
    if((flags & (EXTERNAL | STATIC))==0)...
 // je splnena, jsou-li oba bity vynulovane.
 // ...

Pou'ijeme-li bitové pole, lze toté' naprogramovat elegantněji:

// ...
struct{
    unsigned je_slovni_symbol: 1;
    unsigned je_extern: 1;
    unsigned je_static: 1;
   }Flags;
   Flags.je_extern=Flags.je_static=1; // Nastavi bity
   Flags.je_extern=Flags.je_static=0; // Nuluje bity
   if(Flags.je_extern==0 && Flags.je_static==0)...
// Testuje, jsou-li bity vynulovany
//...

Výhodou bitových polí je příjemnější práce. Nevýhodou je, 'e někde se bitová pole ukládají zleva doprava, jinde zprava doleva. To někdy způsobuje jisté nepříjemnosti (obtí'nější přenositelnost programů). Dále je třeba si pamatovat, 'e bitová pole se mohou ukládat pouze do proměnných typu int nebo unsigned; nejsou to pole a nemají adresy. Není tedy mo'né pou'ívat operátor adresy &.

Obsah

5.  Cvičení

  1. [31.] Definujte strukturu, která bude mít položky prijmeni,jmeno a bydliste. Sestavte funkci napln(), která bude schopna tuto strukturu naplnit daty.
  2. [32.] Strukturu z příkladu 31 alokujte dynamicky a naplňte ji daty opět pomocí funkce napln().
  3. [33.] Sestavte program, který bude vytvářet seznam obdobný seznamu z příkladu 5.7 (nebo 5.8) s tím rozdílem, že naplnění daty budete provádět čtením dat z klávesnice eventuálně z nějakého souboru. Seznam uspořádejte vzestupně.
  4. [34.] Napište program, kterým do vytvořeného seznamu ze cvičení 33 vložíte na správné místo nový objekt.
  5. [35.] Sestavte program, který bude využívat bitové pole pro úschovu data. Program vyzkoušejte tak, že načtete z klávesnice datum do bitového pole. To pak vytiskněte jako unsigned a pak datum vytiskněte jako skutečné datum.

Obsah

Kapitola 6
Základy objektově orientovaného programování

K odkazu V této kapitole se seznámíme se základy objektově orientovaného programování (dále jen OOP). Podrobný výklad se vymyká záměru těchto skript a to nejenom proto, že by přesáhla povolený počet autorských archů. Skripta pojednávají o jazyku C++, který, mimo jiné, umožňuje realizovat objektově orientované projekty. OOP je však metodika, která není svázána s konkrétním programovacím jazykem a která umožňuje strukturovat a konstruovat systémy, přičemž výhody OOP jsou tím zřetelnější, čím větší je projektovaný systém. V těchto skriptech využijeme OOP k tomu, abychom se podrobněji seznámili se strukturou class, kterou již poněkud povrchně známe z 5. kapitoly.

Obsah

1.  Úvod

OOP je technika, která pracuje s tzv. objekty. Objekt může mít následující vlastnosti:

Použití této metodiky je výhodné zejména při tvorbě informačních systémů, kde vlastnosti objektů OOP se blíží vlastnostem reálných objektů (i když pochopitelně svět je ve své mnohotvárnosti přeci jen složitější, než si myslí někteří fundamentalističtí zastánci OOP), počítačové grafice a někdy i ve vědeckých a technických výpočtech, speciálně při složitých projektech.

Obsah

2.  Příklady objektově orientovaného programování v C++

K odkazu
V tomto článku se na příkladech seznámíme s OOP. Příklady budeme stručně komentovat v odladěných programech. Předpokládáme, že čtenář, který došel až k této kapitole, si programy sám implementuje na počítači, eventuálně vylepší a zjistí co se vlastně vytiskne. Výstupy budeme proto komentovat jen výjimečně.

Příklad 6.1. Definujme třídu, která zajistí přičítání a odečítání jedničky k a od proměnné hodnota, která je dostupná pouze metodám (v  C++ funkcím zapouzdřeným ve třídě.



Řešení:

// Program definujici tridu, ktera je nazvana pocitadlo
// Nazev souboru pocitad1.h
class pocitadlo
{
 private:
  unsigned int hodnota;
 public:
 /* prototyp konstruktoru a definice vnitřní funkce. Slovní symbol
    se nemusí psát. Konstruktor při definici typu pocitadlo zajistí
    vytvoření objektu a inicializaci potřebných dat objektu. Jmenuje
    se vzdy jako typ class*/
  pocitadlo(){hodnota=0;}
  // Nasleduji dalsi definice metod jako vnitřní funkce
  void zvetseni(){if(hodnota<65535) hodnota++;}
  void zmenseni(){if(hodnota>0) hodnota--;}
  unsigned int zpristupnena_hodnota(){return(hodnota);}
}; // Vsimnete se, ze definice tridy musi koncit strednikem!!!

// Nasleduje program využívající trídy počitadlo
// Nazev souboru pocitad1.cpp
#include <stdio.h>
#include "pocitad1.h"
void main()
{
 /* Definice objektu c1, c2 a automaticke pocatecni prirazeni
    hodnoty pomocí konstruktoru */
 pocitadlo c1,c2;
 for(int i = 1;i <= 15; i++)
 {
  c1.zvetseni();
  printf("\nc1 = %u",c1.zpristupnena_hodnota());
  c2.zvetseni();
 }
 printf("\nPo ukonceni cyklu je c2 = %u",c2.zpristupnena_hodnota());
 for (i=1;i<=5;i++)
  c2.zmenseni();
 printf("\nPo ukonceni druheho cyklu je c2 = %u",
           c2.zpristupnena_hodnota());
 printf("\nKonecna hodnota c1 =%u",c1.zpristupnena_hodnota());
}

V další alternativě tohoto příkladu budeme definovat funkce třídy mimo tuto třídu, což se obvykle dělá, nelze-li funkce definovat jako vnitřní.

// Program definujici tridu, ktera je nazvana pocitadlo
// Nazev souboru pocitad2.h
class pocitadlo
{
 private:
  unsigned int hodnota;
 public:
  pocitadlo(){hodnota=0;}
  // Nasleduji jen prototypy
  void zvetseni();
  void zmenseni();
  unsigned int zpristupnena_hodnota();
};
/* Nasleduji definice metod. Vsimnete si, ze kazda metoda je
   oznacena jmenem tridy pred niz je uveden typ funkce. Je sa-
   mozrejme, ze mezi definicemi muze byt uveden i konstruktor.
   Tyto definice nemusi byt ani uvedeny v temze souboru jako trida,
   ke ktere patri, ale mohou byt umisteny v souboru jinem. Souborum,
   ktere maji v C++ priponu h se rika hlavickove soubory.*/
void pocitadlo::zvetseni(){if(hodnota<65535) hodnota++;}
void pocitadlo::zmenseni(){if(hodnota>0) hodnota--;}
unsigned int pocitadlo:: zpristupnena_hodnota(){return(hodnota);}
// Tento program vyuziva tridu pocitadlo
// Nazev souboru pocitad2.cpp
#include <stdio.h>
#include "pocitad2.h"
void main()
{
 pocitadlo c1; // Automaticke pocatecni prirazeni hodnoty
 pocitadlo c2; //     "           "         "        "
 for(int i = 1;i <= 15; i++)
 {
  c1.zvetseni();
  printf("\nc1 = %u",c1.zpristupnena_hodnota());
  c2.zvetseni();
 }
 printf("\nPo ukonceni cyklu je c2 = %u",c2.zpristupnena_hodnota());
 for (i=1;i<=5;i++)
  c2.zmenseni();
 printf("\nPo ukonceni druheho cyklu je c2 = %u",
           c2.zpristupnena_hodnota());
 printf("\nKonecna hodnota c1 =%u",c1.zpristupnena_hodnota());
}
Příklad 6.2. Napišme program, který utvoří v hromadě posloupnost objektů, které se říká seznam, každý objekt bude obsahovat celočíselnou hodnotu a ukazatel na další objekt. Poslednímu objektu seznamu bude přiřazena hodnota NULL.



Řešení:

Tento příklad byl řešen v příkladu 5.7. Nyní jen funkce zapouzdříme do třídy objekt a vytvoříme z nich metody. Mezi metodami je nová metoda destruktor. Tuto metodu si můžeme představit jako inverzní funkci ke konstruktoru. Konstruktor vytváří příslušný objekt a přiřazuje datům počáteční hodnoty, je-li to nutné. Destruktor uvolňuje objekt z paměti a ruší ho. V implemntacní části jsme definovali statické proměnné zac_sez pro začátek seznamu a pom_prv pro pomocný prvek, aby byly dostupné právě pro metody dané třídy. V příkladu 6.5 si ukážeme si ukážeme jinou možnost s využitím spřátelených funkcí.

// Hlavickovy soubor seznam.h
#include <stdio.h>
#include<conio.h>
class objekt
{
  private:
  objekt *dalsi;
  int hodnota;
  public:

  // Konstruktor

  objekt(){}

// Prototypy

  void vytvor_seznam(int pocet=1);
  void tiskni_seznam();
  void uvolni_pamet();

  // Destruktor

 ~objekt(){delete(zac_sez,pom_prv);}
};
static objekt *zac_sez, *pom_prv; // Staticke promenne
/* Definice zapouzdrenych funkci. Tyto funkce nelze definovat jako
   vnitrni. (Ctenar si muze zopakovat, proc tomu tak je.) */

void objekt::vytvor_seznam(int pocet)
{
 zac_sez=NULL;pom_prv=new objekt;
 for (int i=pocet;i>=1;i--)
 {
  pom_prv->hodnota=i;
  pom_prv->dalsi=zac_sez;
  zac_sez=pom_prv;
  pom_prv=new objekt;
 }
}
void objekt::tiskni_seznam()
{
 pom_prv=zac_sez;
 int i = 0;
 do
 {
  i++;
  printf("adresa %d. objektu = %p a jeho hodnota = %d\n",i,
  pom_prv,pom_prv->hodnota);
  pom_prv=pom_prv->dalsi;
 } while(pom_prv!=NULL);
}

#include "seznam4.h"
main()
{
  clrscr(); // Mazani obrazovky v systemu, kde byl program laden
  objekt l; // Definice objektu typu objekt
  l.vytvor_seznam(10); // Vytvoreni deseti objektu
  l.tiskni_seznam();   // a jejich tisk
}

Data a metody, jak již víme mohou být private, pak jsou přístupné pouze metodám dané třídy, public, pak jsou přístupná pro všechny podle pravidel platných pro data a funkce v jazyku C++. Dále mohou být definovány metody a data jako protected. Taková data jsou přístupná pro odvozené třídy z třídy dané (potomky třídy).

Příklad 6.3. Sestavme systém OOP, ve kterém můžeme definovat geometrické obrazce, tyto obrazce vytvářet, rušit, posouvat, zvětšovat a zmenšovat. V systému definujme třídu ümísti", jejího potomka "bod" a potomka bodu "kružnici". Systém je otevřený, takže je možné definovat libovolný počet tříd pro zobrazení dalších obrazců.



Řešení:

Tento příklad ukazuje výhody OOP právě při vytváření systémů počítačové grafiky. Lze z něj pochopit princip dědičnosti a polymorfismu. Nevýhodou je, že je nutno pracovat s konkrétní grafickou knihovnou (byla použita grafická knihovna firmy Borland). Vysvětlující poznámky jsou přímo v jednotlivých souborech, protože je to stručnější a (podle autorova názoru i) přehlednější.

/**********************************************************************
* obrazce.h... hlavickovy soubor pro jednoduchy graficky projekt, kde *
*              se definuji jednotlive obrazce na zaklade drive defino-*
*              vanych objektu. Vyuziva se polymorfismu ( prototypy    *
*              funkci zacinajicich slovnim symbolem virtual). Pouziva-*
*              ji se i data typu protected, pristupna funkcim zapouz- *
*              drenych v odvozenych tridach.                          *
**********************************************************************/
// Definice booleovskych (logickych) promennych
enum boolean {false,true};
// Trida "umisti" popisuje umisteni x a y souradnic na obrazovce
class umisti {
protected:  //Dovoluje odvozenym tridam vyuzivat "protected" data
   int x,y;

public: // Tyto funkce jsou pristupne z vnejsiho prostredi
   umisti(int px,int py) {x=px;y=py;} // Konstruktor
   int getx(){return(x);}
   int gety() {return(y);}
  };
// Trida "bod" zajisti viditelnost nebo neviditelnost bodu
// Trida je potomkem "umisti"; "public" zajisti, ze objekty odvozene
// tridy mohou uzivat metody predka, nebyly-li tyto metody v odvo-
// zene tride predefinovany.
class bod:public umisti{
protected:
   boolean viditelny; // Tridy odvozene z "bod" budou potrebovat pristup

public:
// Prototypy
   bod(int px,int py); //  Konstruktor
   virtual void zhasni(); // Zhasinat lze ruzne obrazce, proto "virtual"
   virtual void rozsvit(); // Totez plati pro rozsviceni
   boolean je_viditelny();
   void posun(int nx,int ny);
   void presun(int presun_o);
  };
// Trida kruznice je potomkem tridy "bod" a zaroven "umisti"
class kruznice: public bod {
protected:
// Novy udaj, ostatni se dedi
   int polomer;
public:
/**********************************************************************
* Prototypy vcetne virtualnich. Nyni se jedna o rozsviceni a zhasnuti *
* kruznice. Tez o jeji zvetseni a zmenseni. Pod tymiz jmeny muzeme    *
* zhasinat, rozsvecet, zvetsovat a zmensovat elipsu, ctverec, proste  *
* libovolny obrazec. Vzdy se vi, co se ma zhasnout, rozsvitit atd.,   *
* i kdyz se zapozdrene funkce jmenuji vzdy stejne.  To je typicky     *
* polymorfismus.                                                      *
**********************************************************************/
  kruznice(int px,int py,int pr);
  virtual void zhasni();
  virtual void rozsvit();
  virtual void expanduj(int expanduj_o);
  virtual void zmensi(int zmensi_o);
 };
/**********************************************************************
* Prototyp specialni funkce uzite v souboru obrazce.cpp pro presun    *
* obrazce.                                                            *
**********************************************************************/
boolean kam_presun(int &deltax,int &deltay);
/**********************************************************************
* obrazce.cpp - Tento soubor obsahuje definice pro tridy bod a kruzni-*
*               ce deklarovane v souboru obrazce.h. Konstruktor       *
*              "umisti" je definovan jako vnitrni primo v souboru     *
*               obrazce.h                                             *
**********************************************************************/
#include <stdio.h>
#include <graphics.h>  // Grafika
#include <conio.h>     // Prace s obrazovkou a klavesnici
#include "obrazce.h"
// Metody tridy bod
// Konstructor
      bod::bod(int px, int py):umisti(px,py)
      {
       viditelny=false; // a priorne neviditelny
      }
      void bod:: rozsvit()
      {
       viditelny=true;
       putpixel(x,y,getcolor()); // Uzije preddefinovanou barvu
      }
      void bod:: zhasni()
      {
       viditelny=false;
       putpixel(x,y,getbkcolor()); // Vymaze bod barvou pozadi
      }
      void bod::posun(int nx,int ny)
      {
       zhasni();
       x=nx; y=ny; // posun bodu
       rozsvit();
      }
// Funkce pro posuny obrazcu
boolean kam_presun(int &deltax,int &deltay)
{
 int keychar;
 boolean quit;
 deltax=0; deltay=0;
 do{
  keychar=getch(); // Cti znak z klavesnice
  if(keychar==13) // Cetl se "enter"
     return(false);
  if(keychar==0)  // Zvlastni znak
   {
    quit=true; // Predpoklad: znak je vyuzitelny
    keychar=getch();
    switch(keychar){
        case 72:deltay=-1;break; // Znak "sipka dolu"
        case 80:deltay=1;break; // Znak "sipka nahoru"
        case 75:deltax=-1;break; // Znak "sipka vlevo"
        case 77:deltax=1;break; // Znak "sipka vpravo"
        default:quit=false;// Nepripustny znak se ignoruje

     }
   };
 }while (!quit);
 return(true);
 }
 void bod:: presun(int presun_o)
 {
  int deltax, deltay, obrazecx, obrazecy;
  rozsvit();
  obrazecx=getx(); obrazecy=gety(); // Pocatecni poloha obrazce
// a ted obrazec popotahujeme po obrazovce
  while (kam_presun(deltax,deltay)){
   obrazecx += (deltax*presun_o);
   obrazecy += (deltay*presun_o);
   posun(obrazecx,obrazecy);
  }
}
// Clenove tridy kruznice
// konstruktor
kruznice::kruznice(int px, int py, int pr):bod(px,py)
{
 polomer=pr;
}
void kruznice::rozsvit()
{
 viditelny=true;
 circle(x,y,polomer); // Kresli se kruznice
}
void kruznice::zhasni()
{
 unsigned int barva; // Uschovej stavajici barvu
 barva=getcolor();
 setcolor(getbkcolor());
 viditelny=false;
 circle(x,y,polomer);
 setcolor(barva); // Vrat barvu, jinak se vse ztrati na prozadi
}
void kruznice::expanduj( int expanduj_o)
{
 zhasni();
 polomer += expanduj_o;
 if (polomer < 0)
  polomer = 0;
 rozsvit();
}
void kruznice::zmensi(int zmensi_o)
{
// Vyuzije se funkce expanduj se zapornym argumentem
 expanduj(-zmensi_o);
}
/************************************************************************
* UKAZOBJ.CPP - Ukazka vyuziti objektove orientovaneho programovani.Pro *
*               vyuziti je nutne zavest soubor obrazce.cpp a graphics.h *
*               a je nutno definovat konkretni cestu k ovladaci nejlepe:*
************************************************************************/
#define CESTA "\\tc\\bgi" /* Tento prikaz v jazyku C++ uzivany mene nez
                             v jazyku C, [protoze lze obvykle (i zde)
                             nahradit definici cons] bude podrobne
                             vysvetlen v kap.7. */
#include "obrazce.cpp"
int main()
{
 int go=DETECT,gm,k;
 kruznice *ukruznice = new kruznice(150,80,50);
/* Nasleduje prikaz umoznujici otevrit grafickou knihovnu v implementaci
   BORLAND TURBO C++; v proměnné go se automaticky ulozi informace o pris-
   lusnem ovladaci (driveru), promenna gm zajisti preklopení do spravneho
   grafickeho rezimu, v konstante CESTA je ulozena cesta do adresare, kde
   je ulozen ovladac */
 initgraph(&go,&gm,CESTA);
 // Popotahuj kruznici o pet obrazovych prvku (angl. pixel)
 ukruznice->presun(5);
 // Stiskni enter a popotahuj kruznici o 30 obrazovych prvku
 ukruznice->presun(30);
 // Stiskni enter a zvetsi kruznici na o 50 obrazovych prvku
 ukruznice->expanduj(50);
 k=getch();
 // Stiskni enter a zmensi kruznici 95 obrazovych prvku
 ukruznice->zmensi(95);
 k=getch();
 // Nasleduje prikaz, ukoncujici v implementaci BORLAND graficky rezim
 closegraph();return(0);
}
K odkazu
Obsah

3.  Spřátelené funkce a třídy

Zapouzdření zajišťuje, že data typu private nejsou přístupná jiným objektům. To má velké výhody při úpravách programových projektů a jejich údržbě, klade to však velké nároky na projektanta, od kterého se očekává, že zajistí dostatečný počet metod pro manipulaci s daty při všech alternativách, které se mohou vyskytnout. To se někdy nemusí podařit, a proto v  C++ existuje mechanismus "přátel". Přátelé (tedy spřátelené funkce a třídy) mají přístup ke všem datům, právě tak, jako metody příslušné třídy. Můžeme si to představit tak, že se do štítu, kterým každá třída chrání svá data, navrtají otvory, kterými jsou data přístupná "přátelům". Nebudeme zde rozebírat výhody a nevýhody těchto ötvorů", citem však každý pozná, že (např. pro údržbu systému) čím méně otvorů a "přátel" bude, tím lépe.

Spřátelelené funkce a třídy se využijí obvykle ve složitých projektech, které se vymykají této publikaci. Abychom však nezůstali jen u planého popisu, sestavíme dva školní příklady, které sice nejsou příliš duchaplné, ale techniku použití "přátel" snad dostatečně osvětlí.

Příklad 6.4. Sestavme program, ve kterém třída zvec disponuje metodou, která zvětší privátní údaj třídy o jedničku. Dále sestavme spřátelenou funkci, která bude schopna tento privátní údaj vytisknout.



Řešení:

Program je jednoduchý a snad i jasný. Zajímavé je na něm jen umístění prototypu spřátelené funkce. Je lhostejné, je-li umístěn v části private nebo public. Funkce pracuje s formálním parametrem typu zvec, který voláme odkazem pro ušetření místa. Je samozřejmé, že lze použít i standardní volání hodnotou, které je opět rychlejší.

#include<stdio.h>
class zvec
{
 private:
  int hodn;
 // Prototyp spratelene funkce
  friend void print(zvec &);
 public:
 // Vnitrni funkce tridy (vcetne konstruktoru)
  zvec(int n=0){hodn=n;}
  void pricti(){hodn++;}
};
// Definice spřátelené funkce
void print(zvec &i){printf("hodnota = %d\n",i.hodn);}

#include"friendf.h"
main()
{ zvec z(10000); z.pricti();
/* Nasleduje pouziti spratelene funkce: */print(z);
 z.pricti();/* a opet spratelena funkce: */print(z);
}
Příklad 6.5. Sestavme stejný program jako v příkladu 6.2 s tím rozdílem, že jednotlivé celočíselné hodnoty se budou číst z klávesnice, při čemž nejprve udáme, kolik jich budeme číst. Navíc budeme definovat spřátelenou třídu, která zařadí nový objekt na správné místo seznamu, budeme-li předpokládat, že prvky seznamu tvoří klesající posloupnost.



Řešení:

Opět je třeba zdůraznit, že i spřátelenou třídu je možno uvést v oblasti public i private. Pro zjednodušení algoritmu se v programu předpokládá klesající posloupnost zařazených hodnot seznamu. Objekty seznamu se tvoří öd zadu", tj. první udaný objekt z klávesnice je v seznamu umístěn jako poslední.

// Soubor friendc.h -  hlavickovy soubor
#include<stdio.h>
#include <dos.h>
class objekt  //Tato trida vlastne nahrazuje strukture
{
          private:
          objekt *dalsi;
          int hodnota;
          friend class seznam;
          friend class zarad_objekt;
};
class seznam  // Spratelena trida seznam
{
      private:
      objekt *zac_sez;
      objekt *pom_prv;
      friend class zarad_objekt;
      public:
      seznam()
      {
       zac_sez=NULL;
      }
      void vytvor_seznam(int);
      void tiskni_seznam();
      void cisteni_pameti();
      ~seznam(){cisteni_pameti();}
};
class zarad_objekt  /* Spratelena trida zarad_objekt k objektu
                       i seznamu */

{
        private:
        objekt *novy_objekt;
        objekt *predchozi_objekt;
        public:
        zarad_objekt(){novy_objekt=new objekt[1];}
        void zarad(seznam &);
};

// Soubor friendc.cpp   - implementace metod
#include"friendc.h"
void seznam:: cisteni_pameti()
          {
           objekt *pred=zac_sez;
           if(pred==NULL)return;
           do
           {objekt *predpred=pred;
            pred=pred->dalsi;
            delete predpred;
           }while(pred!=0);
          }

void seznam::vytvor_seznam(int pocet=10)
          {
           printf("\nCti %d prvku \n",pocet);
           zac_sez=NULL;pom_prv=new objekt;
           for (int i=pocet;i>=1;i--)
           {
            scanf("%d",&pom_prv->hodnota);
            pom_prv->dalsi=zac_sez;
            zac_sez=pom_prv;
            pom_prv=new objekt;
           }
          }
void seznam::tiskni_seznam()
          {
           pom_prv=zac_sez;
           int i = 0;
           while(pom_prv!=0)
           {
             i++;
             printf("adresa %d. objektu = %p a jeho hodnota = %d\n",i,
                     pom_prv,pom_prv->hodnota);
             pom_prv=pom_prv->dalsi;
           }
         }

void zarad_objekt::zarad(seznam &s)
          {
           printf("\nCti hodnotu noveho objektu\n");
           scanf("%d",&novy_objekt->hodnota);
           predchozi_objekt=s.pom_prv=s.zac_sez;
           while(s.pom_prv!=NULL)
           {
            if(s.pom_prv->hodnota<=novy_objekt->hodnota)
             if(s.pom_prv==s.zac_sez)
             {novy_objekt->dalsi=s.pom_prv;
              s.zac_sez=novy_objekt;break;
             }
             else
             {
              novy_objekt->dalsi=s.pom_prv;
              predchozi_objekt->dalsi=novy_objekt;break;
             }
            else
             if(s.pom_prv->dalsi==NULL)
             {
               novy_objekt->dalsi=NULL;s.pom_prv->dalsi=novy_objekt;
               break;
             }
            {
              predchozi_objekt=s.pom_prv;s.pom_prv=s.pom_prv->dalsi;
            }
           }
          }

// Soubor frhlac.cpp  -  hlavni program
#include "friendc.cpp"
main()
{
 seznam sez;zarad_objekt a;
 printf("\nCti pocet clenu seznamu\n");
 int n;
 scanf("%d",&n);
 sez.vytvor_seznam(n);
 sez.tiskni_seznam();
 a.zarad(sez);
 sez.tiskni_seznam();
}

Obsah

4.  Přetížené operátory

V odstavci 7.3 jsme mluvili o přetížených funkcích. Nyní se ještě stručně zmíníme o přetížených operátorech. Máme-li možnost definovat objekty nových typů, musíme mít také možnost definovat nové operátory, kterými realizujeme operace nad těmito objekty. Můžeme si vymýšlet názvy nových operátorů, ale můžeme použít operátorů, na které jsem zvyklí. Dejme si příklad. Představme si, že si definujeme operaci sčítání nad nějakými dvěma námi definovanými objekty z1, z2 (třeba komplexním hodnotami). Můžeme to učinit standardním způsobem tak, že napíšeme
z1.pricti(z2);

nebo

z2.pricti(z1);

To znamená, že pošleme zprávu "přičti hodnotu z2" objektu z1 nebo zprávu objektu z2 o "přičtení objektu z1. To je méně přehledné, než zápis

z1 + z2.

Operátor + můžeme tudíž přetížit o další typ na který je aplikovatelný. Můžeme samozřejmě definovat operátor * jako operátor sčítání; kompilátoru (a matematikům) to nevadí, ale normálním lidem ano, proto se takovéto transakce nedoporučují.

Než budeme pokračovat ve výkladu, osvětleme si stručně slovní symbol this. Tento slovní symbol (česky znamenající "tento, tato, toto") je v jazyku C++ definován jako ukazatel na objekt, který posílá zprávu sám sobě. Ve většině případů se nemusí tento ukazatel explicitně zapisovat, ale někdy, jak v závěrečném příkladu tohoto článku uvidíme, ano.

Binární operátory (tj. operátory mající dva operandy) jako např. +, -, *, / atd. se mohou definovat jako metody s jedním parametrem uvnitř třídy (druhým parametrem je pak vždy this nebo jako spřátelené funkce se dvěma parametry. Unární operátory (tj. operátory s jedním operandem) se mohou definovat jako metody bez parametru uvnitř třídy, nebo jako spřátelená funkce s jedním parametrem. Tedy výraz

operand_1 B operand_2může být interpretován jako

operand_1.operatorB(operand_2)nebo

operatorB(operand_1,operand_2).

Uvažujme výraz vyjádřený unárním operátorem U

operandU

nebo

Uoperand.

Ten může být interpretován jako

operand.operatorU()nebo

operatorU(operand).

Přetěžujeme-li operátory ++ a - nelze odlišit prefixovou a postfixovou aplikaci. Někdy je použití spřátelené funkce nezbytné. Např. z+27 lze zapsat jako z.operator+(27) ovšem zápis 27+z zapsaný jako 27.operator(z) je nesmyslný, protože 27 není objekt definovaný uživatelem. Uvedený příklad je natolik ilustrativní, že lze doporučit binární operace definovat výhradně jako spřátelené funkce.

Příklad 6.6. Zapišme program z příkladu 6.1 tak, že počitadlo vylepšíme užitím přetížených operátorů ++ a -.



Řešení:

/***********************************************************************
* Hlavickovy soubor pro lepsi popis tridy pocitadlo s vyuzitim preti-  *
* zenych operatoru ++ a --                                             *
***********************************************************************/
//Soubor vlp_pctd.h
#include<stdio.h>
class pocitadlo
{
 private:
  unsigned int hodnota;
 public:
  pocitadlo(){hodnota=0;} // Konstruktor s nulovanim data hodnota
  // Prototypy operatoru
  void operator++();
  void operator--();
  unsigned operator()();
};
//Implementace metod vylepsene tridy pocitadlo
//Soubor vlp_pctd.cpp
#include"vlp_pctd.h"
void pocitadlo::operator ++()
{
 if (hodnota<65535)
  hodnota++;
}
void pocitadlo::operator --()
{
 if(hodnota>0)
  hodnota--;
}
unsigned int pocitadlo::operator ()()
{
 return hodnota;
}
//Test programu pro vylepsene pocitadlo
//File vlp_pctt.cpp
#include "vlp_pctd.cpp"
#include<stdio.h>
void main()
{
 pocitadlo me_nepostradatelne_pocitadlo;
 for(int i=0;i<12;i++)
 {
  me_nepostradatelne_pocitadlo++;
  printf("\nme_nepostradatelne_pocitadlo = %d",
            me_nepostradatelne_pocitadlo());
 }
 for(i=0;i<2;i++)
  me_nepostradatelne_pocitadlo--;
 printf("\nme_nepostradatelne_pocitadlo = %d",
           me_nepostradatelne_pocitadlo());
}
Příklad 6.7. Sestavme systém umožňující práci se zlomky.



Řešení:

Tento druhý příklad je mnohem rozsáhlejší. V jazyku C++ není standardně definována práce se zlomky, a proto jsme museli následující operátory přetížit tak, aby byly schopny se zlomky pracovat.

Operátor    ¯ Přetížení proudového operátoru na tisk zlomků.¯ Operátor  Užití prirad  Přiřaď čitatel a jmenovatel zlomku. =  Přiřaď jeden zlomek druhému. op=  Realizace operací op=, kde op je +,-,*,nebo /. ==  Rovnost dvou zlomků. !=  Nerovnost dvou zlomků. +  Sčítání zlomků. -  Odčítání zlomků. *  Násobení zlomků. /  Dělení zlomků. real  Konvertování zlomku na číslo desetinné. <<  Přetížení proudového operátoru na tisk zlomků.

Operátory definované ve třídě zlomek odpovídají přesně právě vyjmenovaným operátorům. V sekci private třídy zlomek je uvedena metoda kraceni, která umožní vypočítané zlomky zkrátit, tedy např. -12/36 na -1/3. Konstruktor umožní specifikovat uživateli čitatel a jmenovatel. Není-li čitatel a jmenovatel specifikován, je čitatel roven nule a jmenovatel roven jedničce (využilo se dosazených parametrů). Čtenář si přečte (a doufám, že i prakticky ověří), že operace +,-,*,/,==,!=,real a << jsou definovány jako spřátelené funkce. Všechny s výjimkou funkce real mají dva parametry, a proto není vhodné, aby byly definovány jako metody. Funkce real má jeden parametr, a proto mohla být definována jako metoda.

//Hlavickovy soubor pro praci s racionalnimi cisly
// Soubor zlomek.h
#include<fstream.h>
#include<stdlib.h>
class zlomek
{
 private:
  void kraceni(zlomek &cis);
  long citatel,jmenovatel;
 public:
  zlomek(long cit=0,long jmen=1)
  {
   citatel=cit;
   jmenovatel=jmen;
   kraceni(*this);
  }
  void prirad(long cit,long jmen)
  //Prislusny citatel a jmenovatel se prirazedi racionalnimu cislu.
  //Uziti:
  //zlomek cislo;
  //cislo.prirad(12,18);
  {
   citatel=cit;
   jmenovatel=jmen;
   kraceni(*this);
  }
  zlomek operator =(zlomek &cis)
  /*Argument je volan odkazem, abychom se vyhnuli jeho kopii (uspori se
    pamet)
 */
 //Operator prirazeni
 //Uziti:
 //zlomek cis1,cis2;
 //cis2=cis1: Toto znamena cis2.operator =(cis1);
 {
  citatel=cis.citatel; jmenovatel=cis.jmenovatel;
  kraceni(*this);
  return(*this);
 }
 zlomek operator = (long cele_cislo)
 //Operator prirazeni
 //Uziti:
 //zlomek cis;
 //cis=1234;
 {
  citatel=cele_cislo; jmenovatel=1;
  return(*this);
 }
 zlomek operator +=(zlomek &cis)
 //Operator pricteni
 //Uziti:
 //zlomek cis1,cis2;
 //cis2+=cis1;
 {
  long stary_jmen=jmenovatel;
  long stary_cit=citatel;
  long j=cis.jmenovatel;
  long c=cis.citatel;
  jmenovatel*=j;
  citatel=stary_cit*j+stary_jmen*c;
  kraceni(*this);
  return(*this);
 }
  zlomek operator -=(zlomek &cis)
  //Operator odcitani
  //Uziti:
  //zlomek cis1,cis2;
  //cis2-=cis1;
 {
  long stary_jmen=jmenovatel;
  long stary_cit=citatel;
  long j=cis.jmenovatel;
  long c=cis.citatel;
  jmenovatel *= d;
  citatel=stary_cit*j-stary_jmen*c;
  kraceni(*this);
  return(*this);
 }
 zlomek operator *=(zlomek &cis)
 //Operator nasobeni
 //Uziti:
 //num_n cis1,cis2;
 //cis2*=cis1;
 {
  citatel *= cis.citatel;
  jmenovatel *= cis.jmenovatel;
  kraceni(*this);
  return(*this);
 }
 zlomek operator /=(zlomek &cis)
 //Operator deleni
 //Uziti:
 //zlomek cis1,cis2;
 //cis2/=cis1;
 //Je-li jmenovatel nula, operace se neprovede.
 {
  if(jmenovatel!=0)
  {
   citatel *= cis.jmenovatel;
   jmenovatel *= cis.citatel;
   kraceni(*this);
   return(*this);
  }
 }
//Spratelene funkce pro realizaci binarnich operaci
 friend zlomek
  operator + (zlomek &cis1,zlomek &cis2);
 //Operator pricteni
 //Uziti:
 //zlomek cis1,cis2,cis3;
 //cis3=cis1+cis2;
 //a to znamena: cis3=operator(cis1,cis2);
 friend zlomek
  operator - (zlomek &cis1,zlomek &cis2);
 //Operator odecteni
 //Uziti:
 //zlomek cis1,cis2,cis3;
 //cis=cis1-cis2;
 friend zlomek
  operator * (zlomek &cis1, zlomek &cis2);
 //Operator nasobeni
 //Uziti:
 //zlomek cis1,cis2,cis3;
 //cis3=cis1*cis2;
 friend zlomek
  operator / (zlomek &cis1,zlomek &cis2);
 //Operator deleni
 //Uziti:
 //zlomek cis1,cis2,cis3;
 //cis3=cis1/cis2;
 friend int operator == (zlomek &cis1,zlomek &cis2);
 //Operator rovnosti
 //Uziti:
 //zlomek cis1,cis2,cis3;
 //if(cis1==cis2)...
 friend int operator != (zlomek &cis1,zlomek &cis2);
 //Operator nerovnosti
 //Uziti:
 //zlomek cis1,cis2,cis3;
 //if(cis1!=cis2)...
 friend double real(zlomek &cis);
 //Konverze racionalniho cisla na realne
 //Uziti:
 //zlomek cis1;double r;
 //r=real(cis1);
 friend ostream &operator <<(ostream &s,zlomek &cis1);
 //Proudovy vystup jako pretizeny operator
 //Uziti:
 //zlomek cis;
 //cout<<cis;
};


//Implementace metod tridy zlomek (pro praci s racionalnimi cisly)
// Soubor zlomek.cpp
#include "zlomek.h"
#include<stdlib.h>
void zlomek::kraceni(zlomek &cis)
//Vraceni zkraceneho racionalniho cisla (napr. kraceni(20/30)=2/3)
{
 int nsd; //nsd je nejvetsi spolecny delitel

 if(cis.citatel==0)
 {
  cis.jmenovatel=1;
  return;
 }
 nsd=(abs(cis.citatel)>
      abs(cis.jmenovatel)?
      abs(cis.citatel):
      abs(cis.jmenovatel));
 if(nsd==0) return;
 do
 {
  if(nsd==1)break;
  if((cis.citatel%nsd==0) &&
    (cis.jmenovatel%nsd==0))break;
  else nsd--;
 }while(1);//Pozor! Simulace nekonecneho cyklu. Pohov!
 cis.citatel/=nsd;cis.jmenovatel/=nsd;
 //Prirazeni spravneho znamenka
 if(cis.citatel<0&&cis.jmenovatel<0)
 {
  cis.citatel=-cis.citatel; cis.jmenovatel=-cis.jmenovatel;
 }
 else if(cis.citatel<0||cis.jmenovatel<0)
 {
  cis.citatel=-abs(cis.citatel);
  cis.jmenovatel=abs(cis.jmenovatel);
 }
}
ostream& operator << (ostream &s,zlomek &cis)

{
 if(cis.jmenovatel!=1)
  return (s << cis.citatel << '/' << cis.jmenovatel);
 else
  return (s << cis.citatel);
}
zlomek operator + (zlomek &cis1,zlomek &cis2)
{
 long cvysledek,jvysledek;
 jvysledek=cis1.jmenovatel*cis2.jmenovatel;
 cvysledek=cis1.citatel*cis2.jmenovatel
        +cis2.citatel*cis1.jmenovatel;
 return zlomek(cvysledek,jvysledek);
}
zlomek operator - (zlomek &cis1,zlomek &cis2)
{
 long cvysledek,jvysledek;
 jvysledek=cis1.jmenovatel*cis2.jmenovatel;
 cvysledek=cis1.citatel*cis2.jmenovatel
        -cis2.citatel*cis1.jmenovatel;
 return zlomek(cvysledek,jvysledek);
}
zlomek operator * (zlomek &cis1,zlomek &cis2)
{
 long cvysledek,jvysledek;
 jvysledek=cis1.jmenovatel*cis2.jmenovatel;
 cvysledek=cis1.citatel*cis2.jmenovatel;
 return zlomek(cvysledek,jvysledek);
}
zlomek operator / (zlomek &cis1,zlomek &cis2)
{
 long cvysledek,jvysledek;
 if(cis2.citatel!=0)
 {
  jvysledek=cis1.jmenovatel*cis2.citatel;
  cvysledek=cis1.citatel*cis2.jmenovatel;
  return zlomek(cvysledek,jvysledek);
 }
}
int operator==(zlomek &cis1,zlomek &cis2)
{
 return (float)cis1.citatel/(float)cis1.jmenovatel==
        (float)cis2.citatel/(float)cis2.jmenovatel;
}
int operator!=(zlomek &cis1,zlomek &cis2)
{
 return (float)cis1.citatel/(float)cis1.jmenovatel!=
        (float)cis2.citatel/(float)cis2.jmenovatel;
}
double real(zlomek &cis)
{
 return (double)cis.citatel/(double)cis.jmenovatel;
// nebo return double(cis.citatel/double(cis.jmenovatel)
}

//Test programu pro tridu class zlomek
//Soubor zlomekt.cpp
#include"zlomek.cpp"
#include<conio.h>
main()
{
 //Predefinovani proudove funkce pro vystup do souboru muj_vystup
 ofstream cout("muj_vystup");
 zlomek a(4,5);
 zlomek b(-10,6);
 cout<<"a = "<<a<<"\nb = "<<b;
 a*=b;
 cout<<"\n(a*=b) = "<<a;
 a=b;
 cout<<"\nPo prirazeni a=b, a = "<<a;
 a+=b;
 cout<<"\n(a+=b) = "<<a;
 a-=b;
 cout<<"\n(a-=b) = "<<a;
 a=b;a-=a;
 cout<<"\nPo prirazeni a=b; a-=a; a = "<<a;
 zlomek c(15);
 a+=c;
 cout<<"\n(a+=15) = "<<a;
 a=1234567;
 cout<<"\n(a=1234567); a = "<<a;
 long d=4;
 a=d;
 cout<<"\n(a=long d=4) = "<<a;
 a+=(b=5);
 cout<<"\n(a+=(b=5)) = "<<a;
 zlomek j(4,5); zlomek k(5,4);
 zlomek l=j+k;
 cout<<"\nj = "<<j<<" k = "<<k<<" zlomek l=j+k; l = "<<l;
 l=l+1;
 cout<<"\n(l+1) = "<<l;
 l=l+k;
 cout<<"\nk=5/4; (l=l+k) = "<<l;
 zlomek e,f,g,h,i,m;
 e=17;f=15;g=e/f;
 cout<<'\n'<<e<<" / "<<f<<" = "<<g;
 h=g;i=1;m=i*h;
 cout<<'\n'<<i<<" * "<<h<<" = "<<m;
 if(g==m)
  cout<<"\nHodnota "<<g<<" se rovna"<<m;
 else
  cout<<"\nHodnota "<<g<<" se nerovna "<<m;
 double r=real(g);
 cout<<"\nHodnota zlomku "<<g<<" = "<<r;
 zlomek cislo;
 cislo.prirad(12,15);
 cout<<"\nPo cislo.prirad(12,15); cislo = "<<cislo;
}

Výsledky testovacího programu byly zapsány do souboru muj_vystup ve tvaru:

a = 4/5
b = -5/3
(a*=b) = -4/3
Po prirazeni a=b, a = -5/3
(a+=b) = -10/3
(a-=b) = -5/3
Po prirazeni a=b; a-=a; a = 0
(a+=15) = 15
(a=1234567); a = 1234567
(a=long d=4) = 4
(a+=(b=5)) = 9
j = 4/5 k = 5/4 zlomek l=j+k; l = 41/20
(l+1) = 61/20
k=5/4; (l=l+k) = 43/10
17 / 15 = 17/15
1 * 17/15 = 1
Hodnota 17/15 se nerovna 1
Hodnota zlomku 17/15 = 1.133333
Po cislo.prirad(12,15); cislo = 4/5

Obsah

5.  Závěrečná poznámka k OOP

V celé této kapitole a v kapitole předcházející jsme hovořili o tom, že třídy jsou právě tím nástrojem, který umožňuje objektově orientované programování. V literatuře se často (např. [8]) uvádějí příklady, že objekty typu struct v C++ mohou zahrnovat také metody, ale není zde možnost rozlišit private a public data i metody a ztrácí se tak výhoda zapouzdření. Mělo by tomu tak být, ale zkoušel jsem zaměnit "nástroj" class "nástrojem" struct v implementaci BORLAND TURBO C++ a v překladači C++ v operačním systému LINUX a v obou se objekty typu class a typu struct chovaly naprosto stejně. Nedoporučuji tuto skutečnost využívat, ale považoval jsem za vhodné se o ní závěrem zmínit.

Obsah

6.  Cvičení

  1. [36.] Sestavte program obdobný programu v příkladu 6.5 s tím rozdílem, že prvky seznamu mohou tvořit rostoucí i klesající posloupnost.
  2. [37.] Sestavte program, kterým vytvoříte seznam objektů (čísla nebo písmena, nebo slova), které budete číst z klávesnice nebo z nějakého souboru. Tyto objekty pak seřadíte v neklesající nebo nerostoucí posloupnost. Zda se jedná o neklesající nebo nerostoucí posloupnost, může být zadáno volbou nějakého parametru. Setříděné objekty vytiskněte.
  3. [38.] Sestavte systém pro práci s komplexním hodnotami.
  4. [39.] Sestavte systém pro sčítání a odčítání vektorů, násobení vektoru skalárem, skalární a vektorový součin. Pro jednoduchost uvažujte trojrozměrné vektory.

Obsah

Kapitola 7
Poznámky k předpřekladači

Předpřekladač jsme zatím používali intuitivně tak, že jsme v příkladech v této publikaci prakticky vždy používali příkazy #include, kterými jsme zaváděli hlavičkové soubory např. #include<stdio.h> nebo naše uživatelské soubory, např. #include"zlomek.cpp" nebo #include"zlomek.h". Nyní se v závěru této publikace zmíníme o předpřekladači (obvykle se předpřekladači říká preprocesor) podrobněji.

Činnost předpřekladače se dá shrnout do několika bodů:

Obsah

1.  Direktiva #define

1.1  Direktiva #define bez parametrů

Tato direktiva je velmi často u'ívána v jazyku C jako direktiva bez parametrů, např. začátek programu z příkladu 1.7 v jazyku C++:

/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius
  pro  fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
main()
{
 // Definice konstant pro dolni a horni mez a krok zmeny
 const int DOLNI=0,HORNI=300,KROK=20;
//...
musíme zapsat v jazyku C takto:
/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius
  pro  fahr = 0, 20,..., 300 */
#include<iostream.h>
#include<iomanip.h>
/* Konstanty definovane stylem jazyka C (v jazyku C++ mozne, ale mene
   obvykle */
#define DOLNI 0
#define HORNI 300
#define KROK 20
main()
{
//...

Všimněte si, 'e nesmíme v definici konstanty zapsat na konci středník. Ten by se toti' zapsal s uvedenou hodnotou kdykoliv, kdy' by předpřekladač narazil na příslušný text, např. DOLNI a to by způsobilo syntaktické chyby. Mů'eme samozřejmě psát


#define PI 3.1415926



ale i


#include<math.h> //Hlavickovy soubor pro matematickou knihovnu
#define PI (4 * atan(1.0)) // atan je arctg
...
Konstanty, kterým často říkáme makra, jsou-li takto definovány se nerozvinou, jsou-li uzavřeny v uvozovkách, např.
#define MAKRO past
d#include<stdio.h>
main()
{printf("Toto je MAKRO");}

Tento program vytiskne text: Toto je MAKRO a nikoliv Toto je past, co' je právě ta past.

Obsah

1.2  Direktiva #define s parametry

Při sestavování programů se často vyskytne případ, kdy mnohokrát pou'íváme nějakou funkci, která je krátká, nebo při řešení diferenciálních rovnic měníme často okrajové nebo počáteční podmínky. Pak s výhodou mů'eme pou'ít direktivy s parametry. Mů'eme např. psát:

#define na_treti(x) ((x)*(x)*(x))
Zdálo by se, 'e závorky jsou nadbytečné, není tomu tak. Kdybychom toti' napsali:

#define na_treti(x) x*x*x
pak po volání:
na_treti(a+b)
se rozvine do:
a+b*a+b*a+b, co' je chybné. Ani vynechání vnějších závorek není vhodné. Např.

#define cti(c) c=getchar()
se po volání:
if(cti(r) == 'b') rozvine do známé chyby:

if(r=getchar() == 'b')Na závěr tohoto odstavce ještě upozorníme na případ, 'e direktiva s parametry mů'e být někdy tak dlouhá, 'e se nevejde na jeden řádek. Pak ji přerušíme znakem zpětné lomítko a pokračujeme na další řádce. Např. shora uvedené makro na_treti lze zapsat:

#define na_treti(x) ((x)\
                     *(x)*(x))

Posledním, ale důle'itým, poučením bude, 'e mezi makrem a první otevírací okrouhlou závorkou nesmí být mezera.

Obsah

2.  Operátory # a ##

Tyto operátory se neužívají tak často, a proto pro úsporu místa je objasníme pouze na dvou příkladech, ze kterých si čtenář význam snadno domyslí:

Příklad 7.1.

#define otevri_soubor(jmeno_souboru) fopen(#jmeno_souboru,"r")
main()
{// ...
 FILE *ms;
 ms=otevri_soubor(muj_soubor);
 // ...}

Předpřekladač "rozepíše" toto makro na:

//...
 FILE *ms;
 ms=fopen("muj_soubor","r");
//...

Příklad 7.2.

#define print_var(num) printf("var" #num " = %d\n",var##num)
//...
int var1,var2,var3;
//...
print_var(3);
//...

Zde předpřekladač rozepíše makro na:

int var1,var2,var3;
//...
printf("var" "3" " = %d\n",var3);
//...

Obsah

3.  Podmíněný překlad

Při ladění programů pomáhají často profesionálně sestavené ladící programy. Jejich nevýhodou je, že při snaze pokrýt co nejvíce možností jsou tyto programy někdy dost složité a mnoho programátorů proto programy píše tak, že do nich zapisují vlastní ladící tisky, které po odladění pro zvýšení přehlednosti programu ruší, přičemž při troše nepozornosti zruší víc než ladící tisky, z čehož vznikají mnohé nepříjemnosti. V jazycích C a C++ je umožněn velmi praktický tzv. podmíněný překlad, který si na několika krátkých ukázkách vysvětlíme.

Příklad 7.3 Napišme jednoduchou funkci, která bude napsána tak, aby byla přijatelná, jak pro původní jazyk C jak byl definován v [3], tak pro ANSI C a C++. Využijme podmíněného překladu ve třech základních variantách.



Řešení:

  1. varianta: Zde běžným způsobem definujeme makro K&R jako nulové. Část podmíněného příkazu mezi #if a #else se provede, je-li makro rovné jedničce. Je-li makro rovné nule, provede se část mezi #else a #endif. Část za #else mimo #endif může být vynechána, dovoluje-li logika ladícího tisku.
    #define K&R  0
    #define na_treti(x)  ((x)*(x)*(x))
    #if K&R
      f(x)
      int x;
      {return (na_treti(x)-4*x+7);}
    #else
      f(int x)
      { return (na_treti(x)-4*x+7);}
    #endif
    

  2. varianta: Zde bude nedefinováno makro K&R, což realizujeme direktivou #undef (Pozor! Makro je prázdné. Pohov!). Podmíněný překlad se pouze změní tak, že místo příkazu #if bude příkaz #ifdef. Příkazy mezi #ifdef a #else se provedou, je-li makro definované. Zbytek si čtenář domyslí sám jako cvičení.
    #undef K&R
    #define na_treti(x)  ((x)*(x)*(x))
    #ifdef K&R
      f(x)
      int x;
      {return (na_treti(x)-4*x+7);}
    #else
      f(int x)
      { return (na_treti(x)-4*x+7);}
    #endif
    

  3. varianta: Této variantě čtenář jistě porozumí bez velkého přemýšlení a se zkušenostmi z 2. varianty sám.
    #define K&R
    #define na_treti(x)  ((x)*(x)*(x))
    #ifndef K&R
      f(x)
      int x;
      {return (na_treti(x)-4*x+7);}
    #else
      f(int x)
      { return (na_treti(x)-4*x+7);}
    #endif
    

Literatura

[1]
Brodský, J.-Skočovský, L.: Operační systém UNIX a jazyk C. Praha, SNTL, 1989
[2]
Herout, P: Učebnice jazyka C. České Budějovice, KOPP, 1992
[3]
Kernighan, B.W.-Ritchie, D.M.: Programovací jazyk C. Bratislava, Praha, Alfa, SNTL, 1988; slovenský překlad originálu The C Programming Language. Englewood Cliffs, Prentice-Hall, 1978
[4]
Kračmar S.-Vogel, J.: Programovací jazyk C. Praha, Ediční středisko ČVUT, 1994
[5]
Renner, G.: Borland C++. Kompendium znalostí a zkušeností. Brno, UNIS, 1992
[6]
Richta K.-Brůha, I.: Programovací jazyk C. Praha, Ediční středisko ČVUT, 1991
[7]
Stroustrup, B.:The C++ programming language. New York, Addison-Wesley Publishing Company, 1987
[8]
Wiener, R.: An introduction to object-oriented programming and C++. New York, Addison-Wesley Publishing Company, 1988


File translated from TEX by TTH, version 1.41.

Obsah