C je skvelý jazyk na turborýchle chrústanie čísiel. Urobme si príklad hodný radostných liet základnej školy: vypíšme si malú násobilku pre dané číslo.
Najprv Java
public class Nasobilka {
public static void main(String[] args) {
int cislo = 8;
for(int i = 1; i <= 10; i++) {
System.out.println(i + " x " + cislo + " = " + (i * cislo));
}
}
}
Výstupom bude
1 x 8 = 8
2 x 8 = 16
3 x 8 = 24
atď.
O niečo prehľadnejšie sa to dá v Jave zrealizovať aj menej známou metódou printf()
, ktorá … bola priamo inšpirovaná Cčkom (a ktorej trvalo deväť (!) rokov, kým sa do Javy dostala).
System.out.printf("%d x %d = %d\n", i, cislo, i * cislo);
Volanie pozostáva z formátovacieho reťazca obsahujúceho niečo ako „premenné”, za ktoré sa dosadia hodnoty v takom poradí, v akom je uvedený druhý, tretí… parameter. V tomto prípade máme tri premenné %d
(reprezentujúce číslo v desiatkovej, decimal, sústave), za ktoré sa postupne dosadia hodnoty i
, cislo
a i * cislo
.
Ako to bude vyzerať v C?
Bude to veľmi veľmi podobné. Vieme totiž rovno použiť printf()
v analogickom duchu a dokonca s analogickým zápisom premenných. Súc poučený prvou lekciou môžeme urobiť takmer priamy prepis, ale rovno poviem, že zlyhá. A to na prekvapivom mieste:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int cislo = 8;
for(int i = 1; i <= 10; i++) {
printf("%d x %d = %d", i, cislo, i * cislo);
}
return EXIT_SUCCESS;
}
Kompilátor nás oblaží:
main.c:7: error: 'for' loop initial declarations are only allowed in C99 mode
Problém je v zápise for
cyklu. Áno, ten je taký istý ako v Jave, ibaže… jedna veľmi dôležitá zásada:
Všetky lokálne premenné musia byť deklarované na začiatku procedúry.
V novších (a menej používaných verziách C99) sa od toho upúšťa, ale C89/90 je stále„najštandardnejší” štandard, ktorého sa zahodno pridržiavať. Ešte stále existujú kompilátory, ktoré majú s novšími vlastnosťami mentálny problém a keďže chceme písať programy, ktoré sú skompilovateľné na čo najväčšom množstve platforiem, budeme sa ho pridržiavať.
Opravme to teda:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int cislo = 8;
int i;
for(i = 0; i <= 10; i++) {
printf("%d x %d = %d\n", i, cislo, i * cislo);
}
return EXIT_SUCCESS;
}
Všimnime si, ako sme na začiatku funkcie deklarovali dve lokálne premenné: cislo
(ktoré sme rovno nainicializovali) a i
, ktorú sme inicializovali v cykle for
. Program už pobeží radostne a bude vypisovať to, čo chceme.
Ale pozor, opäť jeden zádrhel:
Lokálne premenné, ktoré neboli inicializované, obsahujú náhodné dáta.
Premenná i
obsahuje na začiatku náhodné dáta. Chaos. Neporiadok. Náhodné bity. Možno nulu, možno 42, možno mínus stotridsať. Rozhodne je dobré každú premennú pri deklarácii hneď aj inicializovať.
V tomto prípade to nebolo potrebné, lebo ihneď na ďalšom riadku sa do premennej i
vložila nula v rámci cyklu for
. Ale v iných prípadoch rozhodne inicializujte premenné: radšej do nich vložiť hodnotu dvakrát, ako pátrať po záhadných chybách.
Urobme teraz úkrok bokom a vytrhajme si vlasy nad troma zádrheľmi s printf()
om.
printf()
a zádrhel číslo 1.
Násobilka beží a ktosi dostal dokonale nezmyselný nápad: čo keď chcem vypísať len jedno číslo?
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf(8);
return EXIT_SUCCESS;
}
Malý program pre vývojára a … toľko chýb!
error: passing argument 1 of 'printf' makes pointer from integer without a cast
expected 'const char *' but argument is of type 'int'
Čo do…???
Vysvetlenie je ťažké bez znalosti pointerov, tak zatiaľ len dedomrázovsko-bocianovský komentár: funkcia printf()
potrebuje mať prvý argument ako reťazec. Je 8
reťazec? Nie, je to číslo, int
eger, čo tvrdí aj kompilátor: "argument is of type 'int'"
. V Cčku platia všakovaké prevody medzi dátovými typmi a 8
sa dá použiť ako reťazec… ale rozhodne nie ako reťazec "8"
. Nechcite zatiaľ vedieť zatiaľ, ako.
Keby sme nemali pozapínané kadejaké varovania, -Wall
y, -Wextra
y a podobne, program by sa skompiloval… a pri behu hádzal bludy.
Správna verzia je takto:
printf("%d", 8);
Poučenie:
Prvý parameter
printf()
musí byť formátovací reťazec!
printf()
a zádrhel číslo 2.
Keby niekoho napadol priamy prepis z Javy
int cislo = 8;
for(int i = 1; i <= 10; i++) {
System.out.println(i + " x " + cislo + " = " + (i * cislo));
}
do C:
int cislo = 8;
int i;
for(i = 1; i <= 10; i++) {
printf(i + " x " + cislo + " = " + (i * cislo));
}
tak by narazil:
error: invalid operands to binary + (have 'char *' and 'char *')
V Jave platia kadejaké divoké prevody medzi reťazcami a inými dátovými typmi. Ak vezmete číslo a prirátate k nemu reťazec (i + " x "
), vypadne z toho reťazec.
V C nič aké nefunguje, resp. opäť: vypadne z toho niečo, z čoho vám bez znalostí pointerov vypadnú oči. Veď už ani teraz sotva rozumieť hláške.
Pamätajte si zatiaľ toto:
Nikdy nesčítavajte čísla s reťazcami!
printf()
a zádrhel číslo 3.
Ešte jeden zádrheľ, azda to nikdy neskončí?? Otázka za 50 bodov, čo urobí tento program?
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
printf("%d", i);
return EXIT_SUCCESS;
}
Pri vhodne nastavených varovaniach sa ani neskompiluje:
error: 'i' is used uninitialized in this function
Premenná i
sa ide použiť bez toho, aby bola inicializovaná. Keby varovania neboli zapnuté, užijete si kadejaké hodnoty: prvý beh vypíše možno nulu, druhý mínus päťsto, tretí ktoviečo. Urobte si lotériu: stokrát vám to vráti nulu a pri prezentovaní projektu určite nejaký blud.
Poučenie:
Vždy inicializujte svoje premenné!
Záver
Zažili sme veľa zádrheľov, ale zatiaľ takmer všetky boli ihneď odchytené. Nabudúce ďalšie veselice so vstupom a výstupom!