[Přednáška 2] | [Obsah] | [Přednáška 4] |
int
.
Velikost pole (počet položek) musí být známa při překladu a není možné ji měnit (proto statické pole). Pole může být jedno
či vícerozměrné. Při deklaraci jakéhokoliv statického pole zadáváme do hranatých závorek počet prvků pole.
int
, které se jmenuje A
. Pole
má deset prvků.
int A[10];Konstanta může být buď zapsaná přímo (viz. číslo 10) nebo symbolická, definovaná pomocí direktivy
#define
, není dovoleno
použít typovanou konstantu s přidělenou pamětí definovanou pomocí const
:
#define MAXPOCET 10 . . . int A[MAXPOCET];Po této deklaraci překladač vyhradí souvislou paměť pro deset čísel typu
int
, obr. 1.
A[3] = 0; y = A[i]*3;V jazyce C má index prvního prvku hodnotu nula, index posledního prvku je n-1, kde n je počet prvků pole. Důvodem je požadavek na rychlý výpočet mapovací funkce pole, tj. adresu indexovaného prvku. Tento způsob indexace není možno měnit! Jazyk C neprovádí žádné kontroly správnosti mezí indexů (ani při překladu, ani ve výsledném kódu), důvodem je opět snaha o co nejrychlejší výpočet adresy příslušného prvku pole. Všechny kontroly jsou ponechány v plné míře na programátorovi. Pokud se stane, že index má hodnotu mimo deklarovaný rozsah, pracuje se s daty uložené mimo pole a může se stát, že si programátor nevědomky přepíše hodnoty jiných proměnných.
Prvky pole lze inicializovat již při deklaraci pomocí tzv. konstruktoru pole:
int A[3] = { 25, 30, -5 };
Napište program, který uloží do statického pole násobky čísla 5 a vytiskne je. Vstupem programu (z klávesnice) je horní limit násobků: zadáme-li např. číslo 12, programu uloží násobky 0 x 5 až 12 x 5. Statické pole deklarujte o velikosti např. 50 prvků. Pokud zadaná mez překročí staticky deklarovanou velikost pole, program se ukončí s chybovým hlášením.Řešení:
Dev C++: nasob.dev, nasob.c CodeBlocks: nasob.cbp, nasob.c
float
, které se jmenuje
mat
je na následujícím řádku:
float mat[20][20];Přístup k prvkům pole je opět pomocí indexů, index každé dimenze je v samostatných závorkách. O mezích indexů platí stejná pravidla jako v případě jednorozměrného pole, tedy výše deklarovanou matici mohu indexovat
mat[0][0]
až mat[19][19]
.
int
:
int pole[2][2]= {{1,2},{3,4}};Můžeme si jej představit jako matici (malá čísla 0,1 značí indexy), obr. 2:
Obrázek 2: Dvourozměrné pole jako matice
sizeof(int)=4
. Uložení statického pole v paměti je znázorněno na obr. 3 (malá čísla značí hypotetické adresy v paměti):
Obrázek 3: Uložení statického dvourozměrného pole v paměti
Řešení:
Dev C++: soucinmat.dev, soucinmat.c CodeBlocks: soucinmat.cbp, soucinmat.c
*
“. Na následujícím řádku je příklad deklarace proměnné p typu ukazatel
na int
:
int *p;Tedy, proměnná p obsahuje adresu paměťového místa, které obsahuje hodnotu typu
int
. Takto deklarovaná proměnná však neobsahuje
ještě konkrétní adresu, není totiž inicializovaná, jejím obsahem je nějaká náhodná adresa. Inicializovat ji můžeme např. přiřazením adresy jiné proměnné:
int x; int *p; p = &x;Z výkladu o funkci
scanf
víme, že operátor „&“ vrací
adresu objektu, tedy hodnotou výrazu &x
je adresa, na které je
umístěna proměnná x. Tuto adresu přiřadíme do proměnné p.
Proměnná p nyní ukazuje na proměnnou x (obr. 4).
*p = 3;Do paměťového místa, kam ukazuje proměnná, jsme nyní uložili hodnotu 3. Pokud uvažujeme předcházejí přiřazení
p = &x;
, změnili
jsme hodnotu proměnné x.
p = 3;
, změnili bychom hodnotu
samotného ukazatele; proměnná p by ukazovala do paměti na buňku s adresou 3.)
Shrneme si tedy ještě jednou situaci na obr. 4. Máme dvě proměnné x a p:
proměnná x je typu int
a v paměti je uložena na adrese 3426, druhá proměnná p
je typu ukazatel na int
a v paměti je uložena na adrese 2100.
Proměnná p obsahuje adresu proměnné x, tedy hodnotu (číslo) 3426
(v paměťové buňce s adresou 2100 je hodnota 3426). Proměnná x obsahuje
celé číslo 3 (v paměťové buňce s adresou 3426 je hodnota 3).
malloc
(memory allocation) z knihovny stdlib.h
nebo alloc.h
.
Hlavička funkce má podobu:
(void *)malloc(size_t n)
size_t
je celočíselný typ, zpravidla definován
jako unsigned int
). Funkce vrací ukazatel na počátek přiděleného bloku paměti, tj. adresu počátku přiděleného bloku, pokud má operační systém požadovanou velikost
paměti k dispozici. Tuto adresu ukládáme právě do proměnné typu ukazatel. Ukazatel typu (void *)
, který funkce malloc
vrací, představuje obecný ukazatel,
který není vázán na konkrétní datový typ. Protože překladač kontroluje kompatibilitu typů ukazatelů při přiřazování, je nutné při volání funkce výsledek přetypovat.
(viz příklad).
V souvislosti s ukazateli je nutná zmínka o konstantě NULL
, která je definována jako symbolická (většinou
má hodnotu 0). Znamená hodnotu „ukazatele nikam“ – je obdobou hodnoty nill v Pascalu. V případě, že operační systém nemá
k dispozici dostatek volné paměti, vrací funkce malloc
hodnotu NULL
. Správně napsaný program by
měl otestovat výsledek volání funkce malloc
:
int *p; p = (int *)malloc(512); // alokuji 512 bytu pameti if (p == NULL) { printf("Nedostatek pameti"); return; }S využítím přiřazovacího výrazu v podmínce můžeme napsat kód efektivněji:
int *p; if ((p = (int *)malloc(512))== NULL) { printf("Nedostatek pameti"); return; }V praxi je běžné, že potřebujeme alokovat paměť pro dynamické pole o n prvcích určitého typu. K výpočtu potřebné velikosti paměti v bytech využijeme operátor
sizeof
s parametrem daného typu. Konkrétně, dynamické pole o n prvcích typu int
alokujeme tímto způsobem:
int *p; p = (int *)malloc(sizeof(int)*n);Čtenář se může ještě setkat s jimými funkcemi pro alokaci paměti -
calloc
a realloc
.void *calloc(size_t n, size_t size)
alokuje paměť pro n položek, každou o velikosti size bytů. Odpovídá tedy volání
funkce malloc(size*n)
, navíc celý přidělený paměťový blok nuluje.void *realloc(void *block, size_t size)
provádí realokaci paměti. Parametrem je ukazatel block na již dříve alokovanou
paměť funkcemi malloc, calloc, realloc
, size je nová požadovaná velikost paměti (logicky zpravidla větší než dříve
alokovaná). Funkce vrátí ukazatel na nově alokovaný blok paměti o velikosti size, obsah původního bloku zkopíruje na počátek nově
alokovaného bloku. Pokud má parametr block hodnotu NULL
, funkce se chová jako malloc
. Není-li k dispozici
dostatek pamětí, vrací NULL
.
Před ukončením činnosti (nebo v okamžiku, kdy ji již nepotřebuje) by měl program alokovanou paměti uvolnit (vrátit k dispozici operačnímu systému).
K uvolnění paměti slouží funkce free
z knihovny stdlib.h
:
Parametrem je ukazatel na blok paměti, který byl dříve alokován pomocí funkcevoid free(void *block);
malloc
(resp. realloc
či
calloc
).
Hodnotu ukazatele je možné vytisknout pomocí specifikátoru %p: printf("%p",p)
vytiskne šestnáctkově adresu, která je uložena v proměnné p.
Přístup k prvkům dynamicky alokovaného pole je stejný jako u pole statického, tedy pomocí indexů. K prvkům lze přistupovat i pomocí ukazatelové aritmetiky, kterou uvedeme později.
Nyní uvedeme jednoduchý příklad dynamické alokace pole pro uložení čísel a výpisu tohoto pole v opačném pořadí.
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int *p; int n; // velikost pole int i; printf("Zadejte pocet zpracovavanych cisel: "); scanf("%d",&n); if ((p=(int*)malloc(sizeof(int)*n))==NULL) { printf("Neni dostatek dynamicke pameti."); return 1; } printf("Zadejte %d celych cisel odelenych mezerou:\n",n); // nacteni prvku pole for(i=0;i<n;i++) scanf("%d",&p[i]); putchar('\n'); // vypis v opacnem poradi for(i=n-1;i>=0;i--) printf("%d ",p[i]); putchar('\n'); free(p); return 0; }Kód je k dispozici i k přímému stažení:
Dev C++: dyn_pole1.dev, dyn_pole1.c CodeBlocks: dyn_pole1.cbp, dyn_pole1.c
Mějme např. následující deklarace a dynamicky alokované pole o velikosti 10 prvků typu int :
int *pole; int *uk; pole = (int *)malloc(sizeof(int)*10); uk = pole+5;Ukazatel pole odkazuje na počátek dynamicky alokované paměti. Hodnotou výrazu
pole + 2
je ukazatel na
třetí prvek pole, tj. prvek s indexem 2 (obr. 5). Neznamená to však, že ukazatel pole + 2
ukazuje na buňku paměti,
která má adresu vyšší o hodnotu dva. Při ukazatelové aritmetice se bere v úvahu velikost prvků, na které
ukazatel ukazuje. Předpokládejme, že sizeof(int)=4, sizeof(char)=1
a že paměť je organizována (adresována) po slabikách - bytech (jak
to ostatně dodnes počítače PC mají). Ukazatel pole+2
tedy ukazuje na adresu zvětšenou o 8, tedy na adresu
((char*)pole)+2*sizeof(int)
.
Obrázek 5: Ukazatelová aritmetika
uk+2
ukazuje na osmý prvek pole. Pokud bychom do kódu následně zapsali porovnávací výraz uk == pole+5
,
jeho hodnota by byla nenulová (pravda).pole-1
ukazuje na oblast paměti,
která k poli již nepatří. Protože jazyk C nekontroluje meze polí, můžeme tento výraz v programu napsat (pokud budeme manipulovat s daty na této
adrese, přepíšeme zpravidla hodnoty jiných proměnných).
*pole = 2; *(pole+2) = 10; *uk= -4; // zde je možné napsat též *(uk+0) = -4; *(uk+2) = 5;Zde je důležité poznamenat, že přístup k prvkům statického a dynamického pole je naprosto stejný. Následující dva zápisy jsou v jazyce C zcela ekvivalentní, je jedno, který z nich v programu použijeme:
*(pole+2) = 10; pole[2] = 10;Pokud máme deklarované statické pole, např.
int A[30]
, A je konstatní ukazatel na počátek pole, lze tedy psát
*(A+20)=10
a tento zápis je ekvivalentní zápisu A[20]=10
. Do samotné proměnné A nelze přiřadit žádnou
hodnotu, neboť jde o konstatní ukazatel inicializovaný při překladu (překladač hlásí chybu).
Ukazatelová aritmetika se efektivně využívá ve funkci scanf
, kde se jako skutečné parametry uvádějí adresy paměti, na které
se má uložit zkonvertovaná hodnota. Místo scanf("%d",&a[i]);
je lépe psát scanf("%d",a+i);
.
sizeof(int)=4
. Mějme následující fragment kódu:
int *p = (int*)malloc(sizeof(int)*10); int *k; k = p + 5;Hodnota rozdílu
(k-p)
je 5, tedy ((char*)k-(char*)p)/sizeof(int)
.
Na závěr ještě ukážeme, jak provést výpis prvků pole pouze s využitím ukazatelů, ukazatelové aritmetiky a porovnání ukazatelů. Výpis pozice hledaného prvku je proveden s využitím rozdílu ukazatelů:
int A[5] = {1,2,3,4,5}; int x; int *p, int *za_konec; za_konec = A+5; for(p=A;p<za_konec;p++) printf("%d ",*p); printf("Zadej hledane cislo: "); scanf("%d",&x); for(p=A;p<za_konec && *p != x;p++) ; //strednik ma vyznam prazdneho prikazu if (p==za_konec) printf("Cislo nenalezeno"); else printf("Cislo je na pozici %d\n",p-A+1);Kód je k dispozici ke stažení:
Dev C++: priklad_ukaz.dev, priklad_ukaz.c CodeBlocks: priklad_ukaz.cbp, priklad_ukaz.c
[Přednáška 2] | [Obsah] | [Přednáška 4] |