[Cvičení 3] [Obsah] [Cvičení 5]

Cvičení 4


Témata


Statická pole

Statické pole je homogenní datová struktura skládající se z určitého počtu (stejných) položek, např. čísel typu 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.

Jednorozměrná pole

Na následujícím řádku je příklad deklarace jednorozměrného pole prvků typu int, které se jmenuje A. Pole má dvacet prvků.
int A[20];
Konstanta může být buď zapsaná přímo (viz. číslo 20) nebo symbolická, definovaná pomocí direktivy #define, není dovoleno použít typovanou konstantu s přidělenou pamětí definovanou pomocí const:
#define MAXPOCET 20
   .
   .
   .
int A[MAXPOCET];
Po této deklaraci překladač vyhradí souvislou paměť pro dvacet čísel typu int. Přístup k prvkům pole se provádí pomocí indexů. Index se zapisuje do hranatých závorek, např.:
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.

Hodnoty prvků pole je možné inicializovat již při deklaraci, pomocí tzv. konstruktoru pole:

int B[3] = {1,3,5};

Pomocí klíčového slova typedef lze deklarovat uživatelské typy. Uživatelský typ pole čísel typu float o 20 prvcích definujeme následovně:

typedef float TPole20[20]; /* toto je deklarace nového typu pole TPole20, nikoliv samotného pole */
Konkrétní pole (proměnnou typu pole) tohoto typu pak deklarujeme:
TPole20 p; /* deklarujeme pole p */
p[0] = 3.2; p[19] = -3.45;
Výhodou přístupu je možnost snadné úpravy kódu - použijeme-li ve všech deklaracích proměnných, parametrech funkcí atd., identifikátor uživatelského typu TPole20, stačí změnit definici typu pole např. na int v jediném místě kódu.

Příklad:

Napište program, který uloží do pole násobky čísla 5 (malou násobilku) a vytiskne je.

Řešení:

Dev C++:nasob.dev, nasob.c
CodeBlocks:nasob.cbp, nasob.c

Vícerozměrná pole

Při deklaraci vícerozměrného pole se počet prvků každé dimenze píše do samostatných hranatých závorek bez uvedení speciálního oddělovače! Deklarace dvourozměrného pole (matice 20 x 20) o 400 prvcích typu 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]mat[19][19]. Obdobně, dvourozměrné pole je možné také inicializovat pomocí konstruktoru:
int M[2][2] = {{1,2},{3,4}};
Poznámka: dvourozměrné pole je v jazyce C uloženo po řádcích.

Úloha 4.1

Napište program, který vypočítá a vytiskne součin dvou matic. Nejprve načte z klávesnice rozměry matic a vlastní matice. Program ověří, zda je možné matice vynásobit.

Polotovar:

Dev C++:soucinmat.dev, soucinmat.c
CodeBlocks:soucinmat.cbp, soucinmat.c
Řešení:
Dev C++:soucinmat.dev, soucinmat.c
CodeBlocks:soucinmat.cbp, soucinmat.c

Typ ukazatel, proměnná typu ukazatel

Obsahem „obyčejné“ proměnné je hodnota, tj. paměťová buňka (resp. buňky), na kterou je proměnná mapována, obsahuje tuto hodnotu. Proměnná typu ukazatel (pointer) neobsahuje přímo hodnotu, ale adresu paměťového místa, kde se hodnota nachází. Deklarace proměnných typu ukazatel se zapisuje pomocí znaku „*“. 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 (obr. 1).

Schématické znázornění proměnné typu ukazatel

Obrázek 1: Proměnná typu ukazatel

Takto deklarovaná proměnná však neobsahuje ještě konkrétní adresu, není totiž inicializovaná, jejím obsahem je nějaká náhodná adresa (na rozdíl situace znázorněné na obr. 1). 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.

Přístup k hodnotě (k paměťové buňce), na kterou proměnná typu ukazatel ukazuje, se provádí pomocí operátoru dereference, což je hvězdička „*“:

*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.
(Poznámka: pokud bychom zapomněli do kódu napsat hvězdičku, tj. přiřazení by mělo podobu 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ázku 1. 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).

Poznámka: Pomocí typedef je možné definovat i uživatelský typ ukazatele, např. typ ukazatel na float definujeme:
typedef float* Pfloat; /* Pfloat jako Pointer na float, tato konvence v označování typů je zažitá */
Proměnnou typu ukazatel uk deklarujeme za pomocí výše definovaného typu standardně:
Pfloat uk; 

Dynamická alokace paměti

Alokace paměti

Program může nárokovat požadavky na paměť (alokovat paměť) za běhu podle aktuální potřeby (její velikost není známa při překladu). V jazyce C slouží k dynamické alokaci paměti funkce malloc (memory allocation) z knihovny stdlib.h nebo alloc.h. Hlavička funkce má podobu:
(void *)malloc(size_t n)
Parametrem funkce je počet bytů paměti, které program požaduje (typ 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, pokud má operační systém požadovanou velikost paměti k dispozici. Ukazatel typu (void *) 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, např. takto:

 
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.
Funkce 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.
Funkce 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:

void free(void *block);
Parametrem je ukazatel na blok paměti, který byl dříve alokován pomocí funkce malloc (resp. realloc či calloc).

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í.

Příklad:

#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++:
Dev C++:dyn_pole1.dev, dyn_pole1.c
CodeBlocks:dyn_pole1.cbp, dyn_pole1.c

Úloha 4.2

Napište program, který vypočítá a vytiskne součet dvou vektorů o stejné dimenzi. Nejprve načte z klávesnice počet prvků vektoru (dimenzi n), alokuje dynamicky tři pole. Pak načte prvky prvního vektoru a následně prvky druhého vektoru, každý vektor do samostatného pole.

Polotovar:

Dev C++:soucetv.dev, soucetv.c
CodeBlocks:soucetv.cbp, soucetv.c
Řešení:
Dev C++:soucetv.dev, soucetv.c
CodeBlocks:soucetv.cbp, soucetv.c

Úloha 4.3

Napište program, který vypíše na obrazovku všechna prvočísla od 1 do n, n je přirozené číslo zadané z klávesnice. Využijte algoritmus Eratostenova síta. Pole alokujte dynamicky, dle zadaného n.

Řešení:

Dev C++:eratos.dev, eratos.c
CodeBlocks:eratos.cbp, eratos.c


[Cvičení 3] [Obsah] [Cvičení 5]