Jazyk C - smerník (pointer): Rozdiel medzi revíziami

Z Kiwiki
Skočit na navigaci Skočit na vyhledávání
 
(5 medziľahlých úprav od 2 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
 
[[Kategória:Študijné materiály]]
 
[[Kategória:Študijné materiály]]
[[Kategória:Programovanie]]
 
 
[[Kategória:Informatika]]
 
[[Kategória:Informatika]]
 
Smerníky sú najdôležitejšou časťou jazyka C, ale aj najužitočnejšou a najzradnejšou. Pri správnom použití vedia veľmi uľahčiť život programátora, ale pri nesprávnom, nastane strach a hrôza. Preto poďme pekne poporiadku.
 
Smerníky sú najdôležitejšou časťou jazyka C, ale aj najužitočnejšou a najzradnejšou. Pri správnom použití vedia veľmi uľahčiť život programátora, ale pri nesprávnom, nastane strach a hrôza. Preto poďme pekne poporiadku.
 
{{Skripta_ZI}}
 
{{Skripta_ZI}}
 +
__TOC__
 
=Definícia smerníka=
 
=Definícia smerníka=
 
;Smerník: (anglicky pointer, česky ukazovatel) je obyčajná premenná, ktorá obsahuje adresu inej premennej.
 
;Smerník: (anglicky pointer, česky ukazovatel) je obyčajná premenná, ktorá obsahuje adresu inej premennej.
Riadok 25: Riadok 25:
 
==Súvislosť operátora referencia (&) a dereferencia (*)==
 
==Súvislosť operátora referencia (&) a dereferencia (*)==
 
definujme nasledujúce:
 
definujme nasledujúce:
 +
<source lang="c">
 
  int *ui;
 
  int *ui;
 
  int i;
 
  int i;
 +
</source>
 
Tieto 2 premenné nemajú nič spoločné. ui je smerník na int, i je premenná typu int.
 
Tieto 2 premenné nemajú nič spoločné. ui je smerník na int, i je premenná typu int.
 
;<nowiki>*ui</nowiki>: hodnota, na adrese ui (ui je ukazovatel na int)
 
;<nowiki>*ui</nowiki>: hodnota, na adrese ui (ui je ukazovatel na int)
Riadok 92: Riadok 94:
 
Pole je množina prvkov rovnakého typu, ktorá je označená spoločným názvom. Pole je uložené v operačnej pamäti spojite, pričom prvý prvok (index 0) má najnižšiu a posledný prvok najvyššiu adresu. Medzi poľami a smerníkmi existuje úzka súvislosť. Práca s poľom a jeho prvkami formou používania názvu poľa a indexov je jednoduchšia, názornejšia a možno povedať apriori akceptovateľná. Používanie smerníkov vedie k rýchlejšiemu behu programu, umožňuje dynamickú alokáciu pamäte a môže redukovať celkové požiadavky na pamäť.
 
Pole je množina prvkov rovnakého typu, ktorá je označená spoločným názvom. Pole je uložené v operačnej pamäti spojite, pričom prvý prvok (index 0) má najnižšiu a posledný prvok najvyššiu adresu. Medzi poľami a smerníkmi existuje úzka súvislosť. Práca s poľom a jeho prvkami formou používania názvu poľa a indexov je jednoduchšia, názornejšia a možno povedať apriori akceptovateľná. Používanie smerníkov vedie k rýchlejšiemu behu programu, umožňuje dynamickú alokáciu pamäte a môže redukovať celkové požiadavky na pamäť.
  
Meno poľa je v skutočnosti symbolická konštanta, ktorej hodnota je smerník na umiestneni prvého prvku poľa. Teda pre pole data[] identifikátor data znamená to isté ako &data[0], data + i to isté ako &data[i], čo môžeme zapísať v tvare
+
Meno poľa je v skutočnosti symbolická konštanta, ktorej hodnota je smerník na umiestnenie prvého prvku poľa. Teda pre pole data[] identifikátor data znamená to isté ako &data[0]; data + i to isté ako &data[i], čo môžeme zapísať v tvare
 
+
<source lang="c">
 
  data + i == &data[i]
 
  data + i == &data[i]
 
+
</source>
Aplikáciou operátora indirekcie * na obe strany výrazu dostaneme
+
Aplikáciou operátora dereferencia * na obe strany výrazu dostaneme rovnosť
  
 
  <nowiki>*</nowiki>(data+i) == data[i]
 
  <nowiki>*</nowiki>(data+i) == data[i]
Riadok 138: Riadok 140:
 
==Alokácia pamäti==
 
==Alokácia pamäti==
 
Definujme si smerník na int nasleddovne:
 
Definujme si smerník na int nasleddovne:
 +
<source lang="c">
 
  int *ui;
 
  int *ui;
 +
</source>
 
Do takto novo vytvorenej premennej (resp. smerníka) nemôžeme priradiť žiadnu hodnotu, pretože pre daný smerník sme nealokovali miesto v pamäti, kde bude ukladať svoj obsah. Nasledujúci zápis je '''chybný'''!
 
Do takto novo vytvorenej premennej (resp. smerníka) nemôžeme priradiť žiadnu hodnotu, pretože pre daný smerník sme nealokovali miesto v pamäti, kde bude ukladať svoj obsah. Nasledujúci zápis je '''chybný'''!
 +
<source lang="c">
 
  *ui=3; // !!!chyba
 
  *ui=3; // !!!chyba
 +
</source>
 
Pokúšame sa do smerníka uložiť hodnotu 3, ale túto hodnotu nemáme ešte kde uložiť. Potrebujeme si alokovať miesto.
 
Pokúšame sa do smerníka uložiť hodnotu 3, ale túto hodnotu nemáme ešte kde uložiť. Potrebujeme si alokovať miesto.
 
===Operátor new===
 
===Operátor new===
Riadok 149: Riadok 155:
 
  int *uk=new int; // zápis v jednom riadku
 
  int *uk=new int; // zápis v jednom riadku
 
</source>
 
</source>
 +
 
==Dealokácia pamäti==
 
==Dealokácia pamäti==
 
Pamäť alokovaná operátorom new zostáva rezervovaná až do konca behu programu. Počas behu programu je môžeme uvoľniť použitím operátora '''delete'''. Použitie:
 
Pamäť alokovaná operátorom new zostáva rezervovaná až do konca behu programu. Počas behu programu je môžeme uvoľniť použitím operátora '''delete'''. Použitie:
Riadok 157: Riadok 164:
 
==Polia a ukazovatele==
 
==Polia a ukazovatele==
 
Pri alokácii pamäti pre ukazovateľ si nemusíme alokovať len pre daný dátový typ, ale môžeme si alokovať ľubovoľne veľkú oblasť pamäti (samozrejme s obmedzením veľkosti dostupnej pamäti). Majme:  
 
Pri alokácii pamäti pre ukazovateľ si nemusíme alokovať len pre daný dátový typ, ale môžeme si alokovať ľubovoľne veľkú oblasť pamäti (samozrejme s obmedzením veľkosti dostupnej pamäti). Majme:  
 +
<source lang="c">
 
  int *ui=new int[10];
 
  int *ui=new int[10];
 
+
</source>
 
*ui je ukazovateľ na typ int.  
 
*ui je ukazovateľ na typ int.  
 
*Alokovali sme si pamäť o veľkosti 10 integerov
 
*Alokovali sme si pamäť o veľkosti 10 integerov
Riadok 174: Riadok 182:
  
 
'''Dealokovanie ui'''
 
'''Dealokovanie ui'''
 
+
<source lang="c">
 
  delete []ui;
 
  delete []ui;
 
+
</source>
 
==Viacrozmerné dynamicky alokovateľné polia==
 
==Viacrozmerné dynamicky alokovateľné polia==
  
 
Deklarujme nasledovné:
 
Deklarujme nasledovné:
int *pp=new int[4];
+
<source lang="c">
 
+
  int *pp=new int[4];
 +
</source>
 
Vieme, že nasledujúce výrazy sú ekvivalentné:
 
Vieme, že nasledujúce výrazy sú ekvivalentné:
 +
<source lang="c">
 
  pp[0]==*pp
 
  pp[0]==*pp
 
  pp[1]==*(pp+1)
 
  pp[1]==*(pp+1)
 
  pp[2]==*(pp+2)
 
  pp[2]==*(pp+2)
 
  pp[3]==*(pp+3)
 
  pp[3]==*(pp+3)
 
+
</source>
 
===Dvojrozmerné dynamicky alokované pole===
 
===Dvojrozmerné dynamicky alokované pole===
 
Definujme nasledovné:
 
Definujme nasledovné:
 +
<source lang="c">
 
  int n=10;
 
  int n=10;
 
  int **M;
 
  int **M;
 +
</source>
 
M je smerník na smerník na int.  
 
M je smerník na smerník na int.  
  
 
Alokujme pre M pole pozostávajúce z n smerníkov na celé čísla:
 
Alokujme pre M pole pozostávajúce z n smerníkov na celé čísla:
 +
<source lang="c">
 
  M=new int*[n];
 
  M=new int*[n];
 +
</source>
 
Po vykonaní tohto výrazu je M pole smerníkov na int. Do tohoto poľa ešte nemôžeme nič ukladať, pretože bunky tohto poľa sú smerníky a ešte sme si nealokovali pre ne miesta, kde budú uložené hodnoty. Pre každý prvok poľa M (prvky sú smerníky na int) si alokujme miesto o n integeroch:
 
Po vykonaní tohto výrazu je M pole smerníkov na int. Do tohoto poľa ešte nemôžeme nič ukladať, pretože bunky tohto poľa sú smerníky a ešte sme si nealokovali pre ne miesta, kde budú uložené hodnoty. Pre každý prvok poľa M (prvky sú smerníky na int) si alokujme miesto o n integeroch:
 
<source lang="c">  
 
<source lang="c">  
Riadok 320: Riadok 334:
 
</source>
 
</source>
  
=Odkazy=
+
=Referencie=
 
<references/>
 
<references/>

Aktuálna revízia z 15:36, 26. máj 2020

Smerníky sú najdôležitejšou časťou jazyka C, ale aj najužitočnejšou a najzradnejšou. Pri správnom použití vedia veľmi uľahčiť život programátora, ale pri nesprávnom, nastane strach a hrôza. Preto poďme pekne poporiadku.

Definícia smerníka

Smerník
(anglicky pointer, česky ukazovatel) je obyčajná premenná, ktorá obsahuje adresu inej premennej.

S použitím smerníkov sa stretávame pri spracovaní

  • premenných,
  • polí,
  • reťazcov,
  • parametrov funkcií,
  • dynamických objektov,
  • funkcií (smerník na funkciu).

Definícia v jazyku C

  • Smerník ako dátový typ neexistuje
  • So smerníkom sa vždy spája nejaká premenná
    • smerník na int, smerník na double, smerník na char, atď. ...
  • Definujme ui ako ukazovateľ na typ int
    • int *ui;
  • Definujme uf ako ukazovateľ na typ float
    • float *uf;

Súvislosť operátora referencia (&) a dereferencia (*)

definujme nasledujúce:

 int *ui;
 int i;

Tieto 2 premenné nemajú nič spoločné. ui je smerník na int, i je premenná typu int.

*ui
hodnota, na adrese ui (ui je ukazovatel na int)
&i
adresa premennej i (i je premenná typu int)
ui
adresa v pamäti, kde je uložená hodnota smerníka

Príklad 1[1]

Bez použitia premennej x priamo pri výpočte zvýšte jej hodnotu o 10.

 1 #include<stdio.h>
 2 int main(void)
 3 {
 4     int x = 0 ; //do x sme vložili hodnotu 0
 5     int* smernik; // !!!smerník na int alebo smerník typu int
 6 
 7     smernik = &x; // do smerníka sme uložili adresu x
 8 
 9     printf("npred pricitanim:x = %d",x);
10     *smernik = *smernik + 10;
11             // na adresu ,kde je x ,sme pričítali 10
12             // * dereferenčný operátor
13             // & referenčný operátor
14 
15     printf("npo pricitani: x = %d",x);
16     getchar();
17 }

Analýza programu

Prvé dva riadky by mali byť jasné. Máme premennú typu int, preto potrebujeme vytvoriť smerník typu int.

Pozrime sa bližšie na riadok č. 5 :

smernik = &x;

Znaku & sa hovorí tiež referenčný operátor. Vďaka nemu získame adresu premennej x. Keby sme napísali smerník = x, tak by sme nezískali veľa. Do smerníka by sme si uložili namiesto adresy hodnotu premennej. V našom prípade by to bola 0. Takže smerník by nám ukazoval na adresu 0 a nie na adresu, kde je uložená hodnota premennej x.

Riadok č. 10:

*smernik = *smernik + 10

Znaku * sa hovorí tiež dereferenčný operátor. Vďaka tomuto operátoru získame obsah na adrese, na ktorú ukazuje. Využili sme to na ľavej strane výrazu: *smernik = *smernik + 10

Vďaka dereferenčnému operátoru vieme tiež zapísať hodnoty na adresu kam ukazuje. Využili sme to na pravej strane : *smernik = *smernik + 10.

Riadok *smernik = *smernik + 10; robí to isté ako x = x + 10;

Operátor & sa používa pre premenné. Konštanty a výrazy nemajú adresu, preto tento operátor s nimi nemôžme využiť.

Príklad chybných príkazov:

 smernik = &10 ; // chyba 10 je konštanta => nemá adresu
 smernik = &(x + 10) ; // chyba (x+10) je výraz
 smernik = 1 ; // už sme spomínali, smerník by teraz ukazoval niekam na adresu 1

Nulový smerník NULL

  • Smerník nemôže mať hodnotu 0 (znamenalo by to, že odkazuje na adresu 0), ale môže mať hodnotu NULL.
  • Hodnota NULL je definovaná ako
    • #define NULL 0
    • #define NULL ((void *) 0)
  • Ak má smerník hodnotu NULL, tak ukazuje na nič

Z predchádzajúceho vyplýva jedna vec: smerník môžeme definovať aj na typ void

Smerník a pole

Pole je množina prvkov rovnakého typu, ktorá je označená spoločným názvom. Pole je uložené v operačnej pamäti spojite, pričom prvý prvok (index 0) má najnižšiu a posledný prvok najvyššiu adresu. Medzi poľami a smerníkmi existuje úzka súvislosť. Práca s poľom a jeho prvkami formou používania názvu poľa a indexov je jednoduchšia, názornejšia a možno povedať apriori akceptovateľná. Používanie smerníkov vedie k rýchlejšiemu behu programu, umožňuje dynamickú alokáciu pamäte a môže redukovať celkové požiadavky na pamäť.

Meno poľa je v skutočnosti symbolická konštanta, ktorej hodnota je smerník na umiestnenie prvého prvku poľa. Teda pre pole data[] identifikátor data znamená to isté ako &data[0]; data + i to isté ako &data[i], čo môžeme zapísať v tvare

 data + i == &data[i]

Aplikáciou operátora dereferencia * na obe strany výrazu dostaneme rovnosť

*(data+i) == data[i]

Ak sa i zvyšuje o 1, referencované miesto pamäte sa zvyšuje o počet bytov (dĺžku), daný veľkosťou daného typu.

Jazyk C netestuje hranice polí, čo umožňuje zápis i čítanie mimo rozsahu poľa, pravda, so všetkými z toho vyplývajúcimi dôsledkami. Za kontrolu dodržania rozsahu poľa je preto zodpovedný programátor.

Použitie smerníka a poľa si ilustrujeme časťou programu

 int *p, data[10];
 p=&data[0];

potom *p = data[0] a my môžeme namiesto data[0] v ľubovoľnom výraze používať *p, resp. pre prvok data[i] *(p + i), lebo

 p + i  == &data[i]
 *(p + i) == data[i]

Z uvedeného by sa mohlo zdať, že identifikátory data a p sú ekvivalentné. Je tu však jeden podstatný rozdiel, ktorý spočíva v tom, že data je symbolická smerníková konštanta, zatiaľ čo p je smerníková premenná. Preto operácie ako p++, p = data sú prípustné, zatiaľ čo data++, resp. data = p nie sú povolené. Majme napr.

 float array[100];
 fp = &array[0];

potom fp++ ukazuje na array[1]. Podobne fp = fp + 10 bude ukazovať na array[10]. Predpokladajme, že

  fp1 = &array[100];

Potom fp-- ukazuje na array[99], resp. fp1 = fp1 - 10 alebo fp1 -= 10 bude ukazovať na array[90].

Na smerníky (ak sa vzťahujú na prvky toho istého poľa) je možné aplikovať aj 4 relačné operátory (<, <=, >, >= ) a testovať ich rovnosť, resp. nerovnosť ( ==, != ). Použitie týchto operátorov ilustruje následujúci príklad pre nájdenie najväčšieho prvku v poli data.

Viac o smerníkovej aritmetike nájdete v [2].

Dynamické prideľovanie pamäti

Pomocou mechanizmu smerníkov si dokážeme alokovať orčitú veľkosť pamäti vo svojom programe.

Alokácia pamäti

Definujme si smerník na int nasleddovne:

 int *ui;

Do takto novo vytvorenej premennej (resp. smerníka) nemôžeme priradiť žiadnu hodnotu, pretože pre daný smerník sme nealokovali miesto v pamäti, kde bude ukladať svoj obsah. Nasledujúci zápis je chybný!

 *ui=3; // !!!chyba

Pokúšame sa do smerníka uložiť hodnotu 3, ale túto hodnotu nemáme ešte kde uložiť. Potrebujeme si alokovať miesto.

Operátor new

operátor new (namesto fukcie malloc jazyka C používame operátor new jazyka C++ kvôli jednoduchšej syntaxi) alokuje pre smerník potrebné miesto v pamäti. Pri neúspechu vracia hodnotu NULL.

 int *ui;  // definícia smerníka ui na int
 ui=new int;  // pre smerník ui si alokujeme miesto o veľkosti 1 int
 int *uk=new int; // zápis v jednom riadku

Dealokácia pamäti

Pamäť alokovaná operátorom new zostáva rezervovaná až do konca behu programu. Počas behu programu je môžeme uvoľniť použitím operátora delete. Použitie:

delete ui;
delete uk;

Polia a ukazovatele

Pri alokácii pamäti pre ukazovateľ si nemusíme alokovať len pre daný dátový typ, ale môžeme si alokovať ľubovoľne veľkú oblasť pamäti (samozrejme s obmedzením veľkosti dostupnej pamäti). Majme:

 int *ui=new int[10];
  • ui je ukazovateľ na typ int.
  • Alokovali sme si pamäť o veľkosti 10 integerov

Popis:

  • *ui predstavuje hodnotu prvého alokovaného integera
  • *(ui+1) predstavuje druhého alokovaného integera
  • Môžme teda tentu smerníkpoužiť nasledovne
    • cin>>*ui>>*(ui+1);
    • cout<<*ui<<*(ui+1);
  • ui môžeme chápať ako pole celých čísel o veľkosti 10
  • Ak je teda ui pole celých čísel o veľkosti 10, môžeme k nemu pristupovať ako ku klasickému jednorozmernému poľu:
    • cin>>ui[0]>>ui[1];
    • cout<<ui[0]<<ui[1];

Dealokovanie ui

 delete []ui;

Viacrozmerné dynamicky alokovateľné polia

Deklarujme nasledovné:

 
   int *pp=new int[4];

Vieme, že nasledujúce výrazy sú ekvivalentné:

 
 pp[0]==*pp
 pp[1]==*(pp+1)
 pp[2]==*(pp+2)
 pp[3]==*(pp+3)

Dvojrozmerné dynamicky alokované pole

Definujme nasledovné:

 int n=10;
 int **M;

M je smerník na smerník na int.

Alokujme pre M pole pozostávajúce z n smerníkov na celé čísla:

 M=new int*[n];

Po vykonaní tohto výrazu je M pole smerníkov na int. Do tohoto poľa ešte nemôžeme nič ukladať, pretože bunky tohto poľa sú smerníky a ešte sme si nealokovali pre ne miesta, kde budú uložené hodnoty. Pre každý prvok poľa M (prvky sú smerníky na int) si alokujme miesto o n integeroch:

 
for(int i=0;i<n;i++)	
   M[i]=new int[n];

Takto sme vytvorili dvojrozmerné pole celých čísel o veľkosti n×n. Zaujímavosťou tohto riešenie je že n je premenná a jej hodnotu môžme v programe meniť. Pri statických poliach musel byť rozmer konštantný.

Celý kód ešte raz:

 
  int **M=new int*[n];	        // smerník na smerník  na int
  for(int i=0 ; i<n ; i++)	// M[] je pole smerníkov na typ int
    M[i]=new int[n];	        // každému prvku M[i] alokujeme pole integerov o veľkosti n

Príklad 2

Vytvorme program na nájdenie maximálneho prvku poľa[3]: Riešenie pomocou statických polí

 
#include<stdio.h>
int main(void)
{
    int i,j;
    int pocet;
    int max;

    int pole[100]; //predpokladáme, že maximum bude 100 čísel

    //vypýtame si počet zadávaných čísel

    printf("Zadajte pocet zadavanych cisel: ");
    scanf("%d",&pocet);

    //načítame a uložíme jednotlivé čísla do pola

    for(i = 0;i<pocet;i++)
    {
        printf("Zadajte %d. prvok pola: ",i+1);
        scanf("%d",&pole[i]); //teraz prístup cez index
    }

    //jednoduchý cyklus na nájdenie maxima
    max = pole[0];
    for(i = 1;i<pocet;i++)
    {
        if (max<pole[i])
        {
            max = pole[i];
        }
    }
    printf("n Maximalny prvok je : %d",max);
}

Riešenie pomocou dynamicky akokovaných polí

 
#include<stdio.h>
int main(void)
{
    int i,j;
    int pocet;
    int* max; // pozor!!! Tu je smerník!
    int *pole; //predpokladáme, že maximum bude 100 čísel

    //vypýtame si počet zadávaných čísel

    printf("Zadajte pocet zadavanych cisel: ");
    scanf("%d",&pocet);
    pole=new int[pocet];   // vytvoríme si presne také pole aké potrebujeme
    //načítame a uložíme jednotlivé čísla do pola

    for(i = 0;i<pocet;i++)
    {
        printf("Zadajte %d. prvok pola: ",i+1);
        scanf("%d",pole+i); //teraz prístup cez smerníkovú aritmetiku
        // ekvivalent je : scanf("%d",&pole[i]);
    }

    //jednoduchý cyklus na nájdenie maxima

    max = pole; //zmena!!!, ekvivalent je: *max = pole[0];
    for(i = 1;i<pocet;i++)
    {
        if (*max<pole[i]) //aj tu sa porovnávajú hodnoty!!!
        {
            max = pole+i; // ekvivalent: *max=pole[i];
        }
    }
    printf("n Maximalny prvok je : %d",*max);
}

Priamejšie riešenie by bolo, ak by sme namiesto smerníka *max použili premennú max:

 
#include<stdio.h>
int main(void)
{
    int i,j;
    int pocet;
    int max; 
    int *pole;

    printf("Zadajte pocet zadavanych cisel: ");
    scanf("%d",&pocet);
    pole=new int[pocet];  

    for(i = 0;i<pocet;i++)
    {
        printf("Zadajte %d. prvok pola: ",i+1);
        scanf("%d",&pole[i]);
    }

    max = pole[0];
    for(i = 1;i<pocet;i++)
    {
        if (max<pole[i]) 
        {
            max=pole[i];
        }
    }
    printf("n Maximalny prvok je : %d",max);
}

Referencie