Obsah
1. Programovací jazyk C3: složené datové typy a kontejnery
3. Konstrukce vektoru, modifikace prvků vektoru
4. Předávání vektorů do funkcí
6. Konstrukce pole, přístup k prvkům pole a modifikace prvků pole
9. Přímá konstrukce řezu s jeho inicializací
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)
19. Repositář s demonstračními příklady
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.
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>]
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.
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]
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:
- 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.
- Délka řezu (length), což značí počet skutečně prvků přístupných přes řez.
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]
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ě).
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]
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]
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);
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
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=ščř
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/introduction/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/introduction/factorial_macro.c3 |
| 3 | swap_macro.c3 | makro realizující prohození dvou hodnot | https://github.com/tisnik/c3-examples/blob/master/introduction/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/introduction/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/introduction/renderer_v1.c3 |
| 6 | renderer_v2.c3 | anotace parametrů funkcí typu ukazatel (pointer) | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v2.c3 |
| 7 | renderer_v3.c3 | statická kontrola, zda se nepředávají neinicializované ukazatele | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v3.c3 |
| 8 | renderer_v4.c3 | runtime kontrola, zda se nepředávají neinicializované ukazatele | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v4.c3 |
| 9 | renderer_v5.c3 | první (nekorektní) varianta funkce pro inicializaci barvové palety | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v5.c3 |
| 10 | renderer_v6.c3 | druhá (korektní) varianta funkce pro inicializaci barvové palety | https://github.com/tisnik/c3-examples/blob/master/introduction/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/introduction/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/introduction/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:
20. Odkazy na Internetu
- The C3 Programming Language
https://c3-lang.org/ - C3 For C Programmers
https://c3-lang.org/language-overview/primer/ - 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/ProgrammingLanguages/comments/oohij6/c3_is_a_clike_language_trying_to_be_an/ - Tiobe index
https://www.tiobe.com/tiobe-index/ - PYPL PopularitY of Programming Language
https://pypl.github.io/PYPL.html - C3 Tutorial
https://learn-c3.org/ - History of programming languages
https://devskiller.com/history-of-programming-languages/ - History of programming languages (Wikipedia)
https://en.wikipedia.org/wiki/History_of_programming_languages - D language
https://dlang.org/ - Zig programming language
https://ziglang.org/ - V language
https://vlang.io/ - D programming language
https://en.wikipedia.org/wiki/D_(programming_language) - Zig programming language (Wikipedia)
https://en.wikipedia.org/wiki/Zig_(programming_language) - V programming language (Wikipedia)
https://en.wikipedia.org/wiki/V_(programming_language) - Syntax highlighting for C3's programming language
https://github.com/Airbus5717/c3.vim - Go factorial
https://gist.github.com/esimov/9622710 - Generational list of programming languages
https://en.wikipedia.org/wiki/Generational_list_of_programming_languages - The Language Tree: Almost Every Programming Language Ever Made
https://github.com/Phileosopher/langmap - List of C-family programming languages
https://en.wikipedia.org/wiki/List_of_C-family_programming_languages - Compatibility of C and C++
https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B - C++23: compatibility with C
https://www.sandordargo.com/blog/2023/08/23/cpp23-c-compatibility - Can C++ Run C Code? Understanding Language Compatibility
https://www.codewithc.com/can-c-run-c-code-understanding-language-compatibility/ - C3: Comparisons With Other Languages
https://c3-lang.org/faq/compare-languages/ - C3 Programming Language Gains Traction as Modern C Alternative
https://biggo.com/news/202504040125_C3_Programming_Language_Alternative_to_C - The case against a C alternative
https://c3.handmade.network/blog/p/8486-the_case_against_a_c_alternative - C (programming language) Alternatives
https://alternativeto.net/software/c-programming-language-/ - Seriál Programovací jazyk Go
https://www.root.cz/serialy/programovaci-jazyk-go/ - Is C3 the Underdog That Will Overtake Zig and Odin?
https://bitshifters.cc/2025/05/22/c3-c-tradition.html - „Hello, World!“ program
https://en.wikipedia.org/wiki/%22Hello%2C_World!%22_program - The C Programming Language
https://en.wikipedia.org/wiki/The_C_Programming_Language - Kontejner (abstraktní datový typ)
https://cs.wikipedia.org/wiki/Kontejner_(abstraktn%C3%AD_datov%C3%BD_typ) - Are arrays not considered containers because they are not based off of a class?
https://stackoverflow.com/questions/37710975/are-arrays-not-considered-containers-because-they-are-not-based-off-of-a-class - Array declaration (C, C++)
https://en.cppreference.com/w/cpp/language/array.html