1-AIN-140: Princípy počítačov - hardvér - 5. cvičenie

Už vieme ako je možné v pamäti udržať viacero hodnôt v jednom poli, ktoré vyzerá ako jeden dlhý riadok (alebo stĺpec), ale niekedy majú údaje povahu tabuľky s viacerými stĺpcami a riadkami. Typyický príklad je dvojrozmerná mapa pôdorysu budovy, po ktorej sa robot pohybuje - v jednotlivých bunkách poľa môžeme uložiť informáciu o tom, kde sa nachádzajú priechodné chodby a kde steny. Iným príkladom je tabuľka záznamov dát, kde každý záznam pozostáva s N hodnôt, pričom sme vytvorili M záznamov - môžeme ich zoradiť do tabuľky MxN, kde každý riadok 0..(M-1) obsahuje jeden záznam s N položkami.

Použitie dvojrozmerných (prípadne viacrozmerných) polí v jazyku C je podobné ako jednorozmerných. Pole vytvoríme takto:
int tabulka[3][5];  // tabulka s tromi riadkami a piatimi stlpcami
S dvojrozmerným poľom pracujeme podobne ako s jednorozmerným, napríklad takto:
  // vypis pole s 3 riadkami a 5 stlpcami
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 5; j++)
      printf("%4d", tabulka[i][j]);
    printf("\n");
  } 
Tak ako jednorozmerné pole, aj dvojrozmerné pole sa dá inicializovať hodnotami hneď pri vytváraní, napríklad takto:
  int tabulka[3][5] = { { 1, 2, 3, 4, 5}, 
                        { -1, -2, -3, -4, -5},
                        { 1, -2, 3, -4, 5} };
Dokonca sa tá istá inicializácia dá zapísať aj takto:
  int tabulka[3][5] = { 1, 2, 3, 4, 5, -1, -2, -3, -4, -5, 1, -2, 3, -4, 5 };
V pamäti sú totiž tieto všetky údaje uložené efektívne riadok za riadkom. Má to však jednu nepríjemnú nevýhodu. Vieme síce vytiahnuť jeden konkrétny riadok poľa a pracovať iba s ním, napríklad:
  int *tretiRiadok = tabulka[2];
  for (int i = 0; i < 5; i++) 
    printf("%4d", tretiRiadok[i]);
V tomto prípade výraz tabulka[2] určí smerník na prvý (nultý) prvok v treťom riadku. Problém nastáva, keď takéto pole chceme poslať ako argument do funkcie. Zafunguje napríklad toto:
  void vypisPole(int pole[3][5])
  {
    for (int i = 0; i < 3; i++)
    {
      for (int j = 0; j < 5; j++)
        printf("%4d", pole[i][j]);
      printf("\n");
    } 
  }
alebo aj toto:
  void vypisPole(int pole[][5], int pocetRiadkov)
  {
    for (int i = 0; i < pocetRiadkov; i++)
    {
      for (int j = 0; j < 5; j++)
        printf("%4d", pole[i][j]);
      printf("\n");
    } 
  }
Ale bohužiaľ toto už nezafunguje:
  void vypisPole(int pole[][], int pocetRiadkov, int pocetStlpcov)
  {
    for (int i = 0; i < pocetRiadkov; i++)
    {
      for (int j = 0; j < pocetStlpcov; j++)
        printf("%4d", pole[i][j]);
      printf("\n");
    } 
  }
Je to preto, že pri prístupe do poľa pomocou pole[i][j] kompilátor musí vedieť, koľko prvkov je v každom riadku, aby vedel vypočítať na akej adrese sa požadovaný prvok nachádza. To je ale pomerne dosť veľká škoda, lebo kvôli tomu sa nedajú v jazyku C písať univerzálne funkcie, ktoré by pracovali s 2D poľom ľubovoľných rozmerov. Preto sa často namiesto toho používa iná verzia 2D poľa, ktorá vyzerá v pamäti takto:

  /---\     
  |   |     /--------------\
  |  -+---> |  |  |  |  |  |
  |   |     \--------------/
  |---|     
  |   |     /--------------\
  |  -+---> |  |  |  |  |  | 
  |   |     \--------------/
  |---|     
  |   |     /--------------\
  |  -+---> |  |  |  |  |  |
  |   |     \--------------/
  \---/
Ide teda o pole smerníkov na jednorozmerné polia - jednotlivé riadky. V porovnaní s predchádzajúcim spôsobom, ktorý sme opísali vyššie je tu v pamäti navyše uložené pole smerníkov, nielen samotné hodnoty. Zaujímavé je, že takéto pole sa používa veľmi podobne - položku v i-tom riadku a j-tom stĺpci tiež sprístupníme pomocou rovnakého výrazu pole[i][j]. Výhodou je, že jednotlivé riadky môžu mať rôzny počet prvkov, ak by sme to náhodou potrebovali - len si niekde budeme musieť zapamätať koľko ich v ktorom riadku je. Takéto pole môžeme vytvoriť napríklad takto:
  int *tabulka[3];   // trojprvkove pole smernikov na int (ktore mozu ukazovat na jednotlive riadky pola)
Takto vytvorené pole ale v pamäti vytvorilo len červenú časť poľa na obrázku - preto napríklad prístup do bunky tabulka[0][3] by znamenal prístup do neexistujúcej pamäte - a program by buď spadol, alebo zasahoval do pamäte, kde sú uložené iné dáta, čo by mohlo mať veľmi nežiadúce následky. Jednotlivé riadky sa buď musia vytvoriť v dynamickej pamäti (čo je téma presahujúca rozsah nášho cvičenia), alebo ich musíme nasmerovať na nejaké existujúce pole, ktoré už v pamäti je. Napríklad takto:
  int pole[3][5];
  int *tabulka[3] = { pole[0], pole[1], pole[2] };
alebo aj takto:
  int pole[3][5];
  int *tabulka[3];
  for (int i = 0; i < 3; i++) 
    tabulka[i] = pole[i];
Potom môžeme naprogramovať funkciu, ktorá dostane (smerníkové) 2D pole neznámych rozmerov takto:
  void vypisPole(int *pole[], int pocetRiadkov, int pocetStlpcov)
  {
    for (int r = 0; r < pocetRiadkov; r++)
    {
      for (int s = 0; s < pocetStlpcov; s++)
        printf("%4d", pole[r][s]);
      printf("\n");
    }
  }
S týmto v našich úlohách vystačíme. Vyriešme dve jednoduché úlohy:

1. Funkcia sucet(), ktorá vypočíta súčet všetkých prvkov v 2D poli
  int sucet(int *pole[], int pocetRiadkov, int pocetStlpcov)
  {
    int s = 0;
    for (int r = 0; r < pocetRiadkov; r++)
      for (int s = 0; s < pocetStlpcov; s++)
        s += pole[r][s];
    return s;
  }

2. Funkcia vzor(), ktorá v poli vytvorí fraktálny vzor
  
#include <stdio.h>

void vzor(int *pole[], int pocetRiadkov, int pocetStlpcov)
{
  for (int r = 0; r < pocetRiadkov; r++)
    for (int s = 0; s < pocetStlpcov; s++)
      pole[r][s] = (s % (1 << r) == 0);
}

// vypis 2D pole
void vypisPole(int *pole[], int pocetRiadkov, int pocetStlpcov)
{
  for (int r = 0; r < pocetRiadkov; r++)
  {
    for (int s = 0; s < pocetStlpcov; s++)
      printf("%c", (pole[r][s]?'*':' '));
    printf("\n");
  }
}

int main()
{
  const int riadkov = 6, stlpcov = 1 << riadkov;
  int p[riadkov][stlpcov];
  int *p2[riadkov];
  for (int i = 0; i < riadkov; i++) p2[i] = p[i];

  vzor(p2, riadkov, stlpcov);
  vypisPole(p2, riadkov, stlpcov); 
  return 0;
}

V úlohách cvičenia 5 budeme pokračovať v úlohách s robotmi a na domácu úlohu si precvičíme prácu s dvojrozmernými poľami.