
5. lekcia C (Pointery) základná škola
Napísal
libcosenior
, 25. júl 2012 12:33:40 | Naposledy aktualizovaný 5. jan 2014 14:28:07
Článok bol zobrazený 12387 krát
Článok bol zobrazený 12387 krát
5 Pointery
Pointer predstavuje adresu v pamäti a až na tejto adrese je ukrývana príslušná hodnota, na ktorú sme boli doposiaľ zvyknutí. Táto odlišná interpretácia obsahu premenej nás stavia pred nutnosť ako nejakým vhodným spôsobom prekladaču povedať, že hodnota premennej je adresa a nie už cieľová hodnota. To sa prevedie pomocou operátoru *.
Poznámka:
5.1 Základy práce s pointermi
Poznámka:
Poznámky:
int *p_i;
Je možné (a často sa to robí) uviesť definíciu premennej typu int a pointeru na typ int naraz, napr.:
int *p_i, i;
Z tejto možnosti vyplýva častá chyba pri definícii viac pointerov naraz:
int *p_i, p_j;
kde iba p_i je pointer na int a p_j je premenná typu int.
5.1.2 Práca s adresovými operátormi
Zatiaľ sme sa dozvedeli, ako získat hodnotu premennej, ktorej adresu (teda pointer na túto premennú) poznáme. K úplnej znalosti je teda potrebné sa naučit aj opačný spôsob, t. j. ako získat adresu premennej, ktorá uz existuje. Opät to nie je nič tažké, pretože adresa ľubovoľnej premennej sa dá získat pomocou referenčného operátora &, teda:
int i, *p_i = &i;
čo je definícia p_i a jeho súčasná inicializácia adresou premennej i alebo:
p_i = &i;
čo je priraďovací príkaz, ktorý v programe uskutoční priradenie adresy premennej i do premennej (pointeru) p_i.
Poznámka:
Pretože každá pointerova premenná je l-hodnota, je teda možné napísať:
*p_i = 1; // celkom v poriadku
*(p_i + 3) = 5; // podozrive, pokial p_i neukazuje na pole.
*(3 + k) = 6; // je to mozne, ale nespravne, pretoze zapisujeme na neznamu adresu v pamati
Operátor & je ale možné použit iba na premenne. Tie totiž majú vždy adresu, na rozdiel od konštant a výrazov, ktoré ju nemajú.
p_i = &i; /* spravne */
p_i = &(i + 3); /* chyba */
p_i = &15; /* chyba */
Poznámka:
Tu sa situácia trochu komplikuje, pretože je potrebné rozoznávať dva typy správnosti priradenia:
Pre všetky príklady platí definícia: int i, *p_i;
na adresu v p_i musí byť p_i inicializovaná.
Na nasledujúcich priradzovacích príkazoch budú ukázané niektoré možnosti práce s pointermi. Predpokladajme definíciu:
int i, *p_i1, *p_i2;
a ďalej predpokladajme, že premenná i leží na absolútnej adrese 10, p_i1 na adrese 20 a p_i2 na adrese 30.

Poznámky:
Program číta dve celé čísla a zobrazí väčšie z nich.
printf("Adresa i je %p, hodnota p_i je %p \n", &i, p_i);
Výpis adresy, na ktorú ukazuje pointer, použijeme najčastejšie pri ladení, keď si nie sme istí, či pointer ukazuje tam, kam má.
Pozor:
Znovu upozornujeme, že je treba dať si veľky pozor na tieto dve odlišnosti:
int i, *p_i = &i;
a
int i, *p_i;
p_i = &i;
5.1.5 Nulový pointer NULL
Tak ako v Pascale konštanta NIL, je aj v C podobná konštanta NULL. Je to symbolická konštanta definovaná v stdio.h ako napr.:
#define NULL 0 /* alebo ako #define NULL ((void *) 0) */
NULL je možné priradiť bez pretypovania všetkým typom pointerov (pointerom na ľubovolný typ) a používa sa na označenie, že tento pointer neukazuje na nič.
5.1.6 Konverzia pointerov
Všeobecne sa jej snažíme vyhýbať, pretože prináša množstvo problémov, napr. s pointerovou aritmetikou alebo s ukladaním niektorých dátovych typov na určité (párne) adresy v pamäti. Sú však standardné situácie - typicky prideľovanie dynamickej pamäti - pri ktorej je nutné konverziu pointerov používat. Pokiaľ sa nedá v iných než obvyklých situáciach konverzii pointerov vyhnúť, potom je dobré použiť explicitné pretypovanie. Spoľahnúť sa na implicitné pretypovanie je síce možne, ale nie je to vhodné.
Priklad:
char *p_c;
int *p_i;
p_c = p_i; /* nevhodne */
p_c = (char *) p_i; /* lepsie */
5.1.7 Zarovnavanie v pamäti
Pokiaľ pri konverzii pointerov dochádza k neočakávanej chybe, je vhodné skúsiť pretypovanie tam a späť a vypísať hodnoty pointerov, napr.:
printf("p_c pred konverziou %p \n", p_c);
p_i = (int *) p_c;
p_c = (char *) p_i;
printf("p_c po konverzii %p \n", p_c);
Odlišné výsledky, ktoré môžeme dostať, sú sposobené vďaka tomu, že niektoré kompilátory používajú taktiku, že určité dátove typy, napr. int sú uložené v pamäti od párnych adries. Pri pretypovaní pointeru s tým prekladač počíta a zaokrúhľuje prípadnú nepárnu adresu na najbližšiu nižšiu párnu. To má potom výhodu rýchlejšieho prístupu k týmto údajom, ale nevýhodu práve pri pointerovej konverii. Ďalšou nevýhodou je horšie využitie pamäte. Všeobecne sa dá povedať len to, že bez problémov je pointerová konverzia iba z vyšších (dlhších) dátovych typov na nižšie (kratšie), teda napr. pointer na long je možne bez problémov pretypovať na pointer na int alebo na char. Opačne to ale nemusí výjsť správne.
5.2 Pointery a funkcie
5.2.1 Volanie odkazom
Jednou z veľmi užitočných vlastností pointerov je, že umožňujú volanie parametrov "odkazom". Pointery v tomto prípade použijeme, keď chceme vo funkcii trvale zmeniť hodnotu skutočného parametra. V praxi to znamená, že nepredávame hodnotu premennej, ale adresu tejto premennej.
V C je toto "volanie odkazom" opäť volanie hodnotou, keď sa v stacku vytvorí lokálna kópia pre uloženie parametra - adresy skutočného parametra. Táto lokálna premenná síce zanikne s ukončením prislušnej funkcie, ale má tú vlastnosť, že je v nej uložený pointer, pomocou ktorého sa nepriamo zmenia údaje, ktoré nemajú s touto funkciou nič spoločného - boli definované vo vnútri tejto funkcie a nezanikajú s jej koncom. Teda výsledok je rovnaký ako pri skutočnom volaní odkazom v Pascale, ale postup spracovania iný. To čo v Pascale uskutočňoval automaticky kompilátor vďaka kľúčovému slovu VAR, to musíme v C urobiť sami - teda nepracovať s formálnym parametrom ako s normálnou premennou, ale ako s pointerom. Pre zjednodušenie bude však ďalej používaný termín volanie odkazom.
Poznámka:
Nejasnosti môžu vzniknúť až pri štúdiu jazyka C++ - objektovo orientovaného C - ktorý umožňuje skutočné volanie odkazom.
Príklad:
Toto je klasický príklad funkcie pre výmenu obsahov dvoch premenných.
int i = 5, j = 3;
vymen(&i, &j);
Pozor:
Program číta pomocou funkcie citaj_riadok() riadky z klávesnice a počita, koľko na ňom bolo medzier a malých pismen. Funkcia citaj_riadok() vracia hodnotu 1, keď na riadku boli nejaké znaky a hodnotu 0, ak bol riadok prázdny. Program skončí, ak prečíta prazdny riadok.
Možnosť, ako pouziť typ void, je definovať pointer na void, napr.
void *p_void;
Pointer p_void neukazuje na žiadny konkrétny typ, teda dá sa využiť pre ukazovanie na celkom ľubovolný typ, avšak po dôslednom pretypovaní. Občas sa preň použiva výraz generický pointer.
Sú zhruba dve oblasti použitia:
void ako pointer na niekoľko rôznych typov
Ak si spomenieme na funkciu fopen(), vieme, že funkcia môže vracat taktiež pointer na nejaký dátovy typ. Táto možnosť sa v C využiva pomerne často, napr. funkcia:
char *najdi_adresu_znaku(char c)
Táto funkcia by hľadala v pamäti od nejakej adresy zadaný znak a vracala by pointer na tento znak.
často sa tiež využíva možnosť definovať premennú ako pointer na funkciu vracajúcu nejaký typ, napr:
double (*p_fd) ();
Poznámky:
double secti_dbl(double f, double g)
potom je možné napísať priradenie:
p_fd = secti_dbl; /* Pozor! ziadny & /*
ktoré priradí pointeru p_fd adresu funkcie secti_dbl().
Z toho, čo už o funkciách vieme, vyplýva, že meno funkcie sa môže v programe objaviť v týchto prípadoch:
Nasledujúci program vypíše tabuľku hodnôt polynomov p(x) = x * x + 8 a q(x) = x * x * x - 3 v intervale <-1;+1> s krokom 0,2
Zatiaľ sme nemali problémy s tým, ako rozlúštiť, čo ktorá definícia znamená, pretože tieto definície boli veľmi jednoduché.
S príchodom pointerov sa však situácia rapídne mení, pretože je možné definovať pointer na veľmi komplikovaný typ.
Príklady zápisu definícií pomocou pointerov:
int x; /* x je typu int */
float *y; /* y je pointer na typ float */
double *z(); /* z je funkcia vracajuca pointer na double */
int *(*v) (); /* v je pointer na funkciu vracajucu pointer na int */
Pre túto ťažko pochopiteľnú zložitosť našťastie funguje jednoduché mnemotechnické pravidlo, ako prečítat ľubovolne komplikovaný zápis.
int *(*v) ();
Tento zápis definície čítame nasledujúcim spôsobom:
Týmto jednoduchým spôsobom sa dá kedykoľvek prečítat ľubovolná definícia. Len je nutné trochu si to vyskúšat na niekoľkých príkladoch.
5.4 Definícia s využitím operátora typedef
Pomocou operatora typedef je možné vytvoriť nový dátovy typ. Pri definovaní premenných jednoduchých dátových typov sa typedef príliš nepoužíva, zatiaľ čo pri použití pointerov a štruktúr sa využíva veľmi často.
Príkaz:
typedef float *P_FLOAT;
vytvorí nový typ ako pointer na float a pomenuje tento typ identifikátorom P_FLOAT.
Pozor: Nie je to definícia premennej, ktorá vyhradzuje pamät, ale definícia nového typu, ktorá iba určuje vzorec (šablónu) pre ďalšie akcie.
Identifikátor P_FLOAT sa stáva synonymom typu float a je možné ho ďalej v programe použit pre definície, deklarácie, pretypovanie, atď.
Ďalšia možná definícia pomocou typedef, ktorá sa u pointerov často používa:
typedef int *P_INT;
P_INT p_i, p_j; /* spravna definicia dvoch pointerov na int */
Ak ukazujú pointery na ďalšie pointery, je to možné zapísat taktiež pomocou typedef napr.:
int *p_i; **p_p_i;
alebo
typedef int *P_INT;
typedef P_INT *P_P_INT;
P_INT p_i;
P_P_INT p_p_i;
kde p_i je pointer na int a p_p_i je pointer na pointer na int, alebo inak - pointer na typ pointer na typ int.
Príklad:
Definícia nového typu:
typedef double (*P_FD) ();
kde P_FD je typ pointer na funkciu vracajúcu double. Potom sú možné nasledujúce definície:
S pointermi je možné uskutočňovať niektoré aritmetické operácie. Je však nutné poznamenať, že je ich oveľa menej, než aritmetických operácií s normálnou premennou.
Platné operácie s pointermi sú:
5.5.1 Operátor sizeof
Operátor sizeof zistí veľkosť skúmaného dátového typu v Bytoch. Občas sa použiva aj pri práci s jednoduchými dátovými typmi, ale ťažisko jeho práce je práve v spolupráci s pointermi a so zloženými dátovými typmi. Často je totiž nutné zistit veľkost objektov, na ktoré pointery ukazujú alebo majú ukazovať
.
Poznámka:
int i, *p_i;
5.5.2 Súčet pointeru a celého čísla
Výraz:
p + n
znamená, že sa odkazujeme na n-tý prvok za prvkom, na ktorý práve ukazuje pointer p. Hodnota adresy tohoto prvku je potom:
(char *) p + sizeof(*p) * n
teda k pointeru sa nepripočíta príslušné celé číslo, ale násobok tohoto čísla a veľkosti typu, na ktorý pointer ukazuje.
Majme definície a predpokladajme, že pre tieto typy plati:
char *p_c = 10; /* sizeof(char) == 1 */
int *p_i = 10; /* sizeof(int) == 2 */
float *p_f = 10; /* sizeof(float) == 4 */
Spočiatku všetky pointery ukazujú na adresu 10. Po inkrementácii ale platí:
p_c + 1 == 11
p_i + 1 == 12
p_f + 1 == 14
ale:
(char *) p_i + 1 ==11
rovnako ako:
(char *) p_f + 1 == 11
pretože pretypovanie na (char *) zmenilo veľkost objektu.
Pretože súčet celého čísla a pointeru je opäť pointer, je možné písať výrazy typu:
p_i = p_i + 5;
kde p_i bude ukazovať na 5-ty prvok za pôvodným prvkom.
Príklad:
Program prečíta double číslo a zobrazí zodpovedajúce Byty z adresy v pamäti, na ktorej je toto číslo uložené.
Táto aritmetická operácia má úplne rovnakú filozofiu ako pričítanie, teda výraz:
p - n
znamená, že sa odkazujeme na n-tý prvok pred prvkom, na ktorý práve ukazuje pointer p. Hodnota adresy tohoto prvku je potom:
(char *) p - sizeof(*p) * n
5.5.4 Porovnávanie pointerov
Pre porovnávanie veľkosti dvoch pointerov je možné použiť operátory:
< <= > >= == !=
Výrazy typu:
p_i < p_j
majú zmysel iba vtedy, ak sú obidva pointery rovnakého typu a obidva ukazujú na ten istý úsek pamäti, napr. na jedno pole. Potom má tento výraz hodnotu 1 (TRUE) ak je p_i (ako adresa) menšia než p_j (taktiež ako adresa) alebo 0 (FALSE) v ostatných prípadoch.
Poznámka:
Bez explicitného pretypovania sa nedajú porovnávat pointery rôznych typov okrem porovnávania pointerov s NULL pointerom.
Napríklad, ak sú p_c a p_d pointery na char, pričom p_c ukazuje na začiatok bloku údajov dĺžky MAX, potom je možné zistiť, či p_d ukazuje do vnútra tohto bloku takto:
(p_d >= p_c && p_d <= p_c + MAX)
Znaky z tohto poľa by sme tlačili:
for (p_d = p_c; p_d < p_c + MAX; p_d++)
printf("%c", *p_d);
Pointerovu aritmetiku možeme využit pre rýchle kopírovanie pamäti. S využitím predchádzajúceho príkladu budeme kopírovať blok dĺžky MAX z adresy p_c na adresu p_d. Využijeme pomocnú premennú p_t, ktorá je tiež pointer na char.
for (p_t = p_c; p_t < p_c + MAX; *p_d++ = *p_t++)
tu je základný trik:
*p_d++ = *p_t++
Ten sa dá rozpísat do troch príkazov:
p_d -= MAX;
ktorý nastaví p_d na začiatok nového bloku dát.
5.5.5 Odčítavanie pointerov
Výraz:
p_d - p_c
má zmysel iba ak pointery ukazujú na rovnaké pole údajov. V tomto prípade slúži k zisteniu počtu položek poľa medzi týmito pointermi, pričom položkou poľa je dátový typ, na ktorý sú obidva pointery definované.
Výraz:
p_d - p_c
sa dá prepísať ako:
((char *) p_d - (char *) p_c) / sizeof(*p_d)
Predpokladajme opäť predchádzajúci príklad bloku údajov. Nasledujúca časť programu nájde v tomto bloku znak "?" a vytlačí jeho pozíciu. Pokiaľ sa v bloku údajov nenachádza znak "?", vytlačí sa -1.
for (p_d = p_c; *p_d != '?' && p_d < p_c + MAX; p_d++)
printf("%d \n", (p_d < p_c + MAX) ? p_d - p_c + 1 : -1);
Pozor:
Sčítať pointery sa nedá. Výsledkom sčítania by bola nezmyselná hodnota.
5.6 Dynamické prideľovanie a navracanie pamäti
Prideľovanie pamäti za chodu programu tak, aby nedošlo ku kolízii s ostatnými údajmi, je dosť zákerný problém. Našťastie programovacie jazyky poskytujú pre túto zložitú a citlivú operáciu štandartné procedúry a funkcie, ktoré ju uskutočnia za nás, takže sa možnosť omylu výrazne znižuje. Nám potom stačí tieto run-time procedúry a funkcie iba zavolať.
Ich označenie ako run-time, znamená, že tieto rutiny uskutočňujú operácie za behu programu. Sú to operácie, ktorých požiadavky (parametre) nemôžu byť určené v čase prekladu. V C je to funkcia malloc(). Tá pridelí z heapu blok pamäti potrebnej veľkosti
a vráti jeho adresu. Na strojovej úrovni je to volanie funkcii, ktoré manipulujú s pamäťou - uskutočňujú správu pamäti, čo je operácia pomerne zložitá. Veľkosť pamäti, ktorú pridelí, si v C musíme sami priamo určiť.
Všeobecne platí, že veľkosť pridelenej dynamickej pamäti je zavislá na veľkosti objektu, na ktorý príslusný pointer ukazuje. V súvislosti s dynamickým prideľovaním pamäti sa hovorí o tzv. životnosti údajov, čo znamená, ako dlho novo alokovaný objekt v pamäti existuje.
Spomeňme si napr. na automatické premenné, ktoré majú dobu životnosti určenú dobou uskutočňovania funkcie, v ktorej sú definované. Životnosť údajov v heape je od doby pridelenia pamäti až do jej uvolnenia alebo do skončenia programu. V C sa uvolnenie pamäti uskutoční napr. volaním funkcie free().
Poznámka:
Funkčné prototypy ďalej popisovaných funkcií sú uvedené v súboroch stdlib.h (alebo niekedy tiež v alloc.h), ktorý je nutné do programu pripojiť pomocou prikazu:
#include <stdlib.h>
5.6.1 Pridelovanie pamäti
Štandardnou a najčastejšie používanou funkciou pre pridelenie pamäti je funkcia malloc(), ktorej jediný parameter je typu unsigned int. Tento parameter udáva počet Bytov, ktoré chceme alokovat. Funkcia malloc() vracia pointer na void, ktorý predstavuje adresu prvého prideleného prvku. Tento pointer je veľmi vhodné pretypovať na pointer na zodpovedajuci typ. Ak nie je v pamäti dostatok miesta pre pridelenie pozadovaného úseku, vracia malloc() hodnotu NULL.
Poznámky:
Ukážka použitia malloc() vrátane reakcie na prípadny neúspech.
Uvolnovanie alebo navracanie pamäti je opačná akcia než pridelovanie. Platí všeobecná zásada, že už nepotrebnú pamät je dobre okamžite vrátit, a nečakať až na koniec programu. Pre uvolnenie pamäti sa využíva funkcia free(), ktorej parametrom je pointer na typ void, ktorý ukazuje na začiatok skôr prideleného bloku.
Poznamka:
je teda vhodné uviesť bezprostredne aj príkaz: p_c = NULL;
čím zabránime možnému prístupu do uvolnenej pamäti.
5.6.3 Príklady pridelovania pamäti
Majme definície:
char *p_c;
int *p_i, i = 0;
Potom príkaz:
*p_c = 'a';
nie je celkom v poriadku, pretože *p_c ukazuje niekam do pamäti, ktorú nemáme pridelenú. Pred týmto príkazom je teda treba uviesť príkaz:
p_c = malloc(1);
Aby sme dodržiavali dobré návyky, je potrebné naviac zistiť, či sa nám požadovanú pamät podarilo priradiť,
príkaz teda bude:
if ((p_c = malloc(1) == NULL) {
printf("Nie je volna pamat \n");
return;
}
Ak chceme v nasledujúcom kroku výpočtu alokovať 20 Bytov pomocou rovnakého pointeru p_c, potom príkaz:
p_c = malloc(20);
nie je úplne najvhodnejší, pretože sme stratili pointer na skôr alokovanú pamät - je v nej znak 'a' - túto pamät sa nám už nikdy nepodarí uvolniť a do konca programu bude znak 'a' "visieť" niekde v pamäti. Ak potrebujeme uvolniť pamať pre uloženie int hodnoty, potom príkaz:
p_i = malloc(2);
nie je opäť najvhodnejší, pretože je systémovo závisly - typ int nemusí nutne využívat len 2 Byty. Lepší spôsob je:
p_i = malloc(sizeof(int));
Ale ani táto varianta však nie je optimálna, pretože sme p_i priradili pointer na typ void. Bezchybná varianta je:
p_i = (int *) malloc(sizeof(int));
Ak použivame pri alokovaní pamäti operátor typedef, potom napr.:
typedef int *P_INT;
P_INT p_i;
p_i = (P_INT) malloc(sizeof(int));
je absolútne korektný, na rozdiel od:
p_i = (P_INT) malloc(sizeof(P_INT));
pretože veľkosť pointeru na int môže byť odlišná od veľkosti dátového typu int.
5.6.4 Funkcia calloc()
V mnohých prípadoch je nutné alokovať pamäť pre n prvkov, z ktorých každý má veľkosť size.
Pre tento prípad slúži funkcia calloc(n, size), ktorá alokuje toto pole prvkov rovnako ako príkaz:
malloc(n * size)
a naviac ho vynuluje.
Funkcia calloc() opäť vracia pointer na začiatok alokovanej oblasti alebo NULL, ak sa nepodarilo požadovanú pamäť prideliť.
Pozor:
V niektorých systémoch je nutné pamät pridelenú pomocou calloc() uvolniť pomocou funkcie cfree() a nie free()!
5.7 Pointer ako skutočný parameter funkcie
Ak potrebujeme vo funkcii zmeniť nie hodnotu premennej jednoduchého typu, ale hodnotu pointeru, je to samozrejme možné.
Ako na to, to nám ukáže druhá časť nasledujúceho programu.
Program prečíta z klávesnice 10 double čísiel, uloží ich do pamäti a vypočíta ich súčin. Program je rozdelený do troch pomocných funkcií a funkcie main(). Prvá funkcia alokuje blok památi a vráti pointer na jeho začiatok, alebo vráti NULL, ak nie je dostatok pamäti.
Poznámka:

Časté chyby:
p_j = (int *) malloc(sizeof(int));
alebo spravené priradenie:
p_j = &j;
int main(void) 1. chýba include <stdlib.h>
{
int *p_i;
p_i = maloc(5); 2. správne má byť:
} p_i = (int *) malloc(sizeof(int));
3. chýba test na NUL, teda úplne správne má byť:
if ((p_i = (int *) malloc(5) == NULL) {
printf("Malo pamati\n");
exit(1);
}
4. nakoniec, keď už pamäť nebude potrebná, nezabudnúť na jej uvolnenie:
free((void *) p_i);
p_i = NULL;
Čo je dobré si uvedomiť:
1. Zisti veľkosť všetkých základných dátových typov (int, float, ...) v bajtoch.
2. Napíš funkciu int set(char a, char *p_a) s jedným vstupným a druhým výstupným parametrom. Funkcia ako svoju návratovú hodnotu vracia 1, ak bolo vo vstupnom parametre písmeno a 0 v ostatných prípadoch. Do výstupného parametru uloží funkcia opačný typ písmena (veľké prevedie na malé a naopak), ak bol vstupný znak písmeno, alebo ho nezmení, ak znak nebol písmeno.
Otestuj v programe. Vo funkcii main() bude výpis znaku z výstupného parametru funkcie set() na základe vyhodnotenia jej výstupnej hodnoty za pomoci if-else.
Celý program náležite okomentuj.
3. Napíš funkciu long citaj_znak(FILE *fr, int *p_c), ktorá prečíta jeden znak zo súboru TEST.TXT a vráti ho pomocou druhého parametru. Návratovou hodnotou funkcie bude počet volaní tejto funkcie (využitie lokálnej statickej premennej). Hlavný program vypíše pomocou tejto funkcie súbor a nakoniec aj počet prečítaných znakov.
Použi tento súbor TEST.TXT!
4. Napíš funkciu, ktoré usporiada hodnoty 3 premenných int a, b, c tak, aby po skončení kódu funkcie platilo a <= b <= c. Uvedené premenné budú definované vo funkcii main. Vstupné parametre funkcie budú adresy tých premenných. Hodnoty premenných budú zadané z klávesnice (main) a výpis bude vyzerať nejak takto:
vstup: a = 5, b = 58, c = 3
výstup: a = 3, b = 5, c = 58
Pointer predstavuje adresu v pamäti a až na tejto adrese je ukrývana príslušná hodnota, na ktorú sme boli doposiaľ zvyknutí. Táto odlišná interpretácia obsahu premenej nás stavia pred nutnosť ako nejakým vhodným spôsobom prekladaču povedať, že hodnota premennej je adresa a nie už cieľová hodnota. To sa prevedie pomocou operátoru *.
Poznámka:
- Operátor * sa označuje ako dereferenčný operátor. V literatúre sa občas vyskytuje i pojem opačného významu - referenčný operátor, ktorý je predstavovaný operátorom &.
5.1 Základy práce s pointermi
Poznámka:
- Pointer je vždy zviazaný s nejakým dátovym typom. Správne by sa namiesto termínu "pointer" malo vždy uvádzať "pointer na typ ..."
Poznámky:
- Pre ukážku budeme definovať pointer na typ int. Definícia pointerov na iné dátove typy je analogická.
- Všetky identifikátory pointerov budú ďalej začínat jednotne znakmi p_.
int *p_i;
Je možné (a často sa to robí) uviesť definíciu premennej typu int a pointeru na typ int naraz, napr.:
int *p_i, i;
Z tejto možnosti vyplýva častá chyba pri definícii viac pointerov naraz:
int *p_i, p_j;
kde iba p_i je pointer na int a p_j je premenná typu int.
5.1.2 Práca s adresovými operátormi
Zatiaľ sme sa dozvedeli, ako získat hodnotu premennej, ktorej adresu (teda pointer na túto premennú) poznáme. K úplnej znalosti je teda potrebné sa naučit aj opačný spôsob, t. j. ako získat adresu premennej, ktorá uz existuje. Opät to nie je nič tažké, pretože adresa ľubovoľnej premennej sa dá získat pomocou referenčného operátora &, teda:
int i, *p_i = &i;
čo je definícia p_i a jeho súčasná inicializácia adresou premennej i alebo:
p_i = &i;
čo je priraďovací príkaz, ktorý v programe uskutoční priradenie adresy premennej i do premennej (pointeru) p_i.
Poznámka:
- Premená p_i má samozrejme taktiež adresu, ktorá sa ale veľmi nevyužíva.
Pretože každá pointerova premenná je l-hodnota, je teda možné napísať:
*p_i = 1; // celkom v poriadku
*(p_i + 3) = 5; // podozrive, pokial p_i neukazuje na pole.
*(3 + k) = 6; // je to mozne, ale nespravne, pretoze zapisujeme na neznamu adresu v pamati
Operátor & je ale možné použit iba na premenne. Tie totiž majú vždy adresu, na rozdiel od konštant a výrazov, ktoré ju nemajú.
p_i = &i; /* spravne */
p_i = &(i + 3); /* chyba */
p_i = &15; /* chyba */
Poznámka:
- U mikorprocesorov sa často používaju pointery pre priamy prístup do pamäte. Potom má zmysel priradiť pointeru absolútnu adresu, teda napr:
typedef unsigned char BYTE;
BYTE *p_mem;
p_mem = (BYTE *) 0x80;
Tu sa situácia trochu komplikuje, pretože je potrebné rozoznávať dva typy správnosti priradenia:
- statické - priradenie je správne v dobe prekladu
- dynamické - priradenie je správne v dobe prekladu aj pri behu programu
- ľavá strana by mala byť rovnakého typu ako pravá strana, tzn. nemiešať pointery na rôzne typy
- priradzovať len cez inicializované alebo správne nastavené pointery
Pre všetky príklady platí definícia: int i, *p_i;
na adresu v p_i musí byť p_i inicializovaná.
- Staticky správne:
i = 3; /* do i dá hodnotu 3 */
*p_i = 4; /* na adresu v p_i (kde je uložený int) dá hodnotu 4 */
i = *p_i; /* do i dá obsah z adresy v p_i */
*p_i = i; /* na adresu v p_i dá obsah i */
p_i = &i; /* naplní p_i adresou i */ - Staticky nesprávne:
p_i = 3; /* namiesto adresy je do p_i daná bez
pretypovania hodnota 3, teda (absolútna) adresa 3 */
i = p_i; /* do i sa dá obsah p_i, teda adresa namiesto int hodnoty */
i = &p_i; /* do i sa dá adresa p_i */ - Dynamicky správne:
p_i = &i; *p_i = 4; /* je to to isté ako: i = 4; */ pred priradením hodnoty - Dynamicky nesprávne:
*p_i = 4; /* 4 je priradená na náhodnú adresu, ktorá
je v p_i. Toto je najčastejšia chyba! */
Na nasledujúcich priradzovacích príkazoch budú ukázané niektoré možnosti práce s pointermi. Predpokladajme definíciu:
int i, *p_i1, *p_i2;
a ďalej predpokladajme, že premenná i leží na absolútnej adrese 10, p_i1 na adrese 20 a p_i2 na adrese 30.

Poznámky:
- Operátor * má vyššiu prioritu než operátor +, takže príkaz:
i = *p_i1 + 1; je v skutočnosti príkaz:
i = (*p_i1) + 1; - Operátor ++ ma rovnakú prioritu ako operátor *, ale je použity ako postfix, takže príkaz:
i = (*p_i1) ++; ma význam dvoch príkazov:
i = *p_i1; p_i1++; teda do i sa dá obsah adresy, na ktorú ukazuje p_i1 a potom sa pointer p_i1 inkrementuje. Bude teda ukazovať na bezprostredne nasledujúci prvok (adresu) za i. Tento trik sa často používa pri operáciach s reťazcami.
Program číta dve celé čísla a zobrazí väčšie z nich.
#include <stdio.h> main() { int i, j, *p_i; scanf("%d %d", &i, &j); p_i = ( i > j) ? &i : &j; printf("Vacsie je %d \n", *p_i); }Poznámka:
- Ak potrebujeme niekedy vytlačiť adresu na ktorú ukazuje pointer, alebo hodnotu pointeru, potom použijeme:
printf("Adresa i je %p, hodnota p_i je %p \n", &i, p_i);
Výpis adresy, na ktorú ukazuje pointer, použijeme najčastejšie pri ladení, keď si nie sme istí, či pointer ukazuje tam, kam má.
Pozor:
Znovu upozornujeme, že je treba dať si veľky pozor na tieto dve odlišnosti:
int i, *p_i = &i;
a
int i, *p_i;
p_i = &i;
- V prípade vyššie sa jedná o definíciu p_i ako pointeru na typ int (preto tam musi byt * a súčasne o jeho inicializáciu na adresu skôr definovanej premennej i.
- V pripade nižšie je to opät definícia p_i ako pointeru na typ int. Avšak v priradzovacom príkaze (už to nie je inicializácia) nesmie byť *, pretože p_i priraďujeme adresu premennej i - viď tiež príklady statickej správnosti a nesprávnosti v predchadzajúcom texte.
5.1.5 Nulový pointer NULL
Tak ako v Pascale konštanta NIL, je aj v C podobná konštanta NULL. Je to symbolická konštanta definovaná v stdio.h ako napr.:
#define NULL 0 /* alebo ako #define NULL ((void *) 0) */
NULL je možné priradiť bez pretypovania všetkým typom pointerov (pointerom na ľubovolný typ) a používa sa na označenie, že tento pointer neukazuje na nič.
5.1.6 Konverzia pointerov
Všeobecne sa jej snažíme vyhýbať, pretože prináša množstvo problémov, napr. s pointerovou aritmetikou alebo s ukladaním niektorých dátovych typov na určité (párne) adresy v pamäti. Sú však standardné situácie - typicky prideľovanie dynamickej pamäti - pri ktorej je nutné konverziu pointerov používat. Pokiaľ sa nedá v iných než obvyklých situáciach konverzii pointerov vyhnúť, potom je dobré použiť explicitné pretypovanie. Spoľahnúť sa na implicitné pretypovanie je síce možne, ale nie je to vhodné.
Priklad:
char *p_c;
int *p_i;
p_c = p_i; /* nevhodne */
p_c = (char *) p_i; /* lepsie */
5.1.7 Zarovnavanie v pamäti
Pokiaľ pri konverzii pointerov dochádza k neočakávanej chybe, je vhodné skúsiť pretypovanie tam a späť a vypísať hodnoty pointerov, napr.:
printf("p_c pred konverziou %p \n", p_c);
p_i = (int *) p_c;
p_c = (char *) p_i;
printf("p_c po konverzii %p \n", p_c);
Odlišné výsledky, ktoré môžeme dostať, sú sposobené vďaka tomu, že niektoré kompilátory používajú taktiku, že určité dátove typy, napr. int sú uložené v pamäti od párnych adries. Pri pretypovaní pointeru s tým prekladač počíta a zaokrúhľuje prípadnú nepárnu adresu na najbližšiu nižšiu párnu. To má potom výhodu rýchlejšieho prístupu k týmto údajom, ale nevýhodu práve pri pointerovej konverii. Ďalšou nevýhodou je horšie využitie pamäte. Všeobecne sa dá povedať len to, že bez problémov je pointerová konverzia iba z vyšších (dlhších) dátovych typov na nižšie (kratšie), teda napr. pointer na long je možne bez problémov pretypovať na pointer na int alebo na char. Opačne to ale nemusí výjsť správne.
5.2 Pointery a funkcie
5.2.1 Volanie odkazom
Jednou z veľmi užitočných vlastností pointerov je, že umožňujú volanie parametrov "odkazom". Pointery v tomto prípade použijeme, keď chceme vo funkcii trvale zmeniť hodnotu skutočného parametra. V praxi to znamená, že nepredávame hodnotu premennej, ale adresu tejto premennej.
V C je toto "volanie odkazom" opäť volanie hodnotou, keď sa v stacku vytvorí lokálna kópia pre uloženie parametra - adresy skutočného parametra. Táto lokálna premenná síce zanikne s ukončením prislušnej funkcie, ale má tú vlastnosť, že je v nej uložený pointer, pomocou ktorého sa nepriamo zmenia údaje, ktoré nemajú s touto funkciou nič spoločného - boli definované vo vnútri tejto funkcie a nezanikajú s jej koncom. Teda výsledok je rovnaký ako pri skutočnom volaní odkazom v Pascale, ale postup spracovania iný. To čo v Pascale uskutočňoval automaticky kompilátor vďaka kľúčovému slovu VAR, to musíme v C urobiť sami - teda nepracovať s formálnym parametrom ako s normálnou premennou, ale ako s pointerom. Pre zjednodušenie bude však ďalej používaný termín volanie odkazom.
Poznámka:
Nejasnosti môžu vzniknúť až pri štúdiu jazyka C++ - objektovo orientovaného C - ktorý umožňuje skutočné volanie odkazom.
Príklad:
Toto je klasický príklad funkcie pre výmenu obsahov dvoch premenných.
void vymen(int *p_x, int *p_y) { int pom; pom = *p_x; *p_x = *p_y; *p_y = pom; }Funkciu vymen() potom voláme so skutočnými parametrami i a j takto:
int i = 5, j = 3;
vymen(&i, &j);
Pozor:
- Veľmi častá chyba pri volaní je:
vymen(i, j); ktorá sposobí, že sa bude zapisovať na adresy dané obsahom premenných i a j, teda na absolútnu adresu 3 a 5, čo vedie vo väčšine prípadov k zrúteniu programu. - Druhou častou chybou je volanie:
vymen(*i, *j); kde bude zápis prevedený na adresy adries z obsahov i a j, t. j. napr. z absolútnej adresy 3 sa vezme hodnota, ktorá sa použije ako adresa, na ktorej sa niečo zmeni. Výsledkom je opät najčastejšie zruúenie programu.
Program číta pomocou funkcie citaj_riadok() riadky z klávesnice a počita, koľko na ňom bolo medzier a malých pismen. Funkcia citaj_riadok() vracia hodnotu 1, keď na riadku boli nejaké znaky a hodnotu 0, ak bol riadok prázdny. Program skončí, ak prečíta prazdny riadok.
#include <stdio.h> int citaj_riadok(int *p_medzery, int *p_male) { int c, pocet = 0; *p_medzery = 0; *p_male = 0; while ((c = getchar()) != '\n') { pocet++; if (c == ' ') (*p_medzery)++; /* zatvorky nutne */ else if (c >= 'a' && c <= 'z') (*p_male)++; /* zatvorky nutne */ } return ((pocet == 0) ? 0 : 1); } main() { int medzery, male; while (citaj_riadok(&medzery, &male) == 1) { printf("Na riadku bolo %d medzier a %d malych pismen. \n", medzery, male); } }Poznámka:
- Pokiaľ by boli premenné medzery a male definované ako:
int *medzery, *male; potom by nebolo možne volať funkciu citaj_riadok() ako:
citaj_riadok(medzery, male); pretože nebola pridelená pamät, na ktorú ukazujú tieto pointery.
Možnosť, ako pouziť typ void, je definovať pointer na void, napr.
void *p_void;
Pointer p_void neukazuje na žiadny konkrétny typ, teda dá sa využiť pre ukazovanie na celkom ľubovolný typ, avšak po dôslednom pretypovaní. Občas sa preň použiva výraz generický pointer.
Sú zhruba dve oblasti použitia:
void ako pointer na niekoľko rôznych typov
int i; float f; void *p_void = &i; /* p_void ukazuje na i */ main() { *(int *) p_void = 2; /* nastavi i na 2 */ p_void = &f; /* p_void ukazuje na f */ *(float *) p_void = 1.1; /* nastavenie f na 1.1 */ }void ako formálny parameter funkcie
#include <stdio.h> void vymen_pointery(void **p_x, void **p_y) { void *p_pom; p_pom = *p_x; *p_x = *p_y; *p_y = p_pom; } main() { char c = 1, *p_c = &c, d = 2, *p_c = &d; FILE *fin = stdout, /* zamerna chyba */ FILE *fout = stdin; fprint(fin, "c = %d, d = %d \n", *p_c, *p_d); vymen_pointery((void *) &p_c, (void *) &p_d); vymen_pointery(&fin, &fout); fprintf(fout, "c = %d, d = %d \n", *p_c, *p_d); }Poznámka:
- Pretypovanie na (void **) pri prvom volaní funkcie vymen_pointery() je len z dôvodu zamedzenia varovného hlásenia o nerovnakých typoch parametrov. Program funguje i bez neho, ale takto je to čistejšie.
Ak si spomenieme na funkciu fopen(), vieme, že funkcia môže vracat taktiež pointer na nejaký dátovy typ. Táto možnosť sa v C využiva pomerne často, napr. funkcia:
char *najdi_adresu_znaku(char c)
Táto funkcia by hľadala v pamäti od nejakej adresy zadaný znak a vracala by pointer na tento znak.
často sa tiež využíva možnosť definovať premennú ako pointer na funkciu vracajúcu nejaký typ, napr:
double (*p_fd) ();
Poznámky:
- Prázdne zátvorky () pred ukončovacou bodkočiarkou sú nevyhnutné, pretože
double (*p_fd); by znamenalo to iste, co:
double *p_fd; teda že pre p_fd je pointer na double a nie pointer na funkciu vracajúcu double. - Zátvorky okolo mena premennej sú nevyhnutné, pretože:
double *p_fd(); by znamenalo, že tento riadok je deklarácia funkcie pomenovanej p_fd, ktorá vracia pointer na double.
double secti_dbl(double f, double g)
potom je možné napísať priradenie:
p_fd = secti_dbl; /* Pozor! ziadny & /*
ktoré priradí pointeru p_fd adresu funkcie secti_dbl().
Z toho, čo už o funkciách vieme, vyplýva, že meno funkcie sa môže v programe objaviť v týchto prípadoch:
- Definícia funkcie:
double secti_dbl(double f, double g) {return (f + d);} - Deklarácia funkcie:
double secti_dbl(); - Úplný funkčný prototyp:
double secti_dbl(double f, double g); - Neúplný funkčný prototyp:
double secti_dbl(double, double); - Volanie funkcie:
w = secti_dbl(f1, f2); - Priradenie adresy funkcie do premennej, ktorá je typu pointer na zodpovedajúci typ:
p_fd = secti_dbl;
- Pomocou pointeru p_fd, je potom možné volať funkciu secti_dbl() ako:
w = (*p_fd) (f1, f2); alebo úplne rovnako ako:
w = p_fd(f1, f2); pričom prvý spôsob bol ako jediný možný v K&R verzii C, a obidva sú možné v ANSI C.
Nasledujúci program vypíše tabuľku hodnôt polynomov p(x) = x * x + 8 a q(x) = x * x * x - 3 v intervale <-1;+1> s krokom 0,2
#include <stdio.h> #define DOLNY (-1) #define HORNY 1 #define KROK 0.2 double pol1(double x) { return(x * x + 8); } double pol2(double x) { return(x * x * x - 3); } main() { int i; double x; double (*p_fd) (); /* pointer na funkciu vracajucu double */ for (i = 0; i < 2; i++) { /* priradenie adresy funkcie pointeru fd */ p_fd = (i == 0) ? pol1 : pol2; /* tabulacia */ for (x = DOLNY; x <=HORNY; x +=KROK) printf("%15.5lf \t %15.5lf \n", x, (*p_fd) (x)); } }Keď sa nad predchádzajúcim príkladom zamyslíme, zistíme, že by bolo vhodné napísať funkciu tabulacia(), takto:
void tabulacia(double d, double h, double k, double (*p_fd) ()) { double x; for (x = d; x <= h; x += h) printf("%15.5lf \t %15.5lf\n", x, (*p_fd) (x)); }a funkcia main() by vyzerala napr. takto:
main() { tabulacia(DOLNY, HORNY, KROK, pol1); tabulacia(-2.0, 2.0, 0.05, pol2); }5.3 Ako čítať komplikované definície - I
Zatiaľ sme nemali problémy s tým, ako rozlúštiť, čo ktorá definícia znamená, pretože tieto definície boli veľmi jednoduché.
S príchodom pointerov sa však situácia rapídne mení, pretože je možné definovať pointer na veľmi komplikovaný typ.
Príklady zápisu definícií pomocou pointerov:
int x; /* x je typu int */
float *y; /* y je pointer na typ float */
double *z(); /* z je funkcia vracajuca pointer na double */
int *(*v) (); /* v je pointer na funkciu vracajucu pointer na int */
Pre túto ťažko pochopiteľnú zložitosť našťastie funguje jednoduché mnemotechnické pravidlo, ako prečítat ľubovolne komplikovaný zápis.
- Nájdeme identifikátor a od neho čítame doprava.
- Pokiaľ narazíme na samostatnú pravú okrúhlu zátvorku ")" (nie na dvojicu "())", vraciame sa na zodpovedajúcu ľavú okrúhlu zátvorku "(" a od nej čítame opäť doprava a samozrejme preskakujeme všetky už prečítané.
- Ak narazime na ukončovaciu bodkočiarku, potom sa vraciame na najľavejšie, doposiaľ nespracované miesto a od neho čítame dolava.
int *(*v) ();
Tento zápis definície čítame nasledujúcim spôsobom:
- V spleti zátvoriek a hviezdičiek si nájdeme identifikátor, teda "v" a povieme: v je ...
- Od neho sa číta doprava, pokiaľ nenarazíme na ")". Pravá okrúhla zátvorka nás vracia doľava až na zodpovedajúcu ľavú okruhlu zátvorku "(" a od nej sa číta zase doprava, teda znak "*" a pridame: ...pointer na...
- Preskakujeme meno premennej "v" a zátvorku ")", ktoré nám už poslúžili, a čítame stale doprava, pokiaľ nenarazíme na ďalšiu samostatnú pravú ")" alebo na ukončovaciu bodkočiarku, v našom prípade teda "()" a pridáme: ...funkciu vracajúcu ...
- Ukončovacia bodkočiarka nás vracia doľava pred už spracovanú ľavú "(" a pretože sme vpravo už všetko prečítali, čítame teraz opačne - doľava - teda "*" a pridáme: ...pointer na...
- Čítame stále doľava, teda "int" a dodáme: ...int a sme hotoví.
Týmto jednoduchým spôsobom sa dá kedykoľvek prečítat ľubovolná definícia. Len je nutné trochu si to vyskúšat na niekoľkých príkladoch.
5.4 Definícia s využitím operátora typedef
Pomocou operatora typedef je možné vytvoriť nový dátovy typ. Pri definovaní premenných jednoduchých dátových typov sa typedef príliš nepoužíva, zatiaľ čo pri použití pointerov a štruktúr sa využíva veľmi často.
Príkaz:
typedef float *P_FLOAT;
vytvorí nový typ ako pointer na float a pomenuje tento typ identifikátorom P_FLOAT.
Pozor: Nie je to definícia premennej, ktorá vyhradzuje pamät, ale definícia nového typu, ktorá iba určuje vzorec (šablónu) pre ďalšie akcie.
Identifikátor P_FLOAT sa stáva synonymom typu float a je možné ho ďalej v programe použit pre definície, deklarácie, pretypovanie, atď.
Ďalšia možná definícia pomocou typedef, ktorá sa u pointerov často používa:
typedef int *P_INT;
P_INT p_i, p_j; /* spravna definicia dvoch pointerov na int */
Ak ukazujú pointery na ďalšie pointery, je to možné zapísat taktiež pomocou typedef napr.:
int *p_i; **p_p_i;
alebo
typedef int *P_INT;
typedef P_INT *P_P_INT;
P_INT p_i;
P_P_INT p_p_i;
kde p_i je pointer na int a p_p_i je pointer na pointer na int, alebo inak - pointer na typ pointer na typ int.
Príklad:
Definícia nového typu:
typedef double (*P_FD) ();
kde P_FD je typ pointer na funkciu vracajúcu double. Potom sú možné nasledujúce definície:
- Definovanie premennej:
P_FD p_fd;definuje p_fd ako pointer na funkciu vracajúcu double - Definovanie navratového typu funkcie:
P_FD g(void) {
return(sqrt);
}kde g je funkcia vracajúca pointer na štandartnú funkciu sqrt( ) odmocnica z 10 by sa potom vypočítala ako:
(*(g())) (10.0)
S pointermi je možné uskutočňovať niektoré aritmetické operácie. Je však nutné poznamenať, že je ich oveľa menej, než aritmetických operácií s normálnou premennou.
Platné operácie s pointermi sú:
- súcet pointerov a celého čísla
- rozdiel pointeru a celého čísla
- porovnavanie pointerov rovnakých typov
- rozdiel dvoch pointerov rovnakých typov
5.5.1 Operátor sizeof
Operátor sizeof zistí veľkosť skúmaného dátového typu v Bytoch. Občas sa použiva aj pri práci s jednoduchými dátovými typmi, ale ťažisko jeho práce je práve v spolupráci s pointermi a so zloženými dátovými typmi. Často je totiž nutné zistit veľkost objektov, na ktoré pointery ukazujú alebo majú ukazovať
.
Poznámka:
- Pre tých, čo sa zaujímajú aj o efektivitu programu, dodávame, že sizeof nepracuje po spustení programu, ale je vyhodnotený v čase prekladu, takže vlastný beh programu nijako nezdržuje. Je teda veľmi vhodné ho použivať.
int i, *p_i;
- po príkaze:
i = sizeof(p_i); bude v i počet Bytov nutných pre uloženie pointeru na typ int - tento príkaz sa používa málokedy. - Po príkaze:
i = sizeof(*p_i); bude v i počet Bytov nutných pre uloženie objektu, na ktorý ukazuje p_i, teda objektu typu int - tento príkaz sa naopak používa veľmi často.
- Typ hodnoty, ktorú vracia sizeof, nie je určený, ale väčšinou to býva unsigned int alebo unsigned long.
5.5.2 Súčet pointeru a celého čísla
Výraz:
p + n
znamená, že sa odkazujeme na n-tý prvok za prvkom, na ktorý práve ukazuje pointer p. Hodnota adresy tohoto prvku je potom:
(char *) p + sizeof(*p) * n
teda k pointeru sa nepripočíta príslušné celé číslo, ale násobok tohoto čísla a veľkosti typu, na ktorý pointer ukazuje.
Majme definície a predpokladajme, že pre tieto typy plati:
char *p_c = 10; /* sizeof(char) == 1 */
int *p_i = 10; /* sizeof(int) == 2 */
float *p_f = 10; /* sizeof(float) == 4 */
Spočiatku všetky pointery ukazujú na adresu 10. Po inkrementácii ale platí:
p_c + 1 == 11
p_i + 1 == 12
p_f + 1 == 14
ale:
(char *) p_i + 1 ==11
rovnako ako:
(char *) p_f + 1 == 11
pretože pretypovanie na (char *) zmenilo veľkost objektu.
Pretože súčet celého čísla a pointeru je opäť pointer, je možné písať výrazy typu:
p_i = p_i + 5;
kde p_i bude ukazovať na 5-ty prvok za pôvodným prvkom.
Príklad:
Program prečíta double číslo a zobrazí zodpovedajúce Byty z adresy v pamäti, na ktorej je toto číslo uložené.
#include <stdio.h> typedef unsigned char *P_BYTE; main() { double f; P_BYTE p_byte; int i; printf("Zadaj realne cislo : "); scanf("%f", &f); p_byte = (P_BYTE) &f; for (i = 0; i < sizeof(double); p_byte++, i++) printf("%d. byte = %02Xh \n", *p_byte); }Poznámky:
- Pretypovanie p_byte = (P_BYTE) &f; je nutné, pretože &f je adresa typu double a p_byte je typu pointer na unsigned char.
- Ak sa odkazujeme do pamäte, používame explicitne typ unsigned char a pretože char môže byť ako signed, tak aj unsigned (čo záleží na implementácii). Ale my väčšinou chceme prečítat z pamäti Byte vo význame neznamienkovaného celého čísla, teda unsigned char.
Táto aritmetická operácia má úplne rovnakú filozofiu ako pričítanie, teda výraz:
p - n
znamená, že sa odkazujeme na n-tý prvok pred prvkom, na ktorý práve ukazuje pointer p. Hodnota adresy tohoto prvku je potom:
(char *) p - sizeof(*p) * n
5.5.4 Porovnávanie pointerov
Pre porovnávanie veľkosti dvoch pointerov je možné použiť operátory:
< <= > >= == !=
Výrazy typu:
p_i < p_j
majú zmysel iba vtedy, ak sú obidva pointery rovnakého typu a obidva ukazujú na ten istý úsek pamäti, napr. na jedno pole. Potom má tento výraz hodnotu 1 (TRUE) ak je p_i (ako adresa) menšia než p_j (taktiež ako adresa) alebo 0 (FALSE) v ostatných prípadoch.
Poznámka:
- Obmedzenie, že obidva pointery musia ukazovať na ten istý úsek pamäti, sa zavádza preto, že mnoho systémov má pamät segmentovanú a porovnavanie pointerov z rôznych segmentov nedáva žiadne rozumné výsledky.
Bez explicitného pretypovania sa nedajú porovnávat pointery rôznych typov okrem porovnávania pointerov s NULL pointerom.
Napríklad, ak sú p_c a p_d pointery na char, pričom p_c ukazuje na začiatok bloku údajov dĺžky MAX, potom je možné zistiť, či p_d ukazuje do vnútra tohto bloku takto:
(p_d >= p_c && p_d <= p_c + MAX)
Znaky z tohto poľa by sme tlačili:
for (p_d = p_c; p_d < p_c + MAX; p_d++)
printf("%c", *p_d);
Pointerovu aritmetiku možeme využit pre rýchle kopírovanie pamäti. S využitím predchádzajúceho príkladu budeme kopírovať blok dĺžky MAX z adresy p_c na adresu p_d. Využijeme pomocnú premennú p_t, ktorá je tiež pointer na char.
for (p_t = p_c; p_t < p_c + MAX; *p_d++ = *p_t++)
tu je základný trik:
*p_d++ = *p_t++
Ten sa dá rozpísat do troch príkazov:
- Najprv sa uskutoční:
*p_d = *p_t; teda kopírovanie jedneho Bytu - V druhej časti sa uskutočnia dva príkazy:
p_d++; p_t++; teda inkrementácia obidvoch pointerov.
p_d -= MAX;
ktorý nastaví p_d na začiatok nového bloku dát.
5.5.5 Odčítavanie pointerov
Výraz:
p_d - p_c
má zmysel iba ak pointery ukazujú na rovnaké pole údajov. V tomto prípade slúži k zisteniu počtu položek poľa medzi týmito pointermi, pričom položkou poľa je dátový typ, na ktorý sú obidva pointery definované.
Výraz:
p_d - p_c
sa dá prepísať ako:
((char *) p_d - (char *) p_c) / sizeof(*p_d)
Predpokladajme opäť predchádzajúci príklad bloku údajov. Nasledujúca časť programu nájde v tomto bloku znak "?" a vytlačí jeho pozíciu. Pokiaľ sa v bloku údajov nenachádza znak "?", vytlačí sa -1.
for (p_d = p_c; *p_d != '?' && p_d < p_c + MAX; p_d++)
printf("%d \n", (p_d < p_c + MAX) ? p_d - p_c + 1 : -1);
Pozor:
Sčítať pointery sa nedá. Výsledkom sčítania by bola nezmyselná hodnota.
5.6 Dynamické prideľovanie a navracanie pamäti
Prideľovanie pamäti za chodu programu tak, aby nedošlo ku kolízii s ostatnými údajmi, je dosť zákerný problém. Našťastie programovacie jazyky poskytujú pre túto zložitú a citlivú operáciu štandartné procedúry a funkcie, ktoré ju uskutočnia za nás, takže sa možnosť omylu výrazne znižuje. Nám potom stačí tieto run-time procedúry a funkcie iba zavolať.
Ich označenie ako run-time, znamená, že tieto rutiny uskutočňujú operácie za behu programu. Sú to operácie, ktorých požiadavky (parametre) nemôžu byť určené v čase prekladu. V C je to funkcia malloc(). Tá pridelí z heapu blok pamäti potrebnej veľkosti
a vráti jeho adresu. Na strojovej úrovni je to volanie funkcii, ktoré manipulujú s pamäťou - uskutočňujú správu pamäti, čo je operácia pomerne zložitá. Veľkosť pamäti, ktorú pridelí, si v C musíme sami priamo určiť.
Všeobecne platí, že veľkosť pridelenej dynamickej pamäti je zavislá na veľkosti objektu, na ktorý príslusný pointer ukazuje. V súvislosti s dynamickým prideľovaním pamäti sa hovorí o tzv. životnosti údajov, čo znamená, ako dlho novo alokovaný objekt v pamäti existuje.
Spomeňme si napr. na automatické premenné, ktoré majú dobu životnosti určenú dobou uskutočňovania funkcie, v ktorej sú definované. Životnosť údajov v heape je od doby pridelenia pamäti až do jej uvolnenia alebo do skončenia programu. V C sa uvolnenie pamäti uskutoční napr. volaním funkcie free().
Poznámka:
- Z predchádzajúcich riadkov je už asi jasné, že nie je dobré miešať údaje v stacku a v heape - napr. definovať pointer na údaje v stacku.
Funkčné prototypy ďalej popisovaných funkcií sú uvedené v súboroch stdlib.h (alebo niekedy tiež v alloc.h), ktorý je nutné do programu pripojiť pomocou prikazu:
#include <stdlib.h>
5.6.1 Pridelovanie pamäti
Štandardnou a najčastejšie používanou funkciou pre pridelenie pamäti je funkcia malloc(), ktorej jediný parameter je typu unsigned int. Tento parameter udáva počet Bytov, ktoré chceme alokovat. Funkcia malloc() vracia pointer na void, ktorý predstavuje adresu prvého prideleného prvku. Tento pointer je veľmi vhodné pretypovať na pointer na zodpovedajuci typ. Ak nie je v pamäti dostatok miesta pre pridelenie pozadovaného úseku, vracia malloc() hodnotu NULL.
Poznámky:
- Je dobrým zvykom pri každom pridelovaní pamäti testovať návratovú hodnotu na NULL a nespoliehať sa na pocit, že pamäti musí byť dosť. Predídeme tým mnohým problémom. Ladíme totiž najčastejšie programy s malými údajmi, pre ktoré pamät väčšinou stačí. V reálnej prevádzke bude ale program použitý pre skutočné údaje, ktorých je väčšinou oveľa viac.
- V tomto prípade sa nemusíme starať o problémy so zarovnávaním na určité adresy v pamäti, pretože malloc() na tieto problémy pamätá.
Ukážka použitia malloc() vrátane reakcie na prípadny neúspech.
int *p_i; if ((p_i = (int *) malloc(1000) == NULL) { printf("Malo pamati \n"); exit(1); }Poznámka:
- Pri pridelovaní dynamickej pamäti je potrebné si uvedomiť, že síce žiadame o pridelenie určitého počtu Bytov, ale je iba vecou operačného systému, koľko pamäti najviac sa skutočne z heapu pridelí. Napríklad v MS-DOSe sa prideluje pamät po tzv. paragrafoch, čo sú násobky 16-tich Bytov. V praxi to teda znamená, že ak žiadame príkazom:
p_c = malloc(1);o pridelenie jedného jediného Bytu, systém ich v skutočnosti pridelí 16. Dôvod tohto "plýtvania" pamäťou je, že systém musí mať pre každý pridelený blok dynamickej pamäti nejakú administratívu, aby napr. vedel, že je práve tento kúsok pamäti obsadený. Túto skutočnosť je treba si uvedomiť, práve v programoch, ktoré sa snažia prideliť väčšie množstvo kratších úsekov pamäti. Potom pamät dojde skôr, než keby program žiadal o pridelenie jedného veľkého useku.
Uvolnovanie alebo navracanie pamäti je opačná akcia než pridelovanie. Platí všeobecná zásada, že už nepotrebnú pamät je dobre okamžite vrátit, a nečakať až na koniec programu. Pre uvolnenie pamäti sa využíva funkcia free(), ktorej parametrom je pointer na typ void, ktorý ukazuje na začiatok skôr prideleného bloku.
Poznamka:
- Funkcia free() vracia už nepotrebnú pamät spät do heapu, teda uvolní ju pre ďalšie použitie. Dôležité ale je, že free() nemení hodnotu svojho parametra. To znamená, že pointer stále ukazuje na to isté miesto v pamäti. S touto pamäťou sa dá teda ďalej pracovať, ale v skutočnosti už programu nepatrí! Takéto využívanie uvolnenej pamäti môže spôsobiť množstvo problémov.
je teda vhodné uviesť bezprostredne aj príkaz: p_c = NULL;
čím zabránime možnému prístupu do uvolnenej pamäti.
5.6.3 Príklady pridelovania pamäti
Majme definície:
char *p_c;
int *p_i, i = 0;
Potom príkaz:
*p_c = 'a';
nie je celkom v poriadku, pretože *p_c ukazuje niekam do pamäti, ktorú nemáme pridelenú. Pred týmto príkazom je teda treba uviesť príkaz:
p_c = malloc(1);
Aby sme dodržiavali dobré návyky, je potrebné naviac zistiť, či sa nám požadovanú pamät podarilo priradiť,
príkaz teda bude:
if ((p_c = malloc(1) == NULL) {
printf("Nie je volna pamat \n");
return;
}
Ak chceme v nasledujúcom kroku výpočtu alokovať 20 Bytov pomocou rovnakého pointeru p_c, potom príkaz:
p_c = malloc(20);
nie je úplne najvhodnejší, pretože sme stratili pointer na skôr alokovanú pamät - je v nej znak 'a' - túto pamät sa nám už nikdy nepodarí uvolniť a do konca programu bude znak 'a' "visieť" niekde v pamäti. Ak potrebujeme uvolniť pamať pre uloženie int hodnoty, potom príkaz:
p_i = malloc(2);
nie je opäť najvhodnejší, pretože je systémovo závisly - typ int nemusí nutne využívat len 2 Byty. Lepší spôsob je:
p_i = malloc(sizeof(int));
Ale ani táto varianta však nie je optimálna, pretože sme p_i priradili pointer na typ void. Bezchybná varianta je:
p_i = (int *) malloc(sizeof(int));
Ak použivame pri alokovaní pamäti operátor typedef, potom napr.:
typedef int *P_INT;
P_INT p_i;
p_i = (P_INT) malloc(sizeof(int));
je absolútne korektný, na rozdiel od:
p_i = (P_INT) malloc(sizeof(P_INT));
pretože veľkosť pointeru na int môže byť odlišná od veľkosti dátového typu int.
5.6.4 Funkcia calloc()
V mnohých prípadoch je nutné alokovať pamäť pre n prvkov, z ktorých každý má veľkosť size.
Pre tento prípad slúži funkcia calloc(n, size), ktorá alokuje toto pole prvkov rovnako ako príkaz:
malloc(n * size)
a naviac ho vynuluje.
Funkcia calloc() opäť vracia pointer na začiatok alokovanej oblasti alebo NULL, ak sa nepodarilo požadovanú pamäť prideliť.
Pozor:
V niektorých systémoch je nutné pamät pridelenú pomocou calloc() uvolniť pomocou funkcie cfree() a nie free()!
5.7 Pointer ako skutočný parameter funkcie
Ak potrebujeme vo funkcii zmeniť nie hodnotu premennej jednoduchého typu, ale hodnotu pointeru, je to samozrejme možné.
Ako na to, to nám ukáže druhá časť nasledujúceho programu.
Program prečíta z klávesnice 10 double čísiel, uloží ich do pamäti a vypočíta ich súčin. Program je rozdelený do troch pomocných funkcií a funkcie main(). Prvá funkcia alokuje blok památi a vráti pointer na jeho začiatok, alebo vráti NULL, ak nie je dostatok pamäti.
#include <stdio.h> #include <stdlib.h> #define SIZE 10 double *init(void) { return((double *) malloc(SIZE * sizeof(double))); }Druhá funkcia prečíta čísla z klávesnice a uloží ich do pamäti. Jej formálny parameter je pointer na začiatok alokovanej oblasti. Hodnota tohoto parametru sa nemeni.
void citanie(double *p_f) { int i; for (i = 0; i < SIZE; i++) { printf("Zadajte %d. cislo : ", i + 1); scanf("%lf", p_f + i); /* tu nie je & !!! */ } }Tretia funkcia uskutočňuje súčin všetkých prečítanych čísiel tak, že sa posledným číslom naplní formálny parameter sucin a ostatnými číslami sa násobí. Parameter sucin je spracovaný pomocou pointeru, pretože sa jeho hodnota bude meniť.
Poznámka:
- v skutočnosti by bolo vhodnejšie, keby funkcia nasob() vracala typ double ako výsledok násobenia. Pointer na parameter sucin je použitý iba z pedagogických dôvodov.
void nasob(double *p_f, int size, double *sucin) { for (size--, *sucin = *(p_f + size); size >= 0; size--) *sucin *= *(p_f + size); }Hlavná funkcia main() iba volá predchádzajuce funkcie.
main() { double *p_dbl, suc; if ((p_dbl = init()) == NULL) return 1; /* nedostatok pamati - koniec */ citanie(p_dbl); nasob(p_dbl, SIZE, &suc); printf("Sucin cisel je: %12.3f \n", suc); }Poznámka:
- Pri volaní funkcie nasob() sa prvý parameter p_dbl uvádza bez &, pretože je to pointer na double, kdežto tretí parameter suc je uvedený s &, pretože je to premenná typu double.
void init(double **p_f) { *p_f = ((double *) malloc(SIZE * sizeof(double))); }a v hlavnom programe by bola volaná ako:
main() { double *p_dbl; init(&p_dbl); ... }Situacia v pamati bude nasledujuca:

Časté chyby:
int *p_i = 2; // inicializácia p_i na adresu 2 *p_i = 3; // zápis hodnoty 3 do pamäti na adresu 2 void nastav(int *i) { *i = 5; } int main (void) { int j, *p_j; nastav(j); // má byť: nastav(&j); nastav(*j); // má byť: nastav(&j); nastav(*p_j); // má byť: nastav(p_j); nastav(&p_j); // má byt: nastav(p_j); }V obidvoch prípadoch s p_j musí byť najskôr niekde alokovaná pamäť, teda:
p_j = (int *) malloc(sizeof(int));
alebo spravené priradenie:
p_j = &j;
int main(void) 1. chýba include <stdlib.h>
{
int *p_i;
p_i = maloc(5); 2. správne má byť:
} p_i = (int *) malloc(sizeof(int));
3. chýba test na NUL, teda úplne správne má byť:
if ((p_i = (int *) malloc(5) == NULL) {
printf("Malo pamati\n");
exit(1);
}
4. nakoniec, keď už pamäť nebude potrebná, nezabudnúť na jej uvolnenie:
free((void *) p_i);
p_i = NULL;
Čo je dobré si uvedomiť:
- Pointery nie sú celé čísla.
- Vždy používajte pretypovanie na správny typ pointeru.
- Veľkosť objektov pre alokáciu pamäti určujte zásadne pomocou sizeof.
- Vždy testujte, či sa podarilo požadovanú pamät prideliť.
- Nezabudnite na príkaz:
#include <stdlib.h>
alebo:
#include <alloc.h> - Uvolnujte iba pridelenú pamät.
- Funkcia free() uvolní pamät, ale nezmení hodnotu pointeru. Použitie tejto hodnoty má za následok nepredvídatelné správanie sa programu.
- Pre predávanie parametrov odkazom je nutné formálny parameter definovať ako pointer a skutočny parameter uvádzat s operátorom &.
- Ak sa má zmeniť hodnota skutočného parametra, ktorý je pointer, je nutné použiť formálny parameter ako pointer na pointer.
- Pokiaľ je skutočným parametrom NULL pointer, urobte explicitné pretypovanie na typ formálneho parametru.
1. Zisti veľkosť všetkých základných dátových typov (int, float, ...) v bajtoch.
Spoiler
2. Napíš funkciu int set(char a, char *p_a) s jedným vstupným a druhým výstupným parametrom. Funkcia ako svoju návratovú hodnotu vracia 1, ak bolo vo vstupnom parametre písmeno a 0 v ostatných prípadoch. Do výstupného parametru uloží funkcia opačný typ písmena (veľké prevedie na malé a naopak), ak bol vstupný znak písmeno, alebo ho nezmení, ak znak nebol písmeno.
Otestuj v programe. Vo funkcii main() bude výpis znaku z výstupného parametru funkcie set() na základe vyhodnotenia jej výstupnej hodnoty za pomoci if-else.
Celý program náležite okomentuj.
Spoiler
3. Napíš funkciu long citaj_znak(FILE *fr, int *p_c), ktorá prečíta jeden znak zo súboru TEST.TXT a vráti ho pomocou druhého parametru. Návratovou hodnotou funkcie bude počet volaní tejto funkcie (využitie lokálnej statickej premennej). Hlavný program vypíše pomocou tejto funkcie súbor a nakoniec aj počet prečítaných znakov.
Použi tento súbor TEST.TXT!
Spoiler
4. Napíš funkciu, ktoré usporiada hodnoty 3 premenných int a, b, c tak, aby po skončení kódu funkcie platilo a <= b <= c. Uvedené premenné budú definované vo funkcii main. Vstupné parametre funkcie budú adresy tých premenných. Hodnoty premenných budú zadané z klávesnice (main) a výpis bude vyzerať nejak takto:
vstup: a = 5, b = 58, c = 3
výstup: a = 3, b = 5, c = 58
Spoiler
Popis: | Pointery - Definicia udajov typu pointer na typ - Praca s adresovymi operatormi - Priradenie hodnoty pointerom a pomocou pointerov - Pouzitie pointerov v priradovacich prikazoch - Nulovy pointer NULL - Konverzia pointerov - Zarovnavanie v pamati - Pointery a funkcie - Volanie odkazom - Pointer na typ void - void ako pointer na niekolko roznych typov - void ako formalny parameter funkcie - Pointery na funkcie a funkcie ako parametre funkcii - Ako citat komplikovane definicie - I - Definicia s vyuzitim operatoru typedef - Pointerova aritmetika - Operator sizeof - Sucet pointeru a celeho cisla - Odcitanie celeho cisla od pointeru - Porovnavanie pointerov - Odcitavanie pointerov - Dynamicke pridelovanie a navracovanie pamati - Pridelovanie pamati - Uvolnovanie pamati - Priklady pridelovania pamati - Funkcia calloc() - Pointer ako skutocny parameter funkcie - úlohy k lekcii |
Poznámky: | |
Odkazy: |
Najlepšie lekcie
- 1. lekcia C (Úvod) škôlka (5 *)
4. jan 2014 14:39:34
V prvom rade sa potrebuješ na... - 1. lekcia C (Súbory) základná škola (5 *)
6. mar 2014 07:10:03
1. Vstup zo súboru a výstup d... - 7. lekcia C (Prepínač switch) škôlka (4 *)
4. jan 2014 15:08:32
Prepínač switchVýraz za príka... - 2. lekcia C ( Premenné) škôlka (0 *)
4. jan 2014 14:40:58
Premenné a práca s nimiOd ter... - 3. lekcia C (Formátovaný vstup) škôlka (0 *)
4. jan 2014 14:42:34
Fomátovaný vstupDobre vieš, ž... - 4. lekcia C (Logika v Céčku) škôlka (0 *)
4. jan 2014 14:43:52
Logika v CéčkuJe výraz pravd... - 5. lekcia C (Podmienky) škôlka (0 *)
4. jan 2014 14:45:05
Príkaz if, if - else, if - el... - 6. lekcia C (Cykly) škôlka (0 *)
4. jan 2014 14:48:08
Cyklus while, do while, for a... - 8. lekcia C (Opakovanie - úlohy) škôlka (0 *)
4. jan 2014 15:10:05
Opakovanie.Riadiace štruktúry... - 1. lekcia C# (Úvod do WinForms) škôlka (0 *)
12. nov 2012 09:32:36
Prečo som sa zameral na C#Môj...
Najnovšie pridané lekcie
- 10. lekcia C (Bitové operácie a bitové pole) základná škola
5. jan 2014 14:49:30
10 Bitové operácie a bitové p... - 9. lekcia C (Štruktúry, uniony a výčtové typy) základná škola
5. jan 2014 14:47:25
9 Štruktúry, uniony a typy vy... - 8. lekcia C (Viacrozmerné polia) základná škola
5. jan 2014 14:43:54
8 Viacrozmerné poliaJazyk C u... - 7. lekcia C (Reťazce) základná škola
5. jan 2014 14:40:24
7 ReťazceReťazec (string) je... - 6. lekcia C (Jednorozmerné polia) základná škola
5. jan 2014 14:31:10
6 Jednorozmerné poliaPole ako... - 5. lekcia C (Pointery) základná škola
5. jan 2014 14:28:07
5 PointeryPointer predstavuje... - 2. lekcia C# (Práca s premennými, operátory a výrazy) škôlka
12. nov 2012 09:33:58
Najprv si uvedom, že v C# nes... - 4. lekcia C (Funkcie a práca s pamäťou) základná škola
5. jan 2014 14:23:35
Funkcie a práca s pamäťouJazy... - 3. lekcia C (Preprocesor) základná škola
4. jan 2014 15:47:03
3. Preprocesor jazyka C.Doter... - 2. lekcia C (Typová konverzia) základná škola
4. jan 2014 15:37:30
Typová konverzia.Rozumie sa t...
Najnovšie komentáre
Najviac komentované lekcie
- 1. lekcia C (Úvod) škôlka (0 komentárov)
4. jan 2014 14:39:34
V prvom rade sa potrebuješ na... - 3. lekcia C (Formátovaný vstup) škôlka (0 komentárov)
4. jan 2014 14:42:34
Fomátovaný vstupDobre vieš, ž... - 4. lekcia C (Logika v Céčku) škôlka (0 komentárov)
4. jan 2014 14:43:52
Logika v CéčkuJe výraz pravd... - 5. lekcia C (Podmienky) škôlka (0 komentárov)
4. jan 2014 14:45:05
Príkaz if, if - else, if - el... - 7. lekcia C (Prepínač switch) škôlka (0 komentárov)
4. jan 2014 15:08:32
Prepínač switchVýraz za príka... - 8. lekcia C (Opakovanie - úlohy) škôlka (0 komentárov)
4. jan 2014 15:10:05
Opakovanie.Riadiace štruktúry... - 1. lekcia C# (Úvod do WinForms) škôlka (0 komentárov)
12. nov 2012 09:32:36
Prečo som sa zameral na C#Môj... - 1. lekcia C (Súbory) základná škola (0 komentárov)
6. mar 2014 07:10:03
1. Vstup zo súboru a výstup d... - 2. lekcia C (Typová konverzia) základná škola (0 komentárov)
4. jan 2014 15:37:30
Typová konverzia.Rozumie sa t... - 2. lekcia C# (Práca s premennými, operátory a výrazy) škôlka (0 komentárov)
12. nov 2012 09:33:58
Najprv si uvedom, že v C# nes...
Najviac zobrazené lekcie
- 1. lekcia C (Úvod) škôlka (52646 zobrazení)
4. jan 2014 14:39:34
V prvom rade sa potrebuješ na... - 2. lekcia C ( Premenné) škôlka (29749 zobrazení)
4. jan 2014 14:40:58
Premenné a práca s nimiOd ter... - 7. lekcia C (Reťazce) základná škola (24524 zobrazení)
5. jan 2014 14:40:24
7 ReťazceReťazec (string) je... - 6. lekcia C (Jednorozmerné polia) základná škola (23838 zobrazení)
5. jan 2014 14:31:10
6 Jednorozmerné poliaPole ako... - 1. lekcia C (Súbory) základná škola (22120 zobrazení)
6. mar 2014 07:10:03
1. Vstup zo súboru a výstup d... - 6. lekcia C (Cykly) škôlka (21288 zobrazení)
4. jan 2014 14:48:08
Cyklus while, do while, for a... - 5. lekcia C (Podmienky) škôlka (20200 zobrazení)
4. jan 2014 14:45:05
Príkaz if, if - else, if - el... - 4. lekcia C (Funkcie a práca s pamäťou) základná škola (19131 zobrazení)
5. jan 2014 14:23:35
Funkcie a práca s pamäťouJazy... - 8. lekcia C (Viacrozmerné polia) základná škola (18061 zobrazení)
5. jan 2014 14:43:54
8 Viacrozmerné poliaJazyk C u... - 9. lekcia C (Štruktúry, uniony a výčtové typy) základná škola (15763 zobrazení)
5. jan 2014 14:47:25
9 Štruktúry, uniony a typy vy...
Náhodné lekcie
- 8. lekcia C (Opakovanie - úlohy) škôlka
4. jan 2014 15:10:05
Opakovanie.Riadiace štruktúry... - 3. lekcia C (Formátovaný vstup) škôlka
4. jan 2014 14:42:34
Fomátovaný vstupDobre vieš, ž... - 4. lekcia C (Funkcie a práca s pamäťou) základná škola
5. jan 2014 14:23:35
Funkcie a práca s pamäťouJazy... - 2. lekcia C ( Premenné) škôlka
4. jan 2014 14:40:58
Premenné a práca s nimiOd ter... - 3. lekcia C (Preprocesor) základná škola
4. jan 2014 15:47:03
3. Preprocesor jazyka C.Doter... - 6. lekcia C (Jednorozmerné polia) základná škola
5. jan 2014 14:31:10
6 Jednorozmerné poliaPole ako... - 1. lekcia C (Súbory) základná škola
6. mar 2014 07:10:03
1. Vstup zo súboru a výstup d... - 7. lekcia C (Reťazce) základná škola
5. jan 2014 14:40:24
7 ReťazceReťazec (string) je... - 6. lekcia C (Cykly) škôlka
4. jan 2014 14:48:08
Cyklus while, do while, for a... - 1. lekcia C# (Úvod do WinForms) škôlka
12. nov 2012 09:32:36
Prečo som sa zameral na C#Môj...
Na tejto stránke bolo užívateľ(ov) za posledných 30 minút
členov, návštevníkov
0 Komentárov