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 #define
ovať 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 enum
y
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 enum
ami je veľa zábavy, a niektorí na ne prespevujú doslova ódy a uprednostňujú ich pred #define
ovaný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 enum
och, zatiaľčo #define
uté hodnoty sa pri ladení stratia (súvisí to s prácou preprocesora).
Zádrhele a finty pri enumoch
Tajomstvo okolo enum
ov, 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 enum
e 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 enum
y možno switch
ovať:
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 akoFORMAT_VYPISU_POLA
.
Konvencia hovorí, že typedef
nuté 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áš hodnotyFORMAT_VERTIKALNY
aFORMAT_VERTIKALNY
, ja ťa krstím v mene patriarchov K & R akoFORMAT_VYPISU_POLA
.
Nutno poznamenať, že niektorí kerneloví hackeri nemajú radi typedef
y, 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 enum
y: 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
.