17. Ukazatele I
  1. Ukazatele d∞lφme do dvou zßkladnφch kategoriφ: ukazatele na data (objekty) a ukazatele na funkce. Oba dva typy ukazatel∙ jsou specißlnφ objekty, kterΘ obsahujφ adresy v pam∞ti. Oba dva typy ukazatel∙ majφ r∙znΘ vlastnosti, ·Φely pou╛itφ a pravidla zachßzenφ. Obecn∞ °eΦeno, ukazatelΘ na funkce se pou╛φvajφ pro p°φstup k funkcφm a pro p°edßvßnφ funkcφ jako parametr∙ jin²m funkcφm. S t∞mito ukazateli nenφ mo╛nΘ provßd∞t ╛ßdnΘ aritmetickΘ operace. Ukazatele na objekty m∙╛eme inkrementovat a dekrementovat tak, jak je to p°i prohlφ╛enφ polφ nebo slo╛it∞j╣φch struktur pot°eba.

  2. Na ukazatel na funkci se m∙╛eme dφvat jako na adresu, kde je uchovßn provediteln² k≤d funkce, tj. na adresu, kam je p°i volßnφ funkce p°edßno °φzenφ. Ukazatel na funkci mß typ "ukazatel na funkci s urΦit²m poΦtem a typy parametr∙ a vracejφcφ jist² typ" (v jazyce C na parametrech nezßle╛φ). Nap°. po deklaraci
    void (*fce)(int);
    je fce ukazatel na funkci s parametrem typu int, kterß nic nevracφ.
  3. Nßsledujφcφ konzolovß aplikace ukazuje pou╛itφ ukazatele na funkci. Program se nejprve zeptß, zda se mß provßd∞t sΦφtßnφ nebo nßsobenφ. Podle tΘto odpov∞di vlo╛φ do prom∞nnΘ operace ukazatel na funkci seΦti nebo na funkci nasob. Dßle zadßme dv∞ Φφsla, kterß se pou╛ijφ jako parametry vybranΘ funkce.

  4. int secti(int a, int b)
    {
      return a+b;
    }
    int nasob(int a, int b)
    {
      return a*b;
    }
    int main(int argc, char **argv)
    {
      int (*operace) (int, int);
      int x, y, volba;
      cout << "Budeme SΦφtat (1) nebo Nßsobit (2) ?";
      do {
        cin >> volba;
      } while (volba != 1 && volba != 2);
      if (volba == 1) operace = secti;
      if (volba == 2) operace = nasob;
      cout << "Zadej dv∞ celß Φφsla: ";
      cin >> x >> y;
      cout << "V²sledek je " << operace(x, y) << endl;;
      getch();
      return 0;
    }
    S pou╛itφm ukazatele na funkci vytvo°te program vypisujφcφ tabulku malΘ nßsobilky nebo obdobnou tabulku sΦφtßnφ. V²pis celΘ tabulky °e╣te jako funkci (ukazatel na funkci provßd∞jφcφ operaci p°edßvejte jako parametr funkce).
  5. V∞t╣inou se budeme ale zab²vat ukazateli na data. Ukazatel na data musφ b²t deklarovßn tak, aby ukazoval na n∞jak² konkrΘtnφ typ, a to i v p°φpad∞, ╛e tento typ je void (co╛ vlastn∞ znamenß ukazatel na cokoliv). Je-li typ libovoln² p°eddefinovan² nebo u╛ivatelem definovan² typ (vΦetn∞ void), potom deklarace

  6. typ *ptr;            // pozor - neinicializovan² ukazatel
    deklaruje objekt ptr typu "ukazatel na typ". Ne╛ zaΦneme ukazatel pou╛φvat, musφme jej nejprve inicializovat. Prßzdn² ukazatel (ukazatel, kter² na nic neukazuje) by m∞l obsahovat adresu, u kterΘ je zaruΦeno, ╛e se bude li╣it ode v╣ech platn²ch adres v danΘm programu (adresa 0). Pro lep╣φ Φitelnost program∙ je tato adresa (prßzdn² ukazatel) oznaΦena symbolickou konstantou NULL (je definovßna nap°. v stdlib.h). V╣echny ukazatele je mo╛nΘ testovat na rovnost Φi nerovnost s hodnotou NULL.
    Ukazatel typu "ukazatel na void" nesmφ b²t zam∞≥ovßn s prßzdn²m ukazatelem. P°i p°i°azovßnφ hodnot ukazatel∙ je nutno, aby ukazatelΘ byly stejnΘho typu, nebo aby jeden z nich byl typu "ukazatel na void". Jinak je nutno provΘst p°etypovßnφ.
  7. Podφvejme se na p°φklad. P°edpoklßdejme, ╛e mßme pole prvk∙ typu int. JednotlivΘ prvky pole m∙╛eme zp°φstup≥ovat pomocφ operßtoru indexace. To ji╛ znßme z d°φv∞j╣ka.

  8. int pole[] = {5, 10, 15, 20, 25};
    int promenna = pole[3];        // hodnota 20
    TotΘ╛ m∙╛eme provΘst pomocφ ukazatele:
    int pole[] = {5, 10, 15, 20, 25};
    int* ptr = pole;
    int promenna = ptr[3];
    V tomto p°φklad∞ adresa pam∞ti zaΦßtku pole je p°i°azena ukazateli ptr. Tento ukazatel je ukazatelem na datov² typ int (p°i deklaraci je pou╛it symbol *). M∙╛eme deklarovat ukazatel na libovoln² celoΦφseln² datov² typ, stejn∞ jako na objekty (struktury nebo t°φdy). Po p°i°azenφ, ukazatel obsahuje pam∞╗ovou adresu zaΦßtku pole a tedy ukazuje na pole (jmΘno prom∞nnΘ pole pou╛itΘ bez operßtoru indexace, vracφ adresu prvnφho prvku pole).
    V tomto p°φpad∞ m∙╛eme pro p°φstup k poli pou╛φvat ukazatel i jmΘno pole. U dynamick²ch objekt∙ je mo╛no pou╛φt pouze ukazatel (viz dßle).
  9. Aritmetika ukazatel∙ je omezena na sΦφtßnφ, odΦφtßnφ a porovnßvßnφ ukazatel∙. AritmetickΘ operace na ukazatelφch typu "ukazatel na typ" berou v ·vahu dΘlku typu typ, tzn. poΦet slabik pot°ebn²ch na uchovßnφ objektu typu typ. P°i provßd∞nφ aritmetick²ch operacφ se p°edpoklßdß, ╛e ukazatel ukazuje do pole objekt∙. Je-li tedy ukazatel deklarovßn jako ukazatel na objekt typu typ, potom p°iΦtenφ celoΦφselnΘ hodnoty k tomuto ukazateli jej posune o tento poΦet objekt∙ typu typ. Mß-li typ typ velikost 10 slabik, potom p°iΦtenφm hodnoty 5 k tomuto ukazateli jej posuneme o 50 slabik v pam∞ti dßle. Rozdφlem dvou ukazatel∙ je poΦet prvk∙ pole, kterΘ navzßjem odd∞lujφ tyto dva ukazatele. Rozdφl ukazatel∙ mß smysl pouze v p°φpad∞, kdy oba ukazujφ do stejnΘho pole.

  10. Hodnotu ukazatele je mo╛nΘ p°evΘst na hodnotu jinΘho typu ukazatele za pomocφ mechanismu p°etypovßnφ:
    char *str;
    int *ip;
    str = (char *) ip;
    Obecn∞ platφ, ╛e operßtor p°etypovßnφ (typ *) p°evßdφ dan² ukazatel na typ "ukazatel na typ".
  11. V╣echny p°φklady konzolov²ch aplikacφ, se kter²mi jsme se zatφm seznßmili pou╛φvajφ lokßlnφ alokaci objekt∙. Tzn. pam∞╗ po╛adovanß pro prom∞nnou nebo objekt je zφskßna ze zßsobnφku programu. V╣echnu pam∞╗, kterou program pot°ebuje pro lokßlnφ prom∞nnΘ, volßnφ funkcφ apod. bere ze zßsobnφku. Tato pam∞╗ je alokovßna, kdy╛ je zapot°ebφ a uvol≥ovßna, kdy╛ ji╛ zapot°ebφ nenφ. To obvykle nastßvß, kdy╛ program vstupuje do funkce nebo jinΘho lokßlnφho bloku k≤du. Pam∞╗ pro v╣echny lokßlnφ prom∞nnΘ funkce je alokovßna p°i vstupu do funkce. P°i v²stupu z funkce, je v╣echna pam∞╗ alokovanß funkcφ uvoln∞na. Toto probφhß automaticky a nemßme mo╛nost urΦit jak a kdy pam∞╗ bude uvoln∞na.

  12. Lokßlnφ alokace mß v²hody a nev²hody. V²hodou je, ╛e pam∞╗ m∙╛e b²t v zßsobnφku alokovßna velmi rychle. Nev²hodou je, ╛e zßsobnφk mß pevnou velikost a nem∙╛e b²t zm∞n∞n za b∞hu programu. Pokud nß╣ b∞╛φcφ program p°eΦerpß kapacitu zßsobnφku, pak je program ukonΦen chybou.
    Pro prom∞nnΘ standardnφch datov²ch typ∙ a malß pole je lokßlnφ alokace vhodn²m °e╣enφm. Kdy╛ ale zaΦneme pou╛φvat velkß pole, struktury a t°φdy, pak budeme pot°ebovat dynamickou alokaci v hromad∞.
    Hromada zahrnuje v╣echnu volnou operaΦnφ pam∞╗ poΦφtaΦe a volnΘ mφsto na disku (pou╛itΘ pro odklßdacφ soubory). Velikost tΘto pam∞ti b²vß obvykle asi 100 Mslabik. Hromada je tedy podstatn∞ v∞t╣φ ne╛ zßsobnφk. S dynamicky alokovanou pam∞tφ se ale h∙°e pracuje a je to takΘ pomalej╣φ. Neobejdeme se ale bez nφ.
    V p°edchozφch kapitolßch jsme se zab²vali konzolovou aplikacφ udr╛ujφcφ adresß° na╣ich znßm²ch. P°i lokßlnφ alokaci struktury jsme pou╛φvali p°φkazy:
    adresar zaznam;
    strcpy(zaznam.jmeno = "Karel");
    strcpy(zaznam.prijmeni = "Novak");
    // atd.
    P°i dynamickΘ alokaci pou╛φvßme operßtor new a v tomto p°φpad∞ bychom zapsali:
    adresar* zaznam;
    zaznam = new adresar;
    strcpy(zaznam->jmeno = "Karel");
    strcpy(zaznam->prijmeni = "Novak");
    // atd.
    Prvnφ °ßdek deklaruje ukazatel na strukturu adresar. Dal╣φ °ßdek inicializuje tento ukazatel vytvo°enφm novΘ dynamickΘ instance struktury adresar. Takto vytvß°φme a zp°φstup≥ujeme objekty. V dal╣φch p°φkazech vidφme nahrazenφ operßtoru p°φmΘho selektoru slo╛ky (.) operßtorem nep°φmΘho selektoru slo╛ky (->).
    Dynamicky vytvß°enΘ pole struktur vy╛aduje vφce prßce. V lokßlnφ verzi pou╛ijeme nap°.
    adresar seznam[3];
    seznam[0].pcs = 53002;
    zatφmco v dynamickΘ verzi je nutmo postupovat takto:
    adresar* seznam[3];
    for (int i = 0; i < 3; i++)
      seznam[i] = new adresar;
    seznam[0]->pcs = 53002;
    Vidφme, ╛e musφme vytvo°it novou instanci struktury samostatn∞ pro ka╛d² prvek pole. P°φstup k datov²m slo╛kßm pole provßdφme operßtorem indexace a operßtorem nep°φmΘho selektoru slo╛ky.
  13. Operßtory & a * jsou operßtory odkazu (reference) a dereference. Nap°.

  14. &typov²_v²raz
    p°evßdφ typov²_v²raz na ukazatel na typov²_v²raz. V╣imn∞te si, ╛e identifikßtory n∞kter²ch objekt∙ (nap°. jmΘna funkcφ a jmΘna polφ) jsou v jistΘm kontextu automaticky p°evedeny na typ "ukazatel na objekt". Operßtor & je mo╛nΘ s takov²mito objekty pou╛φvat, ale jeho v²skyt je vlastn∞ zbyteΦn² (a matoucφ). Operßtor & °φkß p°ekladaΦi "Dej m∞ adresu prom∞nnΘ a ne obsah prom∞nnΘ".
    Ve v²razu
    * typov²_v²raz
    musφ b²t operand typov²_v²raz typu "ukazatel na typ", kde typ je libovoln² typ. V²sledek dereference je typ typ.
  15. Pokuste se urΦit co d∞lajφ nßsledujφcφ p°φkazy:

  16. int i, *ukazatel = &i;
    cin >> i;
    cout << ukazatel << endl << *ukazatel << endl;
  17. P°edpoklßdejte deklarace:

  18. int promenna;
    int *ukazatel = &promenna;
    int **ukazatel_na_ukazatel = &ukazatel;
    Co provßd∞jφ nßsledujφcφ p°φkazy:
    **ukazatel_na_ukazatel = 10; *ukazatel = 10;
    promenna = 10; *ukazatel_na_ukazatel = &promenna;
  19. Neinicializovan² ukazatel obsahuje, stejn∞ jako jinß neinicializovanß prom∞nnß, nßhodnou hodnotu. Pokus o pou╛itφ neinicializovanΘho ukazatele m∙╛e zp∙sobit havßrii programu. V mnoha p°φpadech provßdφme souΦasnou deklaraci a inicializaci ukazatele. Nap°.

  20. adresar* pom = 0;
    Pokud se pokusφme pou╛φt ukazatel NULL (ukazatel nastaven² na NULL nebo nulu), je automaticky procesorem detekovßn pokus o nedovoln² p°φstup k pam∞ti (program je ukonΦen, ale nemohou nastat r∙znΘ nßhodnΘ chyby).
    Pov╣imn∞te si je╣t∞ zßpisu operßtoru * (p°esn∞ji °eΦeno pou╛φvßnφ mezer p°ed a za *). Je jedno zda zapisujeme
    int* i;
    int *i;
    int * i;
    V╣echny tyto zßpisy jsou ekvivalentnφ a je jedno, kterou mo╛nost zvolφme. Je ale vhodnΘ jednu z t∞chto mo╛nostφ si vybrat a dodr╛ovat ji.
  21. Zadßnφ 4 z kapitoly 15 nynφ zm∞nφme na pou╛itφ dynamickΘ alokace. Nß╣ program se zm∞nφ takto (hlaviΦkov² soubor z∙stane beze zm∞ny):

  22. #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 {
        cout << "JmΘno: ";
        cin.getline(seznam[index]->jmeno, sizeof(seznam[index]->jmeno)-1);
        cout << "P°φjmenφ: ";
        cin.getline(seznam[index]->prijmeni,
                    sizeof(seznam[index]->prijmeni)-1);
        cout << "Ulice: ";
        cin.getline(seznam[index]->ulice, sizeof(seznam[index]->ulice)-1);
        cout << "M∞sto: ";
        cin.getline(seznam[index]->mesto, sizeof(seznam[index]->mesto)-1);
        cout << "PsΦ: ";
        char buff[10];
        cin.getline(buff, sizeof(buff)-1);
        seznam[index]->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 v p°edchozφm v²pisu zobrazeny Φerven∞. Je deklarovßno pole ukazatel∙, pro ka╛d² prvek pole je vytvo°ena instance, operßtory p°φmΘho selektoru slo╛ky jsou v cyklu nahrazeny operßtory nep°φmΘho selektoru slo╛ky a dvakrßt byl pou╛it operßtor dereference. Funkce zobrazZaznam z∙stala beze zm∞ny.
    Toto °e╣enφ nenφ ideßlnφ, bude je╣t∞ vylep╣eno.
  23. Parametry funkcφ v jazyku C jsou volßny hodnotou. To znemo╛≥uje funkci m∞nit hodnotu parametru tak, aby zm∞n∞nß hodnota byla pou╛itelnß mimo funkci, tzn. jazyk C nepou╛φvß parametry volanΘ odkazem. Tento problΘm lze vy°e╣it pomocφ ukazatel∙. Funkci p°edßme jako parametr adresu prom∞nnΘ, s jeji╛ hodnotou pak budeme pracovat. Nap°. nßsledujφcφ funkce zam∞≥uje hodnoty sv²ch parametr∙:

  24. void zamen(int *a, int *b)
    {
      int c = *a;
      *a = *b;
      *b = *c;
    }
    Funkci pak vyvolßme nap°. takto:
    int x = 10, y = 20;
    zamen(&x, &y);
    Upravte tuto funkci tak, aby v²m∞na prvk∙ prob∞hla pouze kdy╛ a > b a aby funkΦnφ hodnota informovala zda k v²m∞n∞ do╣lo. Vyzkou╣ejte v n∞jakΘm programu.
  25. HlaviΦkov² soubor stdlib.h obsahuje °adu r∙zn²ch funkcφ. Jsou zde nap°. funkce provßd∞jφcφ celoΦφselnΘ d∞lenφ a urΦujφcφ maximßlnφ a minimßlnφ hodnotu ze dvou hodnot (div, ldiv, max a min). Dßle tu jsou funkce provßd∞jφcφ p°evody typ∙ (atoi, atol, ecvt, strtod a °ada dal╣φch). Jsou zde takΘ funkce pro generovßnφ nßhodn²ch Φφsel, s kter²mi jsme se ji╛ seznßmili. Seznamte se s t∞mito funkcemi pomocφ ukßzkov²ch program∙ v nßpov∞d∞.

NovΘ pojmy:


 
17. Ukazatele I