Programovací jazyk C3: složené datové typy a kontejnery

23. 9. 2025
Doba čtení: 29 minut

Sdílet

Programovací jazyk C3
Autor: Root.cz s využitím Zoner AI
Zaměříme se na popis složených datových typů a kontejnerů. Mezi základní typy kontejnerů patří vektory, staticky alokovaná pole, dynamicky alokovaná pole a můžeme sem zařadit i řezy.

Obsah

1. Programovací jazyk C3: složené datové typy a kontejnery

2. Vektory (vector)

3. Konstrukce vektoru, modifikace prvků vektoru

4. Předávání vektorů do funkcí

5. Pole (array)

6. Konstrukce pole, přístup k prvkům pole a modifikace prvků pole

7. Předávání polí do funkcí

8. Řezy (slice)

9. Přímá konstrukce řezu s jeho inicializací

10. Získání řezu z pole

11. Další možnosti získání řezu: čím se C3 odlišuje od dalších jazyků

12. Řezy získané z jiných řezů

13. Kontejner typu seznam (list)

14. Předání seznamu do funkcí

15. Dynamicky alokované pole

16. Různé varianty řetězců

17. Řetězce ukončené nulou

18. Porovnání řetězců

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

20. Odkazy na Internetu

1. Programovací jazyk C3: složené datové typy a kontejnery

Ve třetím článku o programovacím jazyce C3 se zaměříme na popis složených datových typů a kontejnerů. Definice kontejneru se většinou jazyk od jazyka odlišuje, ale obecně za kontejnery považujeme takové datové typy, které mohou jako své prvky obsahovat jiné objekty (v obecném smyslu se mezi objekty počítají i numerické hodnoty, znaky, řetězce, struktury atd.). Jazyk C3 je navržen takovým způsobem, aby zjednodušil práci vývojářům přecházejícím z jazyka C, ovšem aby se od C sémanticky příliš neodlišoval.

Z tohoto důvodu – jak ostatně uvidíme v následujících kapitolách – jsou vlastnosti kontejnerů v C3 na půli cesty mezi klasickým jazykem C na jedné straně (zde existují pouze statická a dynamicky alokovaná pole, ostatní kontejnery se programují ručně) a jazykem Go na straně druhé (podpora pro pole s rostoucím počtem prvků přes řezy – slices a taktéž podpora pro práci s asociativními poli – mapami). V jazyce C3 tedy nebude práce s dynamicky se měnícími strukturami zcela bezproblémová, ovšem obecně bude snazší než v klasickém jazyku C.

2. Vektory (vector)

Pro úplnost se v tomto článku zmíníme o všech kontejnerech nabízených programovacím jazykem C3. To mj. znamená, že se alespoň ve stručnosti znovu zmíníme o vektorech, které byly částečně popsány v předchozím článku. Připomeňme si, že vektory jsou kontejnerem, který může obsahovat předem známý počet prvků (tento počet zjišťuje překladač). Vektory se do značné míry podobají klasickým polím (array), ovšem operace s vektory je překládána do SIMD instrukcí, pochopitelně jen za předpokladu, že je to na dané architektuře možné. Ovšem kvůli této vlastnosti je možné do vektorů ukládat pouze prvky celočíselných datových typů, typů s plovoucí řádovou čárkou, pravdivostní hodnoty nebo ukazatele. Nelze tedy například pracovat s vektory řetězců atd.; pokud je to nutné, musí se používat klasická pole.

Délka vektorů (počet prvků) není měnitelná, tj. nelze do nich přidávat další prvky ani prvky mazat. Ovšem samotné prvky vektoru jsou měnitelné (mutable).

3. Konstrukce vektoru, modifikace prvků vektoru

Datový typ vektor se v jazyku C3 zapisuje tímto způsobem:

typ_prvku[<počet_prvků>]

přičemž počet prvků musí být známý již v čase překladu, tedy jedná se o konstantu. Příkladem může být vektor obsahující deset prvků typu int, který můžeme ihned inicializovat:

int[<10>] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

Délka vektoru, která je zjištěna při jeho definici, je dostupná přes atribut nazvaný len. K prvkům vektorů se přistupuje (z hlediska syntaxe) stejně, jako k prvkům polí. Jak již víme z předchozího textu, jsou prvky vektorů obecně měnitelné (mutable), takže můžeme obsah vektoru snadno modifikovat:

for (int i=0; i<a.len; i++) {
    a[i] = (i + 1)*10;
}

V dnešním prvním demonstračním příkladu je ukázána deklarace vektoru s jeho inicializací, dále výpis obsahu tohoto vektoru (hodnoty všech prvků), modifikace prvků vektoru a znovu jeho výpis:

module containers;
import std::io;
 
fn void main()
{
    io::printf("type: %s\n", int[<10>].nameof);
 
    int[<10>] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
    io::printf("a=%s\n", a);
 
    for (int i=0; i<a.len; i++) {
        a[i] = (i + 1)*10;
    }
    io::printf("a=%s\n", a);
}

Výsledky:

type: int[<10>]
a=[<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>]
a=[<10, 20, 30, 40, 50, 60, 70, 80, 90, 100>]

4. Předávání vektorů do funkcí

Zajímavé bude zjistit, jakým způsobem jsou vektory předávány (formou parametrů) do funkcí. V současnosti používaných programovacích jazycích se parametry předávají buď hodnotou (value) nebo odkazem (reference), přičemž mnohé jazyky z různých důvodů tyto způsoby předávání kombinují – hodnoty některých typů jsou tedy předávány hodnotou a u jiných typů přes reference.

