7. diel - Polia v jazyku C
V predchádzajúcom cvičení, Riešené úlohy k 6. lekcii Céčka, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Riešené úlohy k 6. lekcii Céčka , sme si ukázali cykly. Dnes si v tutoriálu predstavíme dátovú štruktúru poľa a vyskúšame si, čo všetko vie.
Poľa
Predstavte si, že si chcete uložiť nejaké údaje o viac prvkoch. Napr.
chcete v pamäti uchovávať 10
čísel, políčka šachovnice
alebo mena 50ti
užívateľov. Asi vám dôjde, že v programovaní
bude nejaká lepšia cesta, než začať búšiť premenné
uzivatel1
, uzivatel2
... až uzivatel50
.
Nehľadiac na to, že ich môže byť potrebné 1000. A ako by sa v tom potom
hľadalo? Brrr, takze nie
Ak potrebujeme uchovávať väčšie množstvo premenných rovnakého
typu, tento problém nám rieši poľa. Môžeme si ho predstaviť ako
rad priehradiek, kde v každej máme uložený jeden prvok. Priehradky sú
očíslované tzv. Indexy, prvý má index 0
.
(Na obrázku je vidieť pole ôsmich čísiel) Programovacie jazyky sa veľmi líšia v tom, ako s poľom pracujú. V nižších kompilovaných jazykoch, ktorým je práve aj jazyk C, sa musí špecifikovať pevná veľkosť poľa v zdrojovom kóde, ktorá už za behu nemožno meniť. Do poľa teda nie je možné pridávať ďalšie priehradky a preto musíme myslieť na to, aby nám vždy stačilo. Jazyk C nám ďalej umožňuje vytvoriť tzv. Dynamicky alokovanej poľa alebo využívať napr. Spojových zoznamov, aby sme tento problém obišli. Jedná sa však o pomerne pokročilú problematiku, ku ktorej sa dostaneme neskôr. Naopak niektoré interpretované jazyky umožňujú nielen deklarovať pole s ľubovoľnou veľkosťou, ale dokonca túto veľkosť na už existujúcom poli meniť (napr. PHP).
Pre hromadnú manipuláciu s prvkami poľa sa používajú cykly.
Pole deklarujeme ako bežnú premennú, iba za jej názov uvedieme hranaté zátvorky s počtom prvkov:
int pole[10];
Slovo pole
je samozrejme názov našej premennej. Teraz máme v
premennej pole
poľa veľkosti desiatich typov int
.
Keďže sme polia ešte len založili a operačný systém nám pre neho
pridelil nejakú pamäť, ktorú mohla predtým používať iná aplikácia,
nemôžeme sa spoľahnúť na to, že sú v poli samé nuly. Rovnako dobre v
ňom zatiaľ môžu byť ľubovoľná náhodné čísla.
K prvkom poľa potom pristupujeme opäť cez hranatú zátvorku, poďme na
prvý index (teda index 0
) uložiť číslo 1
.
int pole[10]; pole[0] = 1;
Plniť poľa takto ručne by bolo príliš pracné, použijeme cyklus a
naplníme si pole číslami od 1
do 10
. K naplneniu
použijeme for
cyklus:
int pole[10]; int i; for (i = 0; i < 10; i++) { pole[i] = i + 1; }
Pozn .: i + 1
do poľa ukladáme preto, že i
ide od nuly a my chceme do poľa uložiť čísla od 1
.
Aby sme pole vypísali, môžeme za predchádzajúci kód pripísať:
{C_CONSOLE}
int pole[10];
int i;
for (i = 0; i < 10; i++)
{
pole[i] = i + 1;
}
for (i = 0; i < 10; i++)
{
printf("%d ", pole[i]);
}
{/C_CONSOLE}
výsledok:
Konzolová aplikácia
1 2 3 4 5 6 7 8 9 10
Pole samozrejme môžeme naplniť ručne a to aj bez toho, aby sme dosadzovali postupne do každého indexu. Použijeme na to zložených zátvoriek a prvky oddeľujeme čiarkou:
int cisla[] = {15, 8, 3, 10, 9, 2, 2};
Všimnite si, že nemusíme udávať veľkosť poľa, prekladač si ju odvodí z počtu prvkov vo výpočte.
Pole často slúži na ukladanie medzivýsledkov, ktoré sa potom ďalej v programe používajú. Keď niečo potrebujeme 10x, tak to nebudeme 10x počítať, ale spočítame to raz a uložíme do poľa, odtiaľ potom výsledok len načítame.
Konštanty
Keďže musíme uviesť veľkosť poľa v zdrojovom kóde a túto veľkosť
zvyčajne používame na niekoľkých miestach, hodilo by sa ju mať niekde
uloženú. Nie je nič horšie, než rozmyslieť si, že chceme pole miesto
15
prvkov veľké len 10
a zabudnúť niekde v dlhom
kóde nejakú pätnástku prepísať, napr. V cykle, ktorý polia vypisuje.
Preto sa veľkosť polí často ukladá do tzv. Konštánt.
Konštanta je hodnota ľubovoľného dátového typu, ktorý sa nemôže
zmeniť. Môžeme ju chápať ako premennú, z ktorej možno iba čítať.
Využíva sa pre prípady, keď chceme definovať v programe nejaké hodnoty,
ktoré sa síce za jeho behu nemení, ale my programátori by sme ich občas
mohli chcieť v kóde zmeniť v rámci nejaké úpravy. Konstanty definujeme
pomocou príkazu #define
, ich názvy je zvykom zapisovať
VELKYMI_PISMENY
. Hodnotu uvedieme iba za medzeru alebo tabulátor,
nie je tu rovná sa. Definícia uvádzame tesne za objednávok
#include
. Uveďme si kompletný zdrojový kód programu vyššie
tak, aby pre veľkosť poľa využíval konštantu:
#include <stdio.h>
#include <stdlib.h>
#define POCET 10
int main(int argc, char** argv) {
// Vytvorenie poľa
int pole[POCET];
// Naplnenie poľa
int i;
for (i = 0; i < POCET; i++)
{
pole[i] = i + 1;
}
// Výpis poľa
for (i = 0; i < POCET; i++)
{
printf("%d ", pole[i]);
}
return (EXIT_SUCCESS);
}
Pozn .: Príkazy začínajúcich #
nie sú príkazy
prekladača, ale tzv. Preprocesoru. To je program, ktorý spracováva zdrojový
kód ako prvý a vkladá do neho určitej úseky kódu, aby to mal prekladač
následne jednoduchšie. Preprocesor v našom prípade do súboru s programom
vloží definície funkcií zo systémových knižníc stdio.h
a
stdlib.h
a ďalej nahradí každý výskyt POCET
za
hodnotu 10
. Presnejšie je konštanta tzv. Makrom a preprocesor
toho vie ešte oveľa viac. Pre naše účely to však teraz zabudneme.
Medze pole
Pozor! Jazyk C žiadnym spôsobom nestráži, či sa pohybujeme v medziach poľa. Je tomu tak kvôli rýchlosti. Môžeme teda napr. Uložiť dáta na 15. prvok, aj keď ich má pole iba 10. Pole je v pamäti uložené ako blok bajtov a céčko počíta podľa indexu adresu, na ktorú prvok zapíše. Môžeme teda chybne zapísať na príliš vysoký index a do pamäti, ktorá nám nepatrí. Týmto spôsobom si môžeme nabúrať nejaká iná dáta v našej aplikácii, ktorá s poľom vôbec nesúvisí, ale náhodou bola uložená v pamäti za ním. Tieto chyby sa všeobecne veľmi ťažko hľadajú a je dobré snažiť sa im vyvarovať.
Polia s dĺžkou, ktorú určíme až za behu aplikácie
Standard C99 umožňuje deklarovať tzv. VLA (Variable Length Array), polia s dĺžkou, ktorú zadáme až za behu programu. Zafunguje teda aj takýto kód:
int velikost; printf("Zadaj veľkosť poľa a ja ho vytvorím: "); scanf("%d", &velikost); int pole[velikost];
Hoci je to práve platiaci štandard, je možné, že kód nebude fungovať na všetkých prekladačom. Podobné funkcionality možno docieliť aj pomocou dynamických polí, s ktorými sa zoznámime v nasledujúcej sekcii seriálu. Stále platí, že akonáhle polia raz vytvoríme, nemôžeme jeho veľkosť zmeniť.
Veľkosť poľa
Pole, ktoré sme vytvorili, je tzv. Staticky alokované. U tohto typu poľa môžeme získať jeho veľkosť aj týmto spôsobom:
{C_CONSOLE}
int pole[10]; // Založíme si pole veľkosti 10
printf("Veľkosť poľa je: %d", sizeof(pole)/sizeof(int));
{/C_CONSOLE}
Jednoducho zistíme koľko bajtov zaberá celé pole a toto číslo vydelíme veľkosťou dátového typu jednej položky. Tým získame počet položiek.
výsledok:
Konzolová aplikácia
Veľkosť poľa je: 10
U dynamických polí a textových reťazcov tento spôsob použiť nedá, skôr si na neho nezvykajte.
Zoradenie polia
Veľmi často sa nám stane, že pole prvkov potrebujeme zoradiť, napr.
Budeme chcieť nájsť najnižšie / najvyšší plat, počet bodov, náklady,
počet kusov a podobne. Hoci je tento typ úlohy s obľubou zadávaný
študentom ako algoritmus na precvičenie, štandardná knižnica jazyka C
poskytuje na tento účel funkciu qsort()
. Jej volanie je trochu
zložitejšie, takže si ho popíšeme skôr intuitívne. Vôbec nič nám však
nebráni, aby sme funkciu používali už teraz, detailne kód pochopíme až
ďalej v seriáli.
#include <stdio.h>
#include <stdlib.h>
int porovnej(const void * a, const void * b)
{
return (*(int*)a - *(int*)b);
}
int main(int argc, char** argv)
{
int cisla[] = {15, 8, 3, 10, 9, 2, 2};
qsort(cisla, 7, sizeof(int), porovnej);
int i;
for (i = 0; i < 7; i++)
{
printf("%d ", cisla[i]);
}
return (EXIT_SUCCESS);
}
Okrem funkcie main()
tu máme navyše porovnávaciu funkciu,
ktorá definuje ako porovnať 2 prvky v poli. Funkcia qsort()
totiž vnútorne funguje tak, že porovnáva vždy 2 prvky medzi sebou a vďaka
tomu vo finále poľa zoradí. Syntax hviezdičiek si nemusíte všímať, ide o
to, že funkcia vracia výraz a - b
, ktorý je kladný ak je
a > b
, nulový ak a = b
a záporný ak
a < b
. Podľa tejto hodnoty qsort()
potom
porovnáva. Ak by sme chceli pole radiť naopak (zostupne), zadali by sme tu
b - a
.
Samotné vytvorenie poľa by malo byť jasné. Pri volaní funkcie
qsort()
je potrebné uviesť okrem pole, ktoré má zoradiť, tiež
počet jeho prvkov, veľkosť jedného prvku v bajtoch a práve porovnávaciu
funkciu. Odteraz sú prvky v poli zoradené a my si ich pre kontrolu vypíšeme
pomocou cyklu for
.
výsledok:
Konzolová aplikácia
2 2 3 8 9 10 15
To by pre dnešok stačilo, môžete si s poľom hrať. V budúcej lekcii, Riešené úlohy k 7. lekcii Céčka , si konečne uvedieme ako pracovať s textovými reťazcami a naučíme sa ukladať text do premenných
V nasledujúcom cvičení, Riešené úlohy k 7. lekcii Céčka, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.