Dajme si ďalšiu z nepraktických úloh v Cčku, ktorá ukáže rozdiely medzi Javou a týmto jazykom.
Janko má zo slovenčiny tieto známky: 1, 1, 5, 2, 3, 2. Vypíšte jeho známky pod seba.
Hej, je to stupidita, ale ukážeme, koľko radosti, strasti a vedľajších problémov pri tom môže vyskákať.
Pre jednoduchosť (aby sme sa nezaprplali vstupno-výstupnými operáciami) budeme predpokladať, že známky sú zadrôtované v súbore.
Implementácia v Jave:
public class Priemer {
public static void main(String[] args) {
int[] hodnoty = { 1, 1, 5, 2, 3, 2 };
for(int hodnota : hodnoty) {
System.out.println(hodnota);
}
}
}
Ako by to vyzeralo v C?
… v tym poľu
Povedzme si niečo o poliach: rozdielov v syntaxi a význame je dosť veľa.
Neopovážte sa robiť toto:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int[] hodnoty = { 1, 1, 5, 2, 3, 2 };
int i;
for(i = 0; i < hodnoty.length; i++) {
printf("%d\n", hodnoty[i]);
}
}
Toto fungovať nebude z dvoch dôvodov. Predovšetkým, syntax pre deklaráciu polí nepozná javácku syntax:
int[] hodnoty = { 1, 1, 5, 2, 3, 2 };
Hláška kompilátora je kúzelná:
error: Expected token ";", found "{"
Správna verzia je takto:
int hodnoty[] = { 1, 1, 5, 2, 3, 2 };
Áno, zátvorky sú až za názvom premennej. Napodiv, Java podporuje oba zápisy (lebo chceli uľahčiť migráciu z C), i keď sa s Cčkarskym takmer nestretnete.
Ďalší priekak je s cyklom. Naše hrôzostrašné hodnoty.length
fungovať nebude ani omylom:
error: request for member 'length' in something not a structure in union.
Ako môžem zistiť dĺžku poľa?
Jednoduchá odpoveď: nemôžete.
Odhmlite zraky: existujú spôsoby, ako to dosiahnuť (viď nižšie), ale okolnosti sú pomerne obskúrne: prakticky len vtedy, ak je pole deklarované ako globálna či lokálna premenné a počet prvkov viete už v čase písania programu.
C totiž implementuje polia veľmi jednoducho: je to len krajší zápis pre viacero po sebe idúcich chlievikov v pamäti:
-------------------------
| 1 | 1 | 5 | 2 | 3 | 2 |
-------------------------
int int int int int int
Nikde sa neeviduje, koľko tých chlievikov ste využili: zodpovednosť za evidenciu je na vás. Áno, C ide po výkonnosti a nie po pohodlnosti. Ak chcete používať nejaký fintivý trik, musíte si ho vyrobiť sami.
OK, urobme ďalší pokus:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int pocet_hodnot = 6;
int hodnoty[] = { 1, 1, 5, 2, 3, 2 };
int i;
for(i = 0; i < pocet_hodnot; i++) {
printf("%d\n", hodnoty[i]);
}
return EXIT_SUCCESS;
}
Toto už bude fungovať ako chceme.
Ale predstavme si takúto situáciu, keď úbohý vývojar o polnoci netrafil počet prvkov.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int pocet_hodnot = 7;
int hodnoty[] = { 1, 1, 5, 2, 3, 2 };
int i;
for(i = 0; i < pocet_hodnot; i++) {
printf("%d\n", hodnoty[i]);
}
return EXIT_SUCCESS;
}
Opakujem: všimnite si, že pocet_hodnot
je sedem, ale skutočný počet prvkov je len šesť. Program sa radostne skompiluje, a dokonca aj zbehne! (Toto je typická chyba zvaná „plusmínus 1”, kde sa vývojári pomýlia o jeden prvok hore-dole.)
Výpis bude kúzelný:
1
1
1
5
2
3
2
0
Alebo:
1
1
1
5
2
3
2
12386051
Alebo:
1
1
1
5
2
3
2
7522
Alebo:
* [tu vložte screenshot hlásenia operačného systému o padnutom programe] *
Základný rys Cčka je (aby sme citovali legendu učebníc C pána Herouta):
Jazyk C zásadně nekontroluje meze polí!
Opäť je to kvôli efektivite. Pole je nič iné, než označená postupnosť chlievikov v pamäti a je na vás, aby ste vedeli, koľko chlievikov (= prvkov poľa) ste si alokovali. Ak vyleziete spoza hraníc poľa, dostanete sa do územia, ktoré vám nepatrí. Možno patrí susednému procesu, možno operačnému systému, ale rozhodne nie je je vaše. A čo nie je vaše, na to sa nedá spoliehať.
Hodnota 0 alebo 12386051 alebo 7522 je náhodná hodnota, ktorá sa práve nachádza v pamäti mimo hraníc, ktoré boli alokované.
Dávajte si na to mimoriadny pozor!
V prípade, že zapisujete do cudzej pamäte, je to rovnako zlé: akurát niekedy máte väčšiu šancu na priamy pád programu.
int hodnoty[] = { 1, 2 };
hodnoty[128] = 3;
Mnoho operačných systémov dovolí čítať z cudzej pamäte, ale zápis vedie rovno k pádu — jednoducho nie je možné zapisovať do pamäte patriacej inému procesu. Na druhej strane sa však môže stať, že pri zápise sa trafíte do pamäte, ktorá prináleží vášmu programu, a začnete si prepisovať obsahy iných premenných, čo je nepredvídateľná katastrofa.
Alternatívna deklarácia polí
Inak môžeme pole deklarovať aj takto:
int hodnoty[2];
hodnoty[0] = 1;
hodnoty[1] = 1;
Inak povedané, rovno pri deklarácii povieme, koľko prvkov bude pole obsahovať. Nezabudnime, že na začiatku sú v poli nedefinované hodnoty!
Tu ale drieme ďalší problém: povedzme, že chceme prechádzať takéto pole. Zbúchame prvú verziu, ktorá funguje, aj keď …
int hodnoty[2];
hodnoty[0] = 1; hodnoty[1] = 1;
int i;
for(i = 0; i < 2; i++) {
printf("%d\n", hodnoty[i]);
}
Tá dvojka vyzerá dosť divne: rozhodne nie je dobré, keď sú v cykle „magické čísla“, lebo máme tendencie na ne zabudnúť a potom sa diviť.
Čo takto…
int pocet_hodnot = 2;
int hodnoty[pocet_hodnot];
Ďalej ani nepokračujeme:
error: ISO C90 forbids variable length array 'hodnoty'
Beda! Premennú nemožno použiť ako hodnotu pre počet prvkov! (Aspoň nie v štandarde C89/90).Veľkosť poľa musí totiž byť známa v čase kompilácie programu — a priradenie do premennej je vec, ktorá sa deje až v čase behu. Samozrejme, že kompilátor by mohol byť bystrý a predvídavý… ale toto je C: bystrosť a predvídavosť sú ponechané na vývojara.
Konštanty, teda niečo ako ony
Ako vyriešiť problém, kde mám premennú s hodnotou, ale chcem, aby jej hodnota bola známa už v čase kompilácie?
Viem použiť direktívy preprocesora. Toto je na samostatný článok, ale v stručnosti: „konštantu“ viem zadeklarovať takto:
#include <stdio.h>
#include <stdlib.h>
#define POCET_HODNOT 2
int main(void)
{
/* ... */
}
Deklarácia
#define POCET_HODNOT 2
povie, že zavádzame konštantu POCET_HODNOT
(konvencia káže veľkými písmenami) s hodnotou 2. Od tejto chvíle ju môžeme radostne používať v zvyšku programu podobne, ako bežnú premennú.
Pozor, medzi názvom premennej a hodnotou nie je =
! Zavádzanie konštánt sa totiž deje v inom programovacom jazyku ;-). Je to ako keby ste miešali dva jazyky: jazyk C a jazyk preprocesora.
#include <stdio.h>
#include <stdlib.h>
#define POCET_HODNOT 2
int main(void)
{
int hodnoty[POCET_HODNOT];
int i;
hodnoty[0] = 1; hodnoty[1] = 3;
for(i = 0; i < POCET_HODNOT; i++) {
printf("%d\n", hodnoty[i]);
}
return EXIT_SUCCESS;
}
Nabudúce o poliach a funkciách!