V demonstračním příkladu ukázaném pod tímto odstavcem je vytvořen vektor, ten je předán do funkce nazvané update_vector, která modifikuje prvky předaného vektoru a po návratu z této funkce je vektor vypsán:

module containers;
import std::io;
 
fn void update_vector(int[<10>] vector) {
    for (int i=0; i<vector.len; i++) {
        vector[i] = (i + 1)*10;
    }
}
 
fn void main()
{
    io::printf("type: %s\n", int[<10>].nameof);
 
    int[<10>] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    update_vector(a);
    io::printf("a=%s\n", a);
}

Z vypsaných výsledků je patrné, že volání funkce update_vector původní vektor nijak nezměnilo. To znamená jediné – vektor byl předán hodnotou (tudíž se provedla jako kopie) a prvky byly změněny v této kopii, nikoli v původním vektoru:

type: int[<10>]
a=[<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>]
a=[<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>]

V případě, že budeme chtít, aby se skutečně modifikoval vektor předaný do funkce update_vector, musíme explicitně vektor předat odkazem, tj. přes ukazatel – viz podtržené části zdrojového kódu:

module containers;
import std::io;
 
fn void update_vector(int[<10>] *vector) {
    for (int i=0; i<vector.len; i++) {
        (*vector)[i] = (i + 1)*10;
    }
}
 
fn void main()
{
    io::printf("type: %s\n", int[<10>].nameof);
 
    int[<10>] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    update_vector(&a);
    io::printf("a=%s\n", a);
}

Z výpisu je zřejmé, že skutečně došlo k modifikaci prvků původního vektoru:

type: int[<10>]
a=[<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>]
a=[<10, 20, 30, 40, 50, 60, 70, 80, 90, 100>]
Poznámka: vektory se tedy předávají hodnotou, stejně jako hodnoty primitivních typů.

5. Pole (array)

Dostáváme se k dalšímu typu kontejneru nabízeného programovacím jazykem C3. Tento typ se nazývá pole (array) a do značné míry se podobá výše zmíněným vektorům. Jedním podstatným rozdílem je ovšem fakt, že prvky pole mohou být jakéhokoli (předem známého) typu, tedy například je možné vytvořit pole řetězců, pole uživatelem definovaných datových struktur, pole vektorů, pole polí atd. I další vlastnosti polí jsou do značné míry stejné, jako tomu bylo u vektorů – počet prvků polí musí být známý už v době překladu, tento počet lze získat přes atribut len, pole nelze (prostředky samotného jazyka) zvětšovat ani zmenšovat, ovšem je možné prvky pole modifikovat.

Poznámka: v oblasti práce s poli je největším rozdílem mezi jazyky C a C3 fakt, že v C3 jsou i pole předávána do funkcí hodnotou (value) a nikoli referencí. Pole tedy nemají v C3 žádné výsadní postavení, což je oproti klasickému C méně matoucí, zejména pro začátečníky.

6. Konstrukce pole, přístup k prvkům pole a modifikace prvků pole

Vzhledem k tomu, že zacházení s poli je v jazyce C3 prakticky totožné, jako práce s vektory (až na to, že typ prvků polí není omezen), ukažme si bez podrobnějšího popisu demonstrační příklad, ve kterém je vytvořeno nové desetiprvkové pole, je ihned provedena jeho inicializace, pole je vytištěno (to jazyk C3 umožňuje, což je velmi praktické), prvky pole jsou modifikovány a následně je vypsán nový obsah pole:

module containers;
import std::io;
 
