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.