Svět jednohlavičkových knihoven pro jazyky C a C++

Dnes
Doba čtení: 30 minut

Sdílet

Autor: Depositphotos
Dnes se seznámíme s konceptem jednohlavičkových knihoven (header-only library) používaných v C i C++. Jedná se o knihovny, které jsou celé tvořeny pouze jediným hlavičkovým souborem obsahujícím definice i deklarace.

Obsah

1. Svět jednohlavičkových knihoven pro jazyky C a C++

2. Výhody a nevýhody jednohlavičkových knihoven

3. Ukázka typické jednohlavičkové knihovny: stb_image_write.h

4. Korektní způsob použití knihovny stb_image_write.h

5. Prototypy funkcí vs definice funkcí

6. Kontroly chybových stavů

7. Oddělený překlad jednohlavičkové knihovny?

8. Rozdělení překladu knihovny stb_image_write.h od zbytku aplikace

9. Kombinace většího množství jednohlavičkových knihoven

10. Vykreslení Perlinova šumu s uložením výsledného obrázku na disk

11. Jednohlavičkové knihovny s implementací (datových) kontejnerů

12. Vektory s dynamicky měnitelnou kapacitou

13. Demonstrační příklady: manipulace s vektory

14. Mapy (slovníky)

15. Demonstrační příklady: použití slovníků

16. Jednohlavičková knihovna pro vyhodnocování výrazů

17. Příklady realizace vyhodnocování výrazů bez proměnných i s proměnnými

18. Příloha: Makefile soubor pro překlad všech demonstračních příkladů

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Svět jednohlavičkových knihoven pro jazyky C a C++

V dnešním článku se seznámíme s konceptem takzvaných jednohlavičkových knihoven (header-only library) v ekosystému programovacích jazyků C a C++. Jak již název napovídá, jedná se o knihovny, které jsou celé tvořeny pouze jediným hlavičkovým souborem, jenž obsahuje definice maker, definice funkcí a současně i jejich deklarace. V závislosti na konkrétní knihovně je možné tyto hlavičkové soubory přímo vložit do zdrojových kódů příkazem preprocesoru #include, ovšem některé z těchto knihoven umožňují i oddělený překlad (knihovna se v takovém případě přeloží do objektového souboru, který se následně může slinkovat s ostatními objektovými soubory). Jednohlavičkové soubory jsou poměrně populární, zejména kvůli snadnosti jejich instalace a zařazení do vyvíjeného projektu. Na druhou stranu však zejména v případě programovacího jazyka C můžeme narazit na určitá omezení, která jsou daná jak typovým systémem tohoto jazyka, tak i neexistencí jmenných prostorů (namespace).

2. Výhody a nevýhody jednohlavičkových knihoven

Jak jsem se již zmínil v úvodní kapitole, jsou jednohlavičkové knihovny ve světě programovacích jazyků C a C++ poměrně populární. Přispívá k tomu i fakt, že takové knihovny lze velmi snadno začlenit do vytvářeného projektu a navíc nejsou vývojáři nuceni používat sofistikované (a mnohdy zbytečně komplikované) správce projektů a balíčků; vystačí si s nástroji make, git nebo i jen s nástrojem wget. Ostatně v této oblasti neexistuje pro jazyky C ani C++ jednotný a uznávaný standard, takže by se mohlo s nadsázkou říci, že jakékoli řešení je lepší než žádné řešení.

Ovšem kromě předností má toto řešení i několik více či méně závažných záporů. Často se zmiňuje fakt, že modifikace provedené v kódu vkládané jednohlavičkové knihovny vyžaduje přeložení všech dalších zdrojových kódů, které tuto knihovnu používají. To je však většinou nutné provést v každém případě, tedy i kdyby se používal oddělený překlad (vyžaduje to koncept hlavičkových souborů). V případě, že se jednohlavičková knihovna vkládá přímo do zdrojového kódu i s těly funkcí, bude pochopitelně delší i čas překladu tohoto zdrojového kódu. Tento problém mnohé knihovny řeší tak, že umožňují oddělený překlad. A poslední problém (který lze též do jisté míry řešit odděleným překladem) spočívá v tom, že jazyk C nepodporuje změnu jmenných prostorů, takže teoreticky může nastat situace, kdy knihovna i kód použijí stejný symbol v různých kontextech. Jednohlavičkové knihovny musí všechny své symboly začínat stejným prefixem a naopak ostatní kód by takový prefix používat neměl.

3. Ukázka typické jednohlavičkové knihovny: stb_image_write.h

První jednohlavičkovou knihovnou, se kterou se v dnešním článku alespoň ve stručnosti seznámíme, je knihovna nazvaná stb_image_write.h. Tato knihovna obsahuje definice funkcí sloužících pro ukládání rastrových obrázků do formátů PNG, BMP, TGA, JPG a HDR. Jedná se o tyto funkce:

int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality);
int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);

Dále tato knihovna obsahuje zobecněné funkce, které lze využít například při posílání rastrového obrázku přes sockety atd. Tyto funkce neprovádí přímé manipulace se soubory, ale provedou uložení rastrového obrázku do zvoleného formátu v operační paměti a přitom zavolají nastavenou callback funkci:

int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data, int stride_in_bytes);
int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality);

Callback funkce, která bude automaticky zavolána, musí mít tuto hlavičku:

void stbi_write_func(void *context, void *data, int size);

Navíc je možné přes další funkce ovlivnit například úroveň komprimace u formátu PNG atd. (výchozí úroveň je nízká, takže lze PNG dále optimalizovat, například nástrojem pngcrush atd.).

Knihovnu stb_image_write.h získáme snadno:

$ wget https://raw.githubusercontent.com/nothings/stb/refs/heads/master/stb_image_write.h
 
Saving 'stb_image_write.h'
HTTP response 200  [https://raw.githubusercontent.com/nothings/stb/refs/heads/master/stb_image_write.h]
stb_image_write.h    100% [=====================================================================================>]   19.88K    --.-KB/s
                          [Files: 1  Bytes: 19.88K [68.56KB/s] Redirects: 0  Todo: 0  Errors: 0                  ]

4. Korektní způsob použití knihovny stb_image_write.h

Použití knihovny stb_image_write.h je poměrně snadné. Ukážeme si to na několika jednoduchých a krátkých demonstračních příkladech. V prvním příkladu provedeme uložení rastrového obrázku o velikosti 4×4 pixely do formátu PNG. Jednotlivé pixely jsou reprezentovány 32bitovou hodnotou RGBA (nejvyšší bajt nese informaci o průhlednosti, další tři bajty pak barvové složky RGB, ovšem v pořadí modrá-zelená-červená). To tedy znamená, že obrázek 4×4 pixely s černým okrajem, uvnitř něhož je uložen červený, zelený, žlutý a modrý pixel, lze reprezentovat následovně:

uint32_t image[] = {
    0xff000000, 0xff000000, 0xff000000, 0xff000000,
    0xff000000, 0xff0000ff, 0xff00ff00, 0xff000000,
    0xff000000, 0xff00ffff, 0xffff0000, 0xff000000,
    0xff000000, 0xff000000, 0xff000000, 0xff000000,
};

Knihovnu stb_image_write.h vložíme do zdrojového kódu takto (včetně definice makrosymbolu):

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

Uložení rastrového obrázku do souboru typu PNG je snadné:

stbi_write_png("test.png", šířka, výška, bajtů_na_pixel, rastrový_obrázek, stride_délka_řádku);

Úplný zdrojový kód dnešního prvního demonstračního příkladu může vypadat následovně:

#include <stdint.h>
 
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
 
#define WIDTH 4
#define HEIGHT 4
#define RGBA 4
 
uint32_t image[] = {
    0xff000000, 0xff000000, 0xff000000, 0xff000000,
    0xff000000, 0xff0000ff, 0xff00ff00, 0xff000000,
    0xff000000, 0xff00ffff, 0xffff0000, 0xff000000,
    0xff000000, 0xff000000, 0xff000000, 0xff000000,
};
 
int main(void) {
    stbi_write_png("test.png", WIDTH, HEIGHT, RGBA, image, 4*sizeof(uint32_t));
    return 0;
}
Poznámka: poslední parametr obsahuje informaci o délce obrazového řádku v bajtech.

Překlad zdrojového kódu tohoto demonstračního příkladu je snadný:

$ gcc -Wall -pedantic simple_image_2.c -o simple_image_2

Po překladu tento příklad spustíme:

$ ./simple_image_2

V pracovním adresáři by měl po spuštění vzniknout rastrový obrázek uložený do souboru s názvem test.png:

$ ls -1
 
simple_image_1.c
simple_image_2
stb_image_write.h
test.png

Ověříme si, zda se skutečně jedná o rastrový obrázek:

$ file test.png
 
test.png: PNG image data, 4 x 4, 8-bit/color RGBA, non-interlaced

Výsledek po zvětšení dvacetinásobném zvětšení získaného rastrového obrázku:

Header-only libraries

 Obrázek 1: Rastrový obrázek 4×4 pixely vytvořený demonstračním příkladem.

Autor: tisnik, podle licence: Rights Managed

5. Prototypy funkcí vs definice funkcí

V předchozím demonstračním příkladu jsme před importem jednohlavičkové knihovny definovali i symbol STB_IMAGE_WRITE_IMPLEMENTATION:

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

Tento symbol zajistí, že se při importu vloží do zdrojového kódu nejenom hlavičky všech funkcí, ale i jejich implementace.

Otestujme si, co se stane v případě, že se pouze pokusíme o vložení hlavičkového souboru bez definice STB_IMAGE_WRITE_IMPLEMENTATION:

#include "stb_image_write.h"

Pokud se nyní pokusíme o překlad takto upraveného příkladu, dojde k chybě ve fázi slinkování:

$ gcc -Wall -pedantic simple_image_1.c
 
/usr/bin/ld: /tmp/ccwoMWRf.o: in function `main':
simple_image_1.c:(.text+0x25): undefined reference to `stbi_write_png'
collect2: error: ld returned 1 exit status
Poznámka: tato chyba není způsobena chybou v jednohlavičkové knihovně, ale jedná se o záměr. Díky této vlastnosti je totiž možné v případě potřeby knihovnu přeložit v samostatném kroku a zajistit tak její oddělený překlad.

Upravený zdroj demonstračního příkladu bude vypadat následovně:

#include <stdint.h>
#include "stb_image_write.h"
 
#define WIDTH 4
#define HEIGHT 4
#define RGBA 4
 
uint32_t image[] = {
    0xff000000, 0xff000000, 0xff000000, 0xff000000,
    0xff000000, 0xff0000ff, 0xff00ff00, 0xff000000,
    0xff000000, 0xff00ffff, 0xffff0000, 0xff000000,
    0xff000000, 0xff000000, 0xff000000, 0xff000000,
};
 
int main(void) {
    stbi_write_png("test.png", WIDTH, HEIGHT, RGBA, image, 4*sizeof(uint32_t));
    return 0;
}

6. Kontroly chybových stavů

Většina jednohlavičkových knihoven je naprogramována zkušenými vývojáři. I z tohoto důvodu je většinou vyřešen i problém hlášení chybových stavů. V jazyce C neexistuje koncept klasických výjimek, takže se většinou chybový stav oznamuje v návratové hodnotě funkce (nebo nastavením nějaké globální proměnné). Příkladem může být i funkce stbi_write_png, která vrací jedničku v případě, že funkce proběhla v pořádku a nulu v opačném případě:

#include <stdio.h>
#include <stdint.h>
 
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
 
#define WIDTH 4
#define HEIGHT 4
#define RGBA 4
 
uint32_t image[] = {
    0xff000000, 0xff000000, 0xff000000, 0xff000000,
    0xff000000, 0xff0000ff, 0xff00ff00, 0xff000000,
    0xff000000, 0xff00ffff, 0xffff0000, 0xff000000,
    0xff000000, 0xff000000, 0xff000000, 0xff000000,
};
 
int main(void) {
    int status = stbi_write_png("test.png", WIDTH, HEIGHT, RGBA, image, 4*sizeof(uint32_t));
    printf("Success? %s\n", status ? "yes" : "no");
    return 0;
}

Tento příklad přeložíme běžným způsobem:

$ gcc -Wall -pedantic simple_image_3.c -o simple_image_3

Po spuštění se nejenom vytvoří výsledný obrázek, ale navíc se i zobrazí informace o tom, jestli byla tato operace úspěšná či nikoli:

$ ./simple_image_3
Success?

7. Oddělený překlad jednohlavičkové knihovny?

Jednou z nevýhod při zápisu celého kódu knihovny v jediném hlavičkovém souboru je fakt, že je (zdánlivě) nutné překlad neustále opakovat při každém vložení kódu knihovny konstrukcí #include. Mnohé jednohlavičkové knihovny tento problém ovšem řeší, a to tak, že umožňují oddělený překlad knihovny. Konkrétně v případě knihovny stb_image_write.h jsme viděli, že pro vložení definic všech funkcí je nutné nejdříve definovat symbol preprocesoru STB_IMAGE_WRITE_IMPLEMENTATION. To tedy znamená, že by bylo možné provést oddělený překlad pouze jednohlavičkové knihovny tak, jak to zhruba odpovídá následujícímu příkazu:

$ gcc -D STB_IMAGE_WRITE_IMPLEMENTATION -c stb_image_write.h

Ovšem zde se projevuje jedna z vlastností překladače GCC – pokud se překládá hlavičkový soubor (to překladač pozná podle koncovky), nebude výsledkem objektový kód (.o), ale soubor s koncovkou .gch, který obsahuje předkompilované hlavičky (což ovšem v tomto případě vůbec nepotřebujeme):

$ ls -1
 
simple_image_1.c
stb_image_write.h
stb_image_write.h.gch

I tento problém je však možné snadno vyřešit, což si ukážeme v další kapitole.

8. Rozdělení překladu knihovny stb_image_write.h od zbytku aplikace

Aby překladač GCC přeložil hlavičkový soubor stejným způsobem jako běžný zdrojový soubor napsaný v jazyku C, musíme překladači předat přepínač -x c. Překlad knihovny stb_image_write do objektového kódu obsahujícího všechny funkce provedeme následovně:

$ gcc -D STB_IMAGE_WRITE_IMPLEMENTATION -x c -c stb_image_write.h

Nyní by měl pracovní adresář obsahovat (minimálně) tyto soubory:

$ ls -1
 
simple_image_1.c
stb_image_write.h
stb_image_write.o

Následně můžeme, zcela odděleně, přeložit zdrojový kód simple_image1.c, který jsme si ukázali ve čtvrté kapitole:

$ gcc -c simple_image_1.c

Obsah pracovního adresáře:

$ ls -1
 
simple_image_1.c
simple_image_1.o
stb_image_write.h
stb_image_write.o

Na závěr oba objektové soubory slinkujeme:

$ gcc simple_image_1.o stb_image_write.o -o simple_image_1

Obsah pracovního adresáře po poslední operaci:

$ ls -1
 
simple_image_1
simple_image_1.c
simple_image_1.o
stb_image_write.h
stb_image_write.o

Nyní již můžeme právě vytvořený spustitelný soubor skutečně spustit:

$ ./simple_image_1

Výsledkem bude nový rastrový obrázek:

$ file test.png
 
test.png: PNG image data, 4 x 4, 8-bit/color RGBA, non-interlaced
Poznámka: všechny funkce přeložené do objektového souboru si můžeme vypsat například takto:
$ nm -g stb_image_write.o | grep " T "
 
0000000000000000 T stbi_flip_vertically_on_write
0000000000000ae0 T stbi_write_bmp
0000000000000a70 T stbi_write_bmp_to_func
0000000000001938 T stbi_write_hdr
00000000000018c8 T stbi_write_hdr_to_func
00000000000054c8 T stbi_write_jpg
0000000000005451 T stbi_write_jpg_to_func
00000000000039a1 T stbi_write_png
0000000000003a5b T stbi_write_png_to_func
0000000000003302 T stbi_write_png_to_mem
0000000000000f90 T stbi_write_tga
0000000000000f20 T stbi_write_tga_to_func
0000000000001c46 T stbi_zlib_compress

9. Kombinace většího množství jednohlavičkových knihoven

V praxi se dříve či později setkáme s požadavkem využití většího množství jednohlavičkových knihoven v jednom projektu. To, zda bude skutečně možné zkombinovat více knihoven, do značné míry závisí na tom, jestli byli původní autoři důslední v pojmenovávání identifikátorů v jednotlivých knihovnách. To se týká maker, datových typů, funkcí a teoreticky i globálních proměnných. Pokud obsahují všechny tyto identifikátory nějaký unikátní prefix, měla by být kombinace většího množství knihoven prakticky proveditelná, což si ostatně ukážeme v navazující kapitole.

10. Vykreslení Perlinova šumu s uložením výsledného obrázku na disk

Kombinaci více jednohlavičkových knihoven si ukážeme na demonstračním příkladu, který po svém spuštění vykreslí známý Perlinův šum a uloží výsledek do rastrového obrázku. Pro výpočet Perlinova šumu použijeme knihovnu stb_perlin.h, jejíž zdrojový kód (v hlavičkovém souboru) získáme snadno:

$ wget https://raw.githubusercontent.com/nothings/stb/refs/heads/master/stb_perlin.h

Obě knihovny můžeme do projektu vložit v libovolném pořadí:

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
 
#define STB_PERLIN_IMPLEMENTATION
#include "stb_perlin.h"

Celý výpočet i s uložením výsledného souboru vypadá následovně:

#include <stdio.h>
#include <stdint.h>
 
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
 
#define STB_PERLIN_IMPLEMENTATION
#include "stb_perlin.h"
 
#define WIDTH 512
#define HEIGHT 512
#define RGBA 4
 
uint32_t image[HEIGHT][WIDTH] = {0};
 
int main(void) {
    int x, y;
    for (y=0; y<HEIGHT; y++) {
        for (x=0; x<WIDTH; x++) {
            int i = (int)(250.0*stb_perlin_turbulence_noise3(x/50., y/50., 0, 2.1, 0.5, 6));
            if (i>255) i=255;
            uint32_t color = (0xff << 24) + (i << 16) + (i << 8) + i;
            image[y][x] = color;
        }
    }
    stbi_write_png("perlin.png", WIDTH, HEIGHT, RGBA, image, WIDTH*sizeof(uint32_t));
    return 0;
}

Po překladu a spuštění tohoto demonstračního příkladu bychom měli získat rastrový obrázek perlin.png, který bude obsahovat Perlinův šum reprezentovaný ve stupních šedi:

Header-only libraries

Obrázek 2: Perlinův šum ve stupních šedi. 

Autor: tisnik, podle licence: Rights Managed

Ukažme si ještě jeden příklad generování Perlinova šumu. Nyní je šum počítán odděleně pro každou barvovou složku:

int r = (int)(300.0*stb_perlin_turbulence_noise3(x/200., y/200., 0, 2.2, 0.5, 6));
int g = (int)(300.0*stb_perlin_turbulence_noise3(x/200., y/200., 0, 1.9, 0.5, 7));
int b = (int)(300.0*stb_perlin_turbulence_noise3(x/200., y/200., 0, 2.1, 0.5, 8));

Upravený zdrojový kód demonstračního příkladu:

#include <stdio.h>
#include <stdint.h>
 
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
 
#define STB_PERLIN_IMPLEMENTATION
#include "stb_perlin.h"
 
#define WIDTH 512
#define HEIGHT 512
#define RGBA 4
 
uint32_t image[HEIGHT][WIDTH] = {0};
 
int main(void) {
    int x, y;
    for (y=0; y<HEIGHT; y++) {
        for (x=0; x<WIDTH; x++) {
            int r = (int)(300.0*stb_perlin_turbulence_noise3(x/200., y/200., 0, 2.2, 0.5, 6));
            if (r>255) r=255;
            int g = (int)(300.0*stb_perlin_turbulence_noise3(x/200., y/200., 0, 1.9, 0.5, 7));
            if (g>255) g=255;
            int b = (int)(300.0*stb_perlin_turbulence_noise3(x/200., y/200., 0, 2.1, 0.5, 8));
            if (b>255) b=255;
            uint32_t color = (0xff << 24) + (r << 16) + (g << 8) + b;
            image[y][x] = color;
        }
    }
    stbi_write_png("perlin.png", WIDTH, HEIGHT, RGBA, image, WIDTH*sizeof(uint32_t));
    return 0;
}

Výsledek (podle očekávání) je zcela odlišný od předchozího příkladu:

Header-only libraries

Obrázek 3: Perlinův šum vygenerovaný pro každou barvovou složku RGB zvlášť. 

Autor: tisnik, podle licence: Rights Managed

11. Jednohlavičkové knihovny s implementací (datových) kontejnerů

Vzhledem k tomu, že standardní knihovna programovacího jazyka C prakticky neobsahuje žádnou implementaci datových kontejnerů, nebude pravděpodobně velkým překvapením, že existuje poměrně velké množství jednohlavičkových knihoven s implementací takových kontejnerů. Setkáme se s implementacemi dynamických polí (ať již mají jakýkoli název), map (slovníků), stromových datových struktur (tree), ale například i jednosměrných a obousměrných front (queue, deque) nebo zásobníků (stack) a cyklických front (ring buffer). V navazujících kapitolách si ukážeme dva zástupce těchto knihoven. První z nich implementuje dynamická pole (ovšem nazývá je vektory), druhá knihovna pak obsahuje implementaci map (slovníků).

Poznámka: u všech těchto knihoven je dobré sledovat, jakou technologií rozšiřují datový systém programovacího jazyka C. Typicky je ve velké míře využit makrosystém céčka. Výsledek není ideální, zejména v porovnání s těmi jazyky, které tyto datové typy nativně podporují.

12. Vektory s dynamicky měnitelnou kapacitou

První knihovnou, která implementuje nějaký datový kontejner, je knihovna nazvaná vec.h. V této knihovně nalezneme implementaci vektorů, což je ovšem poněkud zavádějící označení. Jedná se totiž o vektory v takové podobě, v jaké je můžeme znát z programovacího jazyka C++: vektory jsou ve standardní knihovně C++ realizovány formou polí s rychlým přístupem k prvkům v konstantním čase O(1), ale s možností přidávání a odstraňování prvků (vektory tedy dynamicky mění svoji velikost). A stejným způsobem je navržena i knihovna vec.h.

Hlavičkový soubor s úplnou implementací všech potřebných maker a pomocných funkcí pro práci s vektory získáme snadno:

$ wget https://raw.githubusercontent.com/OguzhanUmutlu/vec.h/refs/heads/main/vec.h

Nejdříve je nutné vytvořit nový typ představující vektor určitých hodnot:

vec_define(type, NAME)

Makrosystémem jazyka C jsou pro tento nový vektor vytvořeny všechny potřebné funkce, například:

NAME_alloc()
NAME_push()
NAME_pop()
NAME_at()
NAME_set()
NAME_empty()
NAME_reverse()
NAME_shrink()

13. Demonstrační příklady: manipulace s vektory

V prvním demonstračním příkladu, který využívá vektory (dynamické pole) nadeklarujeme datové typy Name (prvek vektoru) a Names (vlastní vektor). Následně se do vektoru vygenerovanou funkcí Names_push vloží trojice prvků, získáme prvek na pozici 1 a poté obsah vektoru smažeme:

#include <stdio.h>
#include "vec.h"
 
typedef char* Name;
 
vec_define(Name, Names);
vec_define_free_simple(Name, Names);
vec_define_print(Name, Names, printf("%s", a));
 
int main() {
    Names names;
    Names_init(&names);
 
    Name p1 = {"Alice"};
    Name p2 = {"Bob"};
    Name p3 = {"Charlie"};
 
    Names_push(&names, p1);
    Names_push(&names, p2);
    Names_push(&names, p3);
 
    Names_print(names);
    printf("\n\n");
 
    Name p = Names_at(names, 1);
    printf("Element at index 1: \"%s\"\n", p);
 
    Names_clear(&names);
    return 0;
}

Ve druhém demonstračním příkladu je ukázáno, jakým způsobem je možné iterovat (procházet) přes všechny prvky vektoru. Používá se zde vygenerovaná funkce Names_at, která má konstantní časovou složitost:

#include <stdio.h>
#include "vec.h"
 
typedef char* Name;
 
vec_define(Name, Names);
vec_define_free_simple(Name, Names);
vec_define_print(Name, Names, printf("%s", a));
 
int main() {
    Names names;
    Names_init(&names);
    printf("Initial size=%d and capacity=%d\n", names.size, names.capacity);
 
    Name p1 = {"Alice"};
    Name p2 = {"Bob"};
    Name p3 = {"Charlie"};
 
    Names_push(&names, p1);
    Names_push(&names, p2);
    Names_push(&names, p3);
 
    printf("Actual size=%d and capacity=%d\n", names.size, names.capacity);
 
    Names_print(names);
    printf("\n\n");
 
    int i;
    for (i=0; i<names.size; i++) {
        Name p = Names_at(names, i);
        printf("Element at index %d: \"%s\"\n", i, p);
    }
 
    Names_clear(&names);
    return 0;
}

A konečně příklad třetí je inspirován přímo dokumentací ke knihovně vec.h. Pracuje se zde s typem vektoru nazvaným People, který obsahuje prvky typu Person, což jsou z pohledu programovacího jazyka C struktury:

#include <stdio.h>
#include "vec.h"
 
typedef struct Person {
    char name[50];
    int age;
} Person;
 
vec_define(Person, People);
vec_define_free_simple(Person, People);
vec_define_print(Person, People, printf("%s:%d", a.name, a.age));
 
int main() {
    People people;
    People_init(&people);
 
    Person p1 = {"Alice", 30};
    Person p2 = {"Bob", 25};
    Person p3 = {"Charlie", 35};
 
    People_push(&people, p1);
    People_push(&people, p2);
    People_push(&people, p3);
 
    People_print(people);
 
    Person p = People_at(people, 1);
    printf("Element at index 1: { name: \"%s\", age: %d }\n", p.name, p.age);
 
    Person updated = {"Bob Jr.", 26};
    People_set(&people, 1, updated);
    printf("Modified element at index 1: { name: \"%s\", age: %d }\n",
           People_at(people, 1).name, People_at(people, 1).age);
 
    Person popped = People_pop(&people);
    printf("Popped: { name: \"%s\", age: %d }\n", popped.name, popped.age);
 
    People_clear(&people);
    return 0;
}

14. Mapy (slovníky)

Dynamické pole, jehož jednu implementaci jsme si popsali v předchozích dvou kapitolách, je pochopitelně velmi často využívaným datovým kontejnerem. Ovšem prakticky stejně často se můžeme setkat i s požadavkem na použití mapy (map) resp. slovníku (dictionary). Jednohlavičková implementace tohoto typu kontejneru pochopitelně existuje, a to dokonce v několika variantách. V dnešním článku se ve stručnosti seznámíme s možnostmi poskytovanými knihovnou nazvanou hashmap.h. Tu je možné získat následovně:

$ wget https://raw.githubusercontent.com/sheredom/hashmap.h/refs/heads/master/hashmap.h

Tato knihovna poskytuje všechny potřebné operace pro práci se slovníky: vložení prvku (put), získání (přečtení) prvku (get), smazání prvku (remove), test na existenci prvku (nepřímo) a taktéž procházení prvky uloženými ve slovníku (ovšem v odlišném pořadí, než v jakém byly prvky do slovníku vloženy).

15. Demonstrační příklady: použití slovníků

V této kapitole si ukážeme několik příkladů použití slovníků. V příkladu prvním jsou do slovníku přidány dva prvky pod klíči „root“ a „user“. Povšimněte si, že se předávají jak samotné klíče (řetězce), tak i délky těchto klíčů. Prvky je nutné předávat ukazatelem, což mj. znamená, že následující příklad pracuje korektně jen díky tomu, že se slovník používá v jediné funkci (ukládané hodnoty jsou umístěny na zásobníkový rámec!):

#include <stdio.h>
 
#include "hashmap.h"
 
int main(void) {
    struct hashmap_s hashmap;
    hashmap_create(10, &hashmap);
 
    int x = 0;
    int y = 1000;
 
    hashmap_put(&hashmap, "root", strlen("root"), &x);
    hashmap_put(&hashmap, "user", strlen("user"), &y);
 
    int *id= hashmap_get(&hashmap, "root", strlen("root"));
    if (id != NULL)
        printf("%d\n", *id);
    else
        printf("not found\n");
 
    id = hashmap_get(&hashmap, "user", strlen("user"));
    if (id != NULL)
        printf("%d\n", *id);
    else
        printf("not found\n");
 
    id = hashmap_get(&hashmap, "other", strlen("other"));
    if (id != NULL)
        printf("%d\n", *id);
    else
        printf("not found\n");
 
    hashmap_destroy(&hashmap);
}

Výsledkem budou hodnoty dvou prvků a informace o tom, že třetí prvek nebyl nalezen:

0
1000
not found

Zatímco v prvním demonstračním příkladu byly použity operace get a put, ve druhém příkladu je sledována i kapacita slovníku a navíc se používá operace remove. Ta však nemění kapacitu – reorganizaci slovníku je nutné provést explicitně (to má své kladné i záporné stránky):

#include <stdio.h>
 
#include "hashmap.h"
 
int main(void) {
    struct hashmap_s hashmap;
    hashmap_create(2, &hashmap);
 
    int x = 0;
    int y = 1000;
  printf("Capacity: %d\n", hashmap_capacity(&hashmap));
 
    hashmap_put(&hashmap, "root", strlen("root"), &x);
    hashmap_put(&hashmap, "user", strlen("user"), &y);
    hashmap_put(&hashmap, "foo", strlen("foo"), &y);
    hashmap_put(&hashmap, "bar", strlen("bar"), &y);
    hashmap_put(&hashmap, "baz", strlen("baz"), &y);
 
    printf("Capacity: %d\n", hashmap_capacity(&hashmap));
 
    int *id= hashmap_get(&hashmap, "root", strlen("root"));
    if (id != NULL)
        printf("%d\n", *id);
    else
        printf("not found\n");
 
    id = hashmap_get(&hashmap, "user", strlen("user"));
    if (id != NULL)
        printf("%d\n", *id);
    else
        printf("not found\n");
 
    id = hashmap_get(&hashmap, "other", strlen("other"));
    if (id != NULL)
        printf("%d\n", *id);
    else
        printf("not found\n");
 
    hashmap_remove(&hashmap, "foo", strlen("foo"));
    hashmap_remove(&hashmap, "bar", strlen("bar"));
    hashmap_remove(&hashmap, "baz", strlen("baz"));
    hashmap_remove(&hashmap, "root", strlen("root"));
    hashmap_remove(&hashmap, "user", strlen("user"));
    printf("Capacity: %d\n", hashmap_capacity(&hashmap));
 
    hashmap_destroy(&hashmap);
}

Povšimněte si, že kapacita slovníku skutečně stále roste:

Capacity: 2
Capacity: 8
0
1000
not found
Capacity: 8

V příkladu třetím se slovníkem prochází (v pseudonáhodném pořadí). Pro každý nalezený prvek se volá callback funkce iterate, která vrací hodnotu 1 znamenající „pokračuj dále“ (návratovou hodnotou lze řídit, kdy se má průchod zastavit):

#include <stdio.h>
 
#include "hashmap.h"
 
static int iterate(void* const context, void* const value) {
    int *x = value;
    printf("%d\n", *x);
    return 1;
}
 
int main(void) {
    struct hashmap_s hashmap;
    hashmap_create(10, &hashmap);
 
    int x = 0;
    int y = 1000;
 
    hashmap_put(&hashmap, "root", strlen("root"), &x);
    hashmap_put(&hashmap, "user", strlen("user"), &y);
 
    int a = 1, b = 2, c = 3;
    hashmap_put(&hashmap, "foo", strlen("foo"), &a);
    hashmap_put(&hashmap, "bar", strlen("bar"), &b);
    hashmap_put(&hashmap, "baz", strlen("baz"), &c);
 
    int* value;
    hashmap_iterate(&hashmap, iterate, &value);
    hashmap_destroy(&hashmap);
}

Hodnoty nalezených prvků jsou vráceny a vytištěny v pseudonáhodném pořadí:

1
1000
3
0
2

16. Jednohlavičková knihovna pro vyhodnocování výrazů

Poslední jednohlavičkovou knihovnou, se kterou se v dnešním článku setkáme, je knihovna, která se jmenuje ceval.h. Tato knihovna dokáže vyhodnotit výrazy uložené do řetězce. Výrazy, přesněji řečeno infixové výrazy, mohou obsahovat základní aritmetické operátory, logické operátory, volání některých funkcí (goniometrické atd.) a pochopitelně i závorky ovlivňující prioritu prováděných operací. Ve výrazech se sice očekávají konstantní hodnoty, což je velké omezení, ovšem ukážeme si jeden trik, který nám umožní do výrazů předat i hodnoty proměnných a navíc i „externě“ řídit, které hodnoty lze předat a které nikoli. Samotné vyhodnocení (evaluation) výrazů je tedy bezpečnou operací.

Hlavičkový soubor s touto knihovnou získáme snadno:

$ wget https://raw.githubusercontent.com/erstan/ceval-single-header/refs/heads/e_t/ceval.hSaving 'ceval.h'
 
HTTP response 200  [https://raw.githubusercontent.com/erstan/ceval-single-header/refs/heads/e_t/ceval.h]
ceval.h              100% [=====================================================================================>]    7.33K    --.-KB/s
                          [Files: 1  Bytes: 7.33K [11.64KB/s] Redirects: 0  Todo: 0  Errors: 0                   ]

17. Příklady realizace vyhodnocování výrazů bez proměnných i s proměnnými

V prvním příkladu se vyhodnocuje výraz 1+2*3:

#include <stdio.h>
 
#include "ceval.h"
 
int main(void) {
    static char expression[] = "1+2*3";
    double result = ceval_result(expression);
    printf("Result=%f\n", result);
    return 0;
}

Tento příklad ve skutečnosti nebude přeložen, protože knihovna ceval.h vyžaduje funkce ze standardní knihovny stdlib.h, ovšem nijak se nesnaží načítat hlavičky příslušných funkcí. To je obecně nedostatek a jednohlavičkové knihovny by se měly chovat zcela autonomně.

Zdrojový kód příkladu opravíme:

#include <stdio.h>
#include <stdlib.h>
 
#include "ceval.h"
 
int main(void) {
    static char expression[] = "1+2*3";
    double result = ceval_result(expression);
    printf("Result=%f\n", result);
    return 0;
}

Nyní již dostaneme očekávaný výsledek:

Result=7.000000

Můžeme se pochopitelně pokusit o vyčíslení složitějšího výrazu:

#include <stdio.h>
#include <stdlib.h>
 
#include "ceval.h"
 
int main(void) {
    static char expression[] = "(3-20)*(14+20)+20*(14-3+20)";
    double result = ceval_result(expression);
    printf("Result=%f\n", result);
    return 0;
}

Výsledkem je v tomto případě (kupodivu) hodnota 42:

Result=42.000000

Ukažme si trik, kterým je možné do výrazu předat hodnoty nějakých proměnných (resp. obecně céčkovských výrazů). Výraz použitý v dalším příkladu obsahuje názvy parametrů s procenty, takže ho můžeme transformovat funkcí snprintf s tím, že ve výsledném výrazu budu za parametry dosazeny skutečné hodnoty:

#include <stdio.h>
#include <stdlib.h>
 
#include "ceval.h"
 
int main(void) {
    double a = 3;
    double b = 20;
    double c = 14;
 
    static char expression[] = "(%f-%f)*(%f+%f)+%f*(%f-%f+%f)";
    char buffer[200];
 
    snprintf(buffer, sizeof(buffer), expression, a, b, c, b, b, c, a, b);
    double result = ceval_result(buffer);
    printf("Result=%f\n", result);
    return 0;
}

Výsledkem bude opět hodnota 42:

Result=42.000000

Knihovna ceval.h podporuje i volání některých běžných funkcí, což si taktéž otestujeme:

#include <stdio.h>
#include <stdlib.h>
 
#include "ceval.h"
 
int main(void) {
    static char expression[] = "sin(deg2rad(45))";
    double result = ceval_result(expression);
    printf("Result=%f\n", result);
    return 0;
}

Nyní by měla být výsledkem polovina druhé odmocniny dvojky:

Result=0.707107

Navíc dokáže knihovna ceval.h zobrazit strom odpovídající zadanému výrazu:

#include <stdlib.h>
 
#include "ceval.h"
 
int main(void) {
    static char expression[] = "1+2*3";
    ceval_tree(expression);
    return 0;
}

Strom se zobrazí takovým způsobem, že uzel je zcela vlevo a nejsou zvýrazněny hrany mezi uzly:

                3
        *
                2
+
        1

Zobrazení stromu se složitějším výrazem:

#include <stdlib.h>
 
#include "ceval.h"
 
int main(void) {
    static char expression[] = "(3-20)*(14+20)+20*(14-3+20)";
    ceval_tree(expression);
    return 0;
}

Výsledek:

                        20
                +
                                3
                        -
                                14
        *
                20
+
                        20
                +
                        14
        *
                        20
                -
                        3

A konečně zobrazení stromu výrazu, ve kterém se výsledek jedné funkce předá do funkce jiné:

#include <stdlib.h>
 
#include "ceval.h"
 
int main(void) {
    static char expression[] = "sin(deg2rad(45))";
    ceval_tree(expression);
    return 0;
}

A takto je naznačena struktura stromu výrazu:

Školení Linux

                45
        deg2rad
sin

18. Příloha: Makefile soubor pro překlad všech demonstračních příkladů

Všechny výše uvedené demonstrační příklady je možné přeložit s využitím souboru Makefile, který je zobrazen pod tímto odstavcem. Povšimněte si, že tento soubor obsahuje i cíle (targets), které v případě potřeby stáhnou i potřebné jednohlavičkové knihovny:

CC=gcc
 
all:    stb_image_write.h ceval.h vec.h hashmap.h \
        evaluation_2 evaluation_3 evaluation_4 evaluation_5 \
        evaluation_6 evaluation_7 evaluation_8 \
        vector_usage_1 vector_usage_2 vector_usage_3 \
        simple_image_1 simple_image_2 simple_image_3 simple_image_4 \
        hashmap_usage_1 hashmap_usage_2 hashmap_usage_3 \
        perlin_noise perlin_noise_2
 
clean:
        rm -f ceval.h
        rm -f stb_image_write.h
        rm -f stb_image_write.h
        rm -f vec.h
        rm -f hashmap.h
        rm -f evaluation_1 evaluation_2 evaluation_3 evaluation_4 evaluation_5
        rm -f evaluation_6 evaluation_7 evaluation_8
        rm -f vector_usage_1 vector_usage_2 vector_usage_3
        rm -f simple_image_1 simple_image_2 simple_image_3 simple_image_4
        rm -f hashmap_usage_1 hashmap_usage_2 hashmap_usage_3
        rm -f perlin_noise perlin_noise_2
 
evaluation_1:   evaluation_1.c ceval.h
        $(CC) -lm -O0 $< -o $@
 
evaluation_2:   evaluation_2.c ceval.h
        $(CC) -lm -O0 $< -o $@
 
evaluation_3:   evaluation_3.c ceval.h
        $(CC) -lm -O0 $< -o $@
 
evaluation_4:   evaluation_4.c ceval.h
        $(CC) -lm -O0 $< -o $@
 
evaluation_5:   evaluation_5.c ceval.h
        $(CC) -lm -O0 $< -o $@
 
evaluation_6:   evaluation_6.c ceval.h
        $(CC) -lm -O0 $< -o $@
 
evaluation_7:   evaluation_7.c ceval.h
        $(CC) -lm -O0 $< -o $@
 
evaluation_8:   evaluation_8.c ceval.h
        $(CC) -lm -O0 $< -o $@
 
hashmap_usage_1:        hashmap_usage_1.c hashmap.h
        $(CC) -O0 $< -o $@
 
hashmap_usage_2:        hashmap_usage_2.c hashmap.h
        $(CC) -O0 $< -o $@
 
hashmap_usage_3:        hashmap_usage_3.c hashmap.h
        $(CC) -O0 $< -o $@
 
vector_usage_1: vector_usage_1.c vec.h
        $(CC) -O0 $< -o $@
 
vector_usage_2: vector_usage_2.c vec.h
        $(CC) -O0 $< -o $@
 
vector_usage_3: vector_usage_3.c vec.h
        $(CC) -O0 $< -o $@
 
simple_image_1: simple_image_1.c stb_image_write.h
        $(CC) -O0 $< -o $@
 
simple_image_2: simple_image_2.c stb_image_write.h
        $(CC) -O0 $< -o $@
 
simple_image_3: simple_image_3.c stb_image_write.h
        $(CC) -O0 $< -o $@
 
simple_image_4: simple_image_4.c stb_image_write.h
        $(CC) -O0 $< -o $@
 
perlin_noise:   perlin_noise.c stb_image_write.h stb_perlin.h
        $(CC) -O0 $< -o $@
 
perlin_noise_2:   perlin_noise_2.c stb_image_write.h stb_perlin.h
        $(CC) -O0 $< -o $@
 
stb_image_write.h:
        wget https://raw.githubusercontent.com/nothings/stb/refs/heads/master/stb_image_write.h
 
ceval.h:
        wget https://raw.githubusercontent.com/erstan/ceval-single-header/refs/heads/e_t/ceval.h
 
vec.h:
        wget https://raw.githubusercontent.com/OguzhanUmutlu/vec.h/refs/heads/main/vec.h
 
hashmap.h:
        wget https://raw.githubusercontent.com/sheredom/hashmap.h/refs/heads/master/hashmap.h
 
stb_perlin.h:
        wget https://raw.githubusercontent.com/nothings/stb/refs/heads/master/stb_perlin.h

19. Repositář s demonstračními příklady

Všechny demonstrační příklady, s nimiž jsme se v dnešním článku seznámili a které jsou určeny pro překlad s využitím prakticky libovolného moderního překladače jazyka C, jsou dostupné, jak je zvykem, na GitHubu. V tabulce níže jsou uvedeny odkazy na jednotlivé zdrojové kódy psané v jazyku C, které typicky vyžadují nějakou „jednosouborovou“ knihovnu – viz výše uvedený soubor Makefile, který obsahuje příslušné příkazy pro stažení těchto knihoven:

# Příklad Stručný popis příkladu Adresa
1 Makefile definice cílů pro překlad všech demonstračních příkladů z této tabulky https://github.com/tisnik/8bit-fame/blob/master/header-only/Makefile
       
2 evaluation1.c vyhodnocování výrazů, ukázka závislosti knihovny na standardní knihovně https://github.com/tisnik/8bit-fame/blob/master/header-only/evaluation1.c
3 evaluation2.c vyhodnocování výrazů, jednoduchý výraz, korektní varianta příkladu https://github.com/tisnik/8bit-fame/blob/master/header-only/evaluation2.c
4 evaluation3.c vyhodnocování výrazů, složitější výraz https://github.com/tisnik/8bit-fame/blob/master/header-only/evaluation3.c
5 evaluation4.c vyhodnocování výrazů, náhrada symbolů za hodnoty proměnných https://github.com/tisnik/8bit-fame/blob/master/header-only/evaluation4.c
6 evaluation5.c vyhodnocování výrazů, vestavěné funkce https://github.com/tisnik/8bit-fame/blob/master/header-only/evaluation5.c
7 evaluation6.c vyhodnocování výrazů, zobrazení stromu s jednoduchým výrazem https://github.com/tisnik/8bit-fame/blob/master/header-only/evaluation6.c
8 evaluation7.c vyhodnocování výrazů, zobrazení stromu se složitějším výrazem https://github.com/tisnik/8bit-fame/blob/master/header-only/evaluation7.c
9 evaluation8.c vyhodnocování výrazů, zobrazení stromu s výrazem, ve kterém se volají funkce https://github.com/tisnik/8bit-fame/blob/master/header-only/evaluation8.c
       
10 hashmap_usage1.c základní operace s mapou v jazyku C https://github.com/tisnik/8bit-fame/blob/master/header-only/hashmap_usage1.c
11 hashmap_usage2.c vymazání prvků z mapy https://github.com/tisnik/8bit-fame/blob/master/header-only/hashmap_usage2.c
12 hashmap_usage3.c iterace nad prvky uloženými v mapě https://github.com/tisnik/8bit-fame/blob/master/header-only/hashmap_usage3.c
       
13 simple_image1.c uložení rastrového obrázku do PNG, příklad s odděleným překladem knihovny https://github.com/tisnik/8bit-fame/blob/master/header-only/simple_image1.c
14 simple_image2.c uložení rastrového obrázku do PNG, příklad, který lze přeložit v jednom kroku https://github.com/tisnik/8bit-fame/blob/master/header-only/simple_image2.c
15 simple_image3.c uložení rastrového obrázku do PNG s kontrolou chyb https://github.com/tisnik/8bit-fame/blob/master/header-only/simple_image3.c
16 simple_image4.c uložení rastrového obrázku do PNG, rozdělení definice a deklarace funkcí z knihovny https://github.com/tisnik/8bit-fame/blob/master/header-only/simple_image4.c
       
17 vector_usage1.c vektor řetězců, základní varianta https://github.com/tisnik/8bit-fame/blob/master/header-only/vector_usage1.c
18 vector_usage2.c vektor řetězců, složitější varianta https://github.com/tisnik/8bit-fame/blob/master/header-only/vector_usage2.c
19 vector_usage3.c vektor struktur (záznamů) https://github.com/tisnik/8bit-fame/blob/master/header-only/vector_usage3.c
       
20 perlin_noise.c výpočet a vykreslení Perlinova šumu s uložením výsledku do obrázku typu PNG https://github.com/tisnik/8bit-fame/blob/master/header-only/perlin_noise.c

20. Odkazy na Internetu

  1. Header-only (Wikipedia)
    https://en.wikipedia.org/wiki/Header-only
  2. C/C++ open-source libraries with minimal dependencies
    https://github.com/r-lyeh/single_file_libs
  3. single-file public domain (or MIT licensed) libraries for C/C++
    https://github.com/nothin­gs/stb/tree/master
  4. Top 19 C single-header Projects
    https://www.libhunt.com/l/c/to­pic/single-header
  5. ceval-single-header
    https://github.com/erstan/ceval-single-header
  6. Zdrojový kód knihovny ceval
    https://github.com/erstan/ceval-single-header/blob/e_t/ceval.h
  7. Are single header libraries good?
    https://www.reddit.com/r/C_Pro­gramming/comments/12u7s37/a­re_single_header_libraries_go­od/
  8. Nanoprintf, a tiny header-only vsnprintf that supports floats! Zero dependencies, zero libc calls. No allocations, < 100B stack, < 5K C89/C99
    https://www.reddit.com/r/C_Pro­gramming/comments/cae52j/com­ment/et9amng/
  9. Lessons learned about how to make a header-file library
    https://github.com/nothin­gs/stb/blob/master/docs/stb_how­to.txt
  10. Libraries That Quietly Revolutionized C
    https://www.youtube.com/wat­ch?v=kS_GqDp6IT4
  11. C (programming language)
    https://en.wikipedia.org/wi­ki/C_(programming_language)
  12. ANSI C
    https://en.wikipedia.org/wiki/ANSI_C
  13. C++
    https://en.wikipedia.org/wiki/C%2B%2B
  14. GNU Make
    https://www.gnu.org/software/make/
  15. Cmake
    https://cmake.org/
  16. Avoid the Temptation of Header-Only Libraries
    https://dev.to/pauljlucas/avoid-the-temptation-of-header-only-libraries-33an
  17. header-only-library
    https://github.com/topics/header-only-library?o=desc&s=updated
  18. vec.h – Lightweight Dynamic Arrays in C
    https://github.com/Oguzha­nUmutlu/vec.h
  19. stb_image_write.h
    https://raw.githubusercon­tent.com/nothings/stb/ref­s/heads/master/stb_image_wri­te.h
  20. The SQLite Amalgamation
    https://sqlite.org/amalgamation.html
  21. SQLite Download Page
    https://sqlite.org/download.html
  22. hashmap.h
    https://github.com/sheredom/hashmap.h
  23. C Function Declaration and Definition
    https://www.w3schools.com/c/c_fun­ctions_decl.php
  24. Using Precompiled Headers
    https://gcc.gnu.org/online­docs/gcc/Precompiled-Headers.html
  25. Perlin noise
    https://en.wikipedia.org/wi­ki/Perlin_noise

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.