18. Ukazatele II
  1. Odkazy jsou specißlnφm typem ukazatel∙, kterΘ umo╛≥ujφ pracovat s ukazateli jako s normßlnφmi objekty. Odkazy jsou deklarovßny pomocφ operßtoru &. Nap°.

  2. mojeStruktura* ukStrukt = new mojeStruktura;
    mojeStruktura& odkaz = *ukStrukt;
    odkaz.X = 100;
    Pov╣imn∞te si, ╛e p°i p°φstupu ke slo╛kßm struktury jsou pou╛ity p°φmΘ selektory slo╛ky. V reßln²ch programech obvykle nenφ zapot°ebφ udr╛ovat p°i pou╛itφ odkazu ukazatel na strukturu a p°edchozφ zßpis lze zkrßtit takto:
    mojeStruktura& odkaz = *new mojeStruktura;
    odkaz.X = 100;
    Nynφ se op∞t vrßtφme k na╣φ konzolovΘ aplikaci adresß°e na╣ich znßm²ch. ╪e╣enφ z konce p°edchozφ kapitoly m∙╛eme zm∞nit takto:
    #include <iostream.h>
    #include <conio.h>
    #include <stdlib.h>
    #pragma hdrstop
    #include "structur.h"
    //---------------------------------------------------------------------------
    #pragma argsused
    void zobrazZaznam(int, adresar adrZaz);
    int main(int argc, char **argv)
    {
      adresar* seznam[3];
      for (int i = 0; i < 3; i++)
        seznam[i] = new adresar;
      cout << endl;
      int index = 0;
      do {
        adresar& zaznam = *seznam[index];
        cout << "JmΘno: ";
        cin.getline(zaznam.jmeno, sizeof(zaznam.jmeno)-1);
        cout << "P°φjmenφ: ";
        cin.getline(zaznam.prijmeni, sizeof(zaznam.prijmeni)-1);
        cout << "Ulice: ";
        cin.getline(zaznam.ulice, sizeof(zaznam.ulice)-1);
        cout << "M∞sto: ";
        cin.getline(zaznam.mesto, sizeof(zaznam.mesto)-1);
        cout << "PsΦ: ";
        char buff[10];
        cin.getline(buff, sizeof(buff)-1);
        zaznam.psc = atoi(buff);
        index++;
        cout << endl;
      } while (index < 3);
      clrscr();
      for(int i = 0; i < 3; i++) {
        zobrazZaznam(i, *seznam[i]);
      }
      cout << "Zadej Φφslo zßznamu: ";
      int zaz;
      do {
        zaz = getch();
        zaz -= 49;
      } while (zaz < 0 || zaz > 2);
      adresar pom = *seznam[zaz];
      clrscr();
      zobrazZaznam(zaz, pom);
      getch();
      return 0;
    }
    void zobrazZaznam(int cis, adresar adrZaz)
    {
      cout << "Zßznam " << (cis + 1) << ":" << endl;
      cout << "JmΘno: " << adrZaz.jmeno << " " << adrZaz.prijmeni << endl;
      cout << "Adresa: " << adrZaz.ulice << endl;
      cout << "        " << adrZaz.mesto << endl;
      cout << "        " << adrZaz.psc << endl << endl;
    }
    Zm∞n∞nΘ °ßdky jsou op∞t zobrazeny Φerven∞. Pov╣imn∞te si deklarace odkazu na strukturu adresar. P°i ka╛dΘm pr∙chodu cyklem je odkazu p°i°azen jin² objekt (nßsledujφcφ prvek v poli). Pro p°φstup k prvk∙m struktury nynφ pou╛φvßme operßtor p°φmΘho selektoru slo╛ky. Jak uvidφme pozd∞jφ, odkaz umo╛≥uje chßpat ukazatel jako objekt. V²sledkem pou╛φvßnφ odkazu dostßvßme krat╣φ a Φiteln∞j╣φ k≤d.
    P°esto╛e odkazy jsou preferovanΘ p°ed ukazateli, nenφ tomu tak v╛dy. Odkazy nelze pou╛φt ve v╣ech p°φpadech. Nap°. odkaz nem∙╛e b²t deklarovßn bez p°i°azenφ hodnoty. Musφ b²t inicializovßn p°i deklaraci. Nßsledujφcφ k≤d zp∙sobφ chybu p°ekladu:
    mojeStruktura* ukStruktura = new mojeStruktura;
    mojeStruktura& odkaz;
    odkaz = *ukStruktura;
    odkaz.X = 100;
    Dal╣φ problΘm s odkazem je ten, ╛e jej nelze nastavit na NULL nebo 0, co╛ u ukazalele lze.
  3. Parametry u funkcφ jsme zatφm p°edßvali hodnotou (v p°edchozφ kapitole byla ukßzka p°edßvßnφ parametr∙ odkazem). V p°φpad∞ struktur a t°φd je ale lep╣φ p°edßvat tyto objekty odkazem. Odkazem m∙╛e b²t p°edßn libovoln² objekt (standardnφ datovΘ typy jako je int nebo char a takΘ instance struktur nebo t°φd). P°i p°edßvßnφ parametru funkce hodnotou je vytvo°ena kopie objektu a funkce pracuje s touto kopiφ. Kdy╛ p°edßvßme parametr odkazem, pak je p°edßn ukazatel na objekt a ne objekt samotn². To mß dv∞ v²hody. Objekt p°edan² funkci m∙╛e b²t funkcφ modifikovßn a p°edßvßnφ odkazem eliminuje re╛ijnφ nßklady na vytvo°enφ kopie objektu.

  4. Mo╛nost modifikace objektu je d∙le╛it²m aspektem p°i p°edßvßnφ parametr∙ odkazem. Podφvejte se na nßsledujφcφ k≤d:
    void Inkrementace(int& xPos, int& yPos)
    {
      xPos++;
      yPos++;
    }
    int x = 20;
    int y = 40;
    Inkrementace(x, y);
    // x je nynφ rovno 21 a y 41
    Pov╣imn∞te si, ╛e po nßvratu z funkce jsou oba p°edanΘ parametry zv∞t╣eny o 1. To je z d∙vodu modifikace aktußlnφho objektu funkcφ prost°ednictvφm ukazatele (nezapome≥te, ╛e odkaz je typ ukazatele).
    Funkce m∙╛e vracet pouze jednu hodnotu. Pomocφ parametr∙ p°edßvan²ch odkazem, m∙╛eme dosßhnout efektu nßvratu vφce hodnot. Funkce stßle vracφ pouze jednu hodnotu, ale objekty p°edanΘ odkazem mohou b²t aktualizovßny a funkce tak zdßnliv∞ vracφ vφce hodnot.
    Dal╣φm d∙vodem pro p°edßvßnφ parametr∙ odkazem je eliminace re╛ijnφch nßklad∙ spojen²ch s vytvß°enφm kopie objekt∙ p°i volßnφ funkce. P°i pou╛φvßnφ standardnφch datov²ch typ∙ jsou re╛ijnφ nßklady na vytvo°enφ kopie zanedbatelnΘ. P°i prßci se strukturami a t°φdami mohou b²t znaΦnΘ. Struktury v libovolnΘ situaci lze p°edat odkazem (viz nßsledujφcφ ukßzka):
    void nejakaFunkce(mojeStruktura& s)
    {
      // ud∞lßme n∞co se s
      return;
    }
    mojeStruktura mojeStr;
    ....
    nejakaFunkce(mojeStr);
    Pou╛itφ odkazu umo╛≥uje modifikaci objektu p°edanΘho funkci. N∞kdy se ale m∙╛eme dostat do situace, kdy tΘto modifikaci chceme zabrßnit.
  5. Jestli╛e prom∞nnou deklarujeme s klφΦov²m slovem const, pak nem∙╛eme zm∞nit jejφ hodnotu. StejnΘ °e╣enφ lze pou╛φt i p°i p°edßvßnφ parametru funkcφ odkazem a ud∞lat tak konstantnφ objekt:

  6. void nejakaFunkce(const mojeStruktura& s)
    {
      // ud∞lßme n∞co se s
      return;
    }
    mojeStruktura mojeStr;
    ....
    nejakaFunkce(mojeStr);
    Nynφ m∙╛eme funkci p°edßvat objekt a nemusφme se obßvat, ╛e jej funkce zm∞nφ. Pokus o modifikaci konstantnφho objektu uvnit° funkce zp∙sobφ p°i p°ekladu chybu. Nap°.
    void nejakaFunkce(const mojeStruktura& s)
    {
      s.slozka = 100;    // chyba, konstantnφ objekt nelze modifikovat
      return;
    }
    Takov²to objekt je konstantnφ pouze uvnit° funkce. P°ed volßnφm funkce a po nßvratu z funkce jej lze modifikovat (pokud nenφ p∙vodn∞ deklarovßn jako konstantnφ).
    Kdy╛ si mßme vybrat, zda parametr budeme p°edßvat odkazem nebo ukazatelem, pak v∞t╣inou dßvßme p°ednost p°edßvßnφ odkazem. P°i p°edßvßnφ znakovΘho pole je ale v²hodn∞j╣φ pou╛φt p°edßvßnφ parametru ukazatelem (ukazatel na znakovΘ pole a jmΘno pole jsou zam∞nitelnΘ).
  7. Zm∞≥te na╣i aplikaci s adresß°em na╣ich znßm²ch tak, ╛e u funkce zobrazZaznam budete strukturu p°edßvat odkazem.
  8. P°i vytvß°enφ a ru╣enφ dynamick²ch prom∞nn²ch se pou╛φvajφ operßtory new a delete. S operßtorem new jsme se ji╛ seznßmili. K uvoln∞nφ pam∞ti pou╛φvßme operßtor delete.

  9. Jak ji╛ bylo uvedeno d°φve, pam∞╗ m∙╛eme alokovat lokßln∞ (v zßsobnφku) nebo dynamicky (v hromad∞). Nßsledujφcφ ukßzka k≤du alokuje dv∞ pole. Jedno je alokovßno v zßsobnφku (lokßlnφ alokace) a druhΘ v hromad∞ (dynamickß alokace):
    char buff[80];
    char* velkyBuff = new char[4096];
    V prvnφm p°φpad∞ velikost alokovanΘ pam∞ti je malß a nenφ podstatnΘ, zda bude pou╛it zßsobnφk nebo hromada. V druhΘm p°φpad∞ se jednß o velkΘ pole a je tedy vhodnΘ jej alokovat v hromad∞ (╣et°φme mφstem v zßsobnφku). V p°φpad∞ polφ (°et∞zec je pole typu char), dynamickΘ a lokßlnφ instance jsou zam∞nitelnΘ. Tzn. pou╛φvajφ stejnou syntaxi:
    strcpy(buff, "Ahoj");
    strcpy(velkyBuff, "Hodn∞ dlouh² °et∞zec ......");
    // a pozd∞ji
    strcpy(velkyBuff, buff);
    JmΘno pole bez operßtoru indexace ukazuje na zaΦßtek pole. Ukazatel takΘ ukazuje na zaΦßtek pole a je tedy jedno zda pou╛ijeme jmΘno pole nebo ukazatel na pole.
    Pokud operßtor new nem∙╛e alokovat po╛adovanou pam∞╗, pak vracφ NULL. To m∙╛eme testovat nap°. takto:
    char* buff = new char[1024];
    if (buff) strcpy(buff, "N∞jak² text");
    else SignalizaceChyby();
    Jestli╛e alokujeme velmi velkou oblast pam∞ti nebo alokujeme pam∞╗ v kritickΘ oblasti programu, pak je vhodnΘ testovat ukazatel na p°φpustnost (·sp∞╣nost alokace). B∞╛nΘ alokace lze ponechat bez testovßnφ.
  10. V╣echna alokovanß pam∞╗ musφ b²t dealokovßna (uvoln∞na), kdy╛ ji ji╛ nepot°ebujeme. U lokßlnφch objekt∙ to probφhß automaticky. P°i pou╛itφ dynamickΘ alokace, je za uvol≥ovßnφ zodpov∞dn² programßtor a k uvol≥ovßnφ pam∞ti pou╛φvß operßtor delete. V╣echna volßnφ new majφ svΘ odpovφdajφcφ delete. Operßtory new a delete tvo°φ dvojici.

  11. Pou╛itφ operßtoru delete je snadnΘ:
    nejakyObjekt* mujObjekt = new nejakyObjekt;
    // n∞jakß Φinnost s objektem
    delete mujObjekt;        // objekt je zru╣en
    P°i pou╛φvßnφ samotnΘho operßtoru delete nejsou ╛ßdnΘ problΘmy, ale je n∞kolik v∞cφ spojen²ch s ukazateli a delete, na kterΘ musφme dßvat pozor. Za prvΘ nesmφme zru╣it ukazatel (p°esn∞ji °eΦeno objekt na kter² ukazatel ukazuje), kter² ji╛ byl zru╣en (vznikne chyba p°φstupu a °ada dal╣φch problΘm∙). Za druhΘ m∙╛eme zru╣it ukazatel, kter² byl nastaven na 0.
    N∞kdy deklarujeme ukazatel pro p°φpad, kdy by mohl b²t pou╛it, ale nevφme jist∞ zda v danΘ instanci na╣eho programu byl skuteΦn∞ pou╛it. Nap°. m∙╛eme mφt objekt, kter² je vytvß°en, kdy╛ u╛ivatel provede jistou volbu v nabφdce. Pokud u╛ivatel nikdy tuto volbu neprovede, pak objekt nebude vytvo°en. ProblΘmem je, ╛e v p°φpad∞ vytvo°enφ objektu je zapot°ebφ zru╣it ukazatel a neru╣it jej, kdy╛ objekt nebyl vytvo°en. Zru╣enφm neinicializovanΘho ukazatele si zp∙sobφme problΘmy, proto╛e nevφme na kterou Φßst pam∞ti ukazuje.
    Jak ji╛ bylo uvedeno d°φve je vhodnΘ inicializovat ukazatel nulou, pokud jej p°φmo neinicializujeme normßlnφ hodnotou. To je ze dvou d∙vod∙ vhodn² zp∙sob. Prvnφ d∙vod byl popsßn v²╣e; neinicializovan² ukazatel obsahuje nßhodnou hodnotu, co╛ je ne╛ßdoucφ. Druh²m d∙vodem je to, ╛e zru╣enφ ukazatele NULL je p°φpustnΘ (m∙╛eme zru╣it tento ukazatel a nemusφme se zab²vat tφm, zda jej u╛ivatel pou╛il):
    ukazatel* nekdy = 0;
    // mo╛nΘ pou╛itφ nekdy
    delete nekdy;
    Zru╣enφ ukazatele v tomto p°φpad∞ je v po°ßdku a a╗ ji╛ ukazatel ukazuje na objekt nebo je NULL.
    M∙╛eme se takΘ dostat do situace, kdy vytvo°φme objekt v jednΘ Φßsti programu a zru╣φme jej v jinΘ Φßsti programu. P°itom Φßst k≤du ru╣φcφ objekt nemusφ b²t nikdy provedena. V tomto p°φpad∞ by bylo vhodnΘ zru╣it objekt p°i ukonΦenφ programu (v n∞kter²ch p°φpadech ale objekt ji╛ mohl b²t zru╣en). K zabrßn∞nφ dvojnßsobnΘho zru╣enφ je vhodnΘ ukazatel po zru╣enφ nastavit na NULL (nebo 0):
    ukazatel* vytvoren = new ukazatel;
    // pozd∞ji
    delete vytvoren;
    vytvoren = 0;
    Pokud nynφ pou╛ijeme na tento objekt dvakrßt operßtor delete, pak se nic nestane, nebo╗ nenφ chybou zru╣it ukazatel NULL.
    Jinou mo╛nostφ, jak zabrßnit problΘmu dvojnßsobnΘho zru╣enφ je testovßnφ ukazatele na nenulovou hodnotu p°ed pou╛itφm delete:
    if (nekdy) delete nekdy;
    To ale takΘ p°edpoklßdß nastavenφ ukazatele na 0 p°i jeho zru╣enφ.
    Pokud p°i dynamickΘm vytvo°enφ objektu pou╛ijeme odkaz, pak syntaxe ru╣enφ je jinß. Nßsledujφcφ p°φklad ukazuje tento problΘm:
    mojeStruktura& odkaz = *new mojeStruktura;
    odkaz.X = 100;
    delete &odkaz;
    Vidφme, ╛e ru╣enφ ukazatele v p°φpad∞ odkazu vy╛aduje pou╛φt adresov² operßtor. Odkaz nelze nastavit na 0 a tedy musφme b²t opatrnφ a neru╣it odkaz dvakrßt.
  12. Kdy╛ se vrßtφme k na╣emu programu adresß°e na╣ich znßm²ch, tak vidφme, ╛e program je chybn² (ve verzi, kde pracujeme s ukazateli a verzi s odkazy). Dynamicky alokovanß pam∞╗ nenφ uvol≥ovßna. Vytvo°enΘ pole struktur alokovan²ch v hromad∞ nenφ nikdy uvoln∞no z pam∞ti. Na konec na╣eho programu (za °ßdek getch();) je nutno vlo╛it:

  13. for (int i = 0; i < 3; i++)
      delete seznam[i];
    Nynφ ji╛ mßme sprßn² program. Projdeme pole ukazatel∙ a zru╣φme ka╛d² z nich samostatn∞.
  14. Kdy╛ volßme new k vytvo°enφ pole, pak m∙╛eme pou╛φt verzi new[] tohoto operßtoru. Nenφ d∙le╛itΘ znßt jak toto pracuje, ale je nutno v∞d∞t jak probφhß ru╣enφ dynamicky alokovan²ch polφ. Pou╛ijeme jednu z p°edchozφch ukßzek, na jejφ╛ konec p°idßme p°φkaz delete[]:

  15. char buff[80];
    char* velkyBuff = new char[4096];
    strcpy(buff, "Ahoj");
    strcpy(velkyBuff, "Hodn∞ dlouh² °et∞zec ......");
    // a pozd∞ji
    delete[] velkyBuff;
    Je zde pou╛it operßtor delete[]. Nebudeme se zde zab²vat technick²m popisem, ale m∙╛eme si b²t jisti, ╛e v╣echny prvky pole jsou zru╣eny. 
  16. Nßsledujφcφ konzolovß aplikace deklaruje dvourozm∞rnΘ pole dynamicky. V na╣em p°φpad∞ mß pole t°i °ßdky a p∞t sloupc∙, ale jeho rozm∞ry m∙╛eme snadno modifikovat. Dvourozm∞rnΘ pole je tvo°eno jednorozm∞rn²m polem s ukazateli na jednotlivΘ °ßdky, tj. s ukazateli op∞t na jednorozm∞rnß pole.

  17. #include <iostream.h>
    #include <conio.h>
    void display(long double **);
    void de_allocate(long double **);
    int m = 3;                               // PoΦet °ßdk∙.
    int n = 5;                               // PoΦet sloupc∙.
    int main(int argc, char **argv) {
       long double **data;
       data = new long double*[m];           // Krok 1: Nastavenφ °ßdk∙.
       for (int j = 0; j < m; j++)
          data[j] = new long double[n];      // Krok 2: Nastavenφ sloupc∙
       for (int i = 0; i < m; i++)
          for (int j = 0; j < n; j++)
              data[i][j] = i + j;            // Inicializace pole
       display(data);
       de_allocate(data);
       getch();
       return 0;
    }
    void display(long double **data) {
       for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++)
                 cout << data[i][j] << " ";
           cout << "\n" << endl;
           }
       }
    void de_allocate(long double **data) {
       for (int i = 0; i < m;  i++)
           delete[] data[i];                 // Krok 1: Zru╣enφ sloupc∙
       delete[] data;                        // Krok 2: Zru╣enφ °ßdk∙
    }
    Sna╛te se pochopit, jak tento program pracuje. Zm∞≥te tento program tak, aby rozm∞ry pole bylo mo╛no zadat a╛ p°i spu╣t∞nφ programu.
  18. HlaviΦkov² soubor ctype.h obsahuje °adu funkcφ testujφcφch znaky (isalnum, isalpha, isdigit, islower a dal╣φ) a funkcφ p°evßd∞jφcφch znaky (toascii, tolower, toupper, atd.). Seznamte se s jejich pou╛φvßnφm (pomocφ nßpov∞dy).

Zßkladnφ pravidla pro pou╛φvßnφ ukazatel∙ a dynamickΘ alokace pam∞ti:

18. Ukazatele II