Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tato kapitola obsahuje základní informace o programování v jazyce C. Předpokládáme, že čtenář má již nějaké zkušenosti s programováním v procedurálním jazyce. Čtenář musí zkousnout situaci, kdy narazíme na programovací konstrukt, který je vysvětlen až později.

První program

/*
	Toto je víceřádkový komentář.
	Nelze je vnořovat.	
*/

// Toto je jednořádkový komentář, platí do konce řádku 

//
// je potřeba kvůli funkci printf, přesný mechanismus je vysvětlen později
//
#include <stdio.h>

//
// definice funkce main, provedení programu odpovídá provedení funkce main
//
int main()
{
	//
	// volání funkce printf
	// argument "Ahoj svete" je řetězcový literál.
	//
	printf("Ahoj svete!!\n");
	return 0;
}

Proměnné, funkce, pole

Proměnné

Proměnnou definujeme následovně.

typ identifier;            // bez inicializace
typ identifier = init;     // s inicializací

Při definici je nutné specifikovat typ proměnné pomocí klíčového slova. Inicializace je nepovinná. Při inicializaci platí pravidla jako při provádění operátoru přiřazení. Přístup k neinicializované proměnné vede k nedefinovanému chování. Neinicializovaná proměnná totiž nějakou hodnotu má, jenom není dopředu na úrovni popisu jazyka definováno jakou.

int x;
int y = 10;

printf("%i\n", y);     // toto volání printf vypíše hodnotu proměnné y
printf("%i\n", x);     // nedefinované chování, přístup k neinicializované proměnné

Blok je část kódu uzavřená mezi { }, které si odpovídají: Bloky lze vytvářet jenom tak, aby se zanořovali, otevírací i uzavírací závorka bloku musí být uvnitř nadřazeného bloku. Takto bloky přirozeně vytváří stromovou hierarchii, jejím kořenen je úroveň samotného souboru, tj. obsah souboru mimo všechny bloky.

C je jazyk s lexikálním rozsahem platnosti proměnných. Proměnná existuje od místa své definice do konce bloku, ve kterém je definována. Pokud je v nějakém bloku použita proměnná (např. součást výrazu atd.), je hledána v aktuálním bloku a poté v blocích nadřazených. Hledání končí na úrovni souboru, pokud tam není proměnná nalezena, vyhlásí překladač chybu. Proměnné, které jsou definovány na úrovni souboru označujeme jako globální. Ve stejném bloku, mimo podbloky, nemohou být definovány dvě proměnné stejného jména.

Důsledkem mechanismu vyhledávání proměnných je jejich shadowing.

int x = 10;
{
	int x = 100;
	printf("%i\n", x);      // vytiskne 100
}
printf("%i\n", x);          // vytiskne 10

Funkce

Funkci definujeme následujícím způsobem.

type identifier (parameter_list)
{
	// body
}

První řádek je tzv. hlavička funkce, kde je postupně typ návratové hodnoty funkce, jméno funkce a v závorkách seznam argumentů funkce: čárkami oddělená posloupnost dvojic typ id, specifikujíc typ argumentů a jejich jména. Příkaz return val; ukončí vykonávání funkce a jako návratovou hodnotu vrátí hodnotu výrazu val. Pokud chceme funkci bez návratové hodnoty, napíšeme na místo typu návratové hodnoty klíčové slovo void a používáme return bez val, nebo jej nepoužijeme vůbec a funkce končí, když kód doběhne na konec těla funkce.

int sum(int a, int b)
{
	return a + b;
}

void print_numbers(int a, int b, int c)
{
	printf("%i %i %i\n", a, b, c);
}

Funkci voláme následovně.

identifier(list_of_values)

Tady je identifier jméno funkce a list_of_values je čárkami oddělený seznam výrazů. Ty se vyhodnotí a výsledky těchto vyhodnocení se přířadí argumentům funkce (hodnoty se kopírují, argumenty se předávají hodnotou). Pokud má funkce návratovou hodnotu, je výsledkem vyhodnocení volání funkce hodnota, kterou funkce vrátí.

int z = sum(1 + 4, 7);
printf("%i\n", z);        // vytiskne 12

print_numbers(z, z, 3);    // vytiskne 12 12 3

Pole

Pole je indexovaná kolekce hodnot stejného typu. Indexujeme pomocí čísel 0, 1, 2 …

Pole lze definovat následujícími způsoby.

type a[no_els]; 
type b[no_els] = init; 
type c[] = init; 

Výraz type je typ prvků, a, b, c jsou jména polí, no_els je počet prvků pole: toto musí být hodnota známá v době překladu, například literál. Výraz init je inicializační výraz pro pole.

{ val_0, val_1, ..., val_n }

Výrazy val_ se vyhodnotí a výsledky vyhodnocení se po řadě přiřadí na indexy 0, 1, …. Pokud je počet výrazů menší než velikost pole, jsou zbylé hodnoty vynulovány. Další možností je

{ [0] = val_0, [3] = val_3, [5] = val_5 }

Výrazy v hranatých závorkách jsou indexy, na které se při inicializaci zapíše hodnota na pravé straně =. Prvky na indexech, které v inicializačním výrazu nejsou, jsou vynulovány.

Pokud v definici pole není uveden počet prvků, je převzat z inicializačního výrazu.

K jednotlivým prvků pole přistupujeme pomocí indexu zapsaného do hranatých závorek za jméno pole.

Pole si nenese informaci o své velikosti a jazyk nekontroluje, zda při přístupu k poli používáme korektní index. Přístup k poli pomocí neplatného indexu vede k nedefinovanému chování.

Pole lze předat jako argument funkci, obvykle je s ním nutné předat i jeho velikost.

/* n je velikost pole a */
int find_max(int a[], int n)
{
	int max = a[0];
	for (int i = 1; i < n; i += 1)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	return max;
}

Tisk

K tisku budeme používat funkci printf ze standardní knihovny. Její hlavička je

int printf(format_string, values)

Argument format_string je řetězec, který může obsahovat speciální instrukce pro tisk. Tyto instrukce jsou ve tvaru %instr, kde instr je vlastní instrukce. Každé instrukci ve format_string odpovídá ve values, seznami výrazů oddělených čárkami, jedna hodnota, která se pomocí dané instrukce vytiskne (bráno zleva doprava). Instrukce se většinou týkají toho, jaký typ hodnoty se tiskne, podrobnosti lze nalézt v manuálu ke standardní knihovně. Základní instrukce jsou v příkladu níže.

printf("hello");     // žádné speciální instrukce, vytiskne se hello
printf("%i", -2);    // instrukce %i -- tisk celého znaménkového čísla
printf("%u", 15);    // instrukce %u -- tisk celého bezznaménkového čísla

instrukce     popis
-------------------
%c            znak
%s            řetězec
%f            číslo s desetinnou čárkou

Typový systém

Jazyk C je staticky typovaný (typy se kontrolují při překladu) a slabě typovaný (děje se mnoho implicitních typových konverzí).

Skalární typy

Pro celá čísla máme char, short, int, long, long long. Tyto typy mají ještě bezznaménkovou variantu uvozenou klíčovým slovem unsigned. Pro čísla s desetinnou čárkou máme typy float, double, long double.

Standard jazyku zaručuje minimální velikosti a rozsahy hodnot jednotlivých typů. (Velikost typu lze zjistit pomocí sizeof(typ).) Dále je zaručeno

sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)

a

sizeof(float) <= sizeof(double) <= sizeof(long double)

Skutečné velikosti se liší podle překladače a platformy. Například na 64 bitovém unixovém OS s překladačem gcc je

typ          sizeof(typ)
------------------------
char             1
short            2
int              4
long             8
long long        8
float            4
double           8
long double     16
------------------------

přičemž celá čísla jsou typicky uložena ve dvojkové (doplňkové) soustavě a čísla s desetinnou čárkou podle IEEE 754 standardu.

Jazyk C nemá typ pro pravdivostní hodnotu. Celočíselná hodnota 0 je nepravda, ostatní hodnoty jsou pravda.

Znaky a řetězce

Typ char je používán uložení znaků podle ASCII tabulky (která znaky kóduje pomocí čísel). Znakové literály jsou mezi apostrofy. Některé speciální znaky je nutné v literálech zapisovat pomocí backquotování, například znak nového řádku je \n.

/*
	x a y níže se rovnají
*/
char x = 'A';      // znakový literál
char y = 65;       // numerický literál

Řetězec v C je uložen v poli typu char. Přitom znaky čteme od začátku pole až po hodnotu 0. Poslední znak řetězce je tak znak na indexu o jedna menším, než na kterém je hodnota 0. Pole, ve kterém je řetězec uložen, tak musí být alespoň o jedna větší, než samotný řetězec (potřebujeme jedno místo pro hodnotu 0), může být ovšem větší. Řetězcové literály píšeme do uvozovek.

char string[] = "hellope";
/*
pole je velikosti 8
a[0] je znak 'h'
a[1] je znak 'e
...
a[6] je znak 'e'
a[7] je znak '\0' (hodnota 0)
*/

Alias typu

Alias vytvoříme typu následujím způsobem.

typedef def_expr;

Výraz def_expr je ve tvaru definice proměnné, k typu v něm vytvoříme alias tvořený identifikátorem proměnné v něm. V následujícím příkladu vytvoříme k typu int alias number.

typedef int number;

Strukturovaný datový typ

Položky různých typů lze spojit do strukturovaného typu, který vytvoříme

struct type_id 
{
	item_1;
	item_2;
	...
	item_n;
};

kde jednotlivé položky item_1item_n jsou zápisy ve formě type item_id, type je tu typ položky a item_id je jméno položky. Nově vytvořený typ je struct type_id. Ke kratšímu pojmenování lze použít alias, je možný i následující zápis.

struct type_id
{
	item_1;
	item_2;
	...
	item_n;
} type_id;

Tímto vytvoříme typ pojmenovaný jenom type_id. (Pokud ponecháme první výskyt type_id, vytvoříme typy struct type_id a type_id současně; první výskyt type_id jde vynechat, potom vytvoříme pouze type_id.)

Předchozí funguje protože

struct type_id 
{
	item_1;
	item_2;
	...
	item_n;
};

s případným vynecháním type_id je platným zápisem typu. Ten lze používat na místech, kde se obvykle píše jméno typu. Můžeme například vytvořit proměnné typu, který nemá jméno.

struct 
{
	item_1;
	item_2;
	...
	item_n;
} var1, var2.;

Inicializační výraz strukturovaného typu vytvoříme následovně.

{ init_1, init_2, ..., init_n }

Výrazy init_1init_n jsou inicializační výrazy jednotlivých položek. Pokud některé položky vynecháme, jsou vynulovány. Alternativně lze použít zápis

{ 
	.item_id_1 = init_1,
	.item_id_2 = init_2,
	...
	.item_id_n = init_n,
}

přičemž lze opět vynechat některé položky. Chceme-li vynulovat všechny položky, lze použít následující výraz.

{ 0 }

Pro přístup k jednotlivým položkám použijeme operátoru . (tečka).

var.item_id

Příklad

/*
	Příklad ukazuje použití struktury pro reprezentaci
	vektoru ve dvourozměrném euklidovkém prostoru.
	Funkce počítají skalární součin a násobení skalárem.
*/
typedef struct 
{
	float x;
	float y;
} vec2f;

float dot_product(vec2f a, vec2f b)
{
	return a.x * b.x + a.y * b.y;
}

vec2f product(vec2f a, float b)
{
	return (vec2f){b * a.x, b * a.y};	
}

int main()
{
	/* inicializace */
	vec2f u = { .x = 0.3, .y = 0.5 };
	vec2f v = { .x = 1.2, .y = 10.5 };
	
	/* zápis do položek */
	u.x += 10;
	v.y = 6;

	/* volání funkcí */
	float dp = dot_product(u, v);
	vec2f p  = product(v, 4.1);
	
	return 0;
}

Výčtový typ

Hodnotami výčtového typu je množina pojmenovaných celočíselných konstant. Typ spolu s konstantami vytvoříme následovně

enum type_id
{
	id_1,
	id_2,
	id_3,
	...
	id_n
};

Vytvoříme typ enum type_id (pro kratšího aliasu lze jako u strukturovaného typu použít typedef). Jména jednotlivých konstant jsou id_1, id_2, ..., jejich hodnoty jsou po řadě 0,1,2 .... Konstantám lze v definici typu přiřadit explicitní hodnoty.

enum type_id
{
	id_1 = value_1,
	id_2,
	id_3 = value_3,
	...
	id_n
};

Přiřazení hodnot není povinné. Pokud nemá konstanta v definici přiřazenu hodnotu, je její hodnota o jedna větší, než hodnota předchozí konstanty. Pokud nemá hodnotu přiřazenu první konstanta, je její hodnota 0.

Typické využití je pro určování druhů entit (například chyb, tokenů apod).

/*
	Následující enum lze použit pro určení typu chyby,
	která se může stát při zpracování souboru. 
*/

typedef enum
{
	FE_NONE,
	FE_NOT_FOUND,
	FE_PERMISSION_DENIED,
	FE_BAD_FORMAT,
} file_error;

/*
	První konstantou je FE_NONE pro žádnou chybu, toto je výhodný
	trik, protože FE_NONE má hodnotu 0, která je jediná nepravda,
	ostatní položky mají hodnotu indikující pravdu.
*/	

file_error err = process_file(filename);
if (err) 
{
	// tady ošetříme chybu
}

/*
	V následujícím výčtovém typu má poslední konstanta
	hodnotu, která se rovná počtu konstant. Toho lze 
	použít pro vytvoření pole, kde jednotlivá políčka
	indexujeme pomocí konstant.
*/

typedef enum
{
	TT_OPERATOR,
	TT_IDENTIFICATOR,
	TT_BRACE,
	TT_TOKEN_TYPE_COUNT,
} token_type;
	
int last_token_offsets[TT_TOKEN_TYPE_COUNT];
last_token_offsets[TT_BRACE] = 10;

Jména konstant jsou globální (pro soubor, ve kterém jsou definována), na začátek těchto jmen většinou dáváme zkratku jména typu, abychom zmenšili pravděpodobnost konfliktu jména konstanty se jménem nečeho jiného. Jména konstant jsou často kapitálkami.

Union

Definice unionu je podobná definici strukturovaného typu

union type_id
{
	item_1,
	item_2,
	...
};

Všechny položky (hodnoty) unionu jsou ovšem v paměti začínají na jednom místě a překrývají se tak (velikost unionu je pak velikostí jeho největší položky, je to úspora místa). Za platnou tak můžeme považovat pouze hodnotu jedné položky. Lze využít při potřebě šetřit pamět, spolu s výčtovým typem lze vytvořit obdobu tzv. tagged unionu.

typedef enum 
{
	RT_INT,
	RT_STRING,
	RT_FLOAT,
	
} result_type;


typedef struct
{
	result_type type;
	union 
	{
		int int_value;
		char *string;
		float float_value;
		
	} value;
} tagged;

/*
	při zpracování hodnoty typu tagged potom nejdříve zkontrolujeme
	položky type a podle ní víme, kterou položku z unionu value máme
	zpracovat.
*/

tagged x = function_that_returns_tagged();
if (x.type == RT_INT)
{
	// zpracujeme int
	process_int(x.value.int_value);
}
else if (x.type == RT_STRING)
{
	// zpracujeme string
	process_string(x.value.string);
}
else 
{
	// zpracujeme float
	process_float(x.value.float_value);
}

Operátory

Operátory (= akce, výpočet) aplikujeme na operandy (= data), a získáme tím nějaký výsledek (= data). Ve složitějších výrazech se operátory vyhodnocují v pořadí podle priority, toto pořadí lze změnit uzávorkováním. Operátory se stejnou prioritou jsou vyhodnocování podle associativity (zleva či zprava), opět to jde změnit pomocí uzávorkování.

Aritmetické operátory

Operátor je počítán z hodnot stejného typu a téhož typu je i výsledek. Pokud je v kódu operátor aplikován na hodnoty různých typů, dojde k implicitním typovým konverzím (tj. změnám typů) operandů směrem k většímu typu (anglicky jde o type promotions).

Toto se řídí následujícími pravidly (aplikovanými seshora dolů):

Je-li jeden operand neceločíselného typu.

  1. Je-li jeden operand long double, je druhý konvertován na long double.
  2. Je-li jeden operand double, je druhý konvertován na double.
  3. Je-li jeden operand float, je druhý konvertován na float

Jsou-li oba operandy celočíselné.

  1. Typy menší než int (znaménkové a bezeznaménkové verze char a short) jsou přetypovány na int, pokud int dokáže reprezentovat všechny hodnoty původního typu. Jinak jsou přetypovány na unsigned int.

  2. Operand menšího typu je převeden na větší typ, který je schopen reprezentovat jeho hodnoty. Typy jsou uspořádány do následujícího pořadí.

    unsigned long long > long long > unsigned long > long > unsigned int > int
    

Níže je tabulka aritmetických operátorů.

Operátor    Arita     Popis
-----------------------------------------------------------------------
 -            1       Prefixový unární operátor, mění znaménko
+,-,*,/       2       Binární operátory, klasické aritmetické operace
%             2       Binární operátor,  zbytek po celočíselném dělení

Priorita a associativita se řídí stejnými pravidly jako v matematice.

Operátor přiřazení

Slouží k přiřazení hodnoty na nějaké zapisovatelné místo. Syntax je následující.

lvalue = rvalue

Výraz lvalue musí označovat místo, kam se dá zapisovat (například proměnnou, položku struktury, prvek v poli), rvalue je výraz, který se vyhodnotí, a výsledek je zapsán do lvalue. Dochází k implicitní konverzi typu rvalue na typ lvalue. Tato konverze může být ztrátová: například dojde k zaokrouhlování, ztrátě desetinné části čísla, zachování pouze spodních bajtů celého čísla. (Některé konverze typů jsou zakázány, operátor provádí pouze konverze mezi kompatibilními typy, detaily lze nalézt ve standardu.) Výsledkem je hodnota rvalue po přiřazení.

Operátor přiřazení lze kombinovat s binárními operátory, analogicky následujícímu příkladu, kde jej kombinujeme s operátorem +.

int x = 10;
/* místo  */
x =  x + 3;
/* můžeme psát */
x += 3;

Operátory porovnání

Následující operátory porovnávají číselné typy. Výsledek je 1 pokud je porovnání pravdivé, nebo 0 jinak. Operátory jsou následující.

<, >, <=, >=, ==

Test rovnosti je tvořen dvěma rovnítky, nikoliv jedním. Jedno rovnítko je operátor přiřazení.

Logické operátory

Operátory pro konjunkci (logická spojka a), disjunkci (logická spojka nebo) a negaci.

Operátor    Popis
--------------------------------------
&&          konjunkce
||          disjunkce
!           negace, unární, prefixový
--------------------------------------

Operátory konjunkce a disjunkce jsou asociativní zleva a vyhodnocují se líně. Uveďme si to na příkladu konjunkce.

/* 
	Uvažme následující konjunkci 
*/
x && y && z && w

/* 
	Díky asociativitě zleva je to vlastně
*/
((x && y) && z) && w

/*
   Pokud je x == 1, y == 1, z == 0, w == 1, probíhá výpočet následovně:

	1. x && y  dá  výsledek 1
	2. 1 && z  dá  výsledek 0, výpočet hodnoty výrazu končí s výsledkem 0
   
    V tomto vyhodnocení líné, 0 && w už se nepočítá.	
	
	Líného vyhodnocení lze s výhodou využít, pokud je ve výrazu 

	(pred && op)
	
	výraz pred kontrolou toho, jestli můžeme výraz op bezpečně vyhodnotit.
	Pokud se totiž pred vyhodnotí na 0, výraz op už se nevyhodnocuje.
*/

Bitové operátory

Bitové operátory aplikujeme na celočíselné operandy. Umožnují pracovat s jednotlivými bity těchto operandů. Jedná se logické operátory aplikované po bitech, nebo bitové posuvy. Operátory si vysvětlíme na následujících příkladech.

Předpokládejme, že x == 100 a y == 50 jsou jednobajtová čísla (typu unsigned char). Ve dvojkové soustavě máme

x ==  0 1 1 0 0 1 0 0
y ==  0 0 1 1 0 0 1 0

Potom máme

konjunkce po bitech
x       0 1 1 0 0 1 0 0
y       0 0 1 1 0 0 1 0
-----------------------
x & y   0 0 1 0 0 0 0 0


disjunkce po bitech 
x       0 1 1 0 0 1 0 0
y       0 0 1 1 0 0 1 0
-----------------------
x | y   0 1 1 1 0 1 1 0

nonekvivalence po bitech
x       0 1 1 0 0 1 0 0
y       0 0 1 1 0 0 1 0
-----------------------
x ^ y   0 1 0 1 0 1 1 0

negace po bitech
x       0 1 1 0 0 1 0 0
-----------------------
~x      1 0 0 1 1 0 1 1

bitový posuv doleva, tady o 2 bity, lze o obecný počet bitů
(zprava nahrazujem prázná místa 0)
x       0 1 1 0 0 1 0 0
-----------------------
x << 2  1 0 0 1 0 0 0 0

bitový posuv doprava o 2 bity, lze o obecný počet bitů 
(zleva nahrazujeme prázdná místa 0 pro unsigned a 1 pro signed typy)
x       0 1 1 0 0 1 0 0
-----------------------
x >> 2  0 0 0 1 1 0 0 1

Na bitové operátory se aplikují typové konverze jak u aritmetických operátorů. U operátorů posuvu musí být druhý operand nezáporný.

Kontrola běhu programu

Větvení

if (tval)
{
	// blok při nenulovém tval
}
else 
{
	// blok při nulovém tval
}

Větev else je nepovinná. Pokud blok obsahuje pouze jeden příkaz, lze závorky vynechat. Při vynechávání závorek bloků, nemusí být na první pohled patrné, ke kterému if patří dané else: toto je dáno pravidlem, že patří k nejbližššímu if, které připadá v úvahu (tj. není obsazeno jiným else podle struktury bloků). Toto pravidlo se zhusta používá v konstrukci známé jako žebřík.

if (tval_1)
{
	// tval_1 je pravda (tj. není 0)
}
else if (tval_2)
{
	// tval_1 je nepravda (tj. je 0)
	// tval_2 je pravda
}
else if (tval_3)
{
	// tval_1 a tval_2 jsou nepravda
	// tval_3 je pravda
}
else 
{
	// tval_1, tval_2, tval_3 jsou nepravda
}

Není špatným zvykem psát závorky pro bloky skoro vždy (mimo speciálních idiomů, například žebříku).

Dalším příkazem pro větvení je switch. Tuto konstrukci si nastudujte z literatury jako cvičení.

Cykly

Základní cyklus.

while (tval)
{
	// tělo
}

Běh je následující

  1. Vyhodnotíme tval, pokud není pravdivý, cyklus končí
  2. Provedeme tělo cyklu a přejdeme k bodu 1.

Krokovací cyklus.

for (iter; tval; incr)
{
	// tělo
}

Běh je následující

  1. Když cyklus začíná, provede se část iter. Ta může obsahovat i definici proměnné, tato proměnná je potom platná jenom v cyklu, uchovává si ale hodnotu mezi jednotlivými iteracemi.
  2. Potom se vyhodnotí tval. Pokud je pravdivá, provede se tělo cyklu, jinak cyklus skončí.
  3. Provede se výraz incr a přechází se k bodu 2.

Části iter, tval i incr lze vynechat (středníky musí zůstat), pokuch chybí tval je brán jako pravdivý.

Cyklus s alespoň jednou iterací.

do 
{
	// tělo
} while (tval);

Podobné cyklu while, pouze vyhodnocujeme tval na konci těla cyklu, nikoliv na začátku. V důsledku toho tělo cyklu provede vždy aspoň jednou.

/*
	Příklad: výpis čísel od 0 do n (n už se netiskne)
*/
int n = 10;

{ 
	// pomocí while
	int i = 0;
	while(i < n)
	{
		printf("%i\n", i);
		i += 1;
	}
}

{
	// pomocí for
	for(int i = 0; i < n; i += 1)
	{
		printf("%i\n", i);
	}	
}

{ 
	// pomocí do while
	// nefunguje pro n == 0 
	int i = 0;
	do
	{
		printf("%i\n", i);
		i += 1;
	} while(i < n);
}

Vykonávání libovolného cyklu lze přerušit provedením příkazu break. Ukončit provádění aktuální iterace cyklu a skočit dopředu na konec těla cyklu lze příkazem continue.

Poznámka k úkolům

Pokud se v úkolech vyskytují čísla, vhodně zvolte typ. Většinou stačí int, případně float. Můžete předpokládat, že funkce dostanou správné argumenty: pokud je například v úkolu napsáno, že argumentem funkce má být přirozené číslo, můžete pro ně zvolit typ int a přitom ve funkci nekontrolovat, že argument je nezáporný. Nemusíte volit unsigned int.

/* funkce zjišťující, jestli je n prvočíslo */
int is_prime(int n)
{
	/* následující test není potřeba */
	if (n <= 0) 
	{
		// a very bad error !!!!
	}
}

Úkoly

  1. Ve vybraném vývojovém prostředí vytvořte program, který vytiskne větu: Ahoj světe. Program zkompilujte a spusťte.

  2. Napište funkce pro výpočet obsahu obdélníka a kruhu.

  3. Napište funkci pro převod malého písmena na velké.

  4. Napište program, který vypíše velikosti všech skalárních typů.

  5. Napište program, který vytiskne první a poslední číslici tříciferného čísla.

  6. Napište funkci, která zvlášť vytiskne celou a desetinnou část desetinného čísla.

  7. Napište funkci, která vrátí i-tý bit čísla (bezznaménkového typu).

  8. Napište funkci, která vypíše hodnoty všech bitů čísla typu unsigned char. (Nevadí, že nepoužijete cyklus, ten bude až příště).

  9. Pomocí unsigned char lze reprezentovat podmnožinu množiny {0,..,7} následovně: bit na indexu i je roven 1, pokud i patří do podmnožiny, jinak je roven 0. Naprogramujte funkce pro test podmnožinovosti a výpočet rozdílu.

  10. Napište funkci, která spočítá minimum tří čísel.

  11. Napište funkci, která rozhodne, zda je daný rok přestupný.

  12. Napište funkci, která vypočítá a-té Fibonacciho číslo.

  13. Napište funkci, která vytiskne všechna čtyřciferná čísla, jejichž součet číslic je dělitelný 7.

  14. Napište funkci, který pro přirozená čísla x, y vypíše všechna čísla větší než x a menší než y taková, že se čtou stejně zepředu jako zezadu (např. 12321, 1001).

  15. Napište funkci, která pro přirozené číslo vytiskne jeho rozklad na prvočísla.

  16. Napište funkci, která rozhodne, zda jsou dvě pole stejná, když * pole jsou stejná, pokud na všech indexech mají stejné hodnoty. * pole jsou stejná, pokud můžeme proházet prvky v prvním poli tak, aby bylo stejné jako druhé pole podle definice z předchozího bodu.

  17. Napište funkci, která ověří, zda-li jsou prvky v poli uspořádány vzestupně.

  18. Napište funkci, který určí maximální počet znaků, na kterých konec řetězce r1 stejný jako začátek řetězce r2.

    Například pro "ahoj" a "hojnost" jsou to tři znaky, odpovídající řetězci "hoj".
    a h o j
    h o j n o s t
    
  19. Napište funkci, která pro řetězce x, y spočítá, kolikrát se x vyskytuje jako podřetězec y. Jednotlivé výskyty se mohou překrývat.