Z Javy migrujuvší céčkar 5.5: parametre, konštanty a enumy

Niektoré úlohy sa omieľajú dokola, lebo sa dedia z generácie na generáciu, alebo ich autori nie sú schopní vymyslieť niečo nové. Nasledovné úloha je ten druhý prípad.

Deja vu

Deja vu z minulého dielu s jemnou modifikáciou:

Janko má zo slovenčiny tieto známky: 1, 1, 5, 2, 3, 2. (Och nie, už zase…) Vypíšte jeho známky pod seba. Alebo vedľa seba.

To znamená, že môžeme modifikovať funkciu tak, aby dostávala na vstup parameter indikujúci formát výstupu — buď horizontálny alebo vertikálny.

V Jave by sme použili parameter typu boolean, ale ako vieme, nič také v C nie je. Tu sa môžeme uchýliť k integerom. Nezabúdame, že hocičo, čo je rovné nule, je „nepravda” a akákoľvek iná hodnota je „pravda“.

void vypis_pole(int pole[], int dlzka, int format);

Kód môže vyzerať takto: je to prvotná strašná verzia:

#include <stdio.h>
#include <stdlib.h>

/* 
    Vypise pole bud vertikalne (format = 0)
    alebo horizontalne (format != 0)
*/
void vypis_pole(int pole[], int dlzka, int format) {
    int i;
    for(i = 0; i < dlzka; i++) {
        if(format == 0) {
            printf("%d\n", pole[i]);
        } else {
            printf("%d ", pole[i]);
        }
    }
}

int main(void)
{
    int pocet_hodnot = 2;
    int hodnoty[] = { 1, 2 };
    printf("Vertikalny vypis:\n");
    vypis_pole(hodnoty, pocet_hodnot, 0);

    printf("Horizontalny vypis:\n");
    vypis_pole(hodnoty, pocet_hodnot, 1);

    return EXIT_SUCCESS;
}

Táto prvá verzia je katastrofická vylepšiteľná z viacerých dôvodov: predovšetkým sa tam objavujú magické čísla ako 0 (vertikálny výpis) a 1 (horizontálny výpis), a používateľ sa veľmi ľahko pomýli. V tomto prípade je lepšie #defineovať všetky magické nuly a jednotky a používať ich logické označenia:

#include <stdio.h>
#include <stdlib.h>

#define FORMAT_HORIZONTALNY 1
#define FORMAT_VERTIKALNY 0

void vypis_pole(int pole[], int dlzka, int format) {
    int i;
    for(i = 0; i < dlzka; i++) {
        if(format == FORMAT_VERTIKALNY) {
            printf("%d\n", pole[i]);
        } else {
            printf("%d ", pole[i]);
        }
    }
}

int main(void)
{
    int pocet_hodnot = 2;
    int hodnoty[] = { 1, 2 };
    printf("Vertikalny vypis:\n");
    vypis_pole(hodnoty, pocet_hodnot, FORMAT_VERTIKALNY);

    printf("Horizontalny vypis:\n");
    vypis_pole(hodnoty, pocet_hodnot, FORMAT_HORIZONTALNY);

    return EXIT_SUCCESS;
}

Vyrátajme enumy

Ešte krajšie je použitie vymenovaných typov — enum. Funguje to presne ako v Jave. Takmer presne. Približne. Teda… v Jave je to lepšie. Máme ale situáciu premennej, ktorá môže nadobúdať len niekoľko málo jasných hodnôt:

enum format_vypisu_pola {
    FORMAT_VERTIKALNY,
    FORMAT_HORIZONTALNY     
}

Porovnajme s Java verziou:

enum FormatVypisuPola {
    FORMAT_VERTIKALNY,
    FORMAT_HORIZONTALNY
}

Kým v Jave sme zadefinovali dátový typ FormatVypisuPola, v C sa bude volať enum format_vypisu_pola. Áno, na začiatku sa vždy musí uviesť aj enum.

Viď hlavička funkcie:

void vypis_pole(int pole[], int dlzka, enum format_vypisu_pola format)

enum v C prakticky funguje ako sada pomenovaných celých čísíel — teda máme definované „niečo ako konštantu” FORMAT_VERTIKALNY s hodnotou 0 a podobne „niečo ako konštantu” FORMAT_HORIZONTALNY s hodnotou 1.

Použitie je potom v analogickom duchu. Zahrajte si hru „nájdi dva rozdiely”, alebo sa nepozerajte na riešenie: funkcia main() je bez zmeny a vo funkcii vypis_pole sa zmenila len hlavička.

#include <stdio.h>
#include <stdlib.h>

enum format_vypisu_pola {
    FORMAT_VERTIKALNY,
    FORMAT_HORIZONTALNY
};

void vypis_pole(int pole[], int dlzka, enum format_vypisu_pola format) {
    int i;
    for(i = 0; i < dlzka; i++) {
        if(format == FORMAT_VERTIKALNY) {
            printf("%d\n", pole[i]);
        } else {
            printf("%d ", pole[i]);
        }
    }
}

int main(void)
{
    int pocet_hodnot = 2;
    int hodnoty[] = { 1, 2 };
    printf("FORMAT_VERTIKALNY vypis:\n");
    vypis_pole(hodnoty, pocet_hodnot, FORMAT_VERTIKALNY);

    printf("FORMAT_HORIZONTALNY vypis:\n");
    vypis_pole(hodnoty, pocet_hodnot, FORMAT_HORIZONTALNY);

    return EXIT_SUCCESS;
}

S enumami je veľa zábavy, a niektorí na ne prespevujú doslova ódy a uprednostňujú ich pred #defineovanými konštantami — stačí poobdivovať článok v angličtine.

V skratke veľká výhoda: veľa debuggerov vie vidieť hodnoty pre konštanty definované v enumoch, zatiaľčo #defineuté hodnoty sa pri ladení stratia (súvisí to s prácou preprocesora).

Zádrhele a finty pri enumoch

Tajomstvo okolo enumov, ktoré si pamätajú len naši otcovia, je v tom, v pôvodnej knihe patriarchov Kernighana & Ritchieho sa nenachádzali. Dolepili ich tam tak-nejak dodatočne (tak ako generiká do Javy ;-)) a z toho vyplýva viacero kúzelných vlastností. Jedna už bola spomenutá vyššie: To, že premenná typu enum niečoniečo je „niečo ako konštanta” je vidieť v nasledovnom príkladčoku.

int prepadlik[2] = {5, 5};
vypis_pole(prepadlik, 2, 0);

Ako hodnotu tretieho parametra sme použili nulu, čo je …. zvislý výpis.

Prvky v enume sú totiž očíslované od nuly, teda v našom prípade patrí 0 zvislému a 1 vodorovnému výpisu. Podobná vlastnosť funguje aj v Jave:

int ordinal = FormatVypisuPola.VERTIKALNY.ordinal()
// v Jave bude `ordinal` rovny 0

To tiež vedie k tomu, že cez enumy možno switchovať:

void vypis_pole(int pole[], int dlzka, enum format_vypisu_pola format) {
    int i;
    for(i = 0; i < dlzka; i++) {
        switch(format) {

        case FORMAT_VERTIKALNY:
            printf("%d\n", pole[i]);
            break;
        case FORMAT_VERTIKALNY:
            printf("%d ", pole[i]);
            break;
        default:
            printf("Neznamy format");
        }
    }
}

Táto vlastnosť vedie tiež k wtf momentu:

enum rocne_obdobie {
    JAR, LETO, JESEN, ZIMA
}

/* ---- /*
vypis_pole(hodnoty, pocet_hodnot, JAR);

… čo povedie k vertikálnemu výpisu. JAR má ordinálnu hodnotu 0, ktorá sa dá radostne použiť a kompilátor to neoverí :-)

Dajte si pozor :-)

enum: alias

Niektorým vývojárom prekáža, že dátový typ pre onen enum je dvojslovný, teda enum format_vypisu_pola. Samozrejme, existuje cesta von! Na rozdiel od Javy možno dátové typy premenovávať, aliasovať, či, povedané cčkarsky, `typedefovať.

Ak sa rozhodneme aliasnúť dátový typ enum format_vypisu_pola ako FORMAT_VYPISU_POLA, hľa:

typedef enum format_vypisu_pola FORMAT_VYPISU_POLA;

Čítame zľava doprava:

Dátový typ enum format_vypisu_pola, ja ťa krstím v mene Kernighana a Ritchieho ako FORMAT_VYPISU_POLA.

Konvencia hovorí, že typedefnuté dátové typy sa uvádzajú veľkými písmenami.

Následne môže hlavička vyzerať takto:

void vypis_pole(int pole[], int dlzka, FORMAT_VYPISU_POLA format) {

Zdatní Cčkari neváhajú urobiť dva kroky naraz: deklarovať enum a rovno ho aj premenovať:

typedef enum {
    FORMAT_VERTIKALNY,
    FORMAT_HORIZONTALNY
} FORMAT_VYPISU_POLA;

Čítame:

Dátový typ enum, ktorý nemáš špeciálne meno, lebo ti ho nikto nedal, ale máš hodnoty FORMAT_VERTIKALNY a FORMAT_VERTIKALNY, ja ťa krstím v mene patriarchov K & R ako FORMAT_VYPISU_POLA.

Nutno poznamenať, že niektorí kerneloví hackeri nemajú radi typedefy, pretože sa to môže ľuďom pliesť — hlavne, keď sa pomiešajú štruktúry s enumami, prípadne premenné s pointrami s premennými bez pointrov.

Ale použitie nechám na vás.

Čo si odniesť

Pokiaľ môžete, používajte enumy: hodia sa na mnoho vecí, hlavne v prípade, že má premenná niekoľko málo hodnôt. Určite sú lepšie než náhodné číselné konštanty a sú o niečo lepšie než konštanty v #define.

Ak chcete, premenujte ich cez typedef.

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *