Obsah
1. Přetěžování operátorů v programovacím jazyce C3
2. Rozdělení programovacích jazyků podle jejich přístupu k operátorům
3. Operátory a další speciální znaky v jazyku C3
4. Krátké připomenutí: definice a volání metod v jazyku C3
5. Metoda pro součet vektorů, která vrací nový vektor
6. Přetížení operátoru + tak, aby se provedl součet dvou vektorů
7. Předání operandů hodnotou nebo odkazem?
8. Několikanásobné přetížení stejného operátoru: přičtení skalární hodnoty vs součet vektorů
9. Přetížení operátoru „zleva“ nebo „zprava“
10. Symetrická varianta přetíženého operátoru součtu
11. Přetížení unárního operátoru – (otočení znaménka)
12. Přetížení operátoru pro porovnání dvou hodnot (rovnost)
13. Přetížení operátoru pro součin tak, aby se prováděl skalární součin dvou vektorů
14. Vynásobení složek vektoru skalární konstantou versus skalární součin vektorů
15. Další operátory, které je možné přetížit
16. Přetížení operátoru pro výběr prvku pro čtení pomocí indexu
17. Klíč ve formě řetězce namísto celočíselného indexu, přetížení přiřazení
18. Přetížení speciálního operátoru len
19. Repositář s demonstračními příklady
1. Přetěžování operátorů v programovacím jazyce C3
Operátory hrají v programovacích jazycích odvozených od jazyka C (nebo možná přesněji řečeno už od Algolu 60) velmi důležité role. Ostatně není divu, že poměrně velká část specifikace těchto jazyků je zaměřena právě na popis vlastností operátorů (jejich sémantiky, priority, asociativity, komutativity atd.). V samotném jazyku C je definováno několik desítek operátorů, které mají pevně daný význam – specifikace tedy přesně určuje, pro jaké typy hodnot je možné operátory použít, jakou mají prioritu atd., které operace nejsou povoleny, které jsou nedefinovány apod. V kontextu dnešního článku je zajímavé, že mnohé standardní céčkovské operátory jsou definovány pro různé datové typy, což nepřímo znamená, že specifikace céčka musí obsahovat popis toho, jak se má část programu chovat v případě, že se například sčítá celé číslo s hodnotou s plovoucí řádovou čárkou atd.
V mnoha programovacích jazycích (z nichž mnohé vznikly až po výše zmíněném céčku) se setkáme s takzvanými přetíženými operátory. Jedná se o operátory, jejichž funkce a vlastnosti se mění podle typu operandů. Pravděpodobně nejznámější formou standardně (již v jazyku) přetížených operátorů jsou operátory určené pro provádění základních aritmetických operací, které bývají definovány pro různé numerické datové typy. Ovšem například operátor + může být přetížen vícekrát, typicky pro operaci spojení řetězců (viz například Javu) či například n-tic a seznamů (tak je tento operátor přetížen v Pythonu). V těchto případech se nejenom mění funkce operátoru (protože součet je zcela rozdílná operace od spojení řetězců), ale i jejich další vlastnosti, v tomto případě například komutativita (a nepřímo taktéž asociativita v případě hodnot s plovoucí řádovou čárkou).
Některé programovací jazyky umožňují definici zcela nových operátorů popř. alespoň přetížení existujících operátorů. Z tohoto pohledu můžeme programovací jazyky rozdělit do čtyř skupin na základě dvou vlastností (které jsou na sobě do určité míry nezávislé):
- Lze definovat nové operátory?
- Lze přetížit stávající operátory?
2. Rozdělení programovacích jazyků podle jejich přístupu k operátorům
Zajímavé je, že každá ze čtyř možných kombinací zmíněných v úvodní kapitole je obsazena nějakým programovacím jazykem, a to zcela nezávisle na dalších vlastnostech daného jazyka, tedy nezávisle na jeho typovém systému, podpoře objektově orientovaného programování, existence maker atd.:
Jazyky, do nichž nelze přidávat další operátory a existující operátory nelze dále přetěžovat
| BASIC |
| C |
| Go |
| Java |
| JavaScript |
| Modula-2 |
| Objective-C |
| Pascal |
| TypeScript |
| Visual Basic |
Jazyky, do nichž nelze přidávat nové operátory, ale existující operátory je možné přetěžovat
| C3 |
| Ada |
| C# |
| C++ |
| D |
| Dart |
| FreeBASIC |
| Groovy |
| Kotlin |
| Lua |
| MATLAB |
| Object Pascal |
| PHP |
| Perl |
| Python |
| Ruby |
| Rust |
| Visual Basic .NET |
Jazyky v nichž je možné definovat nové operátory, existující operátory ovšem nelze dále přetěžovat
| ML |
| Pico |
| Prolog |
| Smalltalk |
Jazyky v nichž lze definovat nové operátory, navíc je možné přetěžovat i existující operátory
| ALGOL 68 |
| Eiffel |
| Fortran |
| F# |
| Haskell |
| Io |
| Nim |
| R |
| Raku |
| Scala |
| Swift |
3. Operátory a další speciální znaky v jazyku C3
Vzhledem k tomu, že je jazyk C3 odvozen od standardního céčka, nebude větším překvapením, že i v něm nalezneme poměrně velké množství různých operátorů (vlastně ještě větší množství, než v céčku). Tyto operátory jsou společně s dalšími speciálními znaky vypsány pod tímto odstavcem:
& @ ~ | ^ :
, / $ . ; )
> < # { } -
( ) * [ ] %
>= <= + += -= !
? ?: && ?? &= |=
^= /= .. == ({ })
[< >] ++ -- %= !=
|| :: << >> !! ...
<<= >>=
Ty znaky nebo skupiny znaků, které tvoří operátory, by bylo teoreticky možné všechny přetížit, ovšem tvůrci jazyka C3 omezili množství operátorů, které se mohou přetížit, na:
- Standardní aritmetické operátory +, -, *, / a %
- Unární operátory + a –
- Bitové operátory ^, | a &
- Porovnání == a !=
- Bitové posuny << a >>
- Operátor indexování [] pro čtení a/nebo i zápis
- Speciální operátor len (viz další text)
Přitom se předpokládá, že operátory budou přetíženy „příčetně“, tj. například při implementaci vlastních numerických typů. Zejména se nedoporučuje přístup jazyka C++, který například přetěžuje operátory << a >> pro operace, které nijak nesouvisí s bitovými posuny. Ostatně právě toto „nepříčetné“ přetěžování operátorů vede k tomu, že je přetěžování operátorů některými vývojáři zamítnuto (což vede k onomu pověstnému vylití dítěte s vaničkou).
4. Krátké připomenutí: definice a volání metod v jazyku C3
V jazyce C3 je možné operátory přetížit pouze pro nové uživatelsky definované datové typy. Samotné přetěžování se provádí definicí metod, do jejichž hlaviček je přidána informace o tom, že se kromě definice metody provádí i definice přetíženého operátoru. Proto si v krátkosti připomeňme, jak se vlastně v C3 s metodami pracuje. Co jsou to metody? Jedná se vlastně o funkce vztažené k nějakému datovému typu. Metody je možné volat dvěma rozličnými způsoby, které si oba ukážeme v demonstračních příkladech.
Programátoři, co nesnášíte BS, ale máte rádi business! Y Soft je česká firma s globálním dopadem (100+ zemí, 1M+ uživatelů a >100% meziroční růst). R&D úplně bez manažerů (130 developerů). Otevíráme 30 pozic pro Cloud a AI: Praha/Brno/Ostrava/remote. Zodpovědnost ano, mikro-management ne. Pojď někam, kde můžeš věci změnit.
Nejprve si zadefinujeme nějaký uživatelský datový typ. Pro jednoduchost se může jednat o reprezentaci vektoru v 3D prostoru pomocí tří složek (s tímto typem se dnes setkáme ještě mnohokrát):
struct Vector3d
{
int x;
int y;
int z;
}
V případě, že budeme chtít pro tento datový typ definovat novou metodu, například metodu určenou pro součet dvou vektorů, může řešení vypadat následovně. Tato metoda mění (mutuje) první vektor, který je jí předán (a proto musí být předán odkazem):
fn void Vector3d.add(Vector3d* first, Vector3d* second)
{
first.x += second.x;
first.y += second.y;
first.z += second.z;
}
Výše uvedenou metodu můžeme volat tak, jakoby se jednalo o funkci umístěnou ve jmenném prostoru Vector3d:
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d.add(&v1, &v2);
Celý příklad s definicí metody i s jejím voláním může vypadat následovně:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn void Vector3d.add(Vector3d* first, Vector3d* second)
{
first.x += second.x;
first.y += second.y;
first.z += second.z;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d.add(&v1, &v2);
io::printf("[%d %d %d]\n", v1.x, v1.y, v1.z);
}
Otestování funkcionality tohoto demonstračního příkladu je snadné:
[11 12 13]
Varianta, ve které je druhý vektor předán hodnotou a nikoli odkazem:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn void Vector3d.add(Vector3d* first, Vector3d second)
{
first.x += second.x;
first.y += second.y;
first.z += second.z;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d.add(&v1, v2);
io::printf("[%d %d %d]\n", v1.x, v1.y, v1.z);
}
Ovšem na druhou stranu další demonstrační příklad již funkční nebude, resp. nebude modifikovat první vektor předaný do metody (ale jen jeho kopii)!
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn void Vector3d.add(Vector3d first, Vector3d second)
{
first.x += second.x;
first.y += second.y;
first.z += second.z;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d.add(v1, v2);
io::printf("[%d %d %d]\n", v1.x, v1.y, v1.z);
}
Ve skutečnosti není výše uvedené volání metody:
Vector3d.add(&v1, &v2);
v mnoha programovacích jazycích příliš idiomatické. Z tohoto důvodu nabízí programovací jazyk C3 i známější a dnes používanější způsob zápisu volání metody:
v1.add(&v2);
Pro úplnost si uveďme úplný zdrojový kód demonstračního příkladu upraveného do podoby, ve které se používá idiomatičtější způsob zápisu volání metody:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn void Vector3d.add(Vector3d* first, Vector3d* second)
{
first.x += second.x;
first.y += second.y;
first.z += second.z;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
v1.add(&v2);
io::printf("[%d %d %d]\n", v1.x, v1.y, v1.z);
}
Otestování funkcionality:
[11 12 13]
5. Metoda pro součet vektorů, která vrací nový vektor
Ještě předtím, než si ukážeme, jakým způsobem je možné přetížit nějaký aritmetický operátor (nebo všechny operátory) pro naši datovou strukturu Vector3d, provedeme poslední úpravu metody provádějící součet vektorů. Tato metoda totiž může vracet nový vektor, který vznikne součtem obou vstupních vektorů (operandů). To sice nemusí být příliš efektivní z pohledu využití operační paměti, ovšem jedná se o velmi idiomatický způsob implementace:
fn Vector3d Vector3d.add(Vector3d first, Vector3d second)
{
Vector3d result;
result.x = first.x + second.x;
result.y = first.y + second.y;
result.z = first.z + second.z;
return result;
}
Volání této metody:
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = Vector3d.add(v1, v2);
Tento způsob volání je ukázán v dalším demonstračním příkladu:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add(Vector3d first, Vector3d second)
{
Vector3d result;
result.x = first.x + second.x;
result.y = first.y + second.y;
result.z = first.z + second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = Vector3d.add(v1, v2);
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
}
Ovšem (jak již víme z předchozích kapitol) je možné metodu add volat i odlišným způsobem:
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1.add(v2);
Opět si pro úplnost ukažme, jak vypadá celý zdrojový kód takto upraveného příkladu:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add(Vector3d first, Vector3d second)
{
Vector3d result;
result.x = first.x + second.x;
result.y = first.y + second.y;
result.z = first.z + second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1.add(v2);
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
}
6. Přetížení operátoru + tak, aby se provedl součet dvou vektorů
Nyní se již konečně dostáváme k přetěžování operátorů. Ve skutečnosti je to v našem případě velmi snadné, protože stačí do původní definici metody add:
fn Vector3d Vector3d.add(Vector3d first, Vector3d second)
{
Vector3d result;
result.x = first.x + second.x;
result.y = first.y + second.y;
result.z = first.z + second.z;
return result;
}
Přidat dekorátor, který překladač jazyka C3 „donutí“ přetížit operátor +:
fn Vector3d Vector3d.add(Vector3d first, Vector3d second) @operator(+)
{
Vector3d result;
result.x = first.x + second.x;
result.y = first.y + second.y;
result.z = first.z + second.z;
return result;
}
Použití nového operátoru je již zcela idiomatické:
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1 + v2;
Opět si ukažme celý zdrojový kód takto upraveného demonstračního příkladu:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add(Vector3d first, Vector3d second) @operator(+)
{
Vector3d result;
result.x = first.x + second.x;
result.y = first.y + second.y;
result.z = first.z + second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1 + v2;
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
}
7. Předání operandů hodnotou nebo odkazem?
Většinou se v definicích metod, které současně přetěžují nějaký aritmetický (binární) operátor, setkáme s tím, že prvním parametrem metody je levý operand popř. reference na tento operand (tedy adresa). To, zda budeme předávat hodnotu nebo referenci, se projevuje pouze na zápisu hlavičky metody, protože její nepřímé volání (přes operand) zůstane naprosto stejné. Ostatně postačuje se podívat na následující dvojici demonstračních příkladů, které se od sebe odlišují pouze hlavičkou metody add:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add(self, Vector3d second) @operator(+)
{
Vector3d result;
result.x = self.x + second.x;
result.y = self.y + second.y;
result.z = self.z + second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1 + v2;
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
}
Prakticky totožný zápis, ovšem první parametr je do metody předáván odkazem a nikoli hodnotou:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add(&self, Vector3d second) @operator(+)
{
self.x += second.x;
self.y += second.y;
self.z += second.z;
return *self;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1 + v2;
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
}
8. Několikanásobné přetížení stejného operátoru: přičtení skalární hodnoty vs součet vektorů
V některých případech se setkáme s požadavkem přetížení nějakého operátoru tak, aby na pravé straně akceptoval operandy různého typu. Příkladem může být požadavek, aby bylo možné k našemu vektoru v prostoru přičíst buď skalární hodnotu nebo další vektor. V jazyku C3 je to možné, protože volání metody lze odlišit na základě jejího jména (pochopitelně), přičemž metody odlišných jmen mohou akceptovat odlišné typy parametrů. Ovšem i u dvou metod s odlišným jménem (a odlišnými typy parametrů) lze dekorátorem dopsat přetížení toho stejného operátoru. Můžeme tedy implementovat dvě varianty operátoru +:
fn Vector3d Vector3d.add_scalar(self, int scalar) @operator(+)
...
...
...
a současně:
fn Vector3d Vector3d.add_vector(self, Vector3d second) @operator(+)
...
...
...
Chování takto přetíženého operátoru si můžeme snadno ověřit pokusem o součet vektorů popř. o přičtení skalární hodnoty k vektoru:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add_scalar(self, int scalar) @operator(+)
{
Vector3d result;
result.x = self.x + scalar;
result.y = self.y + scalar;
result.z = self.z + scalar;
return result;
}
fn Vector3d Vector3d.add_vector(self, Vector3d second) @operator(+)
{
Vector3d result;
result.x = self.x + second.x;
result.y = self.y + second.y;
result.z = self.z + second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1 + v2;
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
Vector3d v4 = v3 + 100;
io::printf("[%d %d %d]\n", v4.x, v4.y, v4.z);
}
Výsledky ukazují, že vše funguje podle očekávání:
[11 12 13] [111 112 113]
9. Přetížení operátoru „zleva“ nebo „zprava“
V demonstračním příkladu z předchozí kapitoly jsme přičítali skalární hodnotu k vektoru, což znamenalo, že se vektor nacházel na levé straně přetíženého operátoru +, zatímco skalární hodnota se nacházela na jeho pravé straně:
Vector3d v4 = v3 + 100;
Co se ovšem stane v případě, že oba operandy prohodíme? V tomto případě bude první operand typu celé číslo a druhý operand typu Vector3d:
Vector3d v4 = 100 + v3;
Můžeme si to vyzkoušet pokusem o překlad následujícího příkladu:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add_scalar(self, int scalar) @operator(+)
{
Vector3d result;
result.x = self.x + scalar;
result.y = self.y + scalar;
result.z = self.z + scalar;
return result;
}
fn Vector3d Vector3d.add_vector(self, Vector3d second) @operator(+)
{
Vector3d result;
result.x = self.x + second.x;
result.y = self.y + second.y;
result.z = self.z + second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1 + v2;
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
Vector3d v4 = 100 + v3;
io::printf("[%d %d %d]\n", v4.x, v4.y, v4.z);
}
Překladač jazyka C3 v tomto případě ohlásí problém, protože není, jakou metodu by měl pro vyžadovaný součet zavolat:
35:
36: io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
37:
38: Vector3d v4 = 100 + v3;
^^^^^^^^
(/tmp/ramdisk/c3c/build/add_operator_7.c3:38:19) Error: Cannot do the addition 'int' + 'Vector3d'.
Řešením může být doplnění původní implementace operátoru +:
fn Vector3d Vector3d.add_scalar(self, int scalar) @operator(+) ... ... ...
Za implementaci „otočeného“ operátoru +. Povšimněte si, že nyní namísto dekorátoru @operator použijeme dekorátor @operator_r, kde _r můžeme číst jako reverse:
fn Vector3d Vector3d.add_scalar_r(self, int scalar) @operator_r(+) ... ... ...
Celý zdrojový kód příkladu doplněného o nový „převrácený“ operátor vypadá následovně:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add_scalar(self, int scalar) @operator(+)
{
Vector3d result;
result.x = self.x + scalar;
result.y = self.y + scalar;
result.z = self.z + scalar;
return result;
}
fn Vector3d Vector3d.add_scalar_r(self, int scalar) @operator_r(+)
{
Vector3d result;
result.x = self.x + scalar;
result.y = self.y + scalar;
result.z = self.z + scalar;
return result;
}
fn Vector3d Vector3d.add_vector(self, Vector3d second) @operator(+)
{
Vector3d result;
result.x = self.x + second.x;
result.y = self.y + second.y;
result.z = self.z + second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1 + v2;
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
Vector3d v4 = 100 + v3;
io::printf("[%d %d %d]\n", v4.x, v4.y, v4.z);
}
Nyní bude možné příklad bez problémů přeložit a spustit:
$ ./c3c compile-run add_operator_8.c3 Program linked to executable './operator_overloading'. Launching ./operator_overloading [11 12 13] [111 112 113]
10. Symetrická varianta přetíženého operátoru součtu
Současná definice přetíženého operátoru + v jeho původní variantě vektor+další_hodnota i v převrácené variantě další_hodnota+vektor je sice v některých případech nutná, ovšem pokud má být implementovaná operace komutativní, je dvojí definice spíše kontraproduktivní (prakticky se totiž opakuje totožný kód, pouze se prohodí parametry metody). Z tohoto důvodu programovací jazyk C3 podporuje „symetrickou“ variantu přetíženého operátoru, která se definuje nikoli pomocí @operator ani @operator_r, ale s využitím dekorátoru @operator_s (suffix s je odvozen od slova symmetric). Pokud je operátor přetížen tímto dekorátorem, bude funkční jak ve své přímé, tak i „převrácené“ variantě.
To tedy znamená, že operátor určený pro přičtení skaláru k vektoru nebo vektoru ke skaláru lze definovat jedinou metodou:
fn Vector3d Vector3d.add_scalar(self, int scalar) @operator_s(+)
Otestujme si to na následujícím demonstračním příkladu, který provede dvě varianty součtu: vektor+vektor (ten je vždy symetrický) a skalár+vektor:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.add_scalar(self, int scalar) @operator_s(+)
{
Vector3d result;
result.x = self.x + scalar;
result.y = self.y + scalar;
result.z = self.z + scalar;
return result;
}
fn Vector3d Vector3d.add_vector(self, Vector3d second) @operator(+)
{
Vector3d result;
result.x = self.x + second.x;
result.y = self.y + second.y;
result.z = self.z + second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = v1 + v2;
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
Vector3d v4 = 100 + v3;
io::printf("[%d %d %d]\n", v4.x, v4.y, v4.z);
}
Výsledky:
$ ./c3c compile-run add_operator_9.c3 Program linked to executable './operator_overloading'. Launching ./operator_overloading [11 12 13] [111 112 113]
11. Přetížení unárního operátoru – (otočení znaménka)
Nyní již umíme realizovat přetížení binárního operátoru (například součtu). Jak se ovšem postupuje v případě, že je zapotřebí přetížit nějaký operátor unární? Typicky se bude jednat o unární operátor -, který (pokud to pro datovou strukturu dává smysl) otáčí znaménko.
Implementace je vlastně velmi jednoduchá, protože stačí nadefinovat metodu s jediným parametrem (self nebo reference na self) a přidat dekorátor pro operátor -. Nemusíme pochopitelně řešit ani „otočenou“ variantu operátorů (s prohozenými operandy) ani jeho symetrickou variantu. Metoda vrátí novou strukturu s převráceným znaménkem:
fn Vector3d Vector3d.unary_minus(self) @operator(-)
{
Vector3d result;
result.x = -self.x;
result.y = -self.y;
result.z = -self.z;
return result;
}
Celý demonstrační příklad i s realizací přetíženého unárního operátoru – vypadá následovně:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.unary_minus(self) @operator(-)
{
Vector3d result;
result.x = -self.x;
result.y = -self.y;
result.z = -self.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
io::printf("[%d %d %d]\n", v1.x, v1.y, v1.z);
Vector3d v2 = -v1;
io::printf("[%d %d %d]\n", v2.x, v2.y, v2.z);
}
Vše si pochopitelně otestujeme:
$ ./c3c compile-run unary_minus.c3 [1 2 3] [-1 -2 -3]
12. Přetížení operátoru pro porovnání dvou hodnot (rovnost)
Dalším operátorem, který je nutné velmi často přetížit, je operátor rovnosti (nebo nerovnosti). Ten může být realizován metodou se dvěma parametry vracející hodnotu typu bool. Metoda je označena pomocí dekorátoru @operator(==):
fn bool Vector3d.compare(self, Vector3d second) @operator(==)
{
bool result;
result = self.x == second.x;
result &= self.y == second.y;
result &= self.z == second.z;
return result;
}
Vyzkoušejme si, jak dopadnou výsledky porovnání dvojice vektorů na rovnost i na nerovnost:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn bool Vector3d.compare(self, Vector3d second) @operator(==)
{
bool result;
result = self.x == second.x;
result &= self.y == second.y;
result &= self.z == second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
Vector3d v3 = {10, 10, 10};
io::printf("v1 == v2: %b\n", v1 == v2);
io::printf("v1 == v3: %b\n", v1 == v3);
io::printf("v2 == v3: %b\n", v2 == v3);
io::printf("v1 != v2: %b\n", v1 != v2);
io::printf("v1 != v3: %b\n", v1 != v3);
io::printf("v2 != v3: %b\n", v2 != v3);
}
Ze zobrazených výsledků je patrné, že i když je přetížen pouze operátor ==, lze pro porovnání dvou vektorů použít i operátor !=:
$ ./c3c compile-run equality_operator.c3 v1 == v2: 0 v1 == v3: 0 v2 == v3: 1 v1 != v2: 1 v1 != v3: 1 v2 != v3: 0
13. Přetížení operátoru pro součin tak, aby se prováděl skalární součin dvou vektorů
V dalším demonstračním příkladu se ještě na chvíli vrátíme k binárním aritmetickým operátorům. Přetížíme operátor *, který provede skalární součin dvou vektorů, tj. oba operandy budou typu Vector3d a výsledkem bude skalární hodnota. To je pochopitelně v jazyce C3 zcela legální realizace přetížení:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn int Vector3d.dot_product(self, Vector3d second) @operator(*)
{
int result;
result = self.x * second.x;
result += self.y * second.y;
result += self.z * second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
int dp = v1 * v2;
io::printf("%d\n", dp);
}
Výsledek výpočtu skalárního součinu obou vektorů:
60
Vzhledem k tomu, že budeme chtít co nejvíce optimální realizaci výpočtu skalárního součinu, můžeme do metody Vector3d.dot_product předávat jeden nebo oba parametry referencí, nikoli hodnotou. Odlišovat se bude hlavička metody, nikoli její tělo (a už vůbec ne volání operátoru):
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn int Vector3d.dot_product(&self, Vector3d second) @operator(*)
{
int result;
result = self.x * second.x;
result += self.y * second.y;
result += self.z * second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
int dp = v1 * v2;
io::printf("%d\n", dp);
}
Opět si otestujme funkcionalitu takto upraveného příkladu:
60
14. Vynásobení složek vektoru skalární konstantou versus skalární součin vektorů
Pokusme se nyní zadání ztížit: budeme chtít realizovat jak skalární součin, tak i vynásobení vektoru skalární hodnotou, což jsou dvě zcela odlišné operace. Díky tomu, že každá operace součinu bude definována v odlišné metodě (liší se jejich jména, parametry, i návratový typ), je možné oba operátory celkem bez problémů přetížit (zapisovat se budou stejným znakem). Navíc můžeme násobení vektoru skalárem realizovat symetrickým operátorem, aby bylo možné provádět jak výpočet vektor*skalár, tak i skalár*vektor:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn Vector3d Vector3d.mul_scalar(&self, int scalar) @operator_s(*)
{
Vector3d result;
result.x = self.x * scalar;
result.y = self.y * scalar;
result.z = self.z * scalar;
return result;
}
fn int Vector3d.dot_product(&self, Vector3d second) @operator(*)
{
int result;
result = self.x * second.x;
result += self.y * second.y;
result += self.z * second.z;
return result;
}
fn void main()
{
Vector3d v1 = {1, 2, 3};
Vector3d v2 = {10, 10, 10};
int dp = v1 * v2;
io::printf("%d\n", dp);
Vector3d v3 = v1 * 2;
io::printf("[%d %d %d]\n", v3.x, v3.y, v3.z);
Vector3d v4 = 2 * v2;
io::printf("[%d %d %d]\n", v4.x, v4.y, v4.z);
}
Z výsledků je patrné, že všechny tři operace jsou definovány:
60 // skalární součin [2 4 6] // vektor*skalár [20 20 20] // skalár*vektor
15. Další operátory, které je možné přetížit
V rámci předchozích kapitol jsme si ukázali, jakým způsobem je možné přetížit standardní aritmetické operátory, a to jak jejich binární varianty (se dvěma operandy), tak i unární varianty (s jedním operandem). Taktéž jsme si ukázali realizaci operátoru ==. Ovšem programovací jazyk C3 neumožňuje přetěžovat další relační operátory ani například operátory určené pro zvýšení či snížení hodnoty o jedničku (což je možná rozumné rozhodnutí).
Přetížit je možné i operátory indexování [] (pro čtení a/nebo zápis) a navíc i speciální operátor len využitý při realizaci počítaných smyček atd. V dalších kapitolách se zaměříme právě na tyto operátory.
16. Přetížení operátoru pro výběr prvku pro čtení pomocí indexu
V dalším demonstračním příkladu přetížíme (možná poněkud uměle) operátor [] v jeho variantě určené pro čtení prvků vektoru. Tento operátor umožní pro první prvek vektoru použít index 0, pro druhý prvek index 1 atd. Ovšem stále se bude jednat pouze o čtení hodnoty prvku vektoru, nikoli o jejich zápis.
Naivní implementace (bez korektní kontroly chybného indexu) může vypadat následovně:
fn int Vector3d.get(self, usz i) @operator([])
{
switch (i) {
case 0: return self.x;
case 1: return self.y;
case 2: return self.z;
default: return 0;
}
}
Otestujme si, zda je indexování realizováno korektně:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn int Vector3d.get(self, usz i) @operator([])
{
switch (i) {
case 0: return self.x;
case 1: return self.y;
case 2: return self.z;
default: return 0;
}
}
fn void main()
{
Vector3d v1 = {10, 20, 30};
for (int i=0; i<=3; i++) {
io::printf("v1[%d] = %d\n", i, v1[i]);
}
}
Výsledky vypadají rozumně (i pro nekorektní index):
v1[0] = 10 v1[1] = 20 v1[2] = 30 v1[3] = 0
Samozřejmě i v tomto případě je možné do metody Vector3d.get předávat vektor odkazem (referencí) a nikoli hodnotou (tedy kopií):
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn int Vector3d.get(&self, usz i) @operator([])
{
switch (i) {
case 0: return self.x;
case 1: return self.y;
case 2: return self.z;
default: return 0;
}
}
fn void main()
{
Vector3d v1 = {10, 20, 30};
for (int i=0; i<=3; i++) {
io::printf("v1[%d] = %d\n", i, v1[i]);
}
}
Výsledky budou opět korektní:
v1[0] = 10 v1[1] = 20 v1[2] = 30 v1[3] = 0
17. Klíč ve formě řetězce namísto celočíselného indexu, přetížení přiřazení
Operátor indexování ovšem může být přetížen i takovým způsobem, že se namísto celočíselného indexu použije klíč. Opět si ukažme (umělý) příklad, ve kterém umožníme prvky vektoru vybírat s využitím klíčů „x“, „y“ nebo „z“. Tato varianta přetíženého operátoru bude využitelnější například tehdy, pokud se bude implementovat nějaká forma slovníku atd.:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn int Vector3d.get(&self, String key) @operator([])
{
switch (key) {
case "x": return self.x;
case "y": return self.y;
case "z": return self.z;
default: return 0;
}
}
fn void main()
{
Vector3d v1 = {10, 20, 30};
io::printf("v1[x] = %d\n", v1["x"]);
io::printf("v1[y] = %d\n", v1["y"]);
io::printf("v1[z] = %d\n", v1["z"]);
}
Otestování ukazuje, že je vše korektní:
v1[x] = 10 v1[y] = 20 v1[z] = 30
V dalším demonstračním příkladu je navíc přetížen i operátor indexování pro zápis, tj. pro přiřazení nové hodnoty do vybraného prvku. Tento operátor se (v době přetížení) jmenuje []=. Pro zajímavost budeme opět implementovat variantu tohoto operátoru s klíči „x“, „y“ a „z“ a nikoli s celočíselnými indexy:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn int Vector3d.get(&self, String key) @operator([])
{
switch (key) {
case "x": return self.x;
case "y": return self.y;
case "z": return self.z;
default: return 0;
}
}
fn void Vector3d.set(&self, String key, int new_value) @operator([]=)
{
switch (key) {
case "x": self.x=new_value;
case "y": self.y=new_value;
case "z": self.z=new_value;
}
}
fn void main()
{
Vector3d v1 = {10, 20, 30};
io::printf("v1[x] = %d\n", v1["x"]);
io::printf("v1[y] = %d\n", v1["y"]);
io::printf("v1[z] = %d\n", v1["z"]);
v1["x"] = 100;
v1["y"] = 200;
v1["z"] = 300;
io::printf("v1[x] = %d\n", v1["x"]);
io::printf("v1[y] = %d\n", v1["y"]);
io::printf("v1[z] = %d\n", v1["z"]);
}
Otestování funkcionality:
v1[x] = 10 v1[y] = 20 v1[z] = 30 v1[x] = 100 v1[y] = 200 v1[z] = 300
18. Přetížení speciálního operátoru len
Poslední operátor, který se v dnešním článku pokusíme přetížit, se jmenuje len. V našem konkrétním případě bude tento operátor vracet konstantní hodnotu 3 (protože náš vektor má tři složky):
fn usz Vector3d.len(&self) @operator(len)
{
return 3;
}
Tento operátor se, společně s přetíženým operátorem indexování, používá pro implementaci programových smyček typu for-each. Operátor len vrací počet iterací a interně se bude volat operátor pro indexování (výběr/čtení) prvků:
foreach (item : v1)
{
io::printf("%d ", item);
}
io::printn();
Opět si vše otestujeme:
module operator_overloading;
import std::io;
struct Vector3d
{
int x;
int y;
int z;
}
fn int Vector3d.get(&self, usz i) @operator([])
{
switch (i) {
case 0: return self.x;
case 1: return self.y;
case 2: return self.z;
default: return 0;
}
}
fn void Vector3d.set(&self, usz i, int new_value) @operator([]=)
{
switch (i) {
case 0: self.x=new_value;
case 1: self.y=new_value;
case 2: self.z=new_value;
}
}
fn usz Vector3d.len(&self) @operator(len)
{
return 3;
}
fn void main()
{
Vector3d v1 = {10, 20, 30};
foreach (item : v1)
{
io::printf("%d ", item);
}
io::printn();
v1[0] = 100;
v1[1] = 200;
v1[2] = 300;
foreach (item : v1)
{
io::printf("%d ", item);
}
io::printn();
}
Výsledek ukazuje, že je skutečně možné naším vektorem „procházet“ prvek po prvku:
10 20 30 100 200 300
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 programovacím 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 použité ve třetím článku o jazyku C3:
Demonstrační příklady ze čtvrtého o jazyku C3:
| # | Příklad | Stručný popis | Adresa |
|---|---|---|---|
| 57 | 01_program_stub.c3 | struktura programu s uvedeným plným jménem modulu | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/01_program_stub.c3 |
| 58 | 02_if.c3 | nejjednodušší forma rozvětvení založené na konstrukci if | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/02_if.c3 |
| 59 | 03_if_else.c3 | plné rozvětvení realizované konstrukcí if-else | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/03_if_else.c3 |
| 60 | 04_improper_if.c3 | nekorektní způsob zápisu programové konstrukce if-else (porovnání s jazykem C) | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/04_improper_if.c3 |
| 61 | 05_improper_if.c3 | nekorektní způsob zápisu programové konstrukce if-else (porovnání s jazykem C) | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/05_improper_if.c3 |
| 62 | 06_if_else_if.c3 | složitější rozvětvení založené na programové konstrukci if-else if-else | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/06_if_else_if.c3 |
| 63 | 07_switch_basic.c3 | základní forma vícenásobného rozvětvení založeného na konstrukci switch-case | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/07_switch_basic.c3 |
| 64 | 08_switch_basic.c3 | větší množství podmínek a programová konstrukce switch-case | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/08_switch_basic.c3 |
| 65 | 09_switch_condition.c3 | podmínky zapsané ve větvích programové konstrukci switch-case vyhodnocované v čase běhu procesu | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/09_switch_condition.c3 |
| 66 | 10_switch_true.c3 | konstrukce switch-case bez uvedeného výrazu za klíčovým slovem switch | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/10_switch_true.c3 |
| 67 | 11_switch_break.c3 | zápis prázdné větve default v programové konstrukci switch-case | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/11_switch_break.c3 |
| 68 | 12_switch_nextcase.c3 | pokračování ve vykonávání konstrukce switch-case vynucené klíčovým slovem nextcase | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/12_switch_nextcase.c3 |
| 69 | 13_for_loop.c3 | základní forma programové smyčky realizované klíčovým slovem for | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/13_for_loop.c3 |
| 70 | 14_foreach_loop.c3 | základní forma programové smyčky typu for-each | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/14_foreach_loop.c3 |
| 71 | 15_foreach_loop.c3 | programová smyčka for-each vracející index prvku i hodnotu prvku | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/15_foreach_loop.c3 |
| 72 | 16_foreach_loop.c3 | modifikace obsahu pole v programové smyčce for-each | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/16_foreach_loop.c3 |
| 73 | 17_foreach_loop.c3 | pokus o modifikaci obsahu procházeného pole | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/17_foreach_loop.c3 |
| 74 | 18_foreach_loop.c3 | modifikace procházeného pole přes ukazatel na prvek | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/18_foreach_loop.c3 |
| 75 | 19_foreach_r_loop.c3 | programová smyčka for-each, ve které se sekvencí prochází v opačném pořadí | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/19_foreach_r_loop.c3 |
| 76 | 20_while_loop.c3 | základní forma programové smyčky typu while | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/20_while_loop.c3 |
| 77 | 21_while_loop2.c3 | programová smyčka typu while s konstrukcí break | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/21_while_loop2.c3 |
| 78 | 22_nested_loops.c3 | realizace vnořených programových smyček | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/22_nested_loops.c3 |
| 79 | 23_break.c3 | vnořené programové smyčky a příkaz break: ukončení vnitřní smyčky | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/23_break.c3 |
| 80 | 24_break.c3 | vnořené programové smyčky a příkaz break: ukončení vnější smyčky | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/24_break.c3 |
| 81 | 25_break.c3 | vnořené programové smyčky a příkaz break, varianta se smyčkami typu while | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/25_break.c3 |
Demonstrační příklady z pátého článku o jazyku C3:
Následují odkazy na demonstrační příklady z článku předchozího:
| # | Příklad | Stručný popis | Adresa | |
|---|---|---|---|---|
| 100 | 01_regular_function.c3 | deklarace běžných funkcí v jazyku C3 | https://github.com/tisnik/c3-examples/blob/master/c3-functions/01_regular_function.c3 | |
| 101 | 02_check_arguments.c3 | kontrola parametrů předávaných do funkce překladačem jazyka C3 | https://github.com/tisnik/c3-examples/blob/master/c3-functions/02_check_arguments.c3 | |
| 102 | 03_default_arguments.c3 | funkce s jedním parametrem, který má nastavenou výchozí hodnotu a jedním běžným parametrem (korektní pořadí parametrů) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/03_default_arguments.c3 | |
| 103 | 04_default_arguments.c3 | funkce se všemi parametry s nastavenu výchozí hodnotou | https://github.com/tisnik/c3-examples/blob/master/c3-functions/04_default_arguments.c3 | |
| 104 | 05_default_arguments.c3 | funkce s jedním parametrem, který má nastavenou výchozí hodnotu a jedním běžným parametrem (nekorektní pořadí parametrů) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/05_default_arguments.c3 | |
| 105 | 06_named_arguments.c3 | explicitní uvedení jmen parametrů při volání funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/06_named_arguments.c3 | |
| 106 | 07_named_arguments.c3 | explicitní uvedení jmen parametrů při volání funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/07_named_arguments.c3 | |
| 107 | 08_named_default_arguments.c3 | pojmenování parametrů s výchozí hodnotou při volání funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/08_named_default_arguments.c3 | |
| 108 | 09_sum.c3 | realizace funkce pro výpočet součtu všech předaných hodnot | https://github.com/tisnik/c3-examples/blob/master/c3-functions/09_sum.c3 | |
| 109 | 10_sum.c3 | předání obsahu pole do funkce s proměnným počtem parametrů | https://github.com/tisnik/c3-examples/blob/master/c3-functions/10_sum.c3 | |
| 110 | 11_varargs.c3 | pořadí předávání parametrů do funkce s proměnným počtem parametrů (nekorektní způsob použití) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/11_varargs.c3 | |
| 111 | 12_varargs.c3 | pořadí předávání parametrů do funkce s proměnným počtem parametrů (korektní způsob použití) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/12_varargs.c3 | |
| 112 | 13_optional.c3 | funkce vracející hodnotu typu Optional | https://github.com/tisnik/c3-examples/blob/master/c3-functions/13_optional.c3 | |
| 113 | 14_optional.c3 | využití operátoru ?? | https://github.com/tisnik/c3-examples/blob/master/c3-functions/14_optional.c3 | |
| 114 | 15_contract.c3 | kontrakty uvedené u funkcí | https://github.com/tisnik/c3-examples/blob/master/c3-functions/15_contract.c3 | |
| 115 | 16_contract.c3 | kontrakty uvedené u funkcí | https://github.com/tisnik/c3-examples/blob/master/c3-functions/16_contract.c3 | |
| 116 | 17_c_declaration.c3 | deklarace funkce bez parametrů „céčkovým způsobem“ (nekorektní zápis) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/17_c_declaration.c3 | |
| 117 | 18_check_return_type.c3 | kontrola návratové hodnoty překladačem jazyka C3 | https://github.com/tisnik/c3-examples/blob/master/c3-functions/18_check_return_type.c3 | |
| 118 | 19_check_return_value.c3 | kontrola počtu návratových hodnot překladačem jazyka C3 | https://github.com/tisnik/c3-examples/blob/master/c3-functions/19_check_return_value.c3 | |
| 119 | 20_in_out_params.c3 | předání ukazatelů do volané funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/20_in_out_params.c3 | |
| 120 | 21_in_out_params.c3 | označení ukazatelů kontrakty [in] a [out] | https://github.com/tisnik/c3-examples/blob/master/c3-functions/21_in_out_params.c3 | |
| 121 | 22_in_out_params.c3 | označení ukazatelů kontrakty [in] a [out] | https://github.com/tisnik/c3-examples/blob/master/c3-functions/22_in_out_params.c3 | |
| 122 | 23_void_pointer.c3 | předávání ukazatelů typu void * do volané funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/23_void_pointer.c3 | |
| 123 | 24_contract.c3 | kontrakt zapsaný před hlavičkou funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/24_contract.c4 |
Demonstrační příklady z předchozího článku o funkcích v C3:
Příklady z článku s popisem makrosystému jazyka C3:
| # | Příklad | Stručný popis | Adresa |
|---|---|---|---|
| 145 | 01_add_macro.c3 | jednoduché makro, které může být expandováno v rámci výrazu | https://github.com/tisnik/c3-examples/blob/master/c3-macros/01_add_macro.c3 |
| 146 | 02_add_macro.c3 | makro je generické, lze použít s hodnotami různých typů | https://github.com/tisnik/c3-examples/blob/master/c3-macros/02_add_macro.c3 |
| 147 | 03_macro_expansion1.c | expanze makro provedená preprocesorem jazyka C (parametry nejsou uzávorkovány) | https://github.com/tisnik/c3-examples/blob/master/c3-macros/03_macro_expansion1.c |
| 148 | 03_macro_expansion2.c | expanze makro provedená preprocesorem jazyka C (parametry jsou uzávorkovány) | https://github.com/tisnik/c3-examples/blob/master/c3-macros/03_macro_expansion2.c |
| 149 | 03_macro_expansion.c3 | řešení v jazyku C3 bez nutnosti uzávorkování parametrů makra | https://github.com/tisnik/c3-examples/blob/master/c3-macros/03_macro_expansion.c3 |
| 150 | 04_macro_expansion1.c | expanze makro provedená preprocesorem jazyka C (tělo makra není v závorkách) | https://github.com/tisnik/c3-examples/blob/master/c3-macros/04_macro_expansion1.c |
| 151 | 04_macro_expansion2.c | expanze makro provedená preprocesorem jazyka C (tělo makra je v závorkách) | https://github.com/tisnik/c3-examples/blob/master/c3-macros/04_macro_expansion2.c |
| 152 | 04_macro_expansion.c3 | řešení v jazyku C3 bez nutnosti zápisu těla makra do závorek | https://github.com/tisnik/c3-examples/blob/master/c3-macros/04_macro_expansion.c3 |
| 153 | 05_typed_macro.c3 | makro s definicí typů parametrů | https://github.com/tisnik/c3-examples/blob/master/c3-macros/05_typed_macro.c3 |
| 154 | 06_typed_macro.c3 | makro s definicí typů parametrů: příklad jeho expanze | https://github.com/tisnik/c3-examples/blob/master/c3-macros/06_typed_macro.c3 |
| 155 | 07_compile_time1.c3 | expanze makra v čase překladu: nefunkční varianta s konstantami | https://github.com/tisnik/c3-examples/blob/master/c3-macros/07_compile_time1.c3 |
| 156 | 08_compile_time2.c3 | expanze makra v čase překladu: funkční varianta s konstantami | https://github.com/tisnik/c3-examples/blob/master/c3-macros/08_compile_time2.c3 |
| 157 | 09_compile_time3.c3 | expanze makra v čase překladu: nefunkční varianta s proměnnými | https://github.com/tisnik/c3-examples/blob/master/c3-macros/09_compile_time3.c3 |
| 158 | 10_swap_macro1.c3 | realizace makra pro prohození obsahu dvou proměnných: nefunkční varianta | https://github.com/tisnik/c3-examples/blob/master/c3-macros/10_swap_macro1.c3 |
| 159 | 11_swap_macro2.c3 | realizace makra pro prohození obsahu dvou proměnných: funkční varianta | https://github.com/tisnik/c3-examples/blob/master/c3-macros/11_swap_macro2.c3 |
| 160 | 12_varargs.c3 | makro s proměnným počtem argumentů | https://github.com/tisnik/c3-examples/blob/master/c3-macros/12_varargs.c3 |
Demonstrační příklady uvedené v minulém článku s popisem nedefinovaného chování v C3:
20. Odkazy na Internetu
- Programovací jazyk C3: evoluce, nikoli revoluce
https://www.root.cz/clanky/programovaci-jazyk-c3-evoluce-nikoli-revoluce/ - Programovací jazyk C3: datové typy pro moderní architektury
https://www.root.cz/clanky/programovaci-jazyk-c3-datove-typy-pro-moderni-architektury/ - Programovací jazyk C3: složené datové typy a kontejnery
https://www.root.cz/clanky/programovaci-jazyk-c3-slozene-datove-typy-a-kontejnery/ - 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 - Understanding the Apple ‘goto fail;’ vulnerability
https://www.blackduck.com/blog/understanding-apple-goto-fail-vulnerability-2.html - Branch (computer science)
https://en.wikipedia.org/wiki/Branch_(computer_science) - Conditional (computer programming)
https://en.wikipedia.org/wiki/Conditional_(computer_programming) - Dangling else
https://en.wikipedia.org/wiki/Dangling_else - Switch statement
https://en.wikipedia.org/wiki/Switch_statement - Compiler correctness
https://en.wikipedia.org/wiki/Compiler_correctness - Anonymous function
https://en.wikipedia.org/wiki/Anonymous_function - Closure (computer programming)
https://en.wikipedia.org/wiki/Closure_(computer_programming) - How to implement closures in C
https://hokstad.com/how-to-implement-closures - An Overview of Macros in Rust
http://words.steveklabnik.com/an-overview-of-macros-in-rust - A Practical Intro to Macros in Rust 1.0
https://danielkeep.github.io/practical-intro-to-macros.html - The Rust Programming Language: macros
https://doc.rust-lang.org/beta/book/macros.html - Rust by example: 15 macro_rules!
http://rustbyexample.com/macros.html - Undefined behavior
https://en.wikipedia.org/wiki/Undefined_behavior - Undefined Behavior in C and C++
https://www.geeksforgeeks.org/cpp/undefined-behavior-c-cpp/ - The Rust reference: Behavior considered undefined
https://doc.rust-lang.org/reference/behavior-considered-undefined.html#behavior-considered-undefined - Why would a language have a concept of undefined behavior instead of raising an error?
https://langdev.stackexchange.com/questions/481/why-would-a-language-have-a-concept-of-undefined-behavior-instead-of-raising-an - Undefined behavior
https://riptutorial.com/c/topic/364/undefined-behavior - C3: Undefined Behaviour
https://c3-lang.org/language-rules/undefined-behaviour/ - Undefined behavior in C and C++
https://russellw.github.io/undefined-behavior - C3 goes game and maths friendly with operator overloading
https://c3.handmade.network/blog/p/9019-c3_goes_game_and_maths_friendly_with_operator_overloading - Are operator overloadings in C++ more trouble than they're worth?
https://stackoverflow.com/questions/707081/are-operator-overloadings-in-c-more-trouble-than-theyre-worth - Why is operator overloading sometimes considered a bad practice?
https://www.reddit.com/r/ProgrammingLanguages/comments/19cl30z/why_is_operator_overloading_sometimes_considered/ - I don't understand the arguments against operator overloading
https://softwareengineering.stackexchange.com/questions/25154/i-dont-understand-the-arguments-against-operator-overloading