fn void main()
{
    io::printf("type: %s\n", int[10].nameof);
 
    int[10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
    io::printf("a=%s\n", a);
 
    for (int i=0; i<a.len; i++) {
        a[i] = (i + 1)*10;
    }
    io::printf("a=%s\n", a);
}

Výsledky získané po překladu a spuštění tohoto demonstračního příkladu:

type: int[10]
a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
Poznámka: povšimněte si chybějících úhlových závorek ve výpisu. Tím velmi snadno z výpisu poznáme, zda byl vytištěn obsah pole nebo vektoru.

7. Předávání polí do funkcí

Pole je možné do funkcí předat buď hodnotou nebo odkazem, tj. naprosto stejnými způsoby, jako tomu bylo u vektorů. A znovu si dovolím připomenout – touto vlastností se jazyk C3 do značné míry liší od standardního jazyka C. Rozdíl mezi předáním hodnotou a odkazem si můžeme (opět) snadno otestovat, a to tak, že budeme prvky pole ve volané funkci modifikovat. Pokud je pole předané hodnotou, nebude mít tato operace na původní pole vliv, pokud je ovšem předané odkazem, tak se původní pole změní:

module containers;
import std::io;
 
fn void update_array(int[10] array) {
    for (int i=0; i<array.len; i++) {
        array[i] = (i + 1)*10;
    }
}
 
fn void main()
{
    io::printf("type: %s\n", int[10].nameof);
 
    int[10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    update_array(a);
    io::printf("a=%s\n", a);
}

Výsledky ukazují, že pole bylo skutečně předáno hodnotou:

type: int[10]
a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Explicitní předání pole odkazem, tj. přes ukazatel, lze realizovat takto:

module containers;
import std::io;
 
fn void update_array(int[10] *array) {
    for (int i=0; i<array.len; i++) {
        (*array)[i] = (i + 1)*10;
    }
}
 
fn void main()
{
    io::printf("type: %s\n", int[10].nameof);
 
    int[10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    update_array(&a);
    io::printf("a=%s\n", a);
}

Nyní se obsah původního pole změní, přesně podle očekávání:

type: int[10]
a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

8. Řezy (slice)

Jak jsme si již řekli v úvodní kapitole, nemusí být klasické pole (array) v praxi dostatečně flexibilní datovou strukturou, která by plně vyhovovala potřebám vytvářené aplikace popř. implementovaného algoritmu. Pro dosažení poněkud větší flexibility byl do programovacího jazyka C3 přidán další datový typ nazývaný řez neboli slice (namísto pojmu řez je možná názornější použít slovo výsek).

Interně se jedná o referenci na pole, které je explicitně „nasalámováno“ operací, s níž se seznámíme v navazujícím textu. Zjednodušeně řečeno se na řezy můžeme dívat jako na dynamická pole (protože klasická pole v C3 nemohou měnit svoji velikost, tj. počet prvků). Koncept řezů ve skutečnosti není žádnou žhavou novinkou a setkáme se s nimi i v dalších programovacích jazycích, například v Go nebo Rustu (i když jejich vlastnosti jsou v porovnání s C3 poněkud odlišné – C3 je méně flexibilní).

Každý řez je v operační paměti uložen ve formě dvojice hodnot. Jedná se tedy o záznam – struct, v jiných jazycích též nazývaný record. Konkrétně je řez tvořen touto dvojicí hodnot:

  1. Ukazatel (reference) na zvolený prvek pole s daty, ke kterým přes řez přistupujeme. Tento ukazatel nemusí ukazovat na první prvek pole, ale na libovolný prvek.
  2. Délka řezu (length), což značí počet skutečně prvků přístupných přes řez.
Poznámka: Tato interní struktura řezů s sebou přináší několik zajímavých důsledků. Je totiž možné, aby existovalo větší množství řezů ukazujících na obecně různé prvky jediného pole a dokonce majících i různé délky. Pokud v této situaci modifikujeme prvek v jednom řezu, znamená to, že se vlastně modifikuje obsah původního pole a i ostatní řezy nový prvek uvidí (přesněji řečeno pokud čteme data z jiného řezu, získáme již modifikované prvky). Co je však užitečnější – s řezy jako s datovým typem se velmi snadno pracuje; řezy mohou být předávány do funkcí, vráceny z funkcí atd. Nedochází přitom ke kopiím celého pole, což je paměťově i časově velmi náročná operace. Řez se sice stále předává hodnotou, ale touto hodnotou je struktura s výše popsanou dvojicí hodnot, nikoli celé (obecně velmi dlouhé) pole.

9. Přímá konstrukce řezu s jeho inicializací

Definice řezu vypadá v jazyce C3 následovně:

typ_prvků[]

což se odlišuje od definice polí:

typ_prvků[počet_prvků]

i od definice vektorů:

typ_prvků[<počet_prvků>]

Po konstrukci řezu se s řezem pracuje (alespoň zdánlivě) naprosto stejně, jako s polem, což si ostatně můžeme velmi snadno ověřit:

module containers;
import std::io;
 
fn void main()
{
    io::printf("type: %s\n", int[].nameof);
 
    int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
    io::printf("a=%s\n", a);
 
    for (int i=0; i<a.len; i++) {
        a[i] = (i + 1)*10;
    }
    io::printf("a=%s\n", a);
}

Z výsledků je patrné, že se řezy (alespoň prozatím) chovají stejně jako pole:

type: int[]
a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

Odlišné chování uvidíme ve chvíli, kdy řez předáme do funkce, která provede jeho modifikaci. Řez je totiž sice stále předáván hodnotou, ovšem touto hodnotou je jen struktura ukazatel+délka, tj. obsah řezu je vlastně předán odkazem:

module containers;
import std::io;
 
fn void update_slice(int[] slice) {
    for (int i=0; i<slice.len; i++) {
        slice[i] = (i + 1)*10;
    }
}
 
fn void main()
{
    io::printf("type: %s\n", int[10].nameof);
 
    int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    update_slice(a);
    io::printf("a=%s\n", a);
}

Chování řezů je patrné z výsledků, které tento příklad vypíše:

type: int[10]
a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
Poznámka: z tohoto pohledu jsou řezy v C3 blíže k céčkovským polím, než skutečná pole.

10. Získání řezu z pole

Datový typ řez získal svůj název kvůli existenci následující operace, kterou lze provést s polem nebo s jiným řezem. Tato operace se zapisuje takto:

pole[index1..index2]

nebo:

řez[index1..index2]

Výsledkem předchozího výrazu je nový (obecně zúžený) pohled na pole, který se nazývá řez. Mohli bychom tedy psát:

řez = pole[index1..index2]

nebo:

nový_řez = původní_řez[index1..index2]

První index, druhý index, či oba indexy lze vynechat. Pokud je vynechán první index, je namísto něho použita nula (začátek pole), pokud je vynechán index druhý, je za něj dosazena hodnota odpovídající poslednímu prvku (včetně).

Poznámka: Přitom je důležité si uvědomit, že řez jako takový neobsahuje zkopírované prvky původního pole nebo řezu, ale jen odkaz na původní pole (neboli pohled na pole). Podrobnosti si uvedeme v dalším textu.

Vyzkoušejme si to na jednoduchém příkladu, z něhož je navíc zřejmé, zda se index posledního prvku uvádí „včetně“ nebo „kromě“:

module containers;
import std::io;
 
fn void main()
{
    int[10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    int[] b = a[3..6];
    io::printf("b=%s\n", b);
 
    int[] c = a[..6];
    io::printf("c=%s\n", c);
 
    int[] d = a[3..];
    io::printf("d=%s\n", d);
 
    int[] e = a[..];
    io::printf("e=%s\n", e);
}

Z výsledků je patrné, že horní index zahrnuje i prvek s tímto indexem („včetně“), čímž se chování jazyka C3 odlišuje od některých dalších programovacích jazyků:

a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b=[4, 5, 6, 7]
c=[1, 2, 3, 4, 5, 6, 7]
d=[4, 5, 6, 7, 8, 9, 10]
e=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

11. Další možnosti získání řezu: čím se C3 odlišuje od dalších jazyků

Některé programovací jazyky nabízí možnost zápisu záporných indexů, což značí, že prvky budou indexovány od konce pole, nikoli od jeho začátku. V programovacím jazyku C3 však byla zvolena odlišná syntaxe – namísto záporných prvků se provádí zápis ^i, který je převeden na len-i, kde len je délka pole:

module containers;
import std::io;
 
fn void main()
{
    int[10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    int[] b = a[^6..^3];
    io::printf("b=%s\n", b);
 
    int[] c = a[..^3];
    io::printf("c=%s\n", c);
 
    int[] d = a[^6..];
    io::printf("d=%s\n", d);
 
    int[] e = a[..];
    io::printf("e=%s\n", e);
}

Podívejme se na výsledky, které získáme po překladu a spuštění tohoto příkladu a které ukazují, jakým způsobem jsou prvky vybírány od konce pole, nikoli od jeho začátku:

a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b=[5, 6, 7, 8]
c=[1, 2, 3, 4, 5, 6, 7, 8]
d=[5, 6, 7, 8, 9, 10]
e=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Taktéž je možné prvky vybírat ještě jedním stylem, konkrétně specifikací prvního prvku a požadované délky řezu. Nyní se namísto dvojtečky zapsané vertikálně použije dvojtečka zapsaná horizontálně:

module containers;
import std::io;
 
fn void main()
{
    int[10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    int[] b = a[3 : 4];
    io::printf("b=%s\n", b);
 
    int[] c = a[3 : 0];
    io::printf("c=%s\n", c);
 
    int[] d = a[^6 : 4];
    io::printf("d=%s\n", d);
}

Nyní budou výsledky odlišné, ovšem povšimněte si, že počet prvků v řezu nyní přesně odpovídá druhé zapsané hodnotě:

a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b=[4, 5, 6, 7]
c=[]
d=[5, 6, 7, 8]
Poznámka: současně se jedná o matoucí syntaktický prvek programovacího jazyka C3, neboť v některých jiných jazycích má dvojtečka odlišný význam odpovídající spíše zápisu ..

12. Řezy získané z jiných řezů

Při konstrukci řezů nejsme omezeni jen na provádění výseku pole. Stejnou operaci totiž můžeme v případě potřeby provést i s jiným řezem, což je ukázáno na následujícím demonstračním příkladu, ve kterém z původního desetiprvkového pole vytvoříme řez s prvky kromě prvního a posledního. Tuto operaci postupně opakujeme, přičemž další řezy jsou vytvořeny z řezu předchozího:

module containers;
import std::io;
 
fn void main()
{
    int[10] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    io::printf("a=%s\n", a);
 
    int[] b = a[1 .. ^2];
    io::printf("b=%s\n", b);
 
    int[] c = b[1 .. ^2];
    io::printf("c=%s\n", c);
 
    int[] d = c[1 .. ^2];
    io::printf("d=%s\n", d);
 
    int[] e = d[1 .. ^2];
    io::printf("e=%s\n", e);
}

Výsledky jasně ukazují, jak je postupné tvoření výseků bez prvního a posledního prvku prováděno:

a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b=[2, 3, 4, 5, 6, 7, 8, 9]
c=[3, 4, 5, 6, 7, 8]
d=[4, 5, 6, 7]
e=[5, 6]

13. Kontejner typu seznam (list)

Dalším typem kontejneru je seznam neboli list. Ten již není podporován přímo syntaxí programovacího jazyka C3, ale jedná se o kontejner realizovaný v knihovně std::collections::list. Tento datový typ je (opět) striktně typovaný, takže je nutné dopředu určit typ prvků seznamu. Nejjednodušší je definice typového aliasu (podrobnosti si uvedeme příště):

alias ListInt = List {int};

Prvky se do seznamu přidávají metodou push a navíc se (většinou) musíme postarat o alokaci i dealokaci paměti. V tom nejjednodušším případě může práce se seznamem vypadat následovně:

module containers;
import std::io;
import std::collections::list;
 
alias ListInt = List {int};
 
fn void main()
{
    ListInt a;
 
    a.init(mem);
 
    for (int i=0; i<10; i++) {
        a.push(i+1);
    }
 
    io::printf("a=%s\n", a);
 
    a.free();
}

Tento demonstrační příklad po svém překladu a spuštění vypíše obsah seznamu, do kterého bylo postupně (dynamicky, v čase běhu aplikace) přidáno deset prvků:

a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Poznámka: kromě metody push je definováno i několik dalších metod určených pro práci se seznamy. S těmi se seznámíme později, ovšem většinou se nejedná o žádné překvapivé chování. Užitečné taktéž je, že celý seznam je možné vytisknout funkcí io::printf i dalšími funkcemi z balíčku io.

14. Předání seznamu do funkcí

Opět si, podobně jako v případě vektorů, polí i řezů, vyzkoušíme, jak se chová kontejner typu seznam v případě, že ho předáme do funkce, v níž se jednotlivé prvky seznamu budou modifikovat (mimochodem – pomocí operátoru indexování, tj.s využitím hranatých závorek). Povšimněte si jedné změny – délka seznamu (tedy počet v něm uložených prvků) se zjišťuje metodou nazvanou len, nikoli atributem se stejným jménem:

module containers;
import std::io;
import std::collections::list;
 
alias ListInt = List {int};
 
fn void update_list(ListInt lst) {
    for (int i=0; i<lst.len(); i++) {
        lst[i] = (i + 1)*10;
    }
}
 
fn void main()
{
    ListInt a;
 
    a.init(mem);
 
    for (int i=0; i<10; i++) {
        a.push(i+1);
    }
 
    io::printf("a=%s\n", a);
 
    update_list(a);
    io::printf("a=%s\n", a);
 
    a.free();
}

Po překladu a spuštění tohoto demonstračního příkladu zjistíme, že seznam je možné ve funkci modifikovat. Je tomu tak z toho důvodu, že samotná struktura seznamu se sice předává hodnotou, ovšem interně seznam obsahuje ukazatele (reference) na jednotlivé prvky:

a=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

15. Dynamicky alokované pole

V programovacím jazyku C je možné alokovat pole funkcí nazvanou malloc, protože (poněkud zjednodušeně řečeno) odpovídá výraz pole[n] přístupu k n-tému prvku pole přes ukazatel (na začátek pole) a vypočítaný offset, tedy výrazu *(pole+n). Jen na okraj: to ovšem znamená, že můžeme psát i n[pole], tedy například 2[a], i když to může vypadat podivně. I v jazyku C3 lze podobným postupem vytvářet dynamicky alokovaná pole. Dokonce se k tomuto účelu používají stejně pojmenované funkce malloc (alokace pole, resp. obecně alokace bloku paměti) a free (uvolnění dříve alokované paměti). Liší se však způsob výpočtu velikosti alokovaného bloku, protože se namísto operátoru sizeof využívá zápis jméno_type.sizeof:

int *a;
 
a=malloc(SIZE * int.sizeof);
Poznámka: vzhledem k tomu, že funkce malloc a free jsou dostupné i ve výchozím jmenném prostoru, není zapotřebí provádět jejich import.

Jakmile je pole alokováno výše zmíněnou funkcí malloc, je možné k jeho prvkům přistupovat jak přes ukazatel, tak i zápisem a[i], přičemž ve druhém případě jazyk sám vypočte příslušnou adresu. Ukažme si to na jednoduchém demonstračním příkladu:

module containers;
import std::io;
 
const int SIZE = 10;
 
fn void main()
{
    int *a;
 
    a=malloc(SIZE * int.sizeof);
 
    for (int i=0; i<SIZE; i++) {
        io::printf("%d ", a[i]);
    }
    io::printn();
 
    for (int i=0; i<SIZE; i++) {
        a[i] = (i + 1)*10;
    }
 
    for (int i=0; i<SIZE; i++) {
        io::printf("%d ", a[i]);
    }
    io::printn();
 
    free(a);
}

Po překladu a spuštění tohoto příkladu se nejdříve vypíše původní obsah pole (zde konkrétně nuly, ale nemusí tomu tak být) a posléze obsah pole po modifikaci obsahu všech jeho prvků:

0 0 0 0 0 0 0 0 0 0
10 20 30 40 50 60 70 80 90 100
Poznámka: v jazyku C3 je vhodné stále rozlišovat dynamicky alokované pole typu typ_prvku* od řezu typu typ_prvku[].

16. Různé varianty řetězců

Posledním datovým typem, se kterým se v dnešním článku alespoň ve stručnosti seznámíme, jsou řetězce. Těch existuje několik typů. Nejčastěji se setkáme s typy String, ZString a DString (ovšem existuje i obdoba „širokých“ řetězců WString). Nejdříve se zaměřme na první typ, tedy String. Jedná se o řetězce, které interně obsahují atribut s délkou (len) a vlastní sekvenci znaků. Jedná se tedy o určitou obdobu řetězců známých z jazyka Pascal. Práce s takovými řetězci je snadná; v dalším příkladu je kromě jejich deklarace ukázán i způsob získání podřetězce operátorem řezu (slice):

module containers;
import std::io;
 
fn void main()
{
    String s = "Hello world!";
 
    io::printf("string '%s' has length %d bytes\n", s, s.len);
 
    io::printf("1st char=%s\n", s[0]);
 
    String substr = s[6..^1];
    io::printf("substr=%s\n", substr);
}

Po spuštění tohoto demonstračního příkladu se vypíše délka řetězce, ASCII hodnota jeho prvního znaku a taktéž vybraný podřetězec:

string 'Hello world!' has length 12 bytes
1st char=72
substr=world!

Zbývá nám ověřit, zda atribut len obsahuje délku vyjádřenou ve znacích (resp. glyfech) nebo v bajtech. Víme již, že jazyk C3 plně podporuje Unicode, takže do řetězce vložíme několik znaků s nabodeníčky:

module containers;
import std::io;
 
fn void main()
{
    String s = "ěščřžýáíéů";
 
    io::printf("string '%s' has length %d bytes\n", s, s.len);
 
    io::printf("1st char=%s\n", s[0]);
 
    String substr = s[2..7];
    io::printf("substr=%s\n", substr);
}

Nyní se vypíšou odlišné hodnoty. Řetězec bude mít délku dvaceti bajtů (ovšem má jen deset glyfů). Zajímavé je také zjištění, jak pracuje operace řezu – ta počítá s indexy bajtů a nikoli znaků! To může vést k mnohdy těžko odhalitelným problémům:

string 'ěščřžýáíéů' has length 20 bytes
1st char=196
substr=ščř
Poznámka: operaci řezu můžeme zapsat i tak, že bude začínat „uprostřed“ kódu glyfu (vyzkoušejte si to zápisem s[1..6] atd.). Výsledky sice budou stále konzistentní, ovšem většinou těžko využitelné.

17. Řetězce ukončené nulou

Programovací jazyk C3 podporuje i céčkovské řetězce ukončené nulou (někdy se setkáme s označením ASCIIZ, kde Z značí „zero“). Tyto řetězce neobsahují informaci o délce, ale jedná se o „pouhou“ sekvenci bajtů, které jsou ukončeny nulou (a pokud na nulu zapomeneme, tak se bude řetězec číst až do prvního nulového bajtu nebo až do chvíle, kdy se překročí hranice datové oblasti určené pro čtení, dojde k buffer overflow nebo k další nepěkné akci). Tyto řetězce jsou plně kompatibilní s jazykem C a používají se mj. i při volání nativních céčkovských funkcí. Práce s nimi je snadná:

module containers;
import std::io;
 
fn void main()
{
    ZString s = "Hello world!\0";
 
    io::printf("zstring '%s' has length %d bytes\n", s, s.len());
}

Po překladu a spuštění tohoto příkladu se opět zobrazí informace o délce řetězce, ovšem bez koncové nuly:

zstring 'Hello world!' has length 12 bytes

Používat je možné i znaky Unicode. Na význam koncové nuly to nemá žádný vliv, protože v kódování UTF-8 žádný znak bajt s touto hodnotou neobsahuje:

module containers;
import std::io;
 
fn void main()
{
    ZString s = "ěščřžýáíéů\0";
 
    io::printf("zstring '%s' has length %d bytes\n", s, s.len());
}

Tento řetězec s deseti glyfy bude mít opět délku dvaceti bajtů:

zstring 'ěščřžýáíéů' has length 20 bytes

18. Porovnání řetězců

Řetězce je možné porovnávat s využitím operátorů == a != (nikoli však dalších operátorů). Zde je vhodné upozornit na to, že se chování jazyka C3 může odlišovat od jiných jazyků, v nichž se mohou pomocí stejných operátorů porovnávat reference, tj. fakt, zda jsou dva řetězce umístěny ve stejné oblasti operační paměti.

V dalším demonstračním příkladu jsou porovnány nikoli pouze konstantní řetězce, ale i řetězec vůči řezu jiného řetězce:

module containers;
import std::io;
 
fn void main()
{
    String s1 = "prvni";
    String s2 = "druhy";
    String s3 = "prvni";
    String s4 = "***druhy***";
    String s5;
 
    io::printf("s1 == s2: %d\n", s1 == s2);
    io::printf("s1 != s2: %d\n", s1 != s2);
 
    s5 = s4[3..7];
 
    io::printf("s1 == s3: %d\n", s1 == s3);
    io::printf("s2 == s5: %d\n", s2 == s5);
}

Nuly znamenají, že výraz vrátil nepravdu (False), jednička znamená pravdu (True):

s1 == s2: 0
s1 != s2: 1
s1 == s3: 1
s2 == s5: 1

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

Demonstrační příklady vytvořené pro nejnovější verzi programovacího jazyka C3 byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/c3-examples. Následují odkazy na jednotlivé příklady (či jejich nedokončené části).

Demonstrační příklady z prvního článku o jazyku C3:

# Příklad Stručný popis Adresa
1 factorial.c3 realizace výpočtu faktoriálu https://github.com/tisnik/c3-examples/blob/master/intro­duction/factorial.c3
2 factorial_macro.c3 výpočet faktoriálu konkrétní hodnoty implementovaný formou makra https://github.com/tisnik/c3-examples/blob/master/intro­duction/factorial_macro.c3
       
3 swap_macro.c3 makro realizující prohození dvou hodnot https://github.com/tisnik/c3-examples/blob/master/intro­duction/swap_macro.c3
       
4 renderer.c výpočet a vykreslení Juliovy množiny implementovaný v jazyku C https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer.c
5 renderer_v1.c3 definice datové struktury s rozměry rastrového obrázku a skeleton všech funkcí https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v1.c3
6 renderer_v2.c3 anotace parametrů funkcí typu ukazatel (pointer) https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v2.c3
7 renderer_v3.c3 statická kontrola, zda se nepředávají neinicializované ukazatele https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v3.c3
8 renderer_v4.c3 runtime kontrola, zda se nepředávají neinicializované ukazatele https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v4.c3
9 renderer_v5.c3 první (nekorektní) varianta funkce pro inicializaci barvové palety https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v5.c3
10 renderer_v6.c3 druhá (korektní) varianta funkce pro inicializaci barvové palety https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v6.c3
11 renderer_v7.c3 volání knihovní I/O funkce a volání nativní céčkovské funkce https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v7.c3
12 renderer_v8.c3 plně funkční program pro výpočet a vykreslení Juliovy množiny https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v8.c3

Demonstrační příklady ze druhého článku o jazyku C3:

# Příklad Stručný popis Adresa
13 01_just_main.c3 struktura nejjednoduššího programu obsahujícího pouze prázdnou funkci main https://github.com/tisnik/c3-examples/blob/master/c3-basics/01_just_main.c3
14 02_module_name.c3 struktura programu s uvedeným plným jménem modulu https://github.com/tisnik/c3-examples/blob/master/c3-basics/02_module_name.c3
15 03_hello_world.c3 klasický program typu „Hello, world!“ napsaný v jazyku C3 https://github.com/tisnik/c3-examples/blob/master/c3-basics/03_hello_world.c3
16 04_exit_value.c3 ukončení procesu s předáním návratového kódu zpět volajícímu programu https://github.com/tisnik/c3-examples/blob/master/c3-basics/04_exit_value.c3
17 05_c_function.c3 zavolání funkce definované v knihovně programovacího jazyka C https://github.com/tisnik/c3-examples/blob/master/c3-basics/05_c_function.c3
       
18 06_bool_type.c3 definice proměnných typu pravdivostní hodnota (bool) https://github.com/tisnik/c3-examples/blob/master/c3-basics/06_bool_type.c3
19 07_int_to_bool.c3 implicitní převod hodnoty typu int na pravdivostní hodnotu (nekorektní řešení) https://github.com/tisnik/c3-examples/blob/master/c3-basics/07_int_to_bool.c3
20 08_int_to_bool.c3 explicitní převod hodnoty typu int na pravdivostní hodnotu (korektní řešení) https://github.com/tisnik/c3-examples/blob/master/c3-basics/08_int_to_bool.c3
21 09_int_to_bool.c3 explicitní převod hodnoty typu int na pravdivostní hodnotu (nekorektní řešení) https://github.com/tisnik/c3-examples/blob/master/c3-basics/09_int_to_bool.c3
22 10_bool_sizeof.c3 zjištění velikosti paměti obsazené hodnotou typu bool https://github.com/tisnik/c3-examples/blob/master/c3-basics/10_bool_sizeof.c3
       
23 11_int_types.c3 definice proměnných typu celé číslo se znaménkem s různou bitovou šířkou https://github.com/tisnik/c3-examples/blob/master/c3-basics/11_int_types.c3
24 12_uint_types.c3 definice proměnných typu celé číslo bez znaménka s různou bitovou šířkou https://github.com/tisnik/c3-examples/blob/master/c3-basics/12_uint_types.c3
25 13_no_suffixes.c3 celočíselné konstanty bez uvedení suffixu (bitové šířky) https://github.com/tisnik/c3-examples/blob/master/c3-basics/13_no_suffixes.c3
26 14_suffixes.c3 celočíselné konstanty s uvedením sufficu (bitové šířky) https://github.com/tisnik/c3-examples/blob/master/c3-basics/14_suffixes.c3
27 15_int_sizeof.c3 zjištění velikosti paměti obsazené celočíselnými hodnotami se znaménkem https://github.com/tisnik/c3-examples/blob/master/c3-basics/15_int_sizeof.c3
28 16_uint_sizeof.c3 zjištění velikosti paměti obsazené celočíselnými hodnotami bez znaménka https://github.com/tisnik/c3-examples/blob/master/c3-basics/16_uint_sizeof.c3
29 17_int_conversions.c3 korektní převody mezi celočíselnými hodnotami s různou bitovou šířkou https://github.com/tisnik/c3-examples/blob/master/c3-basics/17_int_conversions.c3
30 18_int_conversions.c3 nekorektní převody mezi celočíselnými hodnotami s různou bitovou šířkou https://github.com/tisnik/c3-examples/blob/master/c3-basics/18_int_conversions.c3
31 19_int_conversions.c3 explicitní převody a přetečení hodnot https://github.com/tisnik/c3-examples/blob/master/c3-basics/19_int_conversions.c3
       
32 20_float_types.c3 definice proměnných typu numerická hodnota s plovoucí řádovou čárkou (tečkou) https://github.com/tisnik/c3-examples/blob/master/c3-basics/20_float_types.c3
       
33 21_vector_type.c3 definice vektoru obsahujícího celočíselné hodnoty https://github.com/tisnik/c3-examples/blob/master/c3-basics/21_vector_type.c3
34 22_vector_operations.c3 základní operace s celými vektory https://github.com/tisnik/c3-examples/blob/master/c3-basics/22_vector_operations.c3
35 23_vector_sizes.c3 zjištění a tisk velikosti vektorů (různé datové typy prvků vektorů, shodná délka) https://github.com/tisnik/c3-examples/blob/master/c3-basics/23_vector_sizes.c3
36 24_vector_sizes.c3 zjištění a tisk velikosti vektorů (stejné datové typy prvků vektorů, odlišná délka) https://github.com/tisnik/c3-examples/blob/master/c3-basics/24_vector_sizes.c3

Demonstrační příklady z dnešního článku o jazyku C3:

# Příklad Stručný popis Adresa
37 01_vector_type.c3 definice vektoru, modifikace prvků vektoru, tisk obsahu celého vektoru https://github.com/tisnik/c3-examples/blob/master/c3-containers/01_vector_type.c3
38 02_vector_parameter.c3 předání vektoru do funkce hodnotou https://github.com/tisnik/c3-examples/blob/master/c3-containers/02_vector_parameter.c3
39 03_vector_pointer.c3 předání vektoru do funkce odkazem (přes ukazatel) https://github.com/tisnik/c3-examples/blob/master/c3-containers/03_vector_pointer.c3
       
40 04_array_type.c3 definice pole, modifikace prvků pole, tisk obsahu celého pole https://github.com/tisnik/c3-examples/blob/master/c3-containers/04_array_type.c3
41 05_array_parameter.c3 předání pole do funkce hodnotou https://github.com/tisnik/c3-examples/blob/master/c3-containers/05_array_parameter.c3
42 06_array_pointer.c3 předání pole do funkce odkazem (přes ukazatel) https://github.com/tisnik/c3-examples/blob/master/c3-containers/06_array_pointer.c3
       
43 07_slice_type.c3 vytvoření (konstrukce) řezu (slice) https://github.com/tisnik/c3-examples/blob/master/c3-containers/07_slice_type.c3
44 08_slice_parameter.c3 předání řezu do funkce https://github.com/tisnik/c3-examples/blob/master/c3-containers/08_slice_parameter.c3
45 09_slice_slicing.c3 konstrukce řezu z pole stejně pojmenovanou operací (řez od..do) https://github.com/tisnik/c3-examples/blob/master/c3-containers/09_slice_slicing.c3
46 10_slice_slicing.c3 konstrukce řezu z pole stejně pojmenovanou operací (záporné indexy) https://github.com/tisnik/c3-examples/blob/master/c3-containers/10_slice_slicing.c3
47 11_slice_slicing.c3 konstrukce řezu z pole stejně pojmenovanou operací (určení délky řezu) https://github.com/tisnik/c3-examples/blob/master/c3-containers/11_slice_slicing.c3
48 12_slice_of_slice.c3 konstrukce řezu z jiného řezu https://github.com/tisnik/c3-examples/blob/master/c3-containers/12_slice_of_slice.c3
       
49 13_list_type.c3 vytvoření (konstrukce) seznamu https://github.com/tisnik/c3-examples/blob/master/c3-containers/13_list_type.c3
50 14_list_parameter.c3 předání seznamu do funkce https://github.com/tisnik/c3-examples/blob/master/c3-containers/14_list_parameter.c3
       
51 15_dynamic_array.c3 vytvoření (konstrukce) dynamicky alokovaného pole https://github.com/tisnik/c3-examples/blob/master/c3-containers/15_dynamic_array.c3
       
52 16_string_type.c3 základní typ řetězce string https://github.com/tisnik/c3-examples/blob/master/c3-containers/16_string_type.c3
53 17_string_unicode.c3 Unicode znaky v řetězci typu string https://github.com/tisnik/c3-examples/blob/master/c3-containers/17_string_unicode.c3
54 18_zstring_type.c3 řetězce ukončené nulou (C-string, ASCIIZ) https://github.com/tisnik/c3-examples/blob/master/c3-containers/18_zstring_type.c3
55 19_zstring_unicode.c3 Unicode znaky v řetězci typu zstring https://github.com/tisnik/c3-examples/blob/master/c3-containers/19_zstring_unicode.c3
56 20_string_comparison.c3 porovnávání obsahu řetězců https://github.com/tisnik/c3-examples/blob/master/c3-containers/20_string_comparison.c3

20. Odkazy na Internetu

  1. The C3 Programming Language
    https://c3-lang.org/
  2. C3 For C Programmers
    https://c3-lang.org/language-overview/primer/
  3. C3 is a C-like language trying to be an incremental improvement over C rather than a whole new language
    https://www.reddit.com/r/Pro­grammingLanguages/comments/o­ohij6/c3_is_a_clike_langu­age_trying_to_be_an/
  4. Tiobe index
    https://www.tiobe.com/tiobe-index/
  5. PYPL PopularitY of Programming Language
    https://pypl.github.io/PYPL.html
  6. C3 Tutorial
    https://learn-c3.org/
  7. History of programming languages
    https://devskiller.com/history-of-programming-languages/
  8. History of programming languages (Wikipedia)
    https://en.wikipedia.org/wi­ki/History_of_programming_lan­guages
  9. D language
    https://dlang.org/
  10. Zig programming language
    https://ziglang.org/
  11. V language
    https://vlang.io/
  12. D programming language
    https://en.wikipedia.org/wi­ki/D_(programming_language)
  13. Zig programming language (Wikipedia)
    https://en.wikipedia.org/wi­ki/Zig_(programming_langu­age)
  14. V programming language (Wikipedia)
    https://en.wikipedia.org/wi­ki/V_(programming_language)
  15. Syntax highlighting for C3's programming language
    https://github.com/Airbus5717/c3.vim
  16. Go factorial
    https://gist.github.com/e­simov/9622710
  17. Generational list of programming languages
    https://en.wikipedia.org/wi­ki/Generational_list_of_pro­gramming_languages
  18. The Language Tree: Almost Every Programming Language Ever Made
    https://github.com/Phileo­sopher/langmap
  19. List of C-family programming languages
    https://en.wikipedia.org/wi­ki/List_of_C-family_programming_languages
  20. Compatibility of C and C++
    https://en.wikipedia.org/wi­ki/Compatibility_of_C_and_C%2B%2B
  21. C++23: compatibility with C
    https://www.sandordargo.com/blog/2023/08/23/cpp­23-c-compatibility
  22. Can C++ Run C Code? Understanding Language Compatibility
    https://www.codewithc.com/can-c-run-c-code-understanding-language-compatibility/
  23. C3: Comparisons With Other Languages
    https://c3-lang.org/faq/compare-languages/
  24. C3 Programming Language Gains Traction as Modern C Alternative
    https://biggo.com/news/202504040125_C3_Pro­gramming_Language_Alterna­tive_to_C
  25. The case against a C alternative
    https://c3.handmade.networ­k/blog/p/8486-the_case_against_a_c_alternative
  26. C (programming language) Alternatives
    https://alternativeto.net/software/c-programming-language-/
  27. Seriál Programovací jazyk Go
    https://www.root.cz/seria­ly/programovaci-jazyk-go/
  28. Is C3 the Underdog That Will Overtake Zig and Odin?
    https://bitshifters.cc/2025/05/22/c3-c-tradition.html
  29. „Hello, World!“ program
    https://en.wikipedia.org/wi­ki/%22Hello%2C_World!%22_pro­gram
  30. The C Programming Language
    https://en.wikipedia.org/wi­ki/The_C_Programming_Langu­age
  31. Kontejner (abstraktní datový typ)
    https://cs.wikipedia.org/wi­ki/Kontejner_(abstraktn%C3%AD_da­tov%C3%BD_typ)
  32. Are arrays not considered containers because they are not based off of a class?
    https://stackoverflow.com/qu­estions/37710975/are-arrays-not-considered-containers-because-they-are-not-based-off-of-a-class
  33. Array declaration (C, C++)
    https://en.cppreference.com/w/cpp/lan­guage/array.html
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

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