Z Javy migrujuvší céčkar 2: Printf a kamaráti

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, integer, č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, -Wally, -Wextray 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!