Vícerozměrná pole

Někdy potřebujeme v programech reprezentovat věci, které jsou přirozeně vícerozměrné. Typickým příkladem jsou obrázky, které lze reprezentovat jako dvourozměrnou mřížku pixelů (jeden rozměr udává řádky a druhý sloupce).

Paměťové adresy však mají pouze jeden rozměr, jelikož jsou reprezentovány jedním číslem. Jak tedy můžeme do jednorozměrné paměti uložit vícerozměrnou hodnotu? Způsobů je více, nicméně asi nejjednodušší je prostě "vyskládat" jednotlivé rozměry (dimenze) v paměti za sebou, jeden rozměr za druhým. Pokud bychom například měli dvojrozměrnou mřížku1 s rozměry 5x5, můžeme ji reprezentovat tak, že nejprve do paměti uložíme první řádek, poté druhý řádek atd.:

1Reprezentující například obrázek či matici.

2D pole

Tento koncept se označuje jako vícerozměrné pole (multidimensional array).

Způsob vyskládání dimenzí

Je na nás, v jakém pořadí jednotlivé dimenze do paměti uložíme. Pokud bychom se bavili o 2D poli, tak můžeme do paměti uložit řádek po řádku (viz obrázek výše), což se označuje jako row major ordering. Můžeme ale také do paměti vyskládat sloupec po sloupci, což se nazývá column major ordering. Je víceméně jedno, který způsob použijeme, je ale důležité se držet jednoho přístupu, jinak může dojít k záměně indexů. Indexování totiž záleží na tom, jaký způsob vyskládání použijeme. Níže předpokládáme pořadí row major.

Indexování

Při práci s dvourozměrným polem bychom chtěli pracovat s dvourozměrným indexem (řádek i, sloupec j), nicméně při samotném přístupu do paměti pak musíme tento vícerozměrný index převést na 1D index. A naopak, z 1D indexu bychom chtěli mít možnost získat zpět 2D index. Pro výpočet indexů 2D pole s vyska řádky a sirka sloupci můžeme použít tyto jednoduché vzorce:

  • Převod z 2D do 1D - abychom se dostali na cílovou pozici, musíme přeskočit radek řádků, kde každý řádek má sirka prvků, a poté ještě musíme přičíst pozici sloupce (sloupec).
    int index_2d_na_1d(int radek, int sloupec, int sirka) {
        return radek * sirka + sloupec;
    }
    
  • Převod z 1D do 2D - pro převod z 1D indexu zpět na 2D index stačí aplikovat opačný postup. Nejprve vydělíme 1D index počtem sloupců, abychom zjistili, na jakém jsme řádku, a poté použijeme zbytek po dělení, abychom zjistili, na jakém jsme sloupci.
    void index_1d_na_2d(int index, int sirka, int* radek, int* sloupec) {
        *radek = index / sirka;
        *sloupec = index % sirka;
    }
    

Tento koncept lze zobecnit na libovolně rozměrné pole (3D, 4D, …).

Vícerozměrné pole na zásobníku

Pokud známe v době překladu velikost a rozměry vícerozměrného pole, tak můžeme využít vícerozměrných statických polí. Při vytváření pole stačí použít hranaté závorky pro každou dimenzi pole. Například takto lze vytvořit 2D pole s rozměry 3x3 na zásobníku:

int pole[3][3];

Výhoda takovýchto polí je, že překladač provede převod z 2D indexu na 1D index za vás, a můžete tak toto pole přímo indexovat vícerozměrným indexem. Například první prvek pole z kódu výše lze nalézt na pozici pole[0][0], poslední na pozici pole[2][2].

Takováto pole jsou v paměti vyskládána postupně dle jednotlivých dimenzí zleva. Nejprve tedy v paměti leží prvek pole[0][0], poté pole[0][1], …, pole[1][1], pole[1][2] atd. Pokud bychom měli 2D pole a první index bychom pokládali za index řádku, tak toto vyskládání odpovídá row major pořadí.

Vícerozměrná pole v C lze zobecnit do vyšších dimenzí (můžete tak použít například int pole[3][3][3] atd.), nicméně je dobré to nepřehánět, aby kód zůstal přehledný.

Inicializace vícerozměrných polí

Vícerozměrné pole můžete nainicializovat stejně jako klasické pole. Pro zpřehlednění kódu však také můžete použít složené závorky pro oddělení jednotlivých dimenzí:

int pole_2d[3][4] = {  
   {0, 1, 2, 3},    // hodnoty pro první řádek
   {4, 5, 6, 7},    // hodnoty pro druhý řádek
   {8, 9, 10, 11}   // hodnoty pro třetí řádek
};

Vícerozměrné pole na haldě

Pokud potřebujeme vícerozměrné pole s dynamickou velikostí, stačí při volání funkce malloc vytvořit dostatek paměti pro všechny rozměry. Pokud bychom například chtěli naalokovat paměť pro 2D obrázek s vyska řádky a sirka sloupci, můžeme použít následující volání funkce malloc:

int* pamet_obrazku = (int*) malloc(vyska * sirka * sizeof(int));

Nezapomeňte, že pro indexování takového pole budeme muset používat přepočet 1D/2D indexů!