Programování v jazyku C-C++

Transkript

Programování v jazyku C-C++
Programování v jazyku C/C++
Programovací jazyk, zdrojový kód a překladač
Prvním důležitým pojmem je zdrojový kód programu. Zdrojový kód programu není nic jiného, než
hromada textových souborů, v nichž je zapsáno pomocí programovacího jazyka co má program dělat.
Překladač (angl. compiler) je program, který si přečte zdrojový kód a vytvoří z něj program. Bylo vytvořeno
mnoho různých programovacích jazyků a k nim samozřejmě mnoho překladačů (co jazyk, to překladač(e)).
Mezi prvními programovacími jazyky byl assembler. Pomocí tohoto jazyka se přímo zapisovali instrukce
procesoru. Například sečtení dvou čísel v assembleru může vypadat takto:
MOV AX,a
MOV BX,b
ADD AX,BX
MOV a,AX
AX a BX je označení registrů procesoru, „a“ a „b“ jsou proměnné, které obsahují nějaká čísla. MOV je
instrukce která data přesune (nejdříve data z „a“ do AX, pak z „b“ do BX ...) a ADD je instrukce, která přičte
druhé číslo k prvnímu (tj. k AX přičte BX). Překladač assembleru tento zdrojový kód převede do binárního
kódu (tedy programu), který je již procesoru srozumitelný. Později vznikly vyšší programovací jazyky, mezi
něž patří i jazyk C, které již nepracovali přímo s instrukcemi procesoru. Předchozí kód se v jazyce C zapíše
následovně:
a = a+b;
Přenositelnost
Přenositelnost programu je pojem, který nám říká, kde všude náš program poběží. Například program
pro Windows 3.11 poběží i pod Windows 95, ale nepoběží pod Linuxem. Program pro 32-bitová Windows
poběží při troše štěstí i pod 64-bitovými Windows, ale ne naopak.
Přenositelnost programovacího jazyka je vlastnost, která určuje, pro které platformy (operační systémy
a procesory) existuje překladač. Čím více existuje překladačů pro různé procesory a OS, tím je program
"přenositelnější". Překladače pro různé platformy vytvářejí různí lidé, či organizace, proto
vznikají standardy jazyka, které říkají, čemu všemu musí překladač rozumět. Existuje standard ANSI C
(americký standard) vytvořený organizací www.ansi.org a z něj odvozený ISO/IE 9899:1999 vytvořený
organizací pro mezinárodní standard www.iso.org.
Přenositelnost zdrojového kódu je dána přenositelností programovacího jazyka a dodržováním
standardů. Některé překladače totiž umí „něco navíc“, co obvykle usnadňuje psaní zdrojového kódu, ale
snižuje přenositelnost. Další faktor, který má vliv na přenositelnost zdrojového kódu je používání knihoven.
Knihovny jsou soubory obsahující funkce, které může program využívat jejich voláním. Existují standardní
knihovny, které jsou dostupné na všech podporovaných platformách, jiné jsou však dostupné jen na
některých platformách. Existují knihovny pro grafické uživatelské rozhraní (angl. zkratka GUI), které jsou
přenositelné například mezi Windows a Linuxem.
Trocha historie C a C++
Programovací jazyk C je známý svou přenositelností a rychlostí. Byl navržen jako poměrně malý jazyk,
kombinující efektivitu a výkonnost. Byl vytvořen v Bellových laboratořích AT&T Denisem Ritchiem v létech
1969 až 1973. Dále se na vzniku jazyka podíleli Brian Kernighan a Ken Thompson. Byl napsán pro
snadnou přenositelnou implementaci Unixu. Postupem času vznikla ANSI (American National Standard
Institute) norma jazyka C. Shodný standard definuje ISO (International Stnadards Organisation) norma.
Proto se někdy uvádí spojení ANSI/ISO norma.
1
Jazyk C++ je pokračovatelem jazyka C. Nejdříve byl označován jako C with Classes, od roku 1983 C++.
Vyvinul jej Bjarne Stroustrup v AT&T. Jazyk C++ přináší novinky, především OOP (objektově orientované
programování). První oficiální norma jazyka C++ byla přijata v roce 1998.
Vývojová prostředí a překladače
Samotný překladač umožní vytvořit ze zdrojáků program. Vývojová prostředí (zkratka IDE = Integrated
development environment) je sada nástrojů, která pomáhá s laděním programů, hledáním chyb,
udržováním přehledu ve zdrojovém kódu, nápovědou k funkcím atp.
Windows API
Windows API (API = application programming interface, tj. rozhraní pro programování aplikací). Windows
API není nic jiného, než sada knihoven, které lze využívat při programování k vytváření „Windowsovských“
aplikací. Do kódu se vloží pomocí příkazu
#include <windows.h>
hlavičkový soubor <windows.h> a pak je možníé používat funkce, jako například WinMain() (namísto
funkce main(), nebo CreateWindow() pro vytvoření okna atd.
Překladač pro Linux
Překladač gcc/g++ se používá v Linuxu k překladu nejčastěji. Překladač gcc je většinou již nainstalován. V
Linuxu se používá k překladu většiny programů napsaných v C/C++, dokonce i k překladu samotného jádra
Linuxu.
Code::Blocks
Code::Blocks je asi to nejlepší, co je zdarma. Existuje verze pro Linux i Windows. Spolupracuje s mnoha
překladači (například s C++ Builder, který umí Windows API), dokáže importovat projekty z Bloodshed
Dev-C++, je rozšířitelný mnoha pluginami, je přehledný a snadno se v něm tvoří multiplatformní programy
(pro Linux i Windows). Instalace zabere kolem 100MB.
Bloodshed Dev-C++ a wxDev-C++
Bloodshed je (kdysi jedno z nejpopulárnějších) free vývojové prostředí, využívající k překladu minGW (což
je přenesená verze linuxového gcc). Je malý, rychlý a jednoduchý. Jeho vývoj, jak se zdá, v roce 2005
umřel. Vývoj Dev-C++ převzala jiná parta programátorů, nazvala jej wxDev-C++. Více o Dev-C++
DJGPP
DJGPP je linuxový překladač gcc přenesený do prostředí Windows a je zdarma. Kromě překladače také
obsahuje sadu nástrojů a knihoven, které vám pomohou vytvářet 32-bitové programy v prostředí MSDOS/MS-Windows Lze jej používat jako gcc z příkazové řádky, nebo můžete využít vývojové prostředí
RHIDE. Více o DJGPP
Visual Studio
Visual Studio je nejlepší nástroj pro vývoj aplikací pro Windows. Jedná se o tzv RAD (Rapid Applications
Development) prostředí, které obsahuje vizuální pomůcky pro sestavování aplikace. Visual Studio je úzce
spjato s technologií .NET.
2
Visual C++ Express Edition
Visual C++ Express Edition je takový menší bratříček Visual Studia a je zdarma. Rozhraní je totožné, jako
u Visual Studia, jen toho umí méně a neobsahuje 2Gb nápovědy jako Visual Studio. Více o Visual Studiu (a
C++ Express Edition)
Borland C++ Builder
C++ Builder je, obdobně jako Visual Studio, RAD vývojové prostředí od společnosti Borland. Prostředí je
určeno pro jazyk C. Jinak se o něm dá napsat to samé, co o Visual Studiu.
Existuje i free prostředí s názvem Turbo C++, ale poslední verze je z roku 2006, má netušeně zastaralé
požadavky na systém a po jeho instalaci se mi ani nespustil (házelo to nějaké chyby). Prostě s ním nemá smysl
strácet čas.
Borland C++ Builder Compiler
Borland C++ Builder Compiler je pouze překladač pro jazyky C/C++, který můžete ovládat z příkazové
řádky. Je to stejný překladač, který najdete v Borland C++ Builderu, je zdarma (musíte se jen zaregistrovat)
a je bez jakéhokoliv vývojového prostředí. Lze jej používat z příkazové řádky, nebo jej může používat
Code::Blocks. Stáhnout C++ Builder compiler
Eclipse
Eclipse je velice pěkné vývojové prostředí, ale především pro Javu. Do Eclipse lze pomocí pluginů
zakomponovat podporu pro spoustu dalších programovacích jazyků, včetně C/C++. bohužel, nainstalovat
takové pluginy a naučit se je ovládat dá dost práce. Prostředí je to vhodné především pro lidi, kteří
programují v Javě, nebo pro ty, co programují v několika jazycích a chtějí používat jednotné vývojové
prostředí. Eclipse existuje ve verzi pro Windows i Linux.Více o Eclipse
NetBeans
Vývojové prostředí napsané v Javě a primárně pro Javu, ale pomocí pluginu lze používat i pro C/C++.
Existuje verze pro Linux i Windows a je zdarma. Toto prostředí je velkým konkurentem pro Eclipse. Více o
NetBeans
Anjuta
Anjuta je Linuxové vývojové prostředí napsané pro GTK/GNOME, kdysi velmi oblíbené. Bývá součástí
standardních balíčků linuxových distribucí. Více o Anjuta
Sun Studio
Sun Studio je linuxové vývojové prostředí pro C/C++ od společnosti Sun Microsystems. Od verze 11 je
zdarma.Více o Sun Studio
Struktura programu
První program
Následující kód lze uložit do souboru s příponou .c (např. kod.c). Přípona .c je pro některé překladače
(zvláště v Linuxu) povinná. Ve jménech souboru nesmí být diakritika ani mezery!
3
1:
2:
7:
9:
10:
11:
12:
13:
15:
/*------------------------------------------------*/
/* kod.c
*/
#include <stdio.h>
//standardni knihovna
int main(void)
{
printf("Hello World\n");
return 0;
}
/*------------------------------------------------*/
Bílé znaky, jako je mezera, tabulátor nebo znak nového řádku většinou ve zdrojovém kódu jazyka C nemají
žádný význam. Odsazováním program pouze získá na přehlednosti a čitelnosti. Funkci main() by jste
mohli napsat do jednoho řádku, to je překladači jedno. Konce řádků jsou důležité pouze pro direktivy
začínající znakem # (čti „šarp“) a u komentářů začínajících dvěma lomítky, protože jejich konec je dán
právě koncem řádku.
Komentáře
Tím nejjednodušším, co je v programu vidět, jsou komentáře. Vše co je mezi znaky /* a */ je komentář. V
jazyce C++ se navíc považuje za komentář vše za dvěma lomítky // až do konce řádku. Dobré komentáře
vám pomohou se v kódu orientovat.
Knihovny
Existují takzvané standardní knihovny, což jsou knihovny dodávané s překladačem, a uživatelské knihovny,
které si vytváří programátor sám. Hlavičkové soubory jsou soubory, v nichž jsou uloženy definice funkcí z
knihovny. Jejich přípona je většinou .h, ale není povinná (stejně jako .c). Pro C++ se někdy knihovny
označují příponou .hpp.
Direktiva #include říká překladači, jaký hlavičkový soubor má načíst před překladem zdrojového kódu.
Pokud je jméno ve špičatých závorkách < >, pak se soubor hledá ve standardních adresářích. Které to
jsou, to záleží na překladači. Pokud je jméno mezi uvozovkami, hledá se soubor v aktuálním adresáři.
Standardní knihovna stdio = standard input output (knihovna pro standardní vstup a výstup)
Funkce
V příkladu jsou v programu dvě funkce. Funkce main() a funkce printf(). Funkci main() definujeme. To
znamená, že popisujeme co tato funkce bude dělat. Funkci printf() pouze „voláme“. Je to funkce
definovaná v knihovně stdio.h a její definice způsobí vypsání textu.
Funkce má své jméno (main), návratovou hodnotu (int) a argumenty určené v závorkách za jménem
funkce ((void)). Návratová hodnota určuje, jaká data funkce vrátí při svém skončení (např. int). Argumenty
jsou data, která funkce dostává ke zpracování. Hodnota void znamená „nic“, tj. funkce main() žádné
argumenty neočekává. Návratová hodnota je jen jedna a její typ se píše před jméno funkce. Argumenty se
píší za jméno funkce do závorek a je-li jích více, oddělují se čárkou. Funkce printf() dostává jako argument
řetězec (řetězce jsou vždy ve dvojitých uvozovkách). Znaky \n reprezentují nový řádek.
Tělo funkce je všechno to, co je ve špičatých závorkách { }. Funkce se ukončuje klíčovým
slovem return za kterým je návratová hodnota funkce.
Důležité je vědět, že funkce main() je speciální funkce, která je volána v programu jako první.
4
Funkce printf()
Funkce printf() má jako první argument textový řetězec. Ten může obsahovat speciální sekvence.
Sekvence \n přesune kursor na nový řádek. Za znak %s se dosadí textový řetězec, který je dalším
argumentem funkce printf(). Druhý je %i, za který se dosadí celé číslo.
1: /*------------------------------------------------*/
2: /* kod2.c
*/
3: #include <stdio.h>
6: int main(void)
7: {
8:
printf("1 + 1 = %i\n", 1 + 1);
9:
printf("%i + %i = %i\n", 1, -2, 1 + (-2));
10:
printf("%s\n", "Konec programu.");
11:
/* return ukonci program. */
12:
return 0;
13:
printf("Tohle se uz nezobrazi %s!\n");
14: }
16: /*------------------------------------------------*/
Posledního volání funkce printf() je až za příkazem return a proto k jejímu volání v programu nedojde.
Navíc obsahuje chybu. V řetězci je %s , ale funkce nemá žádný další argument. Pokud by tato funkce byla
volána, „sáhla“ by si kamsi do paměti pro neexistující řetězec. Buď by se vytiskly nějaké nesmysly, nebo by
program skončil s chybou (neoprávněný přístup do paměti; program provedl nepovolenou operaci atp.). Na
tuto chybu vás překladač nemusí upozornit, protože překladač zkontroluje jen to, že funkce printf() má mít
jako první argument textový řetězec (a to má).
Datové typy, bloky, deklarace a definice
Základní datové typy
Základní datové typy jazyka C
char
short
1)
int
8
znak
'a', 'A', 'ě'
16
krátké celé číslo
65535, -32767
16/32
celé číslo
--||--
1)
long
32
dlouhé celé číslo
--||--
long long1)
64
ještě delší celé číslo
9223372036854775807, 9223372036854775807
enum
8/16/32 výčtový typ
float
32
racionální číslo
5.0, -5.0
double
64
racionální číslo s dvojitou
přesností
5.0l, 5l, -5.0L
long
double
80
velmi dlouhé racionální číslo
5.5L, 5l, -5.0l
pointer
16/32
ukazatel
Co je na jazyku C zajímavé (a za co ho někteří nemají rádi) je to, že velikost všech datových typů není
standardem pevně dána. Můžete se pouze spolehnout na to, že velikost char = 8 bitů (= 1 byte) a že:
sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long). Závisí to především na procesoru. Na 16bitovém procesoru budete mít nejspíše int velikosti 16 bitů (16bitovému procesoru se s takto velkým číslem
"nejlépe" pracuje).
5
Konkrétní rozsahy datových typů lze najít ve standardní knihovně <limits.h>.
Existuje ještě další způsob jak zjistit velikost datového typu. Například je zde funkce sizeof(), která vrací počet
bytů datového typu (nebo proměnné). Například sizeof(char) by se vždy mělo rovnat 1.
Typy char, short, int, long a long long mohou být se znaménkem nebo bez znaménka. Určí se to pomocí
klíčových slov signed (se znaménkem) nebo unsigned (beze znaménka). „signed“ je defaultní hodnota,
proto jí není třeba psát.
1: /*------------------------------------------------*/
2: /* typy.c
*/
4: #include <stdio.h>
5: int a;
7: int main(void)
8: {
9:
/* deklarace promennych */
10:
unsigned int b;
11:
float c;
13:
a = 5;
14:
b = 11;
15:
a = a / 2;
16:
c = 5.0;
17:
printf("a=%i\nb=%u\nc=%f\n", a, b, c / 2);
19:
return 0;
20: }
22: /*------------------------------------------------*/
Výstup z tohoto programu bude následující:
a=2
b=11
c=2.500000
Boolovské datové typy
V některých programovacích jazycích se používá datový typ bool, který může nabývat pouze hodnot true
(pravda) nebo false (nepravda). Tyto hodnoty se používají ve vyhodnocování podmínek při řízení běhu
programu a podmíněných operátorů. V jazyce C takovýto typ neexistuje.
V jazyce C se jako nepravda vrací 0 a jako pravda 1. A dále platí, že cokoliv nenulového se vyhodnocuje
jako pravda (tj. vše kromě 0 a NULL).
V jazyce C je možné vytvářet vlastní datové typy pomocí operátoru typedef a hodnoty true a false lze tedy
nadefinovat. Standard C99 (stejně jako C++) ale již přináší knihovnu přináší knihovnu <stdbool.h>.
#include <stdbool.h>
bool x = true;
x = false;
Bloky, deklarace a definice
Blokem se rozumí vše, co je mezi špičatými závorkami { a }. Všechny proměnné, které deklarujeme v
bloku, jsou tzv. lokální proměnné a proměnné mimo blok jsou globální. Lokální proměnné platí jen v těle
bloku a v blocích vnořených (jinde je překladač neuvidí).
Proměnné jsou místa kdesi v paměti počítače, které definujeme v programu. Velikost místa v paměti závisí
na datovém typu proměnné. Do proměnných lze vkládat hodnoty, měnit je a číst.
6
Deklarací proměnných určujeme jaké proměnné se v programu objeví. Nejdříve se určí datový typ a za
ním názvy proměnných oddělené čárkou. Deklarace se ukončí středníkem. Při deklaraci je možné do
proměnné rovnou vložit hodnotu. Názvy proměnných nesmí obsahovat mezeru, národní znaky (č, ř, š atp.)
a nesmí začínat číslem. Jejich maximální délka je závislá na překladači. Jazyk C rozlišuje malá a velká
písmena.
unsigned int i,j,k;
signed int cislo1, cislo2 = cislo3 = 25;
Dokud neurčíte jakou má proměnná hodnotu, může v ní být jakékoliv číslo. Proměnné cislo2 a cislo3 mají
hodnotu 25, všechny ostatní mají náhodnou hodnotu!
Ukazatele
Ukazatele (pointry) jsou proměnné, které uchovávají adresu ukazující do paměti počítače. Při jejich
deklaraci je třeba uvést, na jaký datový typ bude ukazatel ukazovat. Paměť počítače je adresována po
bytech. Číslo 0 ukazuje na první bajt v paměti, číslo 1 na druhý bajt atd. Pokud máte šestnáctibitový
překladač, bude velikost ukazatele 16 bitů, u 32 bitového překladače 32 bitů. Deklarace ukazatelů vypadá
takto:
float *uf; // Ukazatel na float, ma velikost typu "ukazatel", nikoliv float!
int *ui;
void *uv;
Proměnná uf je ukazatel na typ float, ui je ukazatel na typ int a uv je tzv. prázdný ukazatel u kterého není
určeno, na jaký datový typ se bude ukazovat. To překladači neumožňuje typovou kontrolu, proto je lepší jej
nepoužívat, pokud to není nutné. Do všech třech proměnných lze vložit libovolné (celé) číslo, které pak
bude překladačem chápáno jako adresa do paměti. K získání adresy nějaké proměnné slouží operátor &. K
získání dat z adresy, která je uložena v ukazateli, slouží operátor * .
1:
2:
4:
6:
7:
8:
9:
10:
11:
13:
14:
15:
16:
17:
18:
19:
21:
22:
23:
26:
27:
28:
29:
31:
/*------------------------------------------------*/
/* ukazatel.c
*/
#include <stdio.h>
int main(void)
{
int i;
/* promenna int */
float f, f2;
int *ui;
/* ukazatel (na typ int) */
float *uf;
f = 5.5;
uf = 50; /* v ukazateli uf je hodnota 50. Co je v pameti na 50 bajtu
nikdo nemuze vedet, proto se takto do ukazatele adresa
nikdy (!) neprirazuje */
uf = &f; /* v ukazateli uf je hodnota adresy promenne f (tak je to
spravne */
ui = &i;
f2 = *uf;
/* do f2 se priradi hodnota z promenne f, tedy 5.5 */
*ui = 10;
/* tam, kam ukazuje adresa v ui (tedy do i) se ulozi 10.
Hodnota promenne ui se nezmeni (je to stale adresa promenne i)*/
printf("f = %f\n", f); /*hodnota v f se od prirazeni 5.5 nezmenila */
printf("f2 = %f\n", f2); /* do f2 se priradila hodnota z f, na kterou
jsme se odkazovali pomoci ukazatele uf */
printf("i = %i\n", i); /* do i jsme pres ukazatel ui priradili 10 */
printf("uf = %u, *uf=%f\n", uf, *uf); /* vytiskneme si hodnotu uf a
hodnotu na teto adrese (tj. adresu f2 a hodnotu f2) */
34:
return 0;
35: }
36: /*------------------------------------------------*/
7
Výstup z programu bude následující:
f = 5.500000
f2 = 5.500000
i = 10
uf = 3221224276, *uf=5.500000
Proměnná uf obsahuje adresu paměti, na které byla proměnná f uložena. Ta se bude při každém spuštění
programu lišit, neboť paměť přiřazuje programu operační systém.
V programu jsou chyby. Přímé přiřazení hodnoty do proměnné uf (uf = 50;) a pak v poslední funkci printf,
kde se tiskne hodnota ukazatele uf jako celé číslo bez znaménka (unsigned int). Je možné, že vám tyto
konstrukce překladač ani nedovolí přeložit!
Ukazatele (pointery) by se správně měli tisknout přes %p:
31: printf("uf = %p, *uf=%f\n", uf, *uf); /* vytiskneme si hodnotu uf ...
Za zmínku ještě stojí přiřazení f2 = *uf. Nejdříve se vezme hodnota z uf a z této adresy se přečte číslo
dlouhé tolik bitů, kolik bitů má typ, pro který je ukazatel určen. Proměnná uf je ukazatel na typ float, který
má 32 bitů. Proto se vezme 32 bitů začínajících na adrese uložené v uf a toto číslo se pak chápe jako
racionální a jako takové se uloží do f2. Kdybychom deklarovali uf jako ukazatel na typ char (char *uf), pak
by jsme se podívali sice na stejnou adresu, ale z ní přečetli jenom 8 bitů!
NULL
NULL je speciální znak, který znamená nic. Vložíme-li do ukazatele hodnotu NULL, pak ukazuje nikam.
NULL se také často používá jako argument funkcí, když tam, kde je argument vyžadován žádný argument
vložit nechceme.
NULL je definováno v knihovně <stddef.h>, která je načítána např. pomocí <stdlib.h> a <stdio.h>.
Klíčová slova, konstanty, řetězce
Klíčová slova
ANSI norma jazyka C určuje následující slova jako klíčová. Tyto slova mají v jazyce C speciální význam a
nelze je používat jako uživatelem definované identifikátory..
auto
double int
struct
break
else
switch case
enum register typedef char
return union
const float
for
signed void
do
if
default
long
extern
short
unsigned continue
goto
sizeof
volatile
static while
Konstanty
Konstanty jsou objekty, jejichž hodnota se za chodu programu nemění. Konstanty se definují za klíčovým
slovem const. Po něm následuje datový typ konstanty. Pokud ne, překladač jej chápe jako int. Dále je v
definici identifikátor konstanty a za rovnítkem hodnota.
const float a = 5;
8
1: /*------------------------------------------------*/
2: /* konst.c
*/
4: #include <stdio.h>
6: int main(void)
7: {
8:
const a = 5;
/* konstantu a vyhodnoti prekladac jako int 5 */
9:
const b = 5.9;
/* konstantu b vyhodnoti prakladac jako int 5 */
10:
const float c = 5;
/* konstanta c je urcena jako float,tj. 5.0 */
11:
int i = 0;
13:
printf("a/2 = %f\n", (a / 2) * 1.0);
/* celociselne deleni */
14:
printf("b/2 = %f\n", (b / 2) * 1.0);
/* celociselne deleni */
15:
printf("c/2 = %f\n\n", (c / 2) * 1.0); /* deleni s des. carkou */
17:
printf("Mala nasobilka pro cislo %i\n\n", a);
18:
printf("%i * %i = %i\n", a, i, i * a);
19:
i = i + 1;
20:
printf("%i * %i = %i\n", a, i, i * a);
21:
i = i + 1;
22:
printf("%i * %i = %i\n", a, i, i * a);
23:
i = i + 1;
24:
printf("%i * %i = %i\n", a, i, i * a);
25:
i = i + 1;
26:
printf("%i * %i = %i\n", a, i, i * a);
27:
i = i + 1;
28:
printf("%i * %i = %i\n", a, i, i * a);
29:
i = i + 1;
30:
printf("%i * %i = %i\n", a, i, i * a);
31:
i = i + 1;
32:
printf("%i * %i = %i\n", a, i, i * a);
33:
i = i + 1;
34:
printf("%i * %i = %i\n", a, i, i * a);
35:
i = i + 1;
36:
printf("%i * %i = %i\n", a, i, i * a);
38:
return 0;
39: }
41: /*------------------------------------------------*/
Výstup z programu:
a/2 = 2.000000
b/2 = 2.000000
c/2 = 2.500000
Mala nasobilka pro cislo 5
5 * 1 = 5
5 * 2 = 10
5 * 3 = 15
5 * 4 = 20
5 * 5 = 25
5 * 6 = 30
5 * 7 = 35
5 * 8 = 40
5 * 9 = 45
5 * 10 = 50
Výsledkem programu je malá násobilka čísla 5. Pokud chcete malou násobilku např. čísla 4, stačí změnit
konstantu a.
Řetězce
Kromě konstantních čísel lze deklarovat též konstantní znaky a řetězce:
const char pismeno = 'a';
9
Textový řetězec je tedy pole znaků (ať už se jedná o konstantní pole nebo ne). Navíc má tu zvláštnost (na
rozdíl od ostatních polí), že má na konci tzv. zarážku. Je to znak, který obsahuje nulu. Tak program pozná,
kde pole v paměti končí. Kde pole začíná je uloženo v ukazateli. Takto se jednoznačně určí, kde se textový
řetězec nachází a co do něj patří. Zatímco znaky jsou v jednoduchých uvozovkách, textové řetězce ve
dvojitých. Nulu na konci textového řetězce za vás vloží překladač sám.
const char *veta = "Toto je konstantni retezec.";
Ukazatel veta je ukazatel na typ char a ukazuje na začátek textového řetězce, tedy na písmeno T. Pokud
bude program pracovat s řetězcem na který ukazatel veta ukazuje, podívá se do paměti kam ukazuje,
přečte první písmeno, pak se podívá na další bajt (velikost typu char je 8 bitů) a přečte další písmeno atd.
až narazí na bajt obsahující nulu a tím skončí.
V jazyce C neexistuje žádný datový typ „řetězec“. Na řetězec (obecně jakékoliv pole objektů) se můžete
odkazovat pouze pomocí ukazatelů.
1: /*------------------------------------------------*/
2: /* konst2.c
*/
4: #include <stdio.h>
6: int main(void)
7: {
8:
const char Kpismeno = 'a';
/* konstanta */
9:
char pismeno = 'b';
10:
const char *veta = "Konstantni veta !\n";
11:
const char *veta2 = "a";
12:
char *ukazatel;
/* ukazatel na typ char */
14:
ukazatel = &pismeno;
/* ukazatel ukazuje na promennou pismeno */
16:
printf("1: Tisknu pismena: %c %c\n", Kpismeno, pismeno);
18:
printf("2: Tisknu vetu: %s", veta);
19:
printf("3: Tisknu vetu: %s\n", veta2);
21:
printf("4: Tisknu pismeno: %c\n", *ukazatel);
23:
printf("5: Tisknu pismeno: %c\n", *veta);
24:
printf("6: Tisknu pismeno: %c\n", *veta2);
26:
return 0;
27:
printf("7: Tisknu vetu: %s\n", ukazatel);
28:
printf("8: Tisknu vetu: %s\n", pismeno);
29:
/* printf("9: Tisknu: %s\n",*pismeno); uplna blbost */
30: }
32: /*------------------------------------------------*/
Výstup z programu:
1: Tisknu pismena: a b
2: Tisknu vetu: Konstantni veta !
3: Tisknu vetu: a
4: Tisknu pismeno: b
5: Tisknu pismeno: K
6: Tisknu pismeno: a
V prvním volání funkce printf() se tisknou znaky. Když funkce printf() narazí na sekvenci %c najde si další
argument a ten pak vytiskne jako znak. Ve druhém volání se tiskne věta, na kterou ukazuje konstantní
ukazatel veta a ve třetím volání se tiskne věta, na kterou ukazuje ukazatel veta2. Funkce printf() si ve
chvíli, kdy narazí na sekvenci %s najde příslušný další argument, který bere jako adresu do paměti. Z této
paměti začne po znacích číst a znaky tisknout, dokud nenarazí na nulový bajt. Věta "a" obsahuje kromě
písmena 'a' také onu zarážku (nulový bajt). Ve čtvrtém volání se tiskne hodnota, na kterou ukazuje
proměnná ukazatel. Jelikož proměnné veta a veta2 jsou také ukazatele, lze je použít stejným způsobem.
Ukazatele veta a veta2 ukazují na začátky vět, tedy přesněji obsahují adresu paměti, na které je uloženo
první písmeno věty. Proto se vytisknou v (pátém a šestém volání printf()) první znaky z věty.
10
Za příkazem return 0 se nacházejí ještě další tři volání funkce printf(). Tyto volání jsou chybné. Sedmé
volání printf() se pokouší vytisknout větu, na kterou ukazuje ukazatel jménem ukazatel. Jenomže ukazatel
neukazuje na větu, ale na znak v proměnné pismeno. Překladač nemůže kontrolovat kam
proměnná ukazatel ukazuje (zda na řetězec nebo znak nebo jestli vůbec někam ..) a tak nepozná chybu.
Pokud by došlo k takovémuto volání funkce printf(), vytiskl by se nejdříve znak v proměnné pismeno (tj.
znak 'b') a pak by se tiskli další bajty v paměti počítače, dokud by se nenarazilo na nulu. Takže by se
vytiskla hromada nesmyslů nebo by mohl program zkolabovat (neoprávněný přístup do paměti).
Kdybychom inicializovali proměnou ukazatel např. takto: ukazatel = veta; bylo by sedmé volání printf()
naprosto v pořádku. Ovšem osmé volání je už blbost. Na místo ukazatele je předán typ char. Na tuto chybu
již překladač upozorní, ale přesto dokáže program přeložit. V osmém volání funkce printf() vezme znak
v proměnné pismeno, použije jej jako adresu do paměti ze které se pokusí přečíst větu. V tomto případě je
téměř jisté, že program zkolabuje kvůli neoprávněnému přístupu do paměti (nebo se vypíše hromada
nesmyslů).
ASCII hodnota písmena 'b' je 98. Poslední volání funkce printf() by se tedy snažilo vytisknout větu začínající na
98. bajtu v paměti, kdyby toto volání překladač umožnil.
Deváté volání je opravdu nesmysl, protože proměnná pismeno není ukazatel, nelze použít operátor *
(hvězdička). To by překladač odmítl přeložit.
Escape sekvence
Escape sekvence jsou sekvence, které nám umožňují vložit do řetězce některé zvláštní znaky.
Escape sekvence
Escape
znak
význam
popis
\0
Null
Nula, ukončení řetězce (má být na konci každého řetězce)
\a
Alert (Bell)
pípnutí
\b
Backspace
návrat o jeden znak zpět
\f
Formfeed
nová stránka nebo obrazovka
\n
Newline
přesun na začátek nového řádku
\r
Carriage return přesun na začátek aktuálního řádku
\t
Horizontal tab
přesun na následující tabelační pozici
\v
Vertical tab
stanovený přesun dolů
\\
Backslash
obrácené lomítko
\'
Single quote
apostrof
\"
Double quote
uvozovky
\?
Question mark otazník
\000
\xHHH
ASCII znak zadaný jako osmičková hodnota
ASCII znak zadaný jako šestnáctková hodnota
Pro vytištění znaku % pomocí funkce printf() je třeba zapsat znak % dvakrát za sebou. Nejedná se o
escape znak, ale o vlastnost funkce printf() interpretovat % ve svém prvním argumentu zvláštním
způsobem.
1: /*------------------------------------------------*/
2: /* esc.c
*/
3: #include <stdio.h>
11
5: int main(void)
6: {
7:
printf("%s %%\n", "%");
8:
printf("%s\r%s\n", "AAAAAAA", "BB");
9:
printf("\"\a\"\n");
10:
printf("\x6a\n");
11:
return 0;
12: }
14: /*------------------------------------------------*/
Výstup z programu:
% %
Při třetím volání printf()
BBAAAAA
by se mělo ozvat krátké pípnutí.
""
j
Standardní vstup a výstup
Každý program při svém spuštění dostane od operačního systému standardní vstup (obvykle klávesnice),
standardní výstup (obvykle monitor) a standardní chybový výstup (obvykle monitor). Standardní vstup se
označuje jako stdin, standardní výstup jako stdout a chybový výstup je stderr. Funkce, které s těmito
vstupy a výstupy pracují jsou definované v hlavičkovém souboru <stdio.h>. V OS máme možnost
standardní vstupy a výstupy přesměrovat (například výstup do souboru místo na obrazovku).
Vstupní a výstupní zařízení lze rozdělit na znaková a bloková. Znaková jsou například klávesnice a
monitor, bloková např. pevné disky. Rozdíl je především v možnosti získávání či vkládání dat z/do zařízení.
Ke znakovým zařízením se přistupuje sekvenčně tedy znak po znaku a k blokovým tzv. náhodným
přístupem (random access).
Funkce printf()
Funkce printf() se používá pro formátovaný standardní výstup (do stdout). Deklarace vypadá takto:
int printf (const char *format [, arg, ...]);
Funkce printf() má návratovou hodnotu typu int. Tato hodnota je rovna počtu znaků zapsaných do výstupu,
nebo speciální hodnota EOF v případě chyby zápisu. Hodnota EOF se používá jako označení konce
souboru (End Of File), taktéž konce standardního vstupu a výstupu. EOF je definován v souboru <stdio.h>.
Prvním argumentem je konstantní řetězec, který funkce printf() tiskne do stdout. Ten může obsahovat
speciální sekvence, na jejichž místo dosazuje funkce další argumenty (začínají znakem %). Argumenty by
měli mýt očekávaný datový typ, který je dán speciální sekvencí. Formát této sekvence je následující:
%[flags][width][.prec][h|l|L]type
Vše, co je uvedeno v hranatých závorkách [ ] v deklaraci může, ale nemusí být. Znak | značí „nebo“.
Například [h|l|L] znamená, že v deklaraci sekvence může být h nebo l nebo L nebo ani jedna z těchto možností.
Přehled typů speciální sekvence řetězce ve funkci printf()
type
d, i
význam
Celé číslo se znaménkem (Zde není mezi d a i rozdíl. Rozdíl viz scanf() níže).
u
Celé číslo bez znaménka.
o
Číslo v osmičkové soustavě.
x, X
Číslo v šestnáctkové soustavě. Písmena ABCDEF se budou tisknout jako malá při použití malého x,
nebo velká při použití velkého X.
p
Ukazatel (pointer)
f
Racionální číslo (float, double) bez exponentu.
12
e, E
Racionální číslo s exponentem, implicitně jedna pozice před desetinnou tečkou a šest za ní.
Exponent uvozuje malé nebo velké E.
g, G
Racionální číslo s exponentem nebo bez něj (podle absolutní hodnoty čísla). Neobsahuje desetinnou
tečku, pokud nemá desetinnou část.
c
Jeden znak.
s
Řetězec.
Příklad:
1: /*------------------------------------------------*/
2: /* print.c
*/
4: #include <stdio.h>
6: int main(void)
7: {
8:
const char *COPYRIGHT = "(C)";
9:
const int ROK = 2003;
11:
printf("%i %u %o %x %X %f %e %G\n", -5, -5, 200, 200, 200, 10.0,
12:
10.0, 10.0);
13:
printf("%s %i\n", COPYRIGHT, ROK);
14:
return 0;
15: }
16: /*------------------------------------------------*/
Výstup:
-5 4294967291 310 c8 C8 10.000000 1.000000e+01 10
(C) 2003
Z čísla -5 se stalo při použití %u číslo 4294967291. To je dáno interpretací bitového zápisu, kdy se první bit
zleva nepočítal jako identifikátor znaménka, ale jako součást čísla.
Položky width a .prec určují délku výstupu. Délka width určuje minimální počet znaků na výstupu. Mohou
být uvozeny mezerami nebo nulami. .prec určuje maximální počet znaků na výstupu pro řetězce. Pro celá
čísla je to minimum zobrazených znaků, pro racionální počet míst za desetinnou tečkou (má to tedy více
významů v závislosti na typu).
Položka width může nabývat následujících hodnot:
Hodnoty width
n
Vytiskne se nejméně n znaků doplněných mezerami
0n Vytiskne se nejméně n znaků doplněných nulami
*
Vytiskne se nejméně n znaků, kde n je další argument funkce printf()
Položka .prec může nabývat následujících hodnot:
Hodnoty .prec
.0
pro e, E, f nezobrazí desetinnou tečku
pro d, i, o, u, x nastaví standardní hodnoty
pro d, i, o, u, x minimální počet číslic
.n
pro e, E, f počet desetinných číslic
pro g, G počet platných míst
pro s maximální počet znaků
.* jako přesnost bude použit následující parametr funkce printf()
13
1: /*------------------------------------------------*/
2: /* print2.c
*/
4: #include <stdio.h>
6: int main(void)
7: {
8:
printf("%06i %06u %06x %6x %06f %06E %06G\n\n", -5, -5, 200, 200,
9:
10.0, 10.0, 10.0);
11:
printf("%*s %1s %6s %%06.2G\n", 10, "%*i", "%06.2f", "%06.0E");
13:
printf("%*i %06.2f %06.0E %06.2G\n", 10, -5, 10.0 / 3, 10.0 / 3,
14:
10.0 / 3);
15:
printf("\n%.8s %0*.*f\n", "Posledni vystup:", 10, 4, 10.0 / 3);
16:
return 0;
17: }
19: /*------------------------------------------------*/
Výstup:
-00005 4294967291 0000c8
c8 10.000000 1.000000E+01 000010
%*i %06.2f %06.0E %06.2G
-5 003.33 03E+00 0003.3
Posledni 00003.3333
Do délky čísla se započítává i pozice pro znaménko. Čísla, která jsou delší než požadovaná minimální
délka se nezkracují, řetězce ano.
Příznak flags může nabývat hodnot z následující tabulky:
Hodnoty flags
-
výsledek je zarovnán zleva
+
u čísla bude vždy zobrazeno znaménko
mezera u kladných čísel bude místo znaménka "+" mezera
pro o, x, X výstup jako konstanty jazyka C
#
pro e, E, f, g, G vždy zobrazí desetinnou tečku
pro g, G ponechá nevýznamné nuly
pro c, d, i, s, u nemá význam.
Znaky h l a L označují typ čísla. Znak h typ short (nemá smysl pro 16bitové překladače), l dlouhé celé
číslo,L long double.
1:
2:
4:
6:
7:
8:
9:
10:
/*------------------------------------------------*/
/* print3.c
*/
#include <stdio.h>
int main(void)
{
long int x;
long double y = 25.0L;
x = -25l; /* je mozno psat jen x = -25, protože prekladac vi, ze x
je long a tak si cislo -25 prevede na -25l;
13:
takhle je to vsak "cistci" a citelnejsi */
15:
printf("%10s <%+5i> <% 5ld> <%x>\n", "Cisla:", 25, x, -25);
16:
printf("%-10s <%-+5i> <% 5Lf> <%#x>\n", "Cisla:", 25, y, -25);
17:
return 0;
18: }
20: /*------------------------------------------------*/
Výstup z programu:
Cisla: < +25> < -25> <ffffffe7>
Cisla:
<+25 > < 25.000000> <0xffffffe7>
14
Funkce scanf()
Funkce scanf se používá pro formátovaný standardní vstup (z stdin), což bývá obvykle vstup z klávesnice.
Deklarace funkce scanf() vypadá takto:
int scanf (const char *format [, address, ...]);
Návratová hodnota je rovna počtu bezchybně načtených a do paměti uložených položek, nebo hodnota
EOF (End Of File) při pokusu číst položky z uzavřeného vstupu. První argument je stejně jako u funkce
printf() řetězec, který obsahuje speciální sekvence určující, co se má načíst. Formát takové sekvence je
následující:
%[*][width][h|l|L]type
Význam položek sekvence pro funkci scanf()
*
přeskočí popsaný vstup (načte, ale nikam nezapisuje)
width maximální počet vstupních znaků
h|l|L modifikace typu (jako u printf())
type typ konverze.
Sekvence vždy začíná znakem procento a končí typem konverze. Možné typy konverzí:
význam type
d
celé číslo
u
celé číslo bez znaménka
o
osmičkové celé číslo
x
šestnáctkové celé číslo
i
celé číslo, zápis odpovídá zápisu konstanty v jazyce C, např. 0x uvozuje číslo v
šestnáctkové soustavě
n
počet dosud přečtených znaků aktuálním voláním funkce scanf()
e, f, g
racionální čísla typu float, lze je modifikovat pomocí l a L
s
řetězec; úvodní oddělovače jsou přeskočeny, na konci je přidán znak '\0'
c
vstup znaku; je li určena šířka, je čten řetězec bez přeskočení oddělovačů
[search_set]
jako s, ale se specifikací vstupní množiny znaků, je možný i interval, například %[0-9], i
negace, například %[^a-c].
Oddělovače jsou tzv. bílé znaky (tabulátor, mezera, konec řádku (ENTER)). Ty se při čtení ze vstupu
přeskakují (výjimkou může být typ c). Načítání tedy probíhá tak, že se nejdříve přeskočí oddělovače a poté
se načte požadovaný typ. Pokud je požadovaný typ například číslo, ale mi místo toho na vstupu máme
písmeno, pak dojde k chybě. Pokud načteme požadovaný typ, uloží se na adresu, která je uložena v
dalším argumentu funkce scanf().
scanf("%i",&x);
Takto se pokusí funkce scanf() načíst číslo a uložit jej do proměnné x, jejíž adresa je dalším argumentem
funkce scanf(). Adresa proměnné se získává pomocí operátoru & a může být uložena v ukazateli. Pokud
se to podaří, vrátí funkce číslo 1 (načtena jedna správná položka). Pokud se to nepodaří (např. místo čísla
zadáme nějaké znaky, nebo vstup ukončíme), vrátí se EOF.
15
1: /*------------------------------------------------*/
2: /* scan1.c
*/
4: #include <stdio.h>
6: int main(void)
7: {
8:
int x = -1;
10:
printf("Zadej cislo jako konstantu jazyka C\n"
11:
"napr. 10 0x0a nebo 012: ");
12:
scanf("%i", &x);
13:
printf("Zadal jsi cislo %i\n", x);
14:
return 0;
15: }
17: /*------------------------------------------------*/
Možné výstupy:
Zadej cislo jako konstantu jazyka C
napr. 10 nebo 0x0a nebo 012: -50
Zadal jsi cislo -50
Zadej cislo jako konstantu jazyka C
napr. 10 nebo 0x0a nebo 012: 0xff
Zadal jsi cislo 255
Zadej cislo jako konstantu jazyka C
napr. 10 nebo 0x0a nebo 012: ff
Zadal jsi cislo -1
Při posledním spuštění programu bylo zadáno „ff“, což není číslo ale textový řetězec, proto
funkce scanf() do proměnné x nic neuložila a tak v ní zůstalo číslo -1.
Vstupní proud (stdin) můžete přerušit ve Windows a v DOSu klávesovou zkratkou CTRL+Z. V Linuxu pomocí
klávesové zkratky CTRL+D.Řetězec ze standardního vstupu je ukončen oddělovačem (mezera, ENTER ).
1: /*------------------------------------------------*/
2: /* scan2.c
*/
4: #include <stdio.h>
6: int main(void)
7: {
8:
int x = 0;
9:
double f = 0.0;
10:
char znak = 'a';
12:
printf("Zadej jeden znak a cislo max. 2 znaky dlouhe: ");
13:
scanf("%c %2d", &znak, &x);
14:
printf("\t--- zadal jsi %c a %i\n", znak, x);
16:
printf("Zadej retezec a pak racionalni cislo: ");
17:
scanf("%*s %lf", &f);
18:
printf("\t--- zadal jsi %f\n", f);
20:
return 0;
21: }
23: /*------------------------------------------------*/
Možné výstupy: Zadej jeden znak a cislo max. 2 znaky dlouhe: B 55
--- zadal jsi B a 55
Zadej retezec a pak racionalni cislo: ahoj 15.3
--- zadal jsi 15.300000
Zadej jeden znak a cislo max. 2 znaky dlouhe: B 55 ahoj 15.3 nazdar
--- zadal jsi B a 55
Zadej retezec a pak racionalni cislo:
--- zadal jsi 15.300000
V prvním případě bylo zadáno B 55 a ENTER. Tím byl předán nějaký vstup a funkce scanf() si prvním
volání načetla nejdříve požadovaný znak a poté číslo. Při druhém volání byl vložen řetězec „ahoj“ a za ním
racionální číslo. Funkce scanf() však řetězec přeskočila (%*s) a načetla číslo za ním.
16
Při druhém spuštění programu byl zadán na vstup rovnou znak, číslo, řetězec, další číslo a další řetězec.
Program při prvním volání funkce scanf() načetl B a 55, spustil printf() a další volání scanf() pokračovalo v
čtení vstupu tam, kde předchozí scanf() skončilo. Přeskočilo řetězec „ahoj“ a načetlo číslo 15.3.
Lze také každý vstup odesílat ENTERem jednotlivě.
Má-li být číslo dlouhé jen 2 znaky, pak každý další znak už druhé volání funkce scanf() vyhodnotí jako
řetězec. Dále číslo může být chápáno též jako řetězec nebo znak. Jde jen o to, jak bude do paměti
ukládáno. Číslo 5, bude uloženo jako číslo 5, znak 5 bude uložen jako číslo z ASCII tabulky odpovídající
znaku 5 (což je číslo 53). Pokud se v prvním argumentu funkce scanf() vyskytne nějaký řetězec, znamená
to, že funkce scanf() má právě tento řetězec načíst (a nikam neuložit). Pokud však takový řetězec na
vstupu není, dojde k chybě.
1:
2:
4:
6:
7:
8:
9:
11:
12:
13:
15:
16:
18:
19:
20:
21:
23:
/*------------------------------------------------*/
/* scan3.c
*/
#include <stdio.h>
int main(void)
{
int zamek = 5;
int navrat;
printf("Hodnota zamku je nyni %+5i\n", zamek);
printf("Pokud chcete hodnotu zmenit, zadejte heslo a novou hodnotu\n"
"a stisknete ENTER. Napriklad: heslo 25\n>> ");
/* heslo je 1234567890 */
navrat = scanf("1234567890 %i", &zamek);
printf("Bylo nacteno %i spravnych polozek\n", navrat);
printf("Hodnota zamku je nyni %+5i\n", zamek);
return 0;
}
/*------------------------------------------------*/
Výstup z programu: Hodnota zamku je nyni
+5
Pokud chcete hodnotu zmenit, zadejte heslo a novou hodnotu
a stisknete ENTER. Napriklad: heslo 25
>> 1234567890 -2345
Bylo nacteno 1 spravnych polozek
Hodnota zamku je nyni -2345
Návratová hodnota funkce scanf() byla vložena do proměnné navrat.
Práce s typy dat
ASCII tabulka
(American Standard Code for Information Interchange)
Znak je v jazyku C typu int a v jazyku C++ typu char. Velikost char je 8 bitů, což je rozsah čísel od 0 do
255. Jakému číslu je přiřazen jaký znak, to určuje právě ASCII tabulka. Čistě textový soubor (tedy soubor
obsahující jen znaky), znamená co jeden znak, to jeden bajt, bez ohledu na to, že v jazyce C je znak typu
int – to je pouze „vnitřní interpretace znaku“ s kterou pracuje program. Výjimkou může být znak konec
řádku, který se v DOSu a Windows reprezentuje dvojicí znaků s kódem (číselnou hodnotou) 10 a 13 (v
Linuxu se zapisuje jen jeden znak – 10). Některým číslům není přidělen viditelný znak. Například kód 1
odpovídá stisku kláves CTRL+a a nemá žádný zobrazitelný ekvivalent. Interpretace číselného kódu se
může měnit, v závislosti na použitém kódování (kódy 128-255). Pod těmito čísly jsou uloženy například
znaky s háčky a čárky, bohužel se kódování od kódování liší (např. pro češtinu se použají kódování
windows-1250 (na Windows), iso-8852-2 (ve starších verzích Linuxu) atp.).
17
Jiným typem kódování než je ASCII, je například UTF-8. To využívá k interpretaci znaku proměnlivý počet
bytů. To je pro jazyk C trochu problém, protože pro jeden znak vyhrazuje vždy stejný počet bitů. Ovšem
prvních 127 znaků v UTF-8 zabírá jeden bajt a jsou stejné jako v ASCII kódování.
Přetypování
Přetypování slouží ke změně datového typu objektu (proměnné, konstanty,...). Přetypování datového typu
se provádí tak, že se před výraz napíše do závorky typ, na který chceme přetypovat. Nelze samozřejmě
přetypovat cokoliv na cokoli, například řetězec na číslo. Přetypování je jeden z mocných nástrojů jazyka C.
1: /*------------------------------------------------*/
2: /* pretyp.c
*/
4: #include <stdio.h>
6: int main(void)
7: {
8:
int a, b;
9:
float c, d;
10:
char ch = 'x';
12:
printf("Zadej delenec: ");
13:
scanf("%d", &a);
14:
printf("Zadej delitel: ");
15:
scanf("%d", &b);
16:
c = a / b;
17:
d = (float) a / b;
18:
printf("Celociselne deleni: %+5.2f\n", c);
19:
printf("Racionalni deleni: %+5.2f\n", d);
21:
printf("ASCII kod znaku %c je %i\n", ch, (int) ch);
22:
return 0;
23: }
25: /*------------------------------------------------*/
Výstup z programu:
Zadej delenec: 50
Zadej delitel: 3
Celociselne deleni: +16.00
Racionalni deleni: +16.67
ASCII kod znaku x je 120
K přetypování výrazů dochází mnohdy automaticky, například po přiřazení do proměnné typu float
celočíselnou konstantu. Ta se nejprve (automaticky) převede na typ float a pak se teprve uloží do
proměnné. Nebo při ukládání čísla 10 do proměnné long. Konstantu 10 překladač vyhodnotí jako int. Proto
se nejdříve musí číslo 10 přetypovat na typ long. Překladač vás v takovém případě může varovat, že zde
dochází k přetypování a to není hezké. Řešením je použít buďto modifikátor L ( long x = 10l; nebo float x =
10.0; místo x = 10;), nebo přetypování (long x= (long) 10;). První způsob se používá právě u konstant,
druhý způsob zvláště u proměnných, protože u nich první způsob použít nelze. ( x= (long) a;).
Chyby přetypování
Přetypovávat můžete i návratové hodnoty funkcí. Například funkce printf() vrací typ int. Návratovou
hodnotu lze získat takto:
navrat = printf("Hello World");
Pokud návratovou hodnotu funkce nikam neukládáme, můžeme to překladači zdělit přetypováním
návratové hodnoty na typ void takto:
(void) printf("Hello World");
18
Překladač by totiž mohl u některých funkcí (u printf() to standardně nedělá) hlásit varování, že funkce vrací
návratovou hodnotu a že se nikam neukládá. Tímto přetypováním překladači dáme jasně najevo, že
návratovou hodnnotu nechceme. Při přetypování však může docházet k problémům, které se těžko
odhalují. Přetypování totiž neprobíhá jako zaokrouhlování, desetinná část se usekne. Malý příklad. Máme
nějakou virtuální obrazovku, která obsahuje nejméně 80 sloupců, ale může jich mít i více. Počet sloupců
máme uložen v proměnné COLS. Obrazovku chceme rozdělit na dvě části tak, aby jedna část obsahovala
12 sloupců plus cca 25% z toho, co je nad 80 sloupců a druhá část zbytek. Využijeme k tomu následující
výpočet:
(12 + ((COLS - 80) * 0.25));
Tedy: okno1 = (12 + ((COLS-80) * 0.25));
okno2 = COLS - (12 + (COLS-80) * 0.25));
Na první pohled se může zdát, že okno1 + okno2 = COLS. Podívejme se, jak může takový výpočet
interpretovat překladač pro COLS = 84 a COLS = 85:
Pro COLS = 84:
okno1
okno1
okno2
okno2
=
=
=
=
(12 + ((84-80) *0.25)) = (12 + (4 *0.25)) = (12 + 1.0) = 13.0
13;
(84 - (12 + (84-80) * 0.25)) = 84 - 13.0 = 72.0
72;
Ale pro COLS = 85:
okno1
okno1
okno2
okno2
=
=
=
=
(12 + ((85-80) *0.25)) = (12 + (5 *0.25)) = (12 + 1.25) = 13.25
13;
(85 - (12 + (85-80) * 0.25)) = 85 - 13.25 = 71.75
71; /* a chyba je na světě */
Řešení je jednoduché. Stačí výraz správně a včas přetypovat. Výpočet s přetypováním pro COLS = 85;
(12 + (int) ((COLS - 80) * 0.25));
okno1 = (12 + (int) ((85-80) *0.25)) = (12 + (int) (5 *0.25)) =
= (12 + (int) 1.25) = 12 + 1 = 13;
okno2 = (85 - (12 + (int) (85-80) * 0.25)) = 85 - (12 + (int) 1.25) =
= 85 - (12 + 1) = 85 - 13 = 72;
Pokud překladač narazí při výpočtu na dva rozdílné typy, interpretuje oba jako ten větší (přesnější) z
nich. Proto se výpočet 5 * 0.25 automaticky interpretuje jako ((float) 5) * 0.25 a ne 5 * (int) 0.25.
Výsledek, který se ukládá do proměnné se do této proměnné musí vejít, proto se přetypovává vždy na
typ proměnné. Při pokusu uložit do proměnné hodnotu větší, než kterou je schopna pojmout
(například char ch = 256;), bude výsledkem nedefinovaná hodnota!
Pole a ukazatele
Datové pole a řetězce
Řetězce jsou vlastně pole typu char zakončená nulovým znakem '\0'. Datové pole je spojité místo
vyhrazené pro několik položek stejného datového typu. K tomu, abychom mohli k těmto položkám
přistupovat, slouží ukazatel na onen datový typ. Pole může mít různou velikost. Kde začíná je dáno právě
ukazatelem, jeho konec si však programátor musí hlídat. Konec pole nijak označen není (s výjimkou
řetězců), proto se často stává ta chyba, že se program pokouší číst mimo pole a to vede ke zhroucení
programu. Pole se deklaruje následovným způsobem:
19
datovy_typ nazev [rozsah];
Například:
char znaky[4]; /* pole pro 4 znaky */
int cisla[1]; /* pole pro jedno cislo */
Proměnné znaky a cisla jsou konstantní ukazatele, které ukazují na první prvek z pole. Jednotlivé položky
pole se adresují od nuly pomocí hranatých závorek. (V jazyku C se indexuje od nuly, nikoliv od jedničky,
jako např. v Pascalu):
cisla[0] = 5;
x = cisla[0];
Číslo v hranatých závorkách při definici pole určuje jeho velikost. V jazyce C to musí být skutečné číslo,
nelze použít proměnnou, ani konstantu (číslo musí překladač znát už v době překladu, aby pro pole
dokázal vyhradit dostatek paměti).
V jazyce C++ lze použít i konstantu (její hodnota je překladači známa, protože se nemění).
Číslo v hranatých závorkách za ukazatelem při použití v kódu se nazývá index pole. Indexem pole se
odkazujeme na n-tou položku pole.
Příklad používání indexu pole.
char znaky[4];
ukázka uložení prázdného čtyřznakového pole v paměti
skutečná adresa ...
1535
1536
1537
1538
...
pozice
...
0
1
2
3
...
bity
??? ???????? ???????? ???????? ???????? ???
Momentálně pole znaky obsahuje náhodné bity. Ukazatel znaky ukazuje na adresu 1535 (to číslo je
samozřejmě smyšlené). Pokud chceme vložit do pole znaky a vytvořit tak řetězec, nesmíme zapomenout
na zarážku (nulový bite).
znaky[0] = 'j';
znaky[1] = 'e';
znaky[2] = '\0';
ukázka uložení dvouznakového řetězce v paměti
skutečná adresa ...
1535
1536
1537
1538
...
pozice
...
0
1
2
3
...
bity
??? 01101010 01100101 00000000 ???????? ???
Pokud pole rovnou inicializujeme, nemusíme uvádět jeho délku. Délka pole bude taková, kolik prvků
předáme při inicializaci.
int cisla[] = { 5, -1, 3, 8};
char pole1[] = {'a', 'h', 'o', 'j', '\0'};
char pole2[] = "ahoj";
/* pole pro 4 cisla */
/* pole pro 5 znaku */
/* pole pro 5 znaku */
Inicializace pole1 a pole2 je ekvivalentní. Nyní můžeme přiřadit: pole2[4] = 'x';. Tím se však připravíme o
zarážku a již nemůžeme s polem pracovat jako s řetězcem (například jej předat funkci printf(), protože by
se po vytištění ahojx pokusila funkce printf() tisknout další byty z paměti za koncem pole).
20
Vícerozměrná pole
Jedná se o pole, jehož prvky jsou rovněž pole. Lze tak vytvářet pole libovolných dimenzí. (Je je třeba dát
pozor na to, kolik takové pole zabírá místa v paměti.) Definice dvourozměrného pole a jeho uložení v
paměti.
char pole[4][2] = {{0,0},{1,1},{3,4},{0xff,0xff}};
Jedná se o čtyřprvkové pole, jehož prvky jsou dvouprvkové pole.
Uložení pole 4x2 v paměti
skutečná adresa ...
1535 1536 1537 1538 1539 1540 1541 1542 ...
pozice
...
[0][0] [0][1] [1][0] [1][1] [2][0] [2][1] [3][0] [3][1] ...
hodnota
??? 0
0
1
1
3
4
255
255
???
Při putování pamětí se mění vždy nejdříve poslední index pole a pak ty předešlé.
Ukazatele a pole
char *retezec1 = "Ahoj";
char retezec2[] = "Ahoj";
Rozdíl je v tom, že retezec2 je konstantní ukazatel (pole), kdežto retezec1 je ukazatel inicializovaný
hodnotou adresy řetězce "Ahoj". Při obou definicích se vytvoří řetězec "Ahoj", což je pole znaků (se
zarážkou '\0' na konci). V příkladu je uveden způsob jak lze adresovat jednorozměrné pole.
1:
2:
4:
6:
7:
9:
10:
12:
14:
15:
16:
17:
19:
20:
21:
22:
24:
25:
27:
/*------------------------------------------------*/
/* pole1.c
*/
#include <stdio.h>
int main(void)
{
float pole[] = { 5.0, 6.0, 7.0, 8.0, 9.0 };
float *ukazatel;
ukazatel = pole;
/* prvni cast */
printf("%.1f ", pole[1]);
printf("%.1f ", pole[1] + 10.0);
printf("%.1f, ", (pole + 1)[2]);
/* druha cast */
printf("%.1f ", *ukazatel);
printf("%.1f ", *ukazatel + 10);
printf("%.1f\n", *(ukazatel + 1));
return 0;
}
/*------------------------------------------------*/
Výstup z programu: 6.0 16.0 8.0, 5.0 15.0 6.0
Jazyk C zároveň pracuje chytře s aritmetikou ukazatelů. Výraz (ukazatel + 1) ukazuje na další prvek pole,
přestože typ float je dlouhý hned 4 bajty a ne jeden. To je jeden z důvodů, proč se při deklaraci ukazatele
určuje jeho typ. Překladač pak zvýší hodnotu ukazatele o správný počet bytů automaticky. Následující
výrazy jsou ekvivalentní:
&pole[n] je totéž co (pole + n) (tj adresa n-tého prvku v paměti)
pole[n] je totéž co *(pole + n) (tj hodnota n-tého prvku v paměti)
21
Ukazatele na ukazatele
V jazyku C může vytvořit ukazatel na libovolný datový typ. Ukazatel tedy může ukazovat i na ukazatel.
char **ukazatel;
Je taktéž možné vytvořit pole ukazatelů (včetně vícerozměrných polí ukazatelů...).
char *ukazatel[10];
1: /*------------------------------------------------*/
2: /* unau1.c
*/
3: #include <stdio.h>
5: int main(void)
6: {
7:
int x;
8:
int *ukazatel_na_int;
9:
int **ukazatel_na_ukazatel;
11:
int pole[] = { 5, 7, 9, 11, 13 };
13:
/* inicializace promennych */
14:
x = 25;
15:
ukazatel_na_int = &x;
16:
ukazatel_na_ukazatel = &ukazatel_na_int;
18:
/* pristup k promenne x */
19:
printf("%2d = %2d = %2d\n", x, *ukazatel_na_int,
20:
*(*ukazatel_na_ukazatel));
22:
/* inicializace */
23:
ukazatel_na_int = pole;
24:
/* ukazatel_na_ukazatel = &ukazatel_na_int; ... toto prirazeni je o
25:
* nekolik radek vyse a hodnota ukazatel_na_ukazatel ukazuje stale na
26:
* ukazatel_na_int */
28:
/* pristup k poli */
29:
printf("%2d = %2d = %2d\n", pole[0], *ukazatel_na_int,
30:
**ukazatel_na_ukazatel);
31:
printf("%2d = %2d = %2d\n", pole[1], *(ukazatel_na_int + 1),
32:
*((*ukazatel_na_ukazatel) + 1));
33:
return 0;
34: }
36: /*------------------------------------------------*/
Výstup z programu: 25 = 25 = 25
5 = 5 = 5
7 = 7 = 7
Jaké bude vyhodnocení výrazu *(*ukazatel_na_ukazatel). Závorky v tomto výrazu nejsou podstatné a jsou
zde jen pro ilustraci. První, co se provede, je vyhodnocení výrazu *ukazatel_na_ukazatel. Hvězdička je
dereferencí adresy, tedy hodnota proměnné, na kterou ukazuje proměnná ukazatel_na_ukazatel. V tomto
případě to znamená, že *ukazatel_na_ukazatel je hodnota proměnné ukazatel_na_int (to je v příkladu
adresa proměnné x). Dejme tomu, že adresa x je 123456. Potom *(*ukazatel_na_ukazatel) je *(123456) a
to je hodnota, na kterou 123456 ukazuje, tedy hodnota proměnné x. Dále výraz *((*ukazatel_na_ukazatel)
+ 1). Dejme tomu, že pole začíná na adrese 12340. Potom ukazatel_na_int je po druhé inicializaci 12340,
(*ukazatel_na_ukazatel) je tedy také 12340. A teď pozor. Výraz (*ukazatel_na_ukazatel + 1) je
(12340+1*n) a výraz *((*ukazatel_na_ukazatel) + 1) je *(12340+1*n). Číslo n je počet bajtů, které zabírá
typ int, protože jedničku přičítáme k ukazateli na int. Tomu se říká aritmetika ukazatelů.
Ovšem pozor. Ukazatele a pole nelze libovolně zaměňovat. Tak například **pole1 a pole5[][5] a pole6[][6]
jsou tři různé typy. S použitím vědomostí o aritmetice ukazatelů vyplývá, jaké chyby by se mohli stát při
libovolných záměnách deklarací těchto proměnných. (Hlavně aplikaci pole1 na některé z dvouromzěrných
polí. Správně by se mělo použít např.: int pole5[N][5]; int (*upole5)[5]; upole5 = pole5; atp.)
22
Operátory a výrazy
Operátory lze rozdělit několika způsoby. Na unární, binární, ternární (s jedním, dvěma nebo třemi
operandy). Operandy jsou data (většinou čísla), se kterými operátory (např. plus) pracují. Dále se rozdělují
na aritmetické, bitové, relační a logické.
Unární operátory
Operátor
Význam
+, -
unární plus a mínus, (určuje znaménko čísla)
&
reference (získání adresy objektu)
*
dereference (získání objektu dle adresy)
!
logická negace
~
bitová negace
++, --
inkrementace a dekrementace hodnoty
(typ)
přetypování
sizeof
operátor pro získání délky objektu nebo typu
Inkrementace ++ a dekrementace -- může být zapsáno jak před výrazem, tak za ním Zvyšují, resp. snižují
hodnotu čísla o jedničku. Pokud leží ++ před proměnnou, nejdříve se zvýší její hodnota a pak se proměnná
použije ve výrazu (pokud v nějakém je), leží-li ++ za proměnnou, nejdříve se proměnná použije ve výrazu a
pak se teprve zvýší její hodnota. Obdobně je to u dekrementace --.
Například:
y = 5;
x = ++y; // y se inkrementuje na 6 a poté se dosadí 6 do x
x = y++; // do x se dosadí y (6) a pak se y inkrementuje (na 7)
Operátor sizeof vrací velikost datového typu nebo objektu v bytech.
float y;
int x = sizeof(float);
x = sizeof(y);
Priorita operátorů
Priorita operátorů stanovuje, která část výrazu se vyhodnotí dříve, pokud to není závorkami určeno jinak.
Klasickým příkladem je vyhodnocení výrazu x = 1+1*0;. Výsledkem bude samozřejmě číslo 1, protože
operátor násobení má větší prioritu než operátor sčítání. Vzhledem k tomu že priorita operátorů je v jazyku
C velice komplikovaná záležitost, je nejlépe používat závorky!
Binární operátory
Operátor
=
+, -,*, /
%
<<, >>
&
|
Význam
přiřazení
plus,mínus,krát,děleno
zbytek po celočíselném dělení (modulo)
bitový posun vlevo, vpravo
bitové AND
bitové OR
23
^
bitové XOR
&&
logické AND
||
logické OR
.
tečka, přímý přístup ke členu struktury
->
nepřímý přístup ke členu struktury
,
čárka, oddělení výrazů
<
menší než
>
větší než
<=
menší nebo rovno
>=
větší nebo rovno
==
rovnost
!=
nerovnost
Při bitovém posunu vlevo (vpravo) se poslední levý (pravý) bit ztrácí a zprava (zleva) je dosazena nula.
Bitové operace je možné provádět jen s celočíselnými hodnotami. Při posunu čísel se znaménkem se však
znaménko zachovává. Vlevo od operátoru je objekt, ve kterém se bity posouvají, vpravo od
operátoru je číslo určující kolikrát se bity posunou.
U logického AND a OR, výrazů >= atp. je výsledkem TRUE nebo FALSE, tedy 1 nebo 0.
Operátor přiřazení lze kombinovat s některými výpočty: +=, -=, *-, /=, <<=, >>=, &=, |=, ^=
Výraz
Ekvivalent
x -= 5;
x = x - 5;
x *= x;
x = x * x;
x >>= 2; x = x >> 2;
Podmíněný operátor
Podmíněný operátor ? : je ternárním operátorem (má 3 operandy). Prvním operandem je výraz, který se
vyhodnotí jako logický výraz (TRUE nebo FALSE). Pokud se vyhodnotí jako TRUE, výsledkem bude druhý
operand (mezi ? a :), jinak třetí operand.
printf("%s\n", (x > y) ? "x je > y" : "x je < y");
Příklad
1:
2:
4:
5:
7:
8:
9:
10:
11:
12:
14:
15:
16:
17:
18:
20:
/*------------------------------------------------*/
/* operator.c
*/
#include <stdio.h>
#include <limits.h>
/* ziskame konstanty UINT_MAX a INT_MIN */
int main(void)
{
int x, y, z;
int pole[] = { 5, -10, 15, -20, 25, -30 };
unsigned int delka_pole;
x = y = z = 10;
x = y++;
z++;
printf("%3d %3d %3d\n", x, y, z);
printf("%3d %3d %3d\n", ++x, y++, ~z);
printf("%3d %3d %3d\n", x, y, z);
delka_pole = sizeof(pole) / sizeof(pole[0]);
24
21:
22:
24:
25:
printf("Mate %2u bitovy prekladac.\n", sizeof(int) * 8);
printf("Pole pole[] zabira %2u bytu.\n\n", sizeof(pole));
printf("UINT_MAX = %u = %u\n", UINT_MAX, ~0);
/* UINT_MAX = max
velikost cisla v typu unsigned int */
printf("INT_MIN = %i = %i \n", INT_MIN, 1 << ((sizeof(int) * 8) - 1));
/* posunu jednicku az do "nejlevejsiho bitu" */
printf("-51 >> 1 = %i\n\n", -51 >> 1);
printf("Zadejte index pole (od 0 do %2u): ", delka_pole - 1);
scanf("%i", &x);
printf("Zadal jsi %i\n", x);
/* musi se zkontrolovat platnost zadaneho indexu pole !! */
x = (x < 0) ? 0 : x;
x = (x >= delka_pole) ? delka_pole - 1 : x;
printf("pole[%u] = %i\n", x, pole[x]);
return 0;
26:
27:
29:
30:
32:
33:
34:
35:
37:
38: }
40: /*------------------------------------------------*/
Výstup z programu:
10 11 11
11 11 -12
11 12 11
Mate 32 bitovy prekladac.
Pole pole[] zabira 24 bytu.
UINT_MAX = 4294967295 = 4294967295
INT_MIN = -2147483648 = -2147483648
-51 >> 1 = -26
Zadejte index pole (od 0 do 5): -11
Zadal jsi -11
pole[0] = 5
Protože je překladač 32 bitový, typ int zabírá 32 bitů, tj 4 bajty. Proto pole zabírá celkem 24 bajtů.
Použití NULL
Využítí hodnoty NULL, společně s podmíněným operátorem:
1:
2:
4:
6:
7:
8:
9:
11:
12:
13:
15:
17:
18:
19:
20:
21:
23:
/*------------------------------------------------*/
/* null1.c
*/
#include <stdio.h>
int main(void)
{
float *uk = NULL;
float p[] = { 10.2, 5.1, 0.6 };
printf("%s\n", uk == NULL ? "Ukazatel neni inicializovan" :
"Ukazatel lze pouzit");
printf("%f\n", uk == NULL ? 0.0 : uk[0]);
uk = p;
printf("%s\n", uk == NULL ? "Ukazatel neni inicializovan" :
"Ukazatel lze pouzit");
printf("%f\n", uk == NULL ? 0.0 : uk[0]);
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
Ukazatel neni inicializovan
0.000000
Ukazatel lze pouzit
10.200000
25
Preprocesor
Před vlastním překladem zdrojového kódu je kód upraven tzv. preprocesorem. Například v Linuxu lze
spustit preprocesor příkazem cpp (C Pre Processor). Preprocesor odstraní ze zdrojového kódu komentáře
a rozvine makra, které jsou ve zdrojovém kódu. Všechny direktivy preprocesoru začínají
znakem # . Například při zapsání kódu #include <stdio.h>, vloží preprocesor na místo tohoto řádku obsah
standardní knihovny <stdio.h>. Direktiva preprocesoru musí být první na novém řádku, před ní mohou být
jen „bílé znaky“ (mezera, tabulátor..). Direktiva končí s koncem řádku.
Direktivy preprocesoru
#define #elif
#ifdef
#else
#endif #error
#ifndef #include #line
#if
#pragma #undef
Standardní makra
Podle normy ANSI C existují následující makra, které musí každý preprocesor jazyka C znát. Standardní
makra preprocesoru začínají a končí dvěma podtržítky.
Standardní makra
Makro
Význam
datový typ
__DATE__ Datum spuštění preprocesoru
string
__TIME__ Čas spuštění preprocesoru
string
__FILE__ Jméno aktuálního vstupního souboru
string
__LINE__ Pořadové číslo aktuálního řádku
int
__STDC__ Určuje, zda překladač splňuje ANSI C int (1 nebo 0)
__cplusplus Určuje, zda byl použit překladač C++ int (1 nebo 0)
Existují i implementačně závislá makra, tedy makra, která jsou definována jen na některých OS a v
některých preprocesorech jazyka C. Například makro __linux__ se může hodit ke zjištění, zda program
překládáme v linuxu.
Nestandardní makra
Makro
Význam
datový typ
__linux__1)
Určuje, zda je zdrojový kód překládán v Linuxu int (1 nebo 0)
__unix__1)
Obdobně jako linux
int (1 nebo 0)
Verse DOSu
string
Určuje, zda jde o procesor z řady i386
int (1 nebo 0)
__MSDOS__
__i386__
__VERSION__ Verze překladače GNU C
string
Definice maker
Kromě standardních maker lze vytvářet makra vlastní. Makro se definuje za direktivou #define a lze jej
zrušit direktivou #undef.
#define NAZEV makro
... zdrojový kód kde makro používáme ...
#undef NAZEV
26
... zde už makro není definované ...
Příklad na použití standardních i uživatelských maker. Takto vypadá zdrojový kód před zpracováním
preprocesorem.
1:
2:
4:
6:
7:
9:
10:
11:
12:
14:
15:
16:
17:
19:
20:
22:
24:
25:
26:
28:
29:
31:
/*------------------------------------------------*/
/* makra.c
*/
#include <stdio.h>
#define N 5
#define VERSE "1.0.0"
int main(void)
{
int pole[N];
unsigned int x;
printf("ANSI C:\t%s\t%i\n", __STDC__ ? "ANO" : "NE ", __STDC__);
printf("i386:\t%s\t%s\n", __i386__ ? "ANO" : "NE ", __VERSION__);
printf("Tento program byl prelozen %s v %s.\n"
"Prave jsme na radku %i.\n", __DATE__, __TIME__, __LINE__);
printf("Zadejte index pole <0,%u>: ", N - 1);
scanf("%u", &x);
x = x >= N ? N - 1 : x;
printf("Zadejte hodnotu pole[%u]: ", x);
scanf("%i", &pole[x]);
printf("verse programu %s\n", VERSE);
return 0;
}
/*------------------------------------------------*/
A takto po zpracování preprocesorem před vlastním překladem. Direktiva #include <stdio.h> je nahrazena
zdrojovým kódem souboru <stdio.h>. (Ten je z výpisu vypštěn - je moc dlouhý).
1:
3:
4:
5:
6:
8:
9:
10:
11:
12:
14:
15:
17:
19:
20:
21:
23:
24:
26:
/*------------------------------------------------*/
int main(void)
{
int pole[5];
unsigned int x;
printf("ANSI C:\t%s\t%i\n", 1 ? "ANO" : "NE ", 1);
printf("i386:\t%s\t%s\n", 1 ? "ANO" : "NE ",
"4.2.3 (Ubuntu 4.2.3-2ubuntu7)");
printf("Tento program byl prelozen %s v %s.\n"
"Prave jsme na radku %i.\n", "Sep 18 2008", "10:35:06", 17);
printf("Zadejte index pole <0,%u>: ", 5 - 1);
scanf("%u", &x);
x = x >= 5 ? 5 - 1 : x;
printf("Zadejte hodnotu pole[%u]: ", x);
scanf("%i", &pole[x]);
printf("verse programu %s\n", "1.0.0");
return 0;
}
/*------------------------------------------------*/
Teprve tento kód je předán překladači k vytvoření programu.
Výstup z programu:
ANSI C: ANO
1
i386:
ANO
2.95.4 20011002 (Debian prerelease)
Tento program byl prelozen Aug 10 2003 v 21:43:55.
Prave jsme na radku 15.
Zadejte index pole <0,4>: 3
Zadejte hodnotu pole[3]: 50
verse programu 1.0.0
27
Makra s argumenty
Makra mohou mít také argumenty. Jsou uzavřeny v kulatých závorkách za jménem makra a pokud je více
jak jeden argument, jsou odděleny čárkou. Mezi jménem makra a závorkou obsahující argumenty nesmí
být mezera.
#define NAZEV(argument1, argument2, ...) makro
#define FOO(x) - 1 / (x)
#define BAR (x) - 1 / (x)
Jejich použití ...
a = FOO(10);
b = BAR;
... vyhodnocení preprocesorem:
a = - 1 / (10);
b = (x) - 1 / (x);
Použití maker s argumenty má své záludnosti se kterými je třeba počítat. Vždy je třeba dát pozor na to,
jakým způsobem je makro ve zdrojovém kódu rozvinuto:
#define mocnina(X) x*x
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
Jejich použití:
a = mocnina(3+2);
b = mocnina(++n);
c = min (x+y,foo(z));
Vyhodnocení preprocesorem:
a = 3+2*3+2;
// tento problém lze vyřešit závorkami v makru
b = ++n*++n;
// proměnná n se inkrementuje dvakrát
c = ((x+y) < (foo(z)) ? (x+y) : foo(z)); // funkce foo je volána dvakrát
Kvůli výše uvedeným problémům se nedoporučuje makry nahrazovat funkce. V jazyce C++ se od
používání maker ustupuje, kde to jen jde.
Operátory # a ##
Operátory # a ## se používají v makrech s parametry. Za operátor # se dosadí řetězec, který je stejný jako
argument makra. Operátor ## spojí své dva argumenty v jeden řetězec.
Definice maker:
#define VYPOCET(X,Y) printf("%s = %i\n",#X " + " #Y,(X)+(Y));
#define SPOJENI(X,Y) printf("%i %i\n",X ## Y, cislo ## X);
Použití maker:
int cislo5 = 10;
VYPOCET(5,6)
SPOJENI(5,6)
28
Vyhodnocení preprocesorem:
printf("%s = %i\n", "5" " + " "6", (5)+(6));
printf("%i %i\n", 56, cislo5);
Podmíněný překlad
Direktiva #if vyhodnocuje podmínku, která za ní následuje (tedy preprocesor jí vyhodnocuje). Pokud je tato
podmínka vyhodnocena jako FALSE, pak vše mezi #if a #endif je ze zdrojového kódu vypuštěno. V
podmínce musí být výrazy, které může vyhodnotit preprocesor (tj. nelze tam používat hodnoty proměnných
atp.). Pomocí klíčového slova defined lze vyhodnocovat, zda existuje nějaké makro (zda bylo definováno).
Výraz #if defined lze zkrátit výrazem #ifdef. Za podmínkou #if může být další (a další) podmínka #elif. Ta se
vyhodnotí v případě, že předchozí podmínka nebyla splněna. Pokud nejsou splněny žádné podmínky,
může být jako poslední direktiva #else, která již žádný výraz nevyhodnocuje a provede se právě v případě,
že všechny podmínky za #if a #elif byly vyhodnoceny jako FALSE. Podmíněný překlad se ukončuje
direktivou #endif. Popsaná syntaxe vypadá následovně:
#if PODMINKA1
... zdrojovy kod pro splnenou podminku1
#elif PODMINKA2
... zdrojovy kod pro splnenou podminku2 (kdyz podminka1 nebyla splnena)
#elif PODMINKA3
.... zdrojovy kod pro splnenou podminku3 ...
...
...
#else
... zdrojovy kod, pokud zadna z predchozich podminek nebyla splnena ...
#endif
Podmínky lze negovat pomocí operátoru ! (vykřičník).
1:
2:
4:
5:
7:
8:
9:
10:
11:
13:
14:
15:
16:
17:
18:
19:
21:
22:
24:
/*------------------------------------------------*/
/* makra2.c
*/
#include <stdio.h>
#define PT 6
int main(void)
{
#if PT > 5
printf("Makro PT je vetsi jak 5\n");
#endif
#if defined unix && defined linux
printf("Hura, ja bezim na unixovem stroji a snad pod linuxem!\n");
#elif defined unix
printf("Hura, ja bezim pod unixem!\n");
#else
printf("Hmm, kde jsem se to kruci ocitl??\n");
#endif
return 0;
}
/*------------------------------------------------*/
Užití knihovny
Knihovnami (hlavičkovými soubory) v jazyce C se nazývají soubory obsahující zdrojový kód. Tyto soubory
se do jiného zdrojového kódu začleňují pomocí direktivy #include. Standardní knihovny hledá překladač na
standardních místech (jsou uzavřeny ve špičatých závorkách <>). Knihovny, jejichž jména jsou uzavřeny
ve dvojitých uvozovkách, se hledají v aktuálním adresáři. Pokud není nalezen, hledá se ve standardních
adresářích.
29
Vytvoření knihovny. Vytvoříme knihovnu s názvem "makra.h" s následujícím kódem:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
/*------------------------------------------------*/
/* makra.h
*/
#define POZDRAV "Nazdarek"
#define COPYRIGHT "(c) 2003"
#define LICENCE
#define TISK(a) printf("%s\n",a);
#define SOUBOR __FILE__
#define BEGIN {
#define END }
/*------------------------------------------------*/
Následné vytvoření souboru (ve stejném adresáři), do kterého je knihovnu "makra.h" začleněna.
1:
2:
3:
4:
6:
7:
8:
9:
10:
11:
12:
13:
/*------------------------------------------------*/
/* makra3.c
*/
#include <stdio.h>
#include "makra.h"
int main(void)
BEGIN TISK(POZDRAV) TISK(SOUBOR)
#ifdef LICENCE
TISK(COPYRIGHT)
#endif
return 0;
END
/*------------------------------------------------*/
Výstup z programu:
Nazdarek
makra3.c
(c) 2003
Makro Licence je definováno bez toho, že by mu byla přiřazena nějakou hodnotu. Pro podmínku #ifdef
stačí, že je definováno. Složené závorky lze zaměnit za BEGIN a END.
Ostatní direktivy
#pragma
Direktiva #pragma uvozuje implementačně závislé direktivy. Pokud překladač narazí na tuto direktivu a
nerozumí jí, pak jí ignoruje (nezpůsobí to žádnou chybu).
Ukázka (z C++ Builderu): #pragma hdrstop
#line
Direktiva #line nastavuje hodnotu makra __LINE__ (a __FILE__).
#line N ["jméno"]
N je číslo, kterým se nastaví __LINE__ pro následující řádek a "jméno" (nepovinné) je jméno, kterým
se nastaví makro __FILE__.
30
Podmínky a cykly
Podmínka if-else
Podmínka if-else je velice podobná podmíněnému operátoru ? a ještě více podmíněnému překladu
preprocesoru.
if (podminka)
telo bloku
else if (podminka)
telo bloku
else if (podminka)
telo bloku
else
telo bloku
Podmínky musí být v kulatých závorkách. Tělo bloku může být blok , nebo jen jeden příkaz (ukončený
středníkem). Podmínek else if může být konečně mnoho a nejsou povinné, stejně jako není povinná
podmínka else. Vyhodnocování podmínek probíhá do té doby, než se některá vyhodnotí jako pravdivá
(TRUE). Pak se provedou příkazy v bezprostředně následujícím bloku. Další podmínky se již
nevyhodnocují. Pokud se žádná podmínka nevyhodnotí jako TRUE, pak se provede tělo bloku
za else (pokud část else existuje). Else se umisťuje vždy na konec.
1:
2:
3:
5:
6:
7:
9:
10:
11:
12:
13:
15:
17:
18:
19:
20:
21:
22:
23:
25:
26:
27:
28:
29:
30:
31:
32:
34:
35:
37:
/*------------------------------------------------*/
/* ifelse.c
*/
#include <stdio.h>
#define CISLO 5
#define MIN 0
#define MAX 10
int main(void)
{
int x = -1;
int navrat;
printf("Na jake cislo myslim? Hadej mezi %2i a %2i: ", MIN, MAX);
navrat = scanf("%i", &x);
if (navrat == EOF) {
printf("\ascanf nemuze cist. Je uzavren standardni vstup stdin\n");
return 1;
/* ukoncime funkci main -> konec programu */
} else if (navrat != 1) {
/* chceme nacist 1 polozku */
printf("\aNezadal jsi cislo!\n");
return 1;
}
if (x < MIN)
printf("Tak to bylo malo trochu hochu!\n");
else if (x > MAX)
printf("Tak to bylo trochu moc!\n");
else if (x == CISLO)
printf("UHADNULS :-))\n");
else
printf("Smula. Zkus to znovu.\n");
return 0;
}
/*------------------------------------------------*/
Možný výstup:
Na jake cislo myslim? Hadej mezi
UHADNULS :-))
31
0 a 10: 5
Přepínač switch
Přepínač switch obsahuje několik návěstí (case) s celočíselnou hodnotou, nebo rozmezí (např. 1 ... 5
nebo 'a' ... 'e'). Mezi třemi tečkami a hodnotami se vynechávají mezery. Na základě argumentu za klíčovým
slovem switch přeskočí program na návěstí se stejnou hodnotou jakou má výraz v kulatých závorkách
za switch a pokračuje vykonáváním příkazů za ním. Přepínač switch může obsahovat návěstí default, na
které program skočí tehdy, když argument za klíčovým slovem switch neodpovídá hodnotě za žádným
návěstím case. Default není povinné a může se použít kdekoliv (první před všemi case, mezi nimi i jako
poslední).
switch (vyraz)
{
case hodnota:
prikazy
case hodnota:
prikazy
default:
prikazy
}
Příkaz break je speciální příkaz, který způsobí „vyskočení“ z těla příkazu switch (a také cyklů, viz dále).
Program pak pokračuje ve vykonávání příkazů za blokem switch.
1:
2:
3:
5:
6:
7:
9:
10:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
32:
/*------------------------------------------------*/
/* switch.c
*/
#include <stdio.h>
int main(void)
{
char znak;
printf("Opravdu chcete smazat vsechna data na disku? [a/n/k]> ");
znak = (char) getchar();
switch (znak) {
default:
printf("Mel si zmacknout \'a\',\'n\' nebo \'k\' a ne \'%c\'\n",
znak);
case 'k':
return 0;
case 'N':
printf("Stejne smazu co muzu!\n");
break;
case 'n':
printf("Nechcete? Smula!\n");
case 'a':
case 'A':
printf("Data byla smazana !!!\n");
break;
}
printf("Ne, nebojte, to byl jenom zertik.\n");
return 0;
}
/*------------------------------------------------*/
Možný výstup:
Opravdu chcete smazat vsechny data na disku? [a/n/k]> n
Nechcete? Smula!
Data byla smazana !!!
Ne, nebojte, to byl jenom zertik.
Druhé použití break (na konci switch) je zbytečné, je tam jen pro ilustraci. Funkce getchar() z knihovny
<stdio.h> vrací hodnotu datového typu int. Proto ji bylo nutno přetypovat na typ char. Tato funkce načte ze
standardního vstupu znak a vrátí jej jako svou návratovou hodnotu, přičemž vrací speciální hodnotu EOF v
případě uzavřeného standardního vstupu.
32
Skok goto
Použití skoku goto může velice znepřehlednit program. Proto se nepoužívá! Ostatně, ani to není nutné.
Pomocí ostatních rozhodovacích příkazů se tomu lze vždy vyhnout.
Za příkazem goto je název návěští, na které má program skočit. Návěští se pak v programu určuje
jednoduše tak, že se zapíše jeho jméno a za ním dvojtečka. Je možné skákat pouze na návěstí v rámci
jedné funkce.
Cyklus for
for (inicializace; podmínka; výraz)
telo cyklu
Cyklus for provádí příkazy v těle cyklu tak dlouho, dokud platí podmínka. Tato podmínka může být
jakýkoliv výraz vracející celočíselnou hodnotu. Může obsahovat konstanty, proměnné, relační operátory
(==,>,|| atp.) a není povinná. V případě chybějící podmínky za ní překladač dosadí číslo jedna, což
bude mít za následek věčné provádění cyklu. Cyklus pak lze ukončit jen pomocí break. Pokud se hned
na poprvé podmínka vyhodnotí jako FALSE, pak tělo cyklu neproběhne ani jednou. Část inicializace také
není povinná. Může v ní být libovolný výraz. Tato část proběhne jen jednou (před začátkem cyklu).
Poslední část v příkazu označená jako výraz se provádí po každém ukončení cyklu (před vyhodnocením
podmínky) a taktéž není povinný.
1:
2:
3:
5:
6:
7:
9:
10:
11:
12:
13:
14:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
27:
/*------------------------------------------------*/
/* for.c
*/
#include <stdio.h>
int main(void)
{
int x;
for (x = 1; x < 10; x++) {
if ((x % 2) != 0)
continue;
printf("Sude cislo: %i\n", x);
}
printf("\nA nebo trochu jinak:\n\n");
x = 1;
for (;;) {
if (x >= 10)
break;
if ((x % 2) == 0)
printf("Sude cislo: %i\n", x);
x++;
}
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
Sude cislo: 2
A nebo trochu jinak:
Sude cislo: 4
Sude cislo: 2
Sude cislo: 6
Sude cislo: 4
Sude cislo: 8
Sude cislo: 6
Sude cislo: 8
Cyklus for se také často využívá při práci s poli. #define N 100
int x, pole [N];
for (x = 0; x <= N - 1;x++)
pole[x] = 0;
33
Cykly do a while
Cyklus do má tu vlastnost, že proběhne alespoň jednou. Je ukončen klíčovým slovem while, za kterým je
podmínka v kulatých závorkách a platí o ní totéž, jako u cyklu for. V cyklu lze pro řízení chodu programu
využít příkazů break i continue. Tělo cyklu může být jeden příkaz nebo blok.
do
telo cyklu
while ( podminka );
Cyklus while je podobný, jen s tím rozdílem, že se podmínka vyhodnocuje před provedením cyklu.
while ( podminka )
telo cyklu
1:
2:
3:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
20:
21:
22:
24:
25:
26:
28:
/*------------------------------------------------*/
/* while.c
*/
#include <stdio.h>
int main(void)
{
int iterace = -1;
int navrat;
do {
printf("Zadejte pocet iteraci <0,10>: ");
navrat = scanf("%i", &iterace);
if (!navrat)
return 1;
/* uzivatel nezadal cislo; navrat == 0 */
if (navrat == EOF) {
printf("stdin je uzavren.\n");
return 1;
}
} while ((iterace < 0) || (iterace > 10));
while (iterace) {
printf("%i ", iterace--);
}
printf("\n");
return 0;
}
/*------------------------------------------------*/
Možný výstup:
Zadejte pocet iteraci <0,10>: -5
Zadejte pocet iteraci <0,10>: 15
Zadejte pocet iteraci <0,10>: 5
5 4 3 2 1
Funkce
Funkce jsou základním stavebním kamenem jazyka C. Při jejich volání program přeskočí do funkce a po
jejím provedení se vrátí za místo, kde byla funkce volána. Opakované volání funkce šetří paměť počítače
(tělo funkce je napsáno jen jednou), na druhou stranu, každé volání funkce stojí nějaký (velmi malý) čas.
for (x = 0; x < 1000; x++)
{
printf("Cyklus cislo: ");
printf("%i\n",x);
}
je mnohem pomalejší než
for (x = 0; x < 1000; x++)
{
printf("Cyklus cislo: %i\n",x);
}
34
Definice funkce
Definice funkce je následující:
navratovy_typ jmeno ([parametry])
{
telo funkce
}
Jméno funkce slouží k jejímu identifikování. Při volání funkce v programu musíme uvést za funkcí kulaté
závorky, a to i tehdy, když funkce nemá žádné argumenty. Samotné jméno funkce bez závorek totiž
reprezentuje adresu v paměti, kde funkce začíná. Toho se dá využít v odkazech na funkci. Parametry
funkce jsou očekávaná data, která bude funkce zpracovávat. Každý parametr má své jméno a musí být
určen jeho datový typ. Pokud funkce nemá žádné parametry, uvádí se v závorkách slůvko void. Pokud je
jich více než jeden, oddělují se čárkou. Existují i funkce s proměnlivým počtem argumentů. Parametrem
může být celé nebo racionální číslo, struktura, nebo ukazatele. Nemůže to být například pole, které
obsahuje x prvků, ale může to být ukazatel na začátek pole. Funkce má jednu návratovou hodnotu. Její
typ se uvádí před jménem funkce. Například funkce main() má vždy návratovou hodnotu typu int. Pokud
není potřeba, aby funkce vracela nějaká data, jako návratová hodnota se uvede void. Funkci lze kdykoliv
ukončit příkazem return, za který se uvádí hodnota nebo výraz, jehož výsledek se stane návratovou
hodnotou. Pokud funkce žádnou návratovou hodnotu nemá, uvádí se return bez hodnoty nebo výrazu. Po
ukončení funkce se pokračuje v provádění kódu za místem kde byla funkce volána. Ukončením funkce
main() končí program. Návratová hodnota funkce main() se vrací operačnímu systému. Zaběhnutá praxe
je, že návratová hodnota 0 znamená úspěšné ukončení programu, jakákoliv jiná hodnota určuje číslo
chyby.
1:
2:
3:
5:
6:
7:
12:
14:
16:
17:
18:
19:
21:
22:
23:
24:
25:
26:
28:
/*------------------------------------------------*/
/* fce1.c
*/
#include <stdio.h>
void tecka(int pocet)
{
if (pocet <= 0) return
for (; pocet > 0; pocet--)
printf(". ");
}
int mocnina(int x)
{
return x * x;
}
int main(void)
{
tecka(10);
printf("-5^2 = %i\n", mocnina(-5));
return 0;
}
/*------------------------------------------------*/
Výstup programu: . . . . . . . . . . -5^2 = 25
Deklarace funkce
Při deklaraci funkce se uvádí pouze datový typ návratové hodnoty a typy argumentů. To je vše, co
potřebuje překladač k tomu, aby její volání mohl do programu zapsat. Mohou se uvést i názvy argumentů,
ale to není nutné. Než je funkce v programu volána, musí být deklarována. Definována může být až
později. Je-li funkce rovnou definována, je definice zároveň i deklarací. Pokud se deklarace s pozdější
definicí neshodují, oznámí překladač chybu.
V následujícím programu je funkce nejdříve deklarována, použita v jiné funkci a pak teprve definována.
35
1:
2:
3:
6:
8:
9:
10:
12:
13:
15:
16:
18:
19:
20:
21:
22:
23:
24:
25:
26:
28:
/*------------------------------------------------*/
/* deklar.c
*/
#include <stdio.h>
float vypocet(int, float, float *);
/* deklarace funkce */
int main(void)
{
float a, b;
a = vypocet(5, 0.3, &b);
printf("Soucet = %5.2f\nNasobek = %5.2f\n", a, b);
return 0;
}
float vypocet(int a, float b, float *c)
{
float f;
*c = (float) a *b;
f = (float) a + b;
b = 55.5;
/* lokalni promenna b nema s promennou b ve funkci
main nic spolecneho */
return f;
}
/*------------------------------------------------*/
Výstup z programu:
Soucet = 5.30
Nasobek = 1.50
x
Rekurzivní funkce
Pokud funkce volá ve svém těle samu sebe, nebo je volána jinou funkcí ve svém těle, hovoříme o rekurzi.
Data, která jsou funkci předávána, se ukládají do takzvaného zásobníku. To je nějaké vyhrazené místo v
paměti. Po skončení funkce se data ze zásobníku zase odstraní. Pokud však funkce během svého
vykonávání zavolá samu sebe, pak se do zásobníku umístí další data a tak stále dokola. To je samozřejmě
velice náročné na paměť a může vést až ke zhroucení programu. Použití rekurze je sice efektní, ale ne
vždy efektivní. Proto je třeba mít dobrý důvod pro její použítí
1:
2:
3:
5:
7:
8:
9:
10:
11:
13:
14:
15:
16:
17:
18:
19:
21:
22:
23:
24:
25:
27:
/*------------------------------------------------*/
/* rekurz.c
*/
#include <stdio.h>
float prvni_funkce(int, float);
/* deklarace */
float druha_funkce(int a, float f)
{
/* definice */
printf("Vola se druha funkce\n");
return prvni_funkce(a, f * (a + 1));
}
float prvni_funkce(int a, float f)
{
/* definice */
printf("Prvni funkce a= %2i, f=%5.2f\n", a, f);
if (a <= 0)
return f;
return druha_funkce(--a, f);
}
int main(void)
{
printf("Vysledek je %f\n", prvni_funkce(5, 5.0));
return 0;
}
/*------------------------------------------------*/
Výstup z programu: Prvni funkce a= 5, f= 5.00
Prvni funkce a= 2, f=300.00
Vola se druha funkce
Vola se druha funkce
Prvni funkce a= 4, f=25.00
Prvni funkce a= 1, f=600.00
Vola se druha funkce
Vola se druha funkce
Prvni funkce a= 3, f=100.00 Prvni funkce a= 0, f=600.00
Vola se druha funkce
Vysledek je 600.000000
36
V příkladu se počítá jakási číselná řada. Druhá funkce zde má slouží jen k nějakému výpisu a k novému
zavolání funkce prvni_funkce. Kdyby se volání return druha_funkce(--a, f); změnilo na return
druha_funkce(a--,f); volala by se druha_funkce() s hodnotou proměnné a, která by nebyla snížena o
jedničku. Ta by se měla snížit až po zavolání funkce. Výsledkem by byl nekonečný cyklus vzájemného
volání funkcí. Na podobné problémy je potřeba dávat pozor. Klasickým příkladem na rekurzi je výpočet
faktoriálu (avšak i faktoriál lze vypočítat elegantně bez rekurze).
1:
2:
3:
5:
6:
7:
8:
9:
10:
12:
13:
14:
15:
16:
17:
18:
20:
/*------------------------------------------------*/
/* faktor.c
*/
#include <stdio.h>
long double faktorial(long double x)
{
if (x == 0L)
return 1L;
return x * faktorial(x - 1L);
}
int main(void)
{
int i;
for (i = 0; i <= 40; i++)
printf("%2i! = %040.0Lf\n", i, faktorial(i));
return 0;
}
/*------------------------------------------------*/
Zkrácený výstup:
0!
1!
2!
3!
4!
=
=
=
=
=
0000001
0000001
0000002
0000006
0000024
5!
6!
7!
8!
=
=
=
=
0000120
0000720
0005040
0040320
Je to smutné, ale jazyk C neumí počítat přesně s racionálními čísly. Je to dáno tím, jak jsou čísla v
paměti uložena a jak se s nimi počítá. Po vydělení 10/3 a opětovného vynásobení výsledku třemi, vyjde
9.999...Čísla typu float mají zaručenou přesnost cca na 14 „nejvýznamnějších“ míst. Vzhledem k
velikosti čísla je chyba sice zanedbatelná, ovšem při použití řekněme v nějakém bankovním úřadu, by asi
neměli velkou radost. Naštěstí lze přesně počítat s celočíselnými proměnnými, takže lze podobně velká
čísla zpracovávat pomocí několika celočíselných proměnných (a složitých algoritmů).
Proměnlivý počet argumentů
Funkci lze vytvořit i bez přesného počtu a typu argumentů. (Jako např. fce printf() nebo scanf(), které také
mají proměnlivý počet argumentů.) Argumenty funkce se ukládají do zásobníku. Aby mohla funkce k těmto
argumentům přistupovat, musíme jí předat alespoň místo, kde začít. Proto musí mít funkce s proměnlivým
počtem argumentů minimálně jeden argument napevno daný. To, že mohou následovat další
argumenty, se při definici nebo deklaraci označí pomocí tří teček (tzv. výpustka). Pro práci s těmito
nedeklarovanými argumenty existuje standardní knihovna <stdarg.h>. V ní jsou definovány tři makra:
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
Výraz va_list představuje datový typ proměnné ap. Je to proměnná, která reprezentuje nedeklarované
argumenty. Makro va_start inicializuje proměnnou ap pomocí poslední napevno deklarované proměnné ve
funkci. Ta je předána jako druhý argument last (díky tomu získá potřebnou adresu do zásobníku s
argumenty). Makro va_arg vrací hodnotu dalšího argumentu v řadě. Jakého typu má být se musí určit
druhým argumentem type. Toto makro je možné volat vícekrát Maximálně však tolikrát, kolik je předáno
funkci při volání nedeklarovaných argumentů. Jinak by docházelo k čtení dat, která již nepatří k
argumentům funkce. Makro va_end zajišťuje bezproblémové ukončení funkce (příkazem return). Ačkoliv
program poběží pravděpodobně i bez volání tohoto makra, je potřeba jej použít. V určitých kritických
37
situacích by mohlo docházet k chybám které se obtížně hledají. Je několik možností jak zjistí funkce s
proměnlivým počtem argumentů, jaké jí byli argumenty předány (jakého typu) a také kolik. Například
funkce printf() má vždy jako první argument řetězec (přesněji řečeno ukazatel na řetězec; řetězec je
znakové pole a pole nelze předávat jako argument funkce). V tomto řetězci hledá speciální sekvence
(%s,%c) a podle nich zjišťuje, že má mít další argument a také jakého typu. A tak je přesně dáno, o kolik
argumentů a jakých typů si funkce řekne. Další možností je, že při deklaraci funkce je deklarován jeden
parametr (minimálně jeden stejně vždy musí být), který bude obsahovat počet proměnných. Další možností
je například definování nějaké zarážky. Třeba že vždy jako poslední argument bude ukazatel s hodnotou
NULL, nebo číslo nula atp.
1:
2:
3:
4:
6:
7:
8:
9:
10:
11:
13:
14:
16:
18:
19:
20:
21:
23:
24:
25:
26:
27:
28:
29:
30:
31:
33:
34:
35:
36:
37:
38:
40:
41:
42:
43:
44:
45:
46:
48:
/*------------------------------------------------*/
/* fce2.c
*/
#include <stdio.h>
#include <stdarg.h>
int maximum(const int pocet, const int znamenko, ...)
{
va_list ap;
int i;
int maxS, dalsiS;
unsigned int maxU, dalsiU;
if (pocet <= 0)
return 0;
va_start(ap, znamenko); /* znamenko je posledni deklarovana promenna */
if (znamenko)
maxS = va_arg(ap, int);
else
maxU = va_arg(ap, unsigned int);
for (i = 0; i < pocet - 1; i++) {
if (znamenko) {
dalsiS = va_arg(ap, int);
maxS = (maxS < dalsiS) ? dalsiS : maxS;
} else {
dalsiU = va_arg(ap, unsigned int);
maxU = (maxU < dalsiU) ? dalsiU : maxU;
}
}
va_end(ap);
if (znamenko)
return maxS;
else
return (signed int) maxU;
}
int main(void)
{
printf("Maximum = %i\n", maximum(3, 1, 7, 9, -2));
printf("Maximum = %x\n", maximum(7, 1, -5, 8, -2, 5, -6, -1, 4));
printf("Maximum = %x\n", maximum(7, 0, -5, 8, -2, 5, -6, -1, 4));
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
Maximum = 9
Maximum = 8
Maximum = ffffffff
Argumenty příkazové řádky
Při spouštění programu z příkazové řádky (ať už v Linuxové konzoli nebo v DOSu), mu lze předávat nějaké
argumenty. Argumenty příkazové řádky jsou všechna slova, zapsaná za název programu v příkazové
řádce. Například v příkazu MOVE S1 S2 jsou slova S1 a S2 argumenty. Slova, která jsou od sebe
oddělena mezerou nebo tabulátorem, jsou předávána zvlášť. Tvoří-li jeden argument celá věta, musí být
38
uzavřena do uvozovek. Argumenty příkazové řádky je možné číst pomocí funkce main, která je
deklarována následujícím způsobem:
int main(int argc, char *argv[]);
Parametr argc představuje počet argumentů příkazové řádky a parametr argv je pole ukazatelů na
argumenty příkazové řádky. Argumenty příkazové řádky jsou textové řetězce (i když je to jenom jeden znak
nebo číslo) a počítá se mezi ně i název programu, který je prvním argumentem.
Například: program Zvolejte 3 krat hura
Pak argc bude mít hodnotu 5, argv[0] ukazatel na řetězec "program", argv[1] ukazatel na řetězec
"Zvolejte", argv[2] ukazatel na řetězec "3" atd.
Příklad programu, který pracuje na základě argumentů příkazové řádky. Převede svůj druhý argument na
velká nebo malá písmena podle toho, jaký je první argument. Funkci, která porovnává dva textové řetězce,
vrací 1 (TRUE) v případě, že jsou schodné, jinak 0 (FALSE).
1:
2:
4:
6:
7:
9:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
26:
27:
29:
30:
31:
32:
33:
35:
36:
37:
38:
39:
40:
41:
43:
44:
45:
47:
49:
50:
51:
52:
53:
/*------------------------------------------------*/
/* argum.c
*/
#include <stdio.h>
#define VELKE "velke"
#define MALE "male"
#define VAROVANI printf("Zadejte:%s%s|%s ARGUMENT\n",argv[0], VELKE,MALE);
int shodne(char *v1, char *v2)
{
int i = 0;
/* pomoci zarazek '\0' zjistujeme konec retezce */
while ((v1[i] != '\0') && (v2[i] != '\0')) {
if (v1[i] != v2[i])
return 0;
/* nejsou shodne; */
i++;
}
if (v1[i] || v2[i]) { /* jeden retezec jeste nezkoncil, nejsou stejne */
return 0;
}
return 1;
/* vsechny znaky byly shodne */
}
void velke(char *);
void male(char *);
int main(int argc, char *argv[])
{
if (argc != 3) {
VAROVANI return 1;
}
if (shodne(VELKE, argv[1]))
velke(argv[2]);
else if (shodne(MALE, argv[1]))
male(argv[2]);
else {
VAROVANI return 1;
}
printf("Druhy argument byl zmenen na: %s\n", argv[2]);
return 0;
}
/* ve funkcich velke a male vyuzijeme vlastnosti ASCII tabulky */
void velke(char *v)
{
int i = 0;
while (v[i]) {
/* '\0' odpovida hodnote FALSE */
if (v[i] >= 'a' && v[i] <= 'z') {
39
54:
55:
56:
57:
58:
59:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
72:
v[i] += ('A' - 'a');
/* rozdil v ASCII tabulce mezi
velkymi a malymi pismeny */
}
i++;
}
/* konec while */
}
void male(char *v)
{
int i = 0;
while (v[i]) {
if (v[i] >= 'A' && v[i] <= 'Z') {
v[i] += ('a' - 'A');
}
i++;
}
}
/*------------------------------------------------*/
Lokální, globální a statické proměnné
Lokální proměnné jsou proměnné definované v těle nějakého bloku, globální mimo něj. Dále platí, že
lokální proměnné mají vyšší prioritu než globální.
1:
2:
3:
5:
7:
8:
9:
10:
11:
13:
14:
15:
17:
18:
19:
20:
22:
/*------------------------------------------------*/
/* promenne.c
*/
#include <stdio.h>
int a = 0, b = 0, c = 0;
/* globalni promenne */
void funkce(int a, int b)
{
int a = -5;
/* lokalni promenne */
printf("fce: a = %i, b = %i, c = %i\n", a, b, c);
}
int main(void)
{
int c = 25;
printf("main: a = %i, b = %i, c = %i\n", a, b, c);
funkce(100, 200);
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
main: a = 0, b = 0, c = 25
fce: a = -5, b = 200, c = 0
O trochu jiné jsou statické proměnné. Statické proměnné se deklarují pomocí klíčového slova static.
Používají se v těle funkcí a rozdíl oproti „obyčejným“ proměnným je v tom, že se jejich hodnota po
skončení průběhu funkce zachovává! Lze tak díky ní třeba zaznamenávat kolikrát byla funkce spuštěna.
Oproti tomu se „obyčejná“ proměnná inicializuje při volání funkce vždy znova. Další výhodou je, že při
skončení funkce máme zajištěno, že statická proměnná stále existuje a tudíž jí pomocí adresy můžeme
upravovat i mimo tělo funkce, zatímco paměť pro „obyčejnou“ proměnnou může překladač v zájmu
optimalizace programu využívat pro jiné účely (statickou proměnnou nemůže, a to je její nevýhoda, protože
se tím sníží možná optimalizace programu).
1:
2:
3:
5:
6:
7:
8:
9:
10:
/*------------------------------------------------*/
/* static.c
*/
#include <stdio.h>
char *funkce(void)
{
static char pole[] = "Ahoj!";
static int x = 1000;
int y = 1000;
x++;
40
11:
12:
13:
14:
16:
17:
18:
19:
21:
22:
23:
24:
25:
27:
29:
30:
32:
y++;
printf("Funkce je volana po %i (%i)\n", x, y);
return pole;
}
int main(void)
{
char *uk;
printf("%s\n", funkce());
uk = funkce();
/* pomoci ukazatele zmenim statickou promennou */
uk[0] = 'C';
uk[1] = 'u';
uk[2] = 's';
uk[3] = '\0';
printf("%s\n", funkce());
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
Funkce je volana po 1001 (1001)
Ahoj!
Funkce je volana po 1002 (1001)
Funkce je volana po 1003 (1001)
Cus
Uživatelské knihovny
Lze vytvářet knihovny (soubory mají nejčastěji příponou .h, pro jazyk C++ .hpp), v nichž jsou napsány
vlastní funkce, nebo definovaná makra. Při psaní velkého programu je to nevyhnutelné.
Soubor "dos1.h": používá k pozastavení programu funkci delay() z knihovny <dos.h>.
1:
2:
3:
5:
6:
7:
8:
10:
/*------------------------------------------------*/
/* dos1.h
*/
#include <dos.h>
void cekej(int cas)
{
delay(cas);
}
/*------------------------------------------------*/
Soubor "windows1.h": používá k pozastavení programu funkci Sleep() z knihovny <windows.h>.
1:
2:
3:
5:
6:
7:
8:
10:
/*------------------------------------------------*/
/* windows1.h
*/
#include <windows.h>
void cekej(int cas)
{
Sleep(cas);
}
/*------------------------------------------------*/
Soubor "unix1.h": používá k pozastavení programu funkci usleep() z knihovny <unistd.h>.
1:
2:
3:
5:
6:
7:
8:
10:
/*------------------------------------------------*/
/* unix1.h
*/
#include <unistd.h>
void cekej(unsigned long cas)
{
usleep(cas);
}
/*------------------------------------------------*/
41
V souboru cekej.c je třeba otestovat mnoho maker abychom zjistili, zda jsme na Windows. Různé
překladače totiž používají různá makra (nejsou nikde standardizována). Nejpravděpodobněji to však bude
makro __WINDOWS__ nebo __WIN32__ (a také __WIN16__ či __WIN64__ pro 16. a 64. bitové
překladače). Ostatní makra, bez dvou podtržítek na začátku a na konci, jsou již zastaralá.
1:
2:
3:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
19:
20:
21:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
36:
/*------------------------------------------------*/
/* cekej.c
*/
#include <stdio.h>
#ifdef unix
#include "unix1.h"
#define VERZE "UNIX"
#define CEKEJ 100000ul
#elif defined __MSDOS__
#define VERZE "MSDOS"
#include "dos1.h"
#define CEKEJ 100
#elif defined __WINDOWS__ ||defined _Windows ||defined _WINDOWS ||
defined __WIN32__ || defined _WIN32 || defined WIN32
#include "windows1.h"
#define VERZE "WINDOWS"
#define CEKEJ 100
#endif
int main(void)
{
int i, j;
for (i = 0; i <= 100; i++) {
printf("\rZdrzuji: [");
for (j = 0; j < 10; j++) {
printf("%c", (j * 10 <= i) ? '.' : ' ');
}
printf("] %3i%%", i);
fflush(stdout);
/* vyprazdnime standardni vystup */
cekej(CEKEJ);
/* implementacne zavisla funkce */
}
printf(" OK\n");
return 0;
}
/*------------------------------------------------*/
V DOSu se k pozastavení programu používá funkce delay(), která je definována v knihovně <dos.h> a
pozastaví program na zadaný počet milisekund, zatímco v Linuxu funkce usleep(), definována v knihovně
<unistd.h>, pozastaví program na zadaný počet mikrosekund. Funkce fflush() pozastaví provádění
programu do té doby, než se souborový proud, který je předáván jako argument, vyprázdní (tj. než se vše
vypíše na obrazovku). Escape sekvence '\r' přesune kurzor na začátek řádku.
Vytváření typů
Struktury
Struktura umožňuje spojit několik datových typů do jednoho. Definice struktury:
struct [jmeno] {
typ jmeno_polozky;
typ jmeno_polozky;
...
} [promenne];
42
Příklad struktury polozka. S definicí struktury jsou zároveň vytvořeny dvě proměnné (struktury)
typu polozka.
struct polozka {
unsigned int rok_narozeni;
int pohlavi;
char jmeno[20];
char prijmeni[20];
} Martin, Pavla;
Struktura vždy začíná klíčovým slovem struct. Poté může, ale také nemusí, být definováno jméno
struktury. Pokud není definováno jméno, není možné později vytvořit další proměnné této struktury, ani ji
použít jako parametr funkce. V těle struktury jsou deklarovány proměnné, které bude struktura obsahovat.
Mohou to být i jiné struktury, nebo ukazatel na sebe sama. (Struktura nemůže obsahovat sebe samu).
Struktury nelze sčítat, odčítat atd. U struktur lze použít pouze operátor přiřazení = . Tím se obsah jedné
struktury zkopíruje do jiné. Pro přístup ke členům struktury se používají operátory . (tečka) a -> (šipka).
Druhý operátor se používá v případě, že pracujeme s ukazatelem na strukturu.
polozka p, *ukazatel;
ukazatel = &p;
/* prirazeni 20 do casti
p.rok_narozeni = 20;
/* stejne prirazeni, jen
(*ukazatel).rok_narozeni
/* stejne prirazeni pres
ukazatel->rok_narozeni =
struktury polozka pojmenovane rok_narozeni */
pres ukazatel */
= 20;
ukazatel, jen jiny (hezci) zapis */
20;
Použité závorky jsou nutné, aby bylo jasné že operátor dereference * (hvězdička) pracuje s proměnnou
ukazatel. Přiřazení se šipkou je výhodnější.
Vytvořením struktury však není vytvořen nový datový typ. Struktura je obdoba datového pole, rozdíl je jen v
tom, že datové pole obsahuje několik objektů stejného typu, zatímco struktura obsahuje různé datové typy.
Jako není problém vytvořit pole obsahující jiné pole (vícerozměrná pole), není problém vytvořit pole
obsahující strukturu. Možnosti použití struktury a také použití ukazatele typu void a aritmetiky ukazatelů:
1:
2:
3:
5:
6:
7:
8:
9:
10:
11:
12:
13:
15:
16:
17:
18:
19:
20:
22:
23:
25:
26:
27:
29:
30:
31:
/*------------------------------------------------*/
/* strukt.c
*/
#include <stdio.h>
#define POCET 40
/* funkce zkopiruj kopiruje retezec v2 do retezce v1 */
void zkopiruj(char *v1, char *v2)
{
int i = 0;
do {
v1[i] = v2[i];
} while (v2[i++]);
}
struct Pisen {
char nazev[POCET];
char zpevak[POCET];
char skladatel[POCET];
float delka, hodnoceni;
} pisne[10], *ukp;
void vytiskni(void *str);
/* deklarujeme funkci,
ktera bude tisknout strukturu Pisen */
int main(void)
{
struct Pisen pisen;
zkopiruj(pisen.nazev, "Twist And Shout");
zkopiruj(pisen.zpevak, "The Beatles");
zkopiruj(pisen.skladatel, "Lennon/McCartney");
43
32:
33:
35:
37:
38:
39:
40:
41:
43:
45:
46:
47:
48:
49:
51:
52:
53:
55:
56:
58:
59:
60:
61:
63:
64:
65:
66:
68:
69:
71:
72:
74:
75:
76:
78:
pisen.delka = 2.36;
pisen.hodnoceni = 1.0;
pisne[0] = pisen;
zkopiruj(pisne[1].nazev, "Eleanor Rigby");
zkopiruj(pisne[1].zpevak, "The Beatles");
zkopiruj(pisne[1].skladatel, "Lennon/McCartney");
pisne[1].delka = 2.06;
pisne[1].hodnoceni = 1.5;
ukp = &pisne[2]; /*do ukazatele ukladame adresu 3 prvku v poli pisne*/
zkopiruj(ukp->nazev, "From Me To You");
zkopiruj(ukp->zpevak, "The Beatles");
zkopiruj(ukp->skladatel, "Lennon/McCartney");
ukp->delka = 1.58;
ukp->hodnoceni = 1.25;
vytiskni(&pisne[0]);
vytiskni(&pisne[1]);
vytiskni(&pisne[2]);
return 0;
}
void vytiskni(void *str)
{
/* str ukazuje na zacatek struktury */
printf("Nazev pisne:\t%s\n", (char *) str);
str += POCET * sizeof(char);
/* delka pole "nazev" ve strukture */
/* str ukazuje za pole "nazev" ve strukture, tj. na
* zacatek pole "zpevak" */
printf("Interperet:\t%s\n", (char *) str);
str += POCET * sizeof(char);
printf("Skladatel(e):\t%s\n", (char *) str);
str += POCET * sizeof(char);
printf("Delka skladby:\t%.2f\n", *((float *) str));
str += 1 * sizeof(float);
printf("Hodnoceni:\t%.2f\n\n", *((float *) str));
}
/*------------------------------------------------*/
Výstup z programu:
Nazev pisne:
Twist And Shout
Interperet:
The Beatles
Skladatel(e):
Lennon/McCartney
Delka skladby: 2.36
Hodnoceni:
1.00
Nazev pisne:
Interperet:
Skladatel(e):
Delka skladby:
Hodnoceni:
Eleanor Rigby
The Beatles
Lennon/McCartney
2.06
1.50
Nazev pisne:
Interperet:
Skladatel(e):
Delka skladby:
Hodnoceni:
From Me To You
The Beatles
Lennon/McCartney
1.58
1.25
Kdyby struktura Pisen neměla jméno Pisen (na řádku 15), nešlo by jí použít jako parametr funkce, vytvořit
její další instanci (na řádku 27) a dokonce ani vytvořit ukazatel na tuto strukturu dále v programu. Existovali
by pouze instance definované při definici struktury (na řádku 20). Zatímco textové řetězce ve funkci printf
se předávají právě pomocí ukazatelů, čísla typu float je nutno předat jako hodnotu (proto ta hvězdička a
závorky navíc). Aby ukazatel ukazoval do správného místa v paměti, je nutné jej postupně posouvat po
položkách struktury o příslušný počet bytů (vracených operátorem sizeof.
44
Před názvem struktury je vždy nutné používat klíčové slůvko struct. Jak při deklaraci parametrů, tak při
definici proměnných struktur.
void vytiskni(struct Pisen arg);
Předávání struktury jako argumentu má jednu velkou nevýhodu. Tato struktura se totiž celá zkopíruje z
proměnné do zásobníku v paměti počítače (do „argumentu funkce“). Při práci s velikými strukturami, pak
taková operace velice zpomaluje chod programu. Mnohem efektivnější je používat ukazatele na strukturu.
Definování typu pomocí typedef
Pomocí typedef se vytvářejí uživatelské datové typy:
typedef definice_typu identifikator;
Definicí typu může být například struktura nebo nějaký základní typ jazyka C, identifikator je pojmenování
nového datového typu:
1:
2:
3:
5:
6:
7:
8:
9:
11:
12:
13:
14:
16:
17:
18:
19:
21:
/*------------------------------------------------*/
/* typedef2.c
*/
#include <stdio.h>
typedef struct pokus {
int x;
int y;
char text[20];
} Pokus;
int main(void)
{
struct pokus a = { 6, 6, "sest" };
Pokus b = { 4, 5, "Ahoj" };
a.x = 0;
b.x = 6;
return 0;
}
/*------------------------------------------------*/
Když už je definován datový typ Pokus, je trochu zbytečné používat strukturu pokus. Používání
uživatelského typu je v mnohém příjemnější, než používání struktury jako takové:
1:
2:
3:
5:
6:
7:
8:
9:
10:
11:
12:
14:
15:
16:
17:
18:
/*------------------------------------------------*/
/* typedef3.c
*/
#include <stdio.h>
/* funkce zkopiruj kopiruje retezec v2 do retezce v1 */
void zkopiruj(char *v1, char *v2)
{
int i = 0;
do {
v1[i] = v2[i];
} while (v2[i++]);
}
typedef struct {
char nazev[40];
char zpevak[40];
char skladatel[40];
float delka;
45
19:
21:
22:
23:
24:
25:
26:
28:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
45:
47:
48:
50:
51:
52:
53:
54:
55:
56:
57:
58:
60:
61:
62:
64:
65:
66:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
84:
} Pisen;
typedef struct {
char nazev[40];
int pocet_pisni;
float cenaSK, cenaCZ;
Pisen pisen[20];
} CD;
void tiskni(CD * cd, int pocet);
int main(void)
{
CD *ualbum, alba[10], album;
/* inicializuji album */
ualbum = &album;
/* pouziti ukazatele album je tu jen pro ilustraci */
zkopiruj(album.nazev, "The Beatles Songs");
album.cenaSK = 325.50;
ualbum->cenaCZ = 286.50;
album.pocet_pisni = 1;
/* vkladam prvni (a posledni) pisen do alba */
zkopiruj(album.pisen[0].nazev, "Twist And Shout");
zkopiruj(ualbum->pisen[0].zpevak, "The Beatles");
zkopiruj(album.pisen[0].skladatel, "Lennon/McCartney");
ualbum->pisen[0].delka = 2.36;
/* ukazka prace z polem alba, do pole alba vkladam prvni album */
alba[0] = album;
/* to same album vlozim jeste jednou, tentokrat prez ukazatel */
alba[1] = *ualbum;
/* zmenim nazev a cenu ... */
zkopiruj(alba[1].nazev, "Salute to The Beatles");
alba[1].cenaSK = 350.0;
/* ... a pridam dalsi pisen */
alba[1].pocet_pisni = 2;
alba[1].pisen[1] = alba[1].pisen[0];
zkopiruj(alba[1].pisen[1].nazev, "Eleanor Rigby");
alba[1].pisen[1].delka = 2.06;
tiskni(alba, 2);
return 0;
}
void tiskni(CD * cd, int pocet)
{
int i, j;
for (i = 0; i < pocet; i++) {
/* tisknu album "i" */
printf("%s\t%5.2fKc (%5.2fSk)\n", cd[i].nazev, cd[i].cenaCZ,
cd[i].cenaSK);
for (j = 0; j < cd[i].pocet_pisni; j++) {
/* tisknu pisne z alba "i" */
printf("\t\tNazev:\t%s\n", cd[i].pisen[j].nazev);
printf("\t\tInterpret:\t%s\n", cd[i].pisen[j].zpevak);
printf("\t\tSkladatel:\t%s\n", cd[i].pisen[j].skladatel);
printf("\t\tDelka:\t%.2f\n", cd[i].pisen[j].delka);
printf("\t\t.......................\n");
}
printf("------------------------------------------\n");
}
}
/*------------------------------------------------*/
Výstup:
The Beatles Songs
286.50Kc (325.50Sk)
Nazev: Twist And Shout
Interpret:
The Beatles
Skladatel:
Lennon/McCartney
Delka: 2.36
.......................
------------------------------------------
46
Salute to The Beatles
220.00Kc (350.00Sk)
Nazev: Twist And Shout
Interpret:
The Beatles
Skladatel:
Lennon/McCartney
Delka: 2.36
.......................
Nazev: Eleanor Rigby
Interpret:
The Beatles
Skladatel:
Lennon/McCartney
Delka: 2.06
.......................
------------------------------------------
Výčtový typ enum
Typ enum umožňuje vytvořit proměnnou, která může obsahovat hodnoty konstant určených při deklaraci
výčtového typu. Lze vytvořit buďto jen výčtový typ enum (stejně jako jenom strukturu), nebo pomocí
typedef definovat nový datový typ.
enum [jmeno] {
seznam konstant
...
} [promenne];
Příklad vytvoření výčtového typu:
enum kalendar {
leden = 1, unor, brezen, duben, kveten, cerven, cervenec, srpen, zari, rijen,
listopad, prosinec
} a, b;
...
a = unor;
...
if (b == duben) { ...
V příkladu je vytvořen výčtový typ se jménem kalendar a jsou definovány dvě proměnné a a b. Konstanty
výčtového typu jsou vždy celočíselné. Pokud nemá konstanta přiřazenu hodnotu, pak má hodnotu o
jednotku vyšší, než konstanta předešlá (unor tedy odpovídá číslu 2, brezen 3 atd). Pokud není definována
hodnota ani konstantě první, automaticky je jí přiřazena nula. Hodnoty lze přiřadit kterékoliv konstantě ve
výčtovém typu. Opět platí, že následující konstanta, pokud nemá přiřazenou hodnotu, je o jednotku větší,
než předcházející konstanta. Překladač tyto konstanty chápe pouze jako čísla. Je tedy dost dobře možné je
srovnávat s čísly (např if(leden == 1)),ale to by zcela postrádalo smysl. Výčtový typ se používá právě proto,
aby se v programu nemusela konkrétní čísla používat. Použití výčtového typu se hodně podobá makrům
preprocesoru. Jeho výhodou je právě to, že lze vytvářet proměnné výčtového typu, čímž se dá program
zpřehlednit a jeho čtení je pak o něco snazší. Příkladu na použití typu enum s konstrukcí typedef.
1:
2:
4:
6:
7:
8:
11:
12:
13:
14:
15:
16:
17:
18:
/*------------------------------------------------*/
/* enum1.c
*/
#include <stdio.h>
typedef enum {
vlevo, vpravo, stred, center = stred
} zarovnani;
void tiskni_znak(char ch, zarovnani zr)
{
switch (zr) {
case vlevo:
printf("%c\n", ch);
break;
case vpravo:
printf("%50c\n", ch);
47
19:
20:
21:
22:
23:
24:
25:
26:
28:
29:
30:
31:
32:
33:
34:
35:
37:
break;
case stred:
printf("%25c\n", ch);
break;
default:
printf("%c%24c%25c\n", '?', '?', '?');
}
}
int main(void)
{
tiskni_znak('A', 50);
tiskni_znak('X', vlevo);
tiskni_znak('Y', center);
tiskni_znak('Z', vpravo);
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
?
?
X
Y
?
Z
Typ union
Syntaxe typu union je stejná jako struktury, až na to, že místo klíčového slova struct se použije klíčové
slovo union. Význam jednotlivých položek je stejný. Jeho použití s konstrukcí typedef také.
union [jmeno] {
typ jmeno_polozky;
typ jmeno_polozky;
...
} [promenne];
Rozdíl tu však je. Zatímco struktura si vytvoří paměťové místo pro všechny položky, typ union zabírá v
paměti jen tolik místa, kolik největší jeho položka. Z toho také vyplývá, že lze používat v jeden okamžik
jen jednu položku. Kterou, to už závisí na programátorovi. Typ union tak může při programování ušetřit
paměť. Navíc lze vytvořit funkci, která bude vracet různé datové typy jako typ union. Prvkem v union může
být i struktura, nebo jiný typ union atp..
Funkce rand() a srand()
Funkce rand() vrací pseudonáhodné číslo v rozmezí 0 až RAND_MAX. Pseudonáhodné protože vrací
čísla z číselné řady generována na základě nějakých matematických podmínek. Aby tato čísla nebyla při
spuštění programu vždy stejná, nastaví se počátek této řady pomocí funkce srand(). Argumentem této
funkce je číslo typu unsigned int. Dobré je předat této funkci jako argument aktuální čas.
Funkce rand() a srand() jsou definovány ve standardním hlavičkovém souboru <stdlib.h>.
1:
2:
4:
5:
7:
8:
9:
11:
12:
13:
14:
/*------------------------------------------------*/
/* union.c
*/
#include <stdio.h>
#include <stdlib.h>
typedef enum {
znak, cele, racionalni
} typ;
typedef union {
char ch;
unsigned int uin;
float fl;
48
15:
17:
18:
19:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
35:
36:
37:
39:
40:
41:
43:
44:
45:
47:
48:
50:
51:
53:
54:
55:
57:
} datovy_typ;
datovy_typ nahodne_cislo(typ t)
{
datovy_typ x;
switch (t) {
case znak:
x.ch = (char) (rand() % ('z' - 'a')) + 'a';
break;
case cele:
x.uin = (unsigned int) rand();
break;
case racionalni:
x.fl = (float) rand() / RAND_MAX;
break;
}
return x;
}
int main(void)
{
datovy_typ un;
printf("Zadej cislo: ");
if (scanf("%u", &un.uin) == 0)
return 1;
srand(un.uin);
un = nahodne_cislo(racionalni);
printf("float = %f\n", un.fl);
un = nahodne_cislo(znak);
printf("char = %c\n", un.ch);
un = nahodne_cislo(cele);
printf("int = %i\n", un.uin);
printf("velikost un = %u == float = %u\n", sizeof(un), sizeof(float));
return 0;
}
/*------------------------------------------------*/
Možný výstup z programu: Zadej cislo: 123
float = 0.060051
char = n
int = 436085873
velikost un = 4 == float = 4
Ukazatele na funkce
Protože i funkce programu leží kdesi v paměti, existuje adresa, která ukazuje na začátek této funkce.
Pomocí ukazatele na funkci ji lze zavolat (a to i s příslušnými argumenty). Je možné tak například napsat
funkci, která bude mít jako argument ukazatel na jinou funkci, kterou pak může spustit. Deklaraci
ukazatele (jmeno) na funkci, která má návratovou hodnotu „typ“. V závorce mohou být uvedené typy
očekávaných argumentů funkce.
typ (*jmeno)([typ_arg,..]);
V následujícím příkladu je deklarován ukazatel uknf, který ukazuje na funkci bez argumentů s návratovou
hodnotou „ukazatel na typ char“. Pro porovnání je hned za touto deklarací deklarace oné funkce. Závorky
při deklaraci ukazatele na funkci jsou vždy nevyhnutelné. Bez nich by v tomto příkladu byla vytvořena
deklarace funkce s návratovým typem „ukazatel na ukazatel na typ char“.
char *(*uknf) (void); /* deklarace ukazatele na funkci, která vrací ukazatel */
char *uknf (void);
/* deklarace funkce, která vrací ukazatel */
V příkladu použitá funkce exit() ukončí program v jakémkoliv místě s návratovou hodnotou, která je jejím
argumentem. Tato funkce je definována v souboru <stdlib.h>.
49
1:
2:
4:
5:
7:
8:
9:
10:
11:
12:
13:
14:
15:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
59:
60:
61:
62:
63:
64:
65:
66:
67:
69:
71:
/*------------------------------------------------*/
/* ukafce.c
*/
#include <stdio.h>
#include <stdlib.h>
/* nejdrive vytvorime strukturu, ktera bude obsahovat nejake pojmenovani
* funkce pro uzivatele programu, cislo funkce, bude zaznamenavat pocet
* spusteni funkce a ukazatel na funkci */
typedef struct {
unsigned int cislo;
char nazev[50];
unsigned int spusteno;
void (*ukfce) (unsigned int *, char *);
} robot;
/* nyni definujeme funkce programu, ktere budou uzivateli nabizeny pomoci
* predchozi struktury */
/* zobrazi pouze velka pismena */
void velka_pismena(unsigned int *spusteno, char *v)
{
int i = 0;
(*spusteno)++;
do {
if ((v[i] >= 'A') && (v[i] <= 'Z')) {
printf("%c", v[i]);
}
} while (v[i++]);
printf("\n");
}
/* zobrazi pouze mala pismena */
void mala_pismena(unsigned int *spusteno, char *v)
{
int i = 0;
(*spusteno)++;
do {
if ((v[i] >= 'a') && (v[i] <= 'z')) {
printf("%c", v[i]);
}
} while (v[i++]);
printf("\n");
}
/* zobrazi vse, co neni pismeno */
void nepismena(unsigned int *spusteno, char *v)
{
int i = 0;
(*spusteno)++;
do {
if (!(((v[i] >= 'a') && (v[i] <= 'z')) ||
((v[i] >= 'A') && (v[i] <= 'Z')))) {
printf("%c", v[i]);
}
} while (v[i++]);
printf("\n");
}
/* zobrazi text pozpatku */
void obrat_text(unsigned int *spusteno, char *v)
{
int i = 0;
if (*spusteno >= 2) {
printf("Pro dalsi pouziti teto funkce se musite registrovat!\n");
return;
}
(*spusteno)++;
while (v[i++]);
/* hledame konec retezce */
for (i--; i >= 0; i--)
50
72:
printf("%c", v[i]);
74:
printf("\n");
75: }
77: /* vlozi do textu mezery */
78: void roztahni(unsigned int *spusteno, char *v)
79: {
80:
int i = 0;
81:
(*spusteno)++;
82:
while (v[i]) {
83:
printf("%c ", v[i++]);
84:
};
85:
printf("\n");
86: }
88: /* funkce exit() ma jine parametry, nez ktere ocekava nas ukazatel, proto
89:je vytvorena funkce konec, ktera simuluje potrebu deklarovanych argumentu */
91: void konec(unsigned int *spusteno, char *v)
92: {
93:
exit(0); /* ukonci program s navratovou hodnotou 0, ktera znaci uspech */
95: }
98: /*fce vlozime do pole struktury robot spolu s nazvem pro uzivatele atd. */
99: robot *inicializuj(void)
100: {
101: /*promenna "r" je staticka, aby po skonceni funkce neprestala existovat! */
103:
static robot r[] = { {1, "Velke pismena", 0, velka_pismena},
104:
{1, "Mala pismena", 0, mala_pismena},
105:
{2, "Co neni pismeno", 0, nepismena},
106:
{3, "Obraceni textu", 0, obrat_text},
107:
{4, "Roztazeni textu", 0, roztahni},
108:
{5, "Ukonceni", 0, konec},
109:
{0, "", 0, NULL} /* "zarazka", podle ktere pozname, ze jsme na konci*/
110:
};
111:
return r;
112: }
115: int main(void)
116: {
117:
int i, navrat;
118:
robot *rb;
/* ukazatel na pole struktury robot */
119:
unsigned int cislo;
/* funkce jsou ve strukture ocislovany,
120:
dle tohoto cisla budou vybirany */
121:
char retezec[51];
/* maximalne 50 znaku + '\0' */
123:
rb = inicializuj(); /* tato funkce vraci ukazatel na svou statickou
124:
promennou. Při opakovaném volání ukazuje stale na to same pole.*/
128:
do {
129:
i = 0;
130:
do {
131:
/* zobrazeni menu - snadne a prehledne */
132:
printf("%2i)\t%s\t(%2i)\n", rb[i].cislo, rb[i].nazev,
133:
rb[i].spusteno);
134:
} while (rb[++i].ukfce != NULL);
136:
printf("Vyber polozku dle cisla a zadejte retezec: ");
138:
/* vysledek prirazeni muzeme pouzit jako hodnotu
139:
(tak ho rovnou porovname s EOF) */
140:
if ((navrat = scanf("%u %50s", &cislo, retezec)) == EOF)
141:
break;
142:
else if (navrat != 2) {
/* chybne nacteni polozek */
143:
printf("\n Chyba vstupu (%i)!\n", navrat);
144:
/* Zrejme nebylo zadano jako prvni cislo, ale retezec. Ten se musi
nyni nacist, jinak by se jej predchozi funkce scanf pokousela v cyklu
146:
neustale nacitat jako cislo a tim by cyklus nikdy neskoncil.
147:
Promenna retezec muze obsahnout maximalne 50 znaku, proto funkci scanf
148:
v prvnim argumentu urcime max. delku nacitaneho retezce. */
151:
scanf("%50s", retezec);
152:
continue;
51
153:
}
154:
i = -1;
155:
while (rb[++i].ukfce != NULL) { /* hledame spravnou strukturu, dokud
156:
nenarazime na nami vytvorenou zarazku */
157:
if (rb[i].cislo == cislo) {
158:
rb[i].ukfce(&rb[i].spusteno, retezec);
159:
/* prochazi se cele pole, takze se spusti vsechny polozky v
160:
* poli, ktere maji rb[].cislo == cislo */
161:
/* kdyby zde byl prikaz break, provedla by se prvni nalezena
162:
polozka a dale by se jiz nic neprohledavalo */
163:
}
164:
}/*nekonecny cyklus, ukonci se return, break (př. exit konci program)*/
165: } while (1);
168:
printf("\nAstalavista !\n");
169:
return 0;
170: }
172: /*------------------------------------------------*/
Možný výstup z programu: 1)
Velke pismena
( 0)
1)
Mala pismena
( 0)
2)
Co neni pismeno ( 0)
3)
Obraceni textu ( 0)
4)
Roztazeni textu ( 0)
5)
Ukonceni
( 0)
Vyber polozku dle cisla a zadejte retezec: 1 VelkaAMalaPismena
VAMP
elkaalaismena
1)
Velke pismena
( 1)
1)
Mala pismena
( 1)
2)
Co neni pismeno ( 0)
3)
Obraceni textu ( 0)
4)
Roztazeni textu ( 0)
5)
Ukonceni
( 0)
Vyber polozku dle cisla a zadejte retezec: 5
konec
Program má jeden drobný nedostatek, a to, že při volání funkce k ukončení programu (je pod číslem 5) je
potřeba zadat za číslo 5 ještě nějaký zbytečný řetězec. Existuje spousta možností, jak se tohoto
nedostatku zbavit. Důležitější je, jak snadno se do takového programu přidá další funkce. Stačí ji jen
napsat a ve funkci inicializuj() jí přidat do pole struktury robot. Nic víc se na programu měnit nemusí.
Ukazatel na funkci se tak může s výhodou použít v programech, které načítají funkce z knihoven. Při
vytvoření funkce stačí jen změnit nebo vytvořit novou knihovnu pro program (a ne hned překládat celý
program). Program si jí načte do paměti a pak s ní může snadno pracovat pomocí ukazatele.
Přehled deklarací
typ jmeno;
Proměnná určeného typu
typ *jmeno;
Ukazatel na určitý typ (respektive pole daného typu)
typ jmeno[ ];
Konstantní ukazatel na pole daného typu (neznámo jak dlouhé)
typ jmeno[10]; Konstantní ukazatel na pole daného typu o velikosti 10-ti proměnných daného typu.
typ **jmeno;
Ukazatel na pole ukazatelů daného typu
typ *jmeno[ ];
Konstantní ukazatel na pole ukazatelů daného typu
typ jmeno[ ][ ]; Konstantní ukazatel na pole konstantních ukazatelů na pole daného typu.
typ jmeno();
Deklarace funkce vracející typ
typ *(jmeno()); Funkce vracející ukazatel na typ. Zvýrazněné závorky jsou nadbytečné.
typ (*jmeno)()
Ukazatel na funkci bez parametrů vracející typ
typ *(*jmeno)() Ukazatel na funkci bez parametrů vracející ukazatel na typ
52
Lze vytvářet i daleko složitější deklarace. Například
unsigned long *(*jmeno[5][4])(char *);
je dvojrozměrné pole obsahující ukazatele na funkci, jejíž návratová hodnota je ukazatel na typ unsigned
long a argumentem této funkce je ukazatel na typ char. Rozluštit takovéto zápisy není zrovna legrace.
Proto je výhodnější využívat typedef pro zjednodušení takovýchto konstrukcí. Předchozí proměnnou jmeno
lze definovat takto:
typedef unsigned long *(*ukazatel_na_fci1)(char *);
ukazatel_na_fci1 jmeno[5][4];
V následujícím příkladu je naprogramována „chůze opilce“. Program si přečte z příkazové řádky
pravděpodobnosti, s jakými půjde opilec vpřed a vzad. Pravděpodobnost, že zůstane stát si dopočte
program sám (zbytek do 100%). Jelikož na příkazové řádce jsou i čísla chápána jako text, použijeme
funkci atoi() ze standardní knihovny na převod řetězce na číslo typu int. Po spuštění programu bude mít
navíc uživatel možnost vybrat si z několika zobrazení opilce. Funkce pro zobrazování opilce budou v
jiném souboru, než hlavní program a budou se volat pomocí ukazatele na funkce.
3:
5:
6:
7:
8:
3:
5:
6:
7:
8:
3:
5:
6:
7:
8:
#include <dos.h>
void cekej(int cas)
{
delay(cas);
}
#include <windows.h>
void cekej(int cas)
{
Sleep(cas);
}
#include <unistd.h>
void cekej(unsigned long cas)
{
usleep(cas);
}
V souboru "define1.h" definujeme potřebné datové typy a makra. Tyto datové typy a makra se budou
používat i v dalších knihovnách, proto je třeba v hlavním programu načíst tuto knihovnu (pomocí #include)
jako jednu z prvních. Výčtový typ smer bude sloužit k určování směru chůze opilce, struktura pohyb bude
obsahovat pravděpodobnosti, s jakou půjde opilec vpřed, vzad nebo zůstane stát a také pozici, kde opilec
právě stojí. Položka stop nebude ani využita, lze jí dopočítat do 100% pomocí položek vpred a vzad. Je
otázkou, zda je lepší vytvořit takovou položku, do které se hodnota jednou uloží a pak už se jen používá,
nebo kdykoliv je v programu potřeba, tak se vypočte výrazem (100-vpred-vzad). Pokud by se tento výraz
musel v programu vícekrát počítat (což program zpomaluje) a i tento výraz zabírá v programu místo, bylo
by výhodnější položku stop použít.
1:
2:
4:
6:
7:
8:
10:
11:
12:
13:
14:
/*------------------------------------------------*/
/* define1.h
*/
#define DELKACHUZE 40
typedef enum {
dopredu, dozadu, stat
} smer;
typedef struct {
int vpred, vzad, stop;
int pozice;
} pohyb;
/*------------------------------------------------*/
53
V souboru "zobraz1.h" jsou definovány všechny funkce, kterými se zobrazuje pohyb opilce. Ukazatele na
tyto funkce jsou uloženy do pole zobraz. Funkce budou volány v programu náhodně. Proto jsou tam
některé záměrně vícekrát, aby byla větší pravděpodobnost, že budou vybrány.
1: /*------------------------------------------------*/
2: /* zobraz1.h
*/
4: #include <stdio.h>
5: #include <stdlib.h>
7: #define TISKNIOPILCER(ZNK) printf("\r%3i%% (%*s%*c %3i%% (%3i)",
p->vzad,\ p->pozice, ZNK, DELKACHUZE - p->pozice,')',p->vpred,pocet);
9: #define TISKNIOPILCEN(ZNK) printf("\n%3i%% (%*s%*c %3i%% (%3i)",
p->vzad,\ p->pozice, ZNK, DELKACHUZE - p->pozice,')',p->vpred,pocet);
12: void zobraz_opilce1(pohyb *, const smer1), const int);
13: void zobraz_opilce2(pohyb *, const smer, const int);
14: void zobraz_opilce3(pohyb *, const smer, const int);
15: void zobraz_opilce4(pohyb *, const smer, const int);
17: void (*zobraz[]) (pohyb *, const smer, const int) = {
18: zobraz_opilce1,zobraz_opilce2, zobraz_opilce3, zobraz_opilce1,
zobraz_opilce3};
21: void zobraz_opilce1(pohyb * p, const smer s, const int pocet)
22: {
23:
switch (s) {
24:
case dopredu:
25:
p->pozice++;
26:
TISKNIOPILCER("->")
27:
break;
28:
case dozadu:
29:
p->pozice--;
30:
TISKNIOPILCER("<-")
31:
break;
32:
case stat:
33:
TISKNIOPILCER("<>")
34:
break;
35:
};
36: }
38: void zobraz_opilce2(pohyb * p, const smer s, const int pocet)
39: {
40:
switch (s) {
41:
case dopredu:
42:
p->pozice++;
43:
TISKNIOPILCEN("->")
44:
break;
45:
case dozadu:
46:
p->pozice--;
47:
TISKNIOPILCEN("<-")
48:
break;
49:
default:
50:
TISKNIOPILCEN("<>")
51:
break;
52:
};
53: }
55: void zobraz_opilce3(pohyb * p, const smer s, const int pocet)
56: {
57:
switch (s) {
58:
case dopredu:
59:
p->pozice++;
60:
TISKNIOPILCER(">>")
61:
break;
62:
case dozadu:
63:
p->pozice--;
54
64:
65:
66:
67:
68:
69:
70:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
89:
TISKNIOPILCER("<<")
break;
default:
TISKNIOPILCER("><")
break;
};
}
void zobraz_opilce4(pohyb * p, const smer s, const int pocet)
{
switch (s) {
case dopredu:
p->pozice++;
printf("\rPOZICE: %2i%*c", p->pozice++, DELKACHUZE + 10, ' ');
break;
case dozadu:
p->pozice--;
printf("\rPOZICE: %2i%*c", p->pozice++, DELKACHUZE + 10, ' ');
break;
default:
printf("\rPOZICE: %2i%*c", p->pozice++, DELKACHUZE + 10, ' ');
break;
};
}
/*------------------------------------------------*/
A nyní srdce programu, soubor opakov1.c.
1:
2:
4:
5:
7:
8:
9:
10:
11:
12:
13:
14:
15:
/*------------------------------------------------*/
/* opakov1.c
*/
#include <stdio.h>
#include <stdlib.h>
#ifdef unix
#include "unix1.h"
#define VERZE "UNIX"
#define CEKEJ 100000ul
#elif defined __MSDOS__
#define VERZE "MSDOS"
#include "dos1.h"
#define CEKEJ 100
#elif defined __WINDOWS__||defined __WIN16__||defined __WIN32__ ||
defined __WIN64__
16: #include "windows1.h"
17: #define VERZE "WINDOWS"
18: #define CEKEJ 100
19: #endif
22:/*V souboru define1.h jsou nadefinovany datove typy, ktere se pouzivaji
i v souboru zobraz1.h. Proto musi byt #include"define.h" prvni */
26: #include "define1.h"
27: #include "zobraz1.h"
29: /*Nacteme cisla z prikazove radky a pote zkontrolujeme, zda maji
30:
rozumne hodnoty. Pokud ne, program ukoncime */
32: pohyb nacti_procenta(int argc, char *argv[])
33: {
34:
pohyb p = { 0, 0, 0, DELKACHUZE / 2 };
35:
if (argc != 3) {
36:
printf("Spatny pocet argumentu\n"
37:
"Zadejte pravdepodobnost chuze vpred a vzad.\n");
38:
exit(0);
39:
}
40:
p.vpred = atoi(argv[1]);
55
41:
p.vzad = atoi(argv[2]);
42:
p.stop = 100 - p.vpred - p.vzad;
44:
if (p.stop < 0) {
45:
printf("Chybne zadane pravdepodobnosti\n");
46:
printf("Jejich soucet nesmi prekrocit 100\n");
47:
exit(0);
48:
}
50:
if (!(p.vpred || p.vzad)) {
51:
printf("Pravdepodobnost vpred nebo vzad musi byt nenulova\n");
52:
exit(0);
53:
}
54:
if ((p.vpred < 0) || (p.vzad < 0)) {
55:
printf("Pravdepodobnosti musi byt kladne!\n");
56:
exit(0);
57:
}
58:
return p;
59: }
62: smer vyber_smer(const pohyb * p) /* nahodne vybirame smer pohybu opilce */
63: {
64:
int x;
65:
x = (rand() % 100);
/* tj. 0 - 99 */
66:
if (x <= p->vpred)
67:
return dopredu;
68:
if (x <= p->vpred + p->vzad)
69:
return dozadu;
70:
return stat;
71: }
74: int main(int argc, char *argv[])
75: {
76:
pohyb opilec;
77:
int pocet_funkci, fce, pocet;
78:
opilec = nacti_procenta(argc, argv);
/*Cisla inicializujeme "nenahodne",pri stejne zadaných procentech se bude
program chovat stejne. Lepe je srand inicializovat treba aktualnim casem */
82:
srand(opilec.vpred * opilec.vzad);
/*NULL je ukazatel "do nikam". Ma stejnou velikost jako
84: kterykoliv ukazatel, Tj. sizeof(NULL) = sizeof(char *) atd. */
86: pocet_funkci = sizeof(zobraz) / sizeof(NULL);
88: pocet = 0;
89: do {
90:
pocet++;
91:
fce = rand()%pocet_funkci; /*vybirame nahodne funkci na zobrazeni */
92:
zobraz[fce] (&opilec, vyber_smer(&opilec), pocet);
93:
fflush(stdout);
/* vyprazdneni vystupu pred pozastavenim */
94:
cekej(CEKEJ);
95: }while ((opilec.pozice > 2) && (opilec.pozice < DELKACHUZE - 1));
97:
printf("\n");
98:
return 0;
99: }
101: /*------------------------------------------------*/
Možný výstup:
50%
50%
50%
50%
50%
50%
50%
50%
(
(
(
(
(
(
(
(
opakov1 30 50
<<
)
>>
)
<>
)
<<
)
->
)
<<
)
<>
)
>>
)
30%
30%
30%
30%
30%
30%
30%
30%
(
(
(
(
(
(
(
(
8)
17)
24)
27)
28)
38)
43)
45)
56
50%
50%
50%
50%
50%
(
(
(
<<
( <<
(<-
<<-
)
)
)
)
)
30%
30%
30%
30%
30%
(
(
(
(
(
49)
52)
64)
70)
72)
Dynamická alokace paměti
Každý program lze v zásadě rozdělit na dvě části. Na část datovou, kde jsou uložena data, se kterými
program pracuje (tj. proměnné) a na část programovou, která obsahuje instrukce programu. Ihned po
spuštění programu jsou data která ukládáte do proměnných uložena v paměti počítače. Taková místa v
paměti se nazývají statická. Takto získávaná paměť má své pro i proti. Výhodou je rychlost a zaručená
dostupnost paměti. Jsou však situace, kdy dopředu nevíme, kolik paměti bude potřeba. Například třídění
čísel. Jelikož dopředu nevíme, kolik čísel bude uživatel chtít setřídit, můžeme to jenom odhadnout a vytvořit
si pro tato čísla pole dlouhé např. 1000 položek typu float. To už je velké pole, které zvětší velikost
programu a zpomalí jeho chod, přičemž se může stát, že uživatel bude využívat v průměru třeba jen 10
položek. Na druhou stranu se také může stát, že bude potřebovat 1001 položek. Výhodnější v tomto
případě je žádat o paměť pro proměnnou (nebo celá pole proměnných) za chodu programu. Tomu se říká
„dynamická alokace paměti“. I to má však své výhody a nevýhody. Výhodou je, že program využívá
právě tolik paměti, kolik potřebuje. Navíc získanou paměť můžete během programu uvolnit (je to vhodné a
před ukončením programu nutné!). Jistou nevýhodou je zvýšená pracnost s dynamickými strukturami (a
časová režie při běhu programu). Proto je třeba zvážit, zda se vyplatí vytvářet dynamické data, nebo využít
statických polí.
Získání a uvolnění paměti pomocí malloc() a free()
Funkce malloc() a free() jsou obsaženy v hlavičkovém souboru <stdlib.h> , a též i datový typ size_t,
používaný k určování velikosti získávané paměti. Je to celočíselný typ, definovaný pomocí typedef a je
dostatečně velký na to, aby se v něm mohla uložit adresa paměti. Proto je lépe používat size_t a ne int,
který nemusí být dostatečně velký na adresování celého rozsahu paměti.
void *malloc(size_t size);
void free(void *ptr);
Funkce malloc() má jako jediný argument počet bajtů paměti, které chceme od operačního systému získat.
Návratová je deklarována jako ukazatel void * (s dynamicky alokovanou pamětí se pracuje pomocí
ukazatelů). Paměť přidělí operační systém a funkce malloc() vrátí ukazatel na začátek této paměti. V
případě, že volné paměti není dostatek, vrátí funkce malloc() hodnotu NULL. Vrácený ukazatel je třeba
přetypovat na správný datový typ, který bude v alokovaném datovém poli používán. Takto získaná paměť,
je souvislá oblast dlouhá size_t bajtů s nedefinovaným obsahem. Maximální velikost dynamicky alokované
paměti je dána jednak fyzickými možnostmi počítače a také velikostí adresování paměti. U 32 bitových
programů je možné adresovat až 4GB. Funkce free() takto alokovanou paměť opět uvolní. Její argument je
ukazatel na začátek alokované paměti (tedy hodnotu, kterou vrací funkce malloc(), calloc() a realloc. Po
volání funkce free() již paměť není přidělena programu a pokus o zápis do této paměti končí běhovou
chybou.
Pokud program neuvolňuje nepotřebnou paměť, dochází k takzvanému „úniku paměti“ (memory leaks).
Déle běžící aplikace si tak říká o stále více a více paměti, až nakonec spotřebuje všechnu, která je
dostupná. Takovými problémy často trpí například Mozilla Firefox (ale i mnoho jiných profesionálních
aplikací).
Následující program načte od uživatele číslo určující počet následujících zadávaných čísel, které se poté
setřídí. Pomocí NULL je kontrolováno, zda byla dynamická paměť skutečně alokována, je přetypována
návratová hodnota a určuje se velikost získávaného datového pole.
1:
2:
4:
5:
7:
8:
/*------------------------------------------------*/
/* dynamic1.c
*/
#include <stdio.h>
#include <stdlib.h>
/* funkce pro setrideni pole od nejmensiho do nejvetsiho */
void setrid(unsigned int delka, float pole[])
57
9:
10:
11:
12:
13:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
28:
29:
30:
31:
35:
39:
40:
41:
42:
43:
45:
46:
{
unsigned int i, j;
float pom;
if (delka <= 1)
return;
for (j = 0; j < delka - 1; j++) {
i = 0;
do {
if (pole[i] > pole[i + 1]) {
pom = pole[i];
pole[i] = pole[i + 1];
pole[i + 1] = pom;
}
i++;
} while (i < delka - 1);
}
}
int main(void)
{
unsigned int i;
unsigned int *ui;
/* ukazatel na dynamicky alokovane cislo, ktere
bude urcovat delku pole pro tridene cisla. V
tomto pripade by bylo lepsi pouzit statickou
promennou (jako unsigned int i) */
float *uf;
/* ukazatel na dynamicky alokovane pole, do ktereho
budou ulozeny tridena cisla */
ui = (int *) malloc(sizeof(int));
if (ui == NULL) {
/* jeden mozny zpusob kontroly */
printf("Nedostatek pameti!\n");
return 0;
}
printf("Zadejte pocet cisel: ");
scanf("%u", ui); /* u funkce scanf by se melo kontrolovat, zda
skutecne nacetla to, co mela.*/
/* pozadam o tolik bytu, kolik ma typ float krat velikost pole */
if ((uf = (float *) malloc(sizeof(float) * (*ui))) == NULL)
/* taky zpusob kontroly :-) */
{
printf("Nedostatek pameti!\n");
return 0;
}
for (i = 0; i < *ui; i++) {
printf("Zadejte cislo [%2u]: ", i + 1);
scanf("%f", uf + i);
/* nebo uf[i] */
}
setrid(*ui, uf);
for (i = 0; i < *ui; i++) {
printf("%5.2f ", uf[i]);
/* nebo uf + i */
}
printf("\n");
free(ui);
free(uf);
return 0;
49:
50:
51:
52:
53:
54:
55:
57:
58:
59:
60:
62:
63:
64:
65:
66:
67:
68:
69:
70: }
72: /*------------------------------------------------*/
Výstup z programu:
Zadejte
Zadejte
Zadejte
Zadejte
pocet
cislo
cislo
cislo
cisel: 5
[ 1]: 4
[ 2]: 3.5
[ 3]: 3.8
58
Zadejte cislo [ 4]: 2
Zadejte cislo [ 5]: 0.5
0.50 2.00 3.50 3.80 4.00
V následujícím příkladu je dynamicky vytvořené dvourozměrné pole (s následným uvolňováním). Jeho
velikost bude 100*200*sizeof(int) bajtů.
1:
2:
4:
5:
7:
8:
9:
11:
12:
13:
15:
16:
17:
18:
20:
21:
22:
24:
25:
26:
27:
28:
30:
31:
32:
34:
35:
36:
38:
39:
40:
41:
42:
43:
44:
46:
48:
49:
50:
52:
53:
55:
/*------------------------------------------------*/
/* dynamic2.c
*/
#include <stdio.h>
#include <stdlib.h>
#define X 100
/* pocet radku */
#define Xv1 10
/* vypis radek Xv1 az Xv2 */
#define Xv2 20
#define Y 200
/* sloupce */
#define Yv1 50
/* vypis sloupcu Yv1 az Yv2 */
#define Yv2 60
int main(void)
{
int **ui;
int a, b;
ui = (int **) malloc(X * (sizeof(int *)));
if (ui == NULL)
return 1;
for (a = 0; a < X; a++) {
ui[a] = (int *) malloc(Y * (sizeof(int)));
if (ui[a] == NULL)
return 1;
}
for (a = 0; a < X; a++)
for (b = 0; b < Y; b++)
ui[a][b] = a * b;
printf("
");
for (b = Yv1; b <= Yv2; b++)
printf("%5i ", b);
printf("\n");
for (a = Xv1; a <= Xv2; a++) {
printf("%3i: ", a);
for (b = Yv1; b <= Yv2; b++)
printf("%5i ", ui[a][b]);
printf("\n");
}
/* jak byla pamet alokovana, tak musi byt dealokovana. */
for (a = 0; a < X; a++)
free(ui[a]);
free(ui);
return 0;
}
/*------------------------------------------------*/
V případě volání pouze free(ui), by se uvolnila jen paměť alokovaná prvním zavoláním funkce malloc().
Navíc by tak byly straceny všechny ukazatele na paměť získávanou v cyklu na řádku 25. (To by byla
krásná ukázka úniku paměti.)
Získání paměti pro struktury pomocí calloc()
Funkce calloc() je deklarována následovně:
void *calloc(size_t nmemb, size_t size);
Funkce calloc() alokuje paměť pro nmemb položek velikosti size. Velikost položky může být například
sizeof(int), počet položek rozměr požadovaného pole. Ovšem častěji se využívá pro vytvoření
59
dynamického pole struktur. Na rozdíl od funkce malloc(), vyplní alokovanou paměť nulami. Všechno ostatní
je stejné (návratová hodnota, uvolňování pomocí free() atd.).
V následujícím příkladu je vytvořen seznam. Seznam obsahuje datové objekty (struktury), které jsou mezi
sebou provázány ukazateli. První prvek seznamu ukazuje na druhý, druhý na třetí atd. V tomto příkladu
bude prvek obsahovat, kromě ukazatele na další prvek, pole typu char, do kterého budou ukládána slova
načtená od uživatele. Po skončení načítání budou slova lexikograficky seřazena. Struktura bude také
obsahovat číslo určující kolikáté bylo slovo načteno. K setřídění je použita funkce strcmp(), obsažena v
hlavičkovém souboru <string.h>. Tato funkce porovnává lexikograficky dva řetězce a vrací nulu, pokud jsou
si rovny!
1:
2:
4:
5:
7:
8:
10:
11:
12:
13:
14:
17:
18:
19:
20:
21:
22:
23:
24:
27:
28:
29:
30:
32:
33:
34:
35:
36:
37:
38:
39:
40:
43:
44:
45:
46:
48:
49:
50:
52:
53:
55:
56:
57:
58:
59:
60:
61:
62:
/*------------------------------------------------*/
/* dynamic3.c
*/
#include <stdio.h>
#include <stdlib.h>
#define MAXSLOVO 20
#define NACTISLOVO "%20s"
struct veta {
unsigned int poradi;
char slovo[MAXSLOVO + 1];
struct veta *dalsi;
/* ukazatel na dalsi strukturu */
};
/* pomocna funkce na kopirovani retezce */
void strcopyruj(char *cil, const char *zdroj)
{
unsigned int i = 0;
do {
cil[i] = zdroj[i];
} while (zdroj[i++]);
}
/* funkce vypisujici seznam */
void vypis_veta(struct veta *zacatek)
{
struct veta *dalsi;
dalsi = zacatek;
printf("\n");
do {
if (strcmp(dalsi->slovo, "")) /* prazdne slova ignoruj */
printf("%3i: %s\n", dalsi->poradi, dalsi->slovo);
dalsi = dalsi->dalsi;
/* prejdi na dalsi slovo */
}
while (dalsi != NULL);
}
void setrid_seznam(struct veta *zacatek)
{
int zmena = 0;
struct veta *dalsi, pom;
dalsi = zacatek;
if (dalsi == NULL)
return;
do {
/* jsme na koneci a nic se nezmenilo? pak mame setrideno */
if ((dalsi->dalsi == NULL) && (zmena == 0))
break;
else if (dalsi->dalsi == NULL) {
dalsi = zacatek;
zmena = 0;
}
if (strcmp(dalsi->slovo, dalsi->dalsi->slovo) > 0) {
pom = *dalsi;
60
63:
strcopyruj(dalsi->slovo, dalsi->dalsi->slovo);
64:
dalsi->poradi = dalsi->dalsi->poradi;
65:
strcopyruj(dalsi->dalsi->slovo, pom.slovo);
66:
dalsi->dalsi->poradi = pom.poradi;
67:
zmena = 1;
68:
}
69:
dalsi = dalsi->dalsi;
70:
} while (1);
71: }
74: /* Nasleduje funkce pro uvolneni pameti. Prvni polozka je staticka,
proto se pomoci free() neuvolnuje a take se musi jeji atribut
"dalsi" nastavit na NULL */
78: void zabij(struct veta *zacatek)
79: {
80:
struct veta *v, *pom;
82:
v = zacatek->dalsi;
83:
while (v != NULL) {
84:
pom = v->dalsi;
85:
free(v);
86:
v = pom;
87:
}
88:
zacatek->dalsi = NULL;
89: }
91: int main(void)
92: {
93:
unsigned int poradi;
94:
int navrat;
95:
struct veta prvni, *posledni;
97:
printf("Zadavejte slova ne delsi nez %i znaku.\n"
98:
"Zadavani vet ukoncete pomoci znaku konec souboru.\n"
99:
"(tj. CTRL+D v unixech nebo CTRL+Z v DOSu a Windows)\n\n",
100:
MAXSLOVO);
102:
poradi = 0;
103:
posledni = &prvni;
/* "prvni" je prvni prvek ze seznamu. Ukazatel
104:
"posledni" ukazuje na posledni prvek v seznamu.*/
105:
do {
106:
poradi++;
107:
navrat = scanf(NACTISLOVO, posledni->slovo);
108:
posledni->poradi = poradi;
109:
if ((navrat == EOF) || (navrat != 1)) {
110:
/* EOF * -> konec vstupu,
111:
* navrat !=1 -> nebyl nacten spravne retezec */
112:
/*dalsi prvek v seznamu jiz nebude. seznam ukončíme zarazkou NULL */
114:
posledni->dalsi = NULL;
115:
break;
116:
}
117:
if ((posledni->dalsi = (struct veta *)
118:
calloc(1, sizeof(struct veta))) == NULL) {
119:
printf("Nedostatek pameti!\n");
120:
exit(1);
121:
}
122:
posledni = posledni->dalsi;
123:
} while (1);
125:
setrid_seznam(&prvni);
126:
vypis_veta(&prvni);
127:
zabij(&prvni);
129:
return 0;
130: }
132: /*------------------------------------------------*/
61
Možný výstup: Zadavejte slova ne delsi nez 20 znaku.
Zadavani vet ukoncete pomoci znaku konec souboru.
(tj. CTRL+D v unixech nebo CTRL+Z v DOSu a Windows)
Kdyz nejde hora k Moha-Medovi, musi Moha-Med k hore.
1:
7:
5:
3:
9:
Když
Moha-Med
Moha-Medovi,
hora
hore.
4:
8:
6:
2:
k
k
musi
nejde
Změna velikosti získané paměti pomocí realloc()
Funkce realloc() umožňuje změnit velikost alokované paměti. Deklarace funkce je následující:
void *realloc(void *ptr, size_t size);
První argument funkce, ptr (pointer) je adresa paměti, která byla alokována pomocí funkcí malloc(), calloc()
nebo realloc(). Velikost tohoto paměťového bloku se zvětší (zmenší) na size bajtů. Návratovou hodnotou je
ukazatel na „pře alokovanou“ paměť. To ovšem nemusí být stejné místo v paměti jako původní paměť (na
kterou ukazoval argument ptr)! Proto pro volání funkce realloc() není možné přistupovat do paměti pomocí
hodnoty v ukazateli ptr, ani ji uvolňovat, lze používat jen paměť na kterou ukazuje hodnota vrácená
funkcí realloc(). Nově alokovaná paměť má nedefinované hodnoty, původní část přealokovné paměti,
zůstává zachována. Pokud by byl argument ptr NULL, pak by bylo volání realloc() obdobné jako malloc().
Nastavení velikosti pole size na nulu dává stejný výsledek, jako volání pro ptr funkce free(). Nepodaří-li se
funkci realloc() získat dostatek nové paměti, vrátí ukazatel NULL. Proto je nebezpečné používat následující
konstrukci:
ukazatel = realloc(ukazatel,SIZE);
Takto by se totiž do ukazatele uložila hodnota NULL a přišli bychom o ukazatel na původní alokovanou
paměť, která zůstává zachována. - V příkladu bude uživatel zadávat programu požadovanou velkost v
megabajtech alokované paměti a program jí bude alokovat.
2:
4:
5:
7:
9:
10:
11:
12:
14:
15:
16:
17:
18:
19:
20:
21:
22:
24:
25:
26:
27:
28:
30:
31:
/* dynamic4.c
*/
#include <stdio.h>
#include <stdlib.h>
#define MEGABAJT 1024*1024
int main(void)
{
unsigned long x, a;
char *pom, *v = NULL;
do {
printf("Zadejte pocet megabajtu, 0 pro konec: ");
scanf("%lu", &x);
pom = (char *) realloc((void *) v, x * MEGABAJT);
if ((pom == NULL) && (x))
printf("Nedostatek pameti!!\n");
else {
v = pom;
/*vyplneni pameti jednickami. To pocitac trosilinku zamestna :-) */
for (a = 0; a < x * MEGABAJT; a++) {
v[a] = '1';
}
}
} while (x);
return 0;
}
62
Knihovna string.h
Obsahuje funkce pro práci s řetězci. (Datové pole obsahující znaky, které jsou zakončeny zarážkou '\0')
Výčet funkcí
Pokud funkce někam kopírují / přidávají data, nekontrolují, zda na to mají dostatek místa. To musí
programátor. Jinak se může snadno stát, že program skončí kvůli „neoprávněnému přístupu do paměti“.
char *strcat( char *dest, const char *src);
Funkce připojí řetězec src k řetězci dest. Funkce vrací ukazatel na řetězec dest.
char *strncat( char *dest, const char *src, size_t n);
Jako funkce strcat, ale přidá jen n znaků z src. Tato funkce je bezpečnější z hlediska možného
"přetečení" (pokus zapisovat více dat, než jakou má velikost dest).
int strcmp(const char *s1, const char *s2);
Porovnává řetězce s1 a s2. Pokud je s1 < s2 pak vrací hodnotu menší než 0, pokud jsou si rovny,
pak vrací 0, pokud je s1 > s2 pak vrací hodnotu větší jak 0.
int strncmp( const char *s1, const char *s2, size_t n);
Jako strcmp(), porovnává však jenom n znaků.
char *strcpy( char *dest, const char *src);
Zkopíruje řetězec src do řetězce dest. Vrací ukazatel na dest.
char *strncpy( char *dest, const char *src, size_t n);
Jako strcpy(), ale zkopíruje maximálně n znaků. (Je-li jich právně n, nepřidá zarážku).
size_t strlen( const char *s);
Vrací délku řetězce s.
char *strchr( const char *s, int c);
Vrací ukazatel na první pozici, kde se vyskytuje znak c, nebo NULL v případě, že jej nenajde.
(Nenechte se zmást, že znak c je typu int a ne char, má to tak být).
char *strrchr( const char *s, int c);
To samé jako strchr(), ale hledá první výskyt zprava.
char *strstr( const char *haystack, const char *needle);
Vrací ukazatel na první výskyt řetězce needle v řetězci haystack. Pokud jej nenajde, vrací NULL.
size_t strspn( const char *s, const char *accept);
Vrací počet znaků, ve kterých se řetězec s schoduje s řetězcem accept.
size_t strcspn( const char *s, const char *reject);
Vrací počet znaků, ve kterých se řetězec s neschoduje s řetězcem reject.
Příklad použití funkcí
Díky zjištění délky prvního řetězce a velikosti pole1 jsme mohli zajistit, aby se z pole2 nezkopírovalo více
znaků, než kolik se jich do pole1 vejde.
1:
2:
4:
5:
7:
8:
9:
10:
12:
13:
14:
15:
16:
/*------------------------------------------------*/
/* retezce.c
*/
#include <stdio.h>
#include <string.h>
int main(void)
{
char c, *ch, pole1[81], pole2[81];
int x;
printf("Zadejte jeden znak a stisknete ENTER: ");
scanf("%c", &c);
printf("Zadejte prvni retezec: ");
scanf("%80s", pole1);
printf("Zadejte druhy retezec: ");
63
17:
scanf("%80s", pole2);
19:
x = strcmp(pole1, pole2);
20:
if (x < 0)
21:
printf("pole1 < pole2\n");
22:
else if (x == 0)
23:
printf("pole1 = pole2\n");
24:
else
25:
printf("pole1 > pole2\n");
27:
printf("Prvnich %i znaku se %s\n", 5,
28:
!strncmp(pole1, pole2, 5) ? "schoduje" : "neschoduje");
29:
printf("Schoduje se presne %i znaku\n", strspn(pole1, pole2));
31:
ch = strchr(pole1, c);
32:
if (ch == NULL)
33:
printf("Znak %c se v retezci \"%s\" nenachazi\n", c, pole1);
34:
else
35:
printf("pole1 od znaku %c: %s\n", c, ch);
37:
x = strlen(pole1);
38:
printf("Delka prvniho retezce: %u\n", x);
39:
strncat(pole1, pole2, sizeof(pole1) - x - 1);
40:
printf("Spojeni: %s\n", pole1);
42:
return 0;
43: }
45: /*------------------------------------------------*/
Možný výstup z programu:
Zadejte jeden znak a stisknete ENTER: y
Zadejte prvni retezec: DlouhyRetezec
Zadejte druhy retezec: Dlouze
pole1 < pole2
// protoze h < z
Prvnich 5 znaku se neschoduje
Schoduje se presne 4 znaku
pole1 od znaku y: yRetezec
Delka prvniho retezce: 13
Spojeni: DlouhyRetezecDlouze
Knihovna time.h
Knihovna <time.h> umožňuje zjistit aktuální čas. Je třeba si však uvědomit, že je to je pojem relativní. V
každém počítači existuje čas lokální a globální (krom toho je také letní a zimní). Globální čas se dříve
označoval jako GMT (Greenwich Mean Time), dnes jako UTC (Coordinated Universal Time). Záleží na
nastavení v počítači. Správně by měl být nastaven čas globální a lokální přepočítávat. Některé informace
o datu a času však knihovna <time.h> neumí. Potřebné výpočty se však dají sestavit (nebo použít nějakou
„nadstandardní“ knihovnu). Například funkce na zjištění přestupného roku se dá napsat takto:
1:
2:
3:
4:
5:
6:
7:
9:
/*---------------------------------------*/
/* funkce vracejici 1 pro prestupny rok, jinak 0. */
/* */
int prestupny_rok(int rok)
{
return (((rok % 4 == 0) && (rok % 100 != 0)) || (rok % 400 == 0));
}
/*---------------------------------------*/
Uchovávání času
Aby bylo možné čas uchovávat v co možná nejmenším počtu bitů, zaznamenává se jako počet sekund od
určitého data. Například v Unixových systémech je to od 1. 1. 1970, v MS-DOSu je to 1. 1. 1980 atp. V
dnešní době je to víceméně zátěž z historických důvodů. S časem uloženým v sekundách se pracuje
pomocí předdefinovaného datového typu time_t. Je to celočíselný typ, například v Linuxu je to typ long.
64
Vzhledem k tomu, že program může být přeložen ve více systémech, nelze podle tohoto čísla datum a čas
jednoznačně určit. Pokud však v programu zaznamenáme dvě takováto čísla, pak jejich odečtením lze
zjistit, kolik sekund mezi jejich zaznamenáním uběhlo. Takovéto jednoduché odečtení však není dle normy
ANSI, proto je potřeba používat funkci difftime(), která je k tomu určena (kvůli přenositelnosti na systémy,
kde time_t není definován jako celé číslo). Lépe se pracuje s dalším předdefinovaným typem – tentokrát se
jedná o strukturu – tm. Její definice vypadá takto:
1: struct tm {
2:
int tm_sec;
3:
int tm_min;
4:
int tm_hour;
5:
int tm_mday;
6:
int tm_mon;
7:
int tm_year;
8:
int tm_wday;
9:
int tm_yday;
10:
int tm_isdst;
11: };
/*
/*
/*
/*
/*
/*
/*
/*
/*
vteřiny 0-61 */
minuty 0-59 */
hodiny 0-23 */
dny v měsíci 1-31 */
měsíc 0-11 */
rok od 1900 */
den v týdnu 0-6, 0 je nedele */
den v roce 0-365 */
příznak letního času*/
Vteřin může být opravdu 62, to není chyba tisku. Prostě to tak bylo definováno?! Příznak tm_isdst je
nulový, pokud není letní čas a záporný, pokud není informace o času dostupná. Pokud je letní čas, je
kladný.
Výčet funkcí
char *asctime(const struct tm *timeptr);
Převede strukturu tm na řetězec ("Wed Jun 30 21:49:08 1993\n"). Návratovou hodnotou je ukazatel
na tento řetězec.
char *ctime(const time_t *timep);
Vrací to samé, jako asctime(localtime(timep));
double difftime(time_t time1, time_t time0);
Vrací rozdíl mezi časem time0 a time1 ve vteřinách.
struct tm *gmtime(const time_t *timep);
Převede čas uložený v timep (pomocí vteřin) na strukturu tm a to v čase UTC (GMT).
Vrací ukazatel na tuto strukturu.
struct tm *localtime(const time_t *timep);
Jako funkce gmtime(), ale vrací lokální čas (nikoliv UTC).
time_t mktime(struct tm *timeptr);
Převádí strukturu na kterou odkazuje timeptr do počtu vteřin, jež jsou návratovou hodnotou.
time_t time(time_t *t);
Vrací počet sekund reprezentující čas (v Unixech počet sekund od 1.1.1970). Tuto hodnotu také
uloží tam, kam ukazuje ukazatel t, pokud nemá hodnotu NULL). Je to jediná funkce, která vrací
aktuální čas.
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
Tato funkce převádí strukturu tm na řetězec. Tento řetězec bude uložen do pole, na které ukazuje
ukazatel s. Maximálně však „max“ znaků. Co bude řetězec obsahovat, to je dáno formátovacím
řetězcem format. Možnosti formátování jsou uvedeny v tabulce:
%a
%A
%b
%B
%c
%d
%H
zkrácený název dne v týdnu
úplný název dne v týdnu
zkrácený název měsíce
úplný název měsíce
datum a čas
den v měsíci 01-31
hodina 00-23
65
%I
%j
%m
%M
%p
%S
%u
%U
%V
%w
%x
%X
%y
%Y
%Z
hodina ve 12-ti hodinovém formátu 01-12
den v roce 001-366
měsíc 01-12
minuty 00-59
am pro dopoledne, pm pro odpoledne
vteřiny 00-61
den v týdnu 1-7, 1 = pondělí
týden v roce 01-53 (neděle je prvním dnem v týdnu)
týden v roce 01-53 (pondělí je prvním dnem v týdnu)
den v týdnu 0-6 (0 = neděle)
datum v lokálním formátu
čas v lokálním formátu
poslední dvě číslice roku 00-99
rok
název časového pásma
Příklad použití funkcí I (sportka)
První příklad je spíš ukázka práce s náhodnými čísly než s časem. Čas je zde využít k inicializaci
náhodných čísel (viz. funkce rand() a srand()). Příklad simuluje losování sportky.
1:
2:
4:
5:
6:
8:
10:
11:
13:
14:
15:
17:
18:
19:
20:
21:
22:
23:
24:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
/*------------------------------------------------*/
/* sportka.c
*/
#include <stdio.h>
#include <stdlib.h>
/* zde jsou definovany funkce rand() a srand() */
#include <time.h>
#define CENA 16
/* korun za vsazeni 1 sloupce */
/* funkce uhodnuto zjisti, kolik cisel z pole cisla[6] je stejnych jako
prvnich 6 cisel v poli tah[7]. Posledni cislo v tomto poli je dodatkove!*/
unsigned int uhodnuto(unsigned int *cisla, unsigned int *tah)
{
unsigned int i, j, vyhra = 0;
for (j = 0; j < 6; j++)
for (i = 0; i < 6; i++)
if (cisla[j] == tah[i]) {
vyhra++;
/* pocet uhadnutych cisel */
break;
/* ukonci vnitrni cyklus */
}
return vyhra;
}
/* funkce vyhra_sportky urci na zaklade uhodnutych cisel vyhru.
Pri uhodnuti 5 cisel a 6te je stejne jako dodatkove, pak mate "druhou" */
unsigned int vyhra_sportky(unsigned int uhodnute, unsigned int *cisla,
unsigned int *tah)
{
unsigned int i, vyhra = 0;
switch (uhodnute) {
case 6:
vyhra = 1;
/* mate prvni */
break;
case 5:
vyhra = 3;
/* mate treti */
66
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
54:
55:
56:
57:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
79:
80:
81:
82:
83:
84:
85:
86:
87:
89:
90:
91:
92:
93:
95:
96:
98:
99:
102:
103:
105:
106:
/* ale mozna mame druhou */
for (i = 0; i < 6; i++)
if (cisla[i] == tah[6]) {
vyhra = 2;
/* mame druhou */
break;
}
break;
case 4:
vyhra = 4;
break;
case 3:
vyhra = 5;
/* mate patou */
break;
default:
/* to je tu trosku zbytecne, protoze vyhra = 0 je jiz vyse,
ale neni to na skodu uz kvuli srozumitelnosti kodu */
vyhra = 0;
/* nic jste nevyhrali */
}
return vyhra;
}
/* tato funkce urci, kolik penez jste vyhrali */
unsigned long penez(unsigned int vyhra)
{
switch (vyhra) {
case 5:
return rand() % 50 + 30;
case 4:
return rand() % 500 + 250;
case 3:
return rand() % 5000 + 1000;
case 2:
return rand() % 500000 + 50000;
case 1:
return (unsigned long) ((rand() % 1000000) + 1) * (rand() % 10) +
1000000;
default:
return 0;
}
}
/* kontroluje, zda cislo exituje v poli zadane delky */
int existuje(unsigned int cislo, unsigned int *pole, unsigned int delka)
{
unsigned int i;
for (i = 0; i < delka; i++)
if (cislo == pole[i])
return 1;
return 0;
/* cislo se nenaslo */
}
int main(void)
{
unsigned int i, cisla[6], tah[7], vyhra, uhodnute, finance, vyhrano;
unsigned int chci_vyhrat;
unsigned long vyhry[] = { 0, 0, 0, 0, 0, 0 };
/* nic, prvni, druha, treti ... vyhra "nic" bude zrejme velmi casto,
proto je pole typu long int */
unsigned long cyklus = 0;
time_t cas1, cas2;
(void) time(&cas1);
/* navratovou hodnotu ignorujeme */
srand((unsigned int) time(NULL));
/* vystupni hodnotu musime z
formatu time_t zkonvertovat na typ unsigned int */
/* inicializace */
vyhrano = 0;
printf("Zadejte poradi, ktere chcete vyhrat (0,1,2,3,4,5): ");
scanf("%u", &chci_vyhrat);
67
108:
/* losovani vsazenych cisel do sportky */
109:
for (i = 0; i < 6; i++) {
110:
cisla[i] = (rand() % 49) + 1;
111:
if (existuje(cisla[i], cisla, i))
112:
i--; /* cislo jiz bylo vylosovano, bude se losovat znovu */
114:
}
116:
/* tisk */
117:
printf("Vsazena cisla:");
118:
for (i = 0; i < 6; i++)
119:
printf("%3u", cisla[i]);
120:
printf("|\n");
122:
printf("Cyklus :%-23s| Vyhrano
Vyhry: 1 2 3 4 5 0\n",
123:
" Vylosovane Cisla");
125:
/* cyklus, dokud nevyhraji co chci */
126:
do {
127:
/* losovani sportky */
128:
for (i = 0; i < 7; i++) {
129:
tah[i] = (rand() % 49) + 1;
130:
if (existuje(tah[i], tah, i))
131:
i--; /* cislo jiz bylo vylosovano, bude se losovat znovu */
133:
}
135:
/* tisk vysledku */
136:
printf("\r%8ld:", ++cyklus);
138:
for (i = 0; i < 6; i++)
139:
printf("%3u", tah[i]);
140:
printf(" /%3u", tah[6]);
/* dodatkove cislo */
142:
uhodnute = uhodnuto(cisla, tah);
/* pocet uhodnutych cisel */
143:
vyhra = vyhra_sportky(uhodnute, cisla, tah); /* prvni,druha atp.*/
144:
finance = penez(vyhra);
145:
vyhry[vyhra] += 1;
/* vyhry[vyhra]++; */
147:
vyhrano += finance;
148:
printf("|%10ld[Kc]
", (unsigned long) vyhrano - cyklus * CENA);
150:
for (i = 1; i < 6; i++)
151:
printf(" %2ld", vyhry[i]);
153:
printf(" %2ld", vyhry[0]);
154:
/* funkce fflush() zajisti, ze program nebude pokracovat dale, dokud se
155:
vystup skutecne nezobrazi v stdout (tj. na obrazovce) */
156:
fflush(stdout);
157:
if (!chci_vyhrat)
158:
break;
/* nechci nic vyhrat, tj. chci_vyhrat == 0 */
159:
} while ((chci_vyhrat < vyhra) || (vyhra == 0));
161:
cas2 = time(NULL);
/* ziskame totez jako (void) time(&cas2); */
162:
printf("\nZiskal jste %i - %i*%ld = %ld Kc\n", vyhrano, CENA, cyklus,
163:
vyhrano - CENA * cyklus);
164:
printf("Cele to trvalo %u vterin.\n",
165:
(unsigned int) difftime(cas2, cas1));
167:
return 0;
168: }
170: /*------------------------------------------------*/
Možný výstup:
Zadejte poradi, ktere chcete vyhrat (0,1,2,3,4,5): 1
Vsazena cisla: 45 30 26 25 1 3|
Cyklus : Vylosovane Cisla
| Vyhrano
Vyhry: 1 2 3 4 5 0
4843101: 1 26 30 3 45 25 / 49| -68238394[Kc] 1 1 77 4696 85315 4753011
Ziskal jste 9251222 - 16*4843101 = -68238394 Kc
Cele to trvalo 968 vterin.
Příklad použití funkcí II
1: /*------------------------------------------------*/
2: /* tm1.c
*/
4: #include <stdio.h>
68
5:
6:
8:
9:
10:
11:
13:
14:
15:
17:
19:
20:
22:
23:
24:
26:
27:
28:
30:
32:
33:
34:
35:
36:
38:
39:
41:
#include <time.h>
#define MAX 80
int main(void)
{
time_t cas;
/* je lhostejno pracujeme-li se strukturou tm nebo ukazatelem na ni */
struct tm gtmcas;
struct tm *ltmcas;
char retezec[MAX];
cas = time(NULL);
gtmcas = *(gmtime(&cas));
ltmcas = localtime(&cas);
/* retezec vraceny funkcemi ctime a asctime je v anglictine a take
ukoncen znakem '\n' (coz je v praxi trochu neprijemne). Vyrazy LC a UTC
nam kvuli tomu prejdou az na dalsi radek */
printf("Je prave \"%s\" LC\n", ctime(&cas));
printf("Je prave \"%s\" GMT\n", asctime(&gtmcas));
printf("Je prave \"%s\" LC\n", asctime(ltmcas));
strftime(retezec,MAX - 1,"Je prave %A%d.%m.%Y,%I:%M %p LC", ltmcas);
printf("%s\n", retezec);
printf("Tm(LC): %02i.%02i.%04i\n", ltmcas->tm_wday, ltmcas->tm_mon,
ltmcas->tm_year + 1900);
cas = (time_t) 0;
printf("Pocatek vseho je %s", ctime(&cas));
return 0;
}
/*------------------------------------------------*/
Možný výstup:
Je prave "Fri Aug 29 15:31:04 2008
" LC
Je prave "Fri Aug 29 13:31:04 2008
" GMT
Je prave "Fri Aug 29 15:31:04 2008
" LC
Je prave Friday 29.08.2008, 03:31 PM LC
Tm(LC): 05.07.2008
Pocatek vseho je Thu Jan 1 01:00:00 1970
Knihovna math.h
Standardní knihovna <math.h> obsahuje spoustu funkcí, které vykonávají běžné matematické operace.
Jejich parametry i návratové hodnoty jsou většinou typu double. Kromě funkcí jsou v této knihovně také
užitečná makra, jako M_PI jež je hodnotou pí. V systémech UNIX bude možná potřeba překladači předat
argument -lm, který zajistí použití sdílené matematické knihovny systému UNIX. Tento parametr je
zkratkou pro /usr/lib/libm.a. Příkaz překladu z příkazové řádky pak může vypadat nějak takto:
gcc -o program kod.c -lm –Wall
Výčet funkcí
double sin(double x);
Vrací sinus argumentu.
double cos(double x);
Vrací kosinus argumentu.
double tan(double x);
Vrací tangens argumentu.
double asin(double x);
Vrací arcu ssinus argumentu.
double acos(double x);
Vrací arkus kosinus argumentu.
double atan(double x);
69
Vrací arkus tangens argumentu.
double atan2(double y, double x);
Funkce atan2() počítá arkus tangens dvou proměnných x a y. Výpočet je podobný výpočtu arkus
tangens y / x, kromě toho, že znaménka obou argumentů jsou použita pro určení kvadrantu, ve
kterém se nachází výsledná hodnota. Funkce atan2() vrací výsledek v radiánech, který je mezi -PI
a PI včetně.
int abs(int j);
Vrací absolutní hodnotu argumentu typu int. Tato funkce není kupodivu definována v <math.h> ale
<stdlib.h>. Přesto paří mezi matematické.
double fabs(double j);
Vrací absolutní hodnotu argumentu typu double.
double aceil(double x);
Zaokrouhlí argument na nejmenší „vyšší“ celé číslo.
double floor (double x);
Zaokrouhlí argument na nejbližší „nižší“ celé číslo.
double round(double x);
Zaokrouhlí číslo na nejbližší celé číslo.
double exp(double x);
Vrací hodnotu e (základ přirozených algoritmů) umocněnou na x.
double log(double x);
Vrací hodnotu přirozeného algoritmu x.
double log10(double x);
Vrací hodnotu desítkového logaritmu x
double pow(double x, double y);
Vrací hodnotu x na y (2^3=8).
double sqrt(double x);
Vrací odmocninu z čísla x.
double fmod(double x, double y);
Funkce fmod() počítá hodnotu zbytku podílu x děleno y. Návratová hodnota je x - n * y, kde n je
podíl x / y, zaokrouhlený směrem k nule na celé číslo. Jinak řečeno, vrací celočíselný zbytek po
celočíselném dělení.
double modf(double x, double *iptr);
Vrátí desetinnou část x a celou část uloží tam, kam ukazuje iptr.
Použití těchto funkcí nepotřebuje žádného komentáře.
1:
2:
3:
4:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
17:
18:
20:
21:
22:
23:
25:
26:
28:
/*------------------------------------------------*/
/* matika.c
*/
#include <stdio.h>
#include <math.h>
int main(void)
{
int ia;
float fa;
double da;
/* kresleni sinusove vlny */
for (fa = 0.0; fa < 2.0 * M_PI; fa += M_PI / 13) {
ia = 40 + 20.0 * sin(fa);
printf("%*c\n", ia, '*');
}
printf("ceil(%f)=%f\tfloor(%f)=%f\n", 5.9, ceil(5.9), 5.9, floor(5.9));
printf("ceil(%f)=%f\tfloor(%f)=%f\n", 5.1, ceil(5.1), 5.1, floor(5.1));
#define MODF 5.5
fa = modf(MODF, &da);
printf("fmod(%4.2f,%4.2f)=%4.2f\nmodf(%4.2f,&y)=%4.2f --> y=%4.2f\n",
100.0, 3.0, fmod(100.0, 3.0), MODF, fa, da);
return 0;
}
/*------------------------------------------------*/
70
Výstup z programu:
*
*
ceil(5.900000)=6.000000 floor(5.900000)=5.000000
ceil(5.100000)=6.000000 floor(5.100000)=5.000000
fmod(100.00,3.00)=1.00
modf(5.50,&y)=0.50 --> y=5.00
Knihovna stdlib.h
Výčet funkcí (výčet není úplný)
long int strtol(const char *nptr, char **endptr, int base);
Převede řetězec nptr na číslo vzhledem k číselné soustavě base. Adresu prvního znaku, který
nepatří do čísla, uloží do endptr, pokud endptr není nastaveno na NULL.
int atoi(const char *nptr);
Převede řetězec nptr na číslo typu int. Tato funkce nedetekuje žádné chyby. Její chování je
totožné s voláním strtol(nptr, (char **)NULL, 10);
double atof(const char *nptr);
Převede řetězec nptr na číslo typu double. Tato funkce nedetekuje žádné chyby.
long atol( const char *nptr);
Převede řetězec nptr na číslo typu long int. Tato funkce nedetekuje žádné chyby.
void srand( unsigned int seed);
Inicializuje generátor pseudonáhodných čísel.
int rand( void);
Vrací pseudonáhodná čísla mezi 0 a RAND_MAX
void *calloc( size_t nmemb, size_t size);
Dynamicky alokuje paměť nmemb položek velikosti size. Do alokované paměti zapíše nuly. Vrací
ukazatel na alokovanou paměť nebo NULL v případě chyby.
void *malloc( size_t size);
Dynamicky alokuje paměť velikosti size. Paměť nevynuluje. Vrací ukazatel na alokovanou paměť
nebo NULL v případě chyby.
void free( void *ptr);
Uvolní dynamicky alokovanou paměť (pomocí malloc(), calloc() nebo realloc()) na kterou ukazuje
ptr.
void *realloc( void *ptr, size_t size);
Změní velikost dynamicky alokované paměti ptr na velikost size.
void abort( void);
Způsobí abnormální ukončení programu. Chování funkce je implementačně závislé.
void exit( int status);
Ukončí program kdekoliv v programu. Program pak vrací hodnotu status. Všechny funkce
registrované funkcemi atexit() a on_exit() jsou vyvolány v opačném pořadí jejich registrace.
int atexit( void (* function)(void));
Přiřadí funkci do seznamu funkcí, které jsou volány při normálním ukončení programu funkcí exit
nebo skončením funkce main(). Argument function je ukazatelem na funkci bez argumentů a bez
návratové hodnoty. Funkce takto registrované jsou volány v opačném pořadí než jsou registrovány.
Funkce atexit vrací 0 v případě úspěchu, -1 v případě chyby (např. nedostatek paměti pro registraci
funkce).
int system (const char *string);
Volá systémový příkaz určený řetězcem string. Například tak můžete zavolat příkaz DOSu
"COPY c:\soubor1.txt c:\soubor2.txt". V Linuxu se nedoporučuje používat funkci system v
programech se superuživatelskými právy. Toho by šlo totiž snadno zneužít. Nepodaří-li se spustit
shell (příkazový řádek), vrací funkce system hodnotu 127, v případě jiné chyby -1. V ostatních
případech vrací návratový kód příslušného příkazu.
char *getenv( const char *name);
Vyhledá hodnotu prostředí systému určenou řetězcem name. Vrátí ukazatel na tuto hodnotu, nebo
NULL v případě neúspěšného hledání. Prostředí je tvořeno řetězci ve formě název=hodnota. Tuto
71
funkci budou asi využívat hlavně příznivci Linuxu, kde se jedná například o hodnoty USER,
LOGNAME, HOME, PATH, SHELL, TERM apod.
int abs( int j);
Vrací absolutní hodnotu argumentu typu int.
long int labs(long int j);
Vrací absolutní hodnotu argumentu typu long int.
div_t div(int numer, int denom);
Počítá podíl numer/denom. Podíl a zbytek po dělení vrací ve struktuře div_t. Tato struktura
obsahuje složku quot, která obsahuje podíl a složku rem, která obsahuje zbytek. Obě složky jsou
celočíselné. Struktura div_t je definována v <stdlib.h>
ldiv_t ldiv( long int numer, long int denom);
Jako funkce div, jenom je všechno v dlouhých celých číslech.
void qsort( void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
Funkce třídí algoritmem quicksort souvislé pole libovolného typu (!), na které ukazuje base o
nmemb položkách velikosti size. K třídění je použita funkce na kterou ukazuje compar. Funkci
compar() budou při volání předávány jako argumenty prvky z pole base. Funkce compar() musí
vracet zápornou hodnotu, pokud je první argument menší než druhý, nulovou hodnotu pokud se
rovnají a kladnou hodnotu pokud je první argument větší než druhý.
Užitečná makra
Kromě těchto funkcí jsou v souboru <stdlib.h> definována i některá užitečná makra. Jejich používání
zvyšuje přenositelnost programu.
EXIT_FAILURE
Hodnota argumentu funkce exit pro neúspěšné ukončení programu. (nenulová hodnota)
EXIT_SUCCESS
Hodnota argumentu funkce exit pro úspěšné ukončení programu. (nulová hodnota)
NULL
Nulový ukazatel.
RAND_MAX
Největší hodnota generovaná funkcí rand().
Poznámky k funkcím
Funkce exit() ukončí program kdekoliv je volána. Volání této funkce je stejné, jako volání return x ve
funkci main(). Hodnota x je vrácena do prostředí, ve kterém je program spuštěn, popřípadě procesu, který
tento program spustil. Tuto hodnotu lze testovat třeba v dávkových souborech (.BAT) v DOSu nebo v
unixových skriptech shellu. Nulová návratová hodnota se považuje za bezchybné ukončení programu.
Návratovou hodnotou různých čísel lze informovat o různých příčinách konce programu. Některé návratové
hodnoty však mají v různých systémech různý specifický význam. Proto, je vhodné ukončovat funkce
(program) s návratovou hodnotou 0 (EXIT_SUCCESS) v případě úspěchu a 1 (EXIT_FAILURE) v případě
neúspěchu.
V systémech Unix je možné zjistit návratový kód programu v systémové proměnné ?. Například příkazem
echo $?.
Funkce exit() navíc před ukončením programu (stejně jako return ve funkci main()) spustí funkce
registrované pomocí atexit(). Tyto registrované funkce jsou volány vždy při skončení programu, nedojde-li
k jeho ukončení funkcí abort() nebo k jinému nestandardnímu konci programu. Využít se toho dá mnoha
způsoby. Například lze vytvořit při spuštění programu nějaký dočasný soubor, který takto registrovaná
funkce smaže. Pokud při spuštění programu soubor existuje, znamená to, že je program spuštěný vícekrát,
nebo že byl nestandardně ukončen. Funkce system() udělá zhruba to, co by udělal operační systém po
zadání příkazu, který je argumentem funkce system. Použití bude v příkladu. Použití funkce getenv() je
72
silně závislé na prostředí, ve kterém je program spouštěn. (viz. programování v Linuxu.) Využívá se
například v souvislosti s dávkovými soubory.
Dále je ukázka definice struktury div_t. Hlavně pozor na dělení nulou!.
1: typedef struct {
2:
int quot;
3:
int rem;
4: } div_t;
/* Quotient. */
/* Remainder. */
Funkce qsort() třídí prvky na základě návratové hodnoty funkce, na kterou ukazuje compar (viz ukazatele
na funkce). Jelikož je tento ukazatel deklarován jako int (*compar)(const void *, const void *);, je třeba
funkci která provádí porovnávání prvků z tříděného pole přetypovat To se provede následujícím způsobem.
qsort(tridene_pole, POCET_PRVKU_POLE, sizeof(prvek_pole), (int (*) (const void *,
const void *)) tridici_funkce);
V prvním příkladu je vytvořeno pole struktury cisla obsahující typ div_t a dvě čísla, jejichž podíl a zbytek po
dělení bude v právě v proměnné typu div_t. Toto pole pak bude setříděno pomocí qsort(). K porovnávání
prvků si slouží funkce srovnej_cislo().
1:
2:
4:
5:
7:
9:
10:
11:
12:
14:
15:
17:
18:
19:
20:
21:
24:
25:
26:
27:
28:
29:
30:
32:
33:
34:
35:
36:
37:
38:
39:
40:
42:
43:
45:
46:
47:
49:
50:
52:
/*------------------------------------------------*/
/* qsort1.c
*/
#include <stdio.h>
#include <stdlib.h>
#define DELKAPOLE 20
typedef struct {
int x, y;
div_t podil;
} cisla;
int srovnej_cislo(cisla * a, cisla * b)
{
if (a->podil.rem > b->podil.rem) /* nejdrive tridime podle zbytku */
return 1;
else if (a->podil.rem < b->podil.rem)
return -1;
/* kdyz jsou si zbytky rovny, tridime podle podilu */
else if (a->podil.quot > b->podil.quot)
return 1;
else if (a->podil.quot < b->podil.quot)
return -1;
else
return 0;
/* jsou si rovny */
}
int main(void)
{
int i;
cisla pole[DELKAPOLE];
for (i = 0; i < DELKAPOLE; i++) {
pole[i].x = i;
pole[i].y = 5;
pole[i].podil = div(pole[i].x, pole[i].y);
}
qsort(pole, sizeof(pole) / sizeof(pole[0]), sizeof(pole[0]),
(int (*)(const void *, const void *)) srovnej_cislo);
for (i = 0; i < DELKAPOLE; i++)
printf("%2i/%2i = %2i (%2i)\n", pole[i].x, pole[i].y,
pole[i].podil.quot, pole[i].podil.rem);
return 0;
}
/*------------------------------------------------*/
73
Výstup z programu:
0/ 5 =
5/ 5 =
10/ 5 =
15/ 5 =
1/ 5 =
6/ 5 =
11/ 5 =
16/ 5 =
2/ 5 =
7/ 5 =
0 ( 0)
1 ( 0)
2 ( 0)
3 ( 0)
0 ( 1)
1 ( 1)
2 ( 1)
3 ( 1)
0 ( 2)
1 ( 2)
12/ 5 =
17/ 5 =
3/ 5 =
8/ 5 =
13/ 5 =
18/ 5 =
4/ 5 =
9/ 5 =
14/ 5 =
19/ 5 =
2 ( 2)
3 ( 2)
0 ( 3)
1 ( 3)
2 ( 3)
3 ( 3)
0 ( 4)
1 ( 4)
2 ( 4)
3 ( 4)
V následujícím příkladu jsou použity funkce exit(), abort() a atexit(). Program bude vracet hodnotu, kterou
uživatel zadá z příkazové řádky.
1:
2:
4:
5:
7:
8:
9:
10:
12:
13:
14:
15:
17:
18:
20:
21:
24:
25:
26:
27:
28:
30:
31:
33:
/*------------------------------------------------*/
/* konec.c
*/
#include <stdio.h>
#include <stdlib.h>
void koncim1(void)
{
printf("Elektrina odpojena!\n");
}
void koncim2(void)
{
printf("Systemy vypnuty!\n");
}
int main(int argc, char *argv[])
{
atexit(koncim1); /* nejdrive probehne funkce koncim2 a pak koncim1,
atexit(koncim2);
tedy v obracenem poradi nez byly registrovany */
if (argc != 2) {
printf("Na prikazove radce nebyla zadana navratova hodnota!\n");
abort();
}
exit(atoi(argv[1]));
return 0;
/* tato cast programu za exit nikdy neprobehne */
}
/*------------------------------------------------*/
Chování programu v systému Linux.
konec
// prikaz v Linuxu - spusteni programu
Na prikazove radce nebyla zadana navratova hodnota!
Neúspěšně ukončen (SIGABRT)// hlaseni operacniho systemu
echo $?
// prikaz v Linuxu - vypis navratove hodnoty programu
134
V DOSu se místo „Neúspěšně ukončen (SIGABRT)“ ukáže „Abnormal program termination“. Dalším
možnost spuštění (v Linuxu):
konec 5
Systemy vypnuty!
Elektrina odpojena!
echo $?
5
Tento program (se jménem konec) bude využit i v následujícím příkladu (system1). Použita bude
funkce system(), která bude program konec volat. Program konec se bude hledat ve stejném adresáři, ve
kterém bude program system1 spuštěn.
74
Jména programů jsou v systému DOS a Windows samozřejmě zakončena příponou .exe, ale to na příkladu nic
nemění.
Problém je však v návratové hodnotě funkce system(). V případě neúspěšného spuštění příkazového
interpretu (např. bash nebo command.com) vrátí 127, v případě neúspěchu spuštění příkazu (např. příkaz
neexistuje) vrátí -1. V případě, že příkaz (zadaný funkci system() jako textový řetězec) proběhne, vrátí
funkce system() jeho návratový kód. Ovšem v Linuxu je tento návratový kód pozměněn a proto se musí na
něj aplikovat makro WEXITSTATUS definované v souboru <sys/wait.h>. Proto je v příkladu použit
podmíněný překlad a pro neunixové systémy je makro WEXITSTATUS definováno tak, že nic nedělá.
1:
2:
4:
5:
7:
8:
9:
11:
12:
13:
15:
16:
17:
19:
20:
21:
22:
24:
25:
26:
28:
29:
31:
/*------------------------------------------------*/
/* system1.c
*/
#include <stdio.h>
#include <stdlib.h>
#ifdef unix
#include <sys/wait.h>
#endif
#ifndef unix
#define WEXITSTATUS(X) X
#endif
int main(void)
{
int navrat;
printf("Spoustim program> konec\n");
navrat = system("konec");
printf("Program skoncil s navratovou hodnotou %i\n\n",
WEXITSTATUS(navrat));
printf("Spoustim program> konec 5\n");
navrat = system("konec 5");
printf("Program skoncil (%i)\n\n", WEXITSTATUS(navrat));
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
Spoustim program> konec
Na prikazove radce nebyla zadana navratova hodnota!
Neúspěšně ukončen (SIGABRT)
Program skoncil s navratovou hodnotou 134
Spoustim program> konec 5
Systemy vypnuty!
Elektrina odpojena!
Program skoncil (5)
Knihovny stdarg.h, limits.h a signal.h
Knihovna stdarg.h
Tato knihovna slouží k práci funkcí s proměnlivým počtem argumentů. Popis maker, které obsahuje a jejich
použití je probráno v kapitole o proměnlivém počtu argumentů.
Knihovna limits.h
Knihovna <limits.h> obsahuje makra určující rozsah hodnot celočíselných datových typů. Tyto makra lze
použít i v preprocesoru pro podmíněný překlad, jinak se nejčastěji používají v podmínkách programu.
Hodnoty, uvedené v tabulce jsou jen orientační a v různých implementacích překladačů se liší (jinak by
snad ani neměli význam). Například INT_MAX je pro 16bitové překladače 32767 a pro 32bitové
2147483647. Makra začínající písmenem U jsou pro čísla unsigned (čísla bez znaménka, např UINT_MAX.
75
Jde vždy o hodnotu MAX, protože MIN je u čísel bez znaménka pochopitelně 0 (např. UINT_MIN není
definováno)).
Konstanty z knihovny <limits.h>
Makro
Hodnota
Význam
8 Počet bitů v datovém typu char.
CHAR_BIT
-128 Minimální hodnota typu signed char (typ char se znaménkem).
SCHAR_MIN
SCHAR_MAX
127 Maximální hodnota typu signed char
UCHAR_MAX
255 Maximální hodnota typu unsigned char
-32768
SHRT_MIN
Minimální hodnota typu signed short int (krátké celé číslo se
znaménkem)
SHRT_MAX
32767 Maximální hodnota typu signed short int
USHRT_MAX
65535 Maximální hodnota typu unsigned short int
INT_MIN
INT_MAX
(-INT_MAX -1) Minimální hodnota typu signed int
2147483647 Maximální hodnota typu signed int
UINT_MAX
4294967295U Maximální hodnota typu unsigned int
LONG_MAX
2147483647L
LONG_MIN
ULONG_MAX
(-LONG_MAX 1L)
4294967295UL
Knihovna signal.h
Signály slouží k oznámení nějaké události. Například při dělení nulou je poslán signál SIGFPE. Ve chvíli,
kdy je programu poslán signál, provádění programu se pozastaví a signál je zpracován příslušnou funkcí
signálu přiřazenou. Některé signály lze ignorovat a některé naopak nelze ani zpracovat ani ignorovat (např.
signál SIGKILL). Pokud signálu není žádná funkci přiřazena, bude provedena standardní obsluha signálu.
Například při dělení nulou to bude mít za následek ukončení programu. Signály nemusí být vyvolány jen
událostmi programu, ale mohou být posílány programu operačním systémem nebo jinými programy (v
Linuxu můžete programu poslat signál pomocí příkazu kill()).
Tabulka signálů ANSI
Signál
Význam
SIGINT
Přerušení provádění programu
SIGABRT Zastavení programu
SIGILL
Neplatná instrukce (např. instrukce procesoru Pentium na stroji i386
SIGFPE
Aritmetická chyba (dělení nulou, výsledek mimo rozsah)
SIGSEGV Chybný přístup do paměti.(Program čte z míst v paměti, ze kterých by neměl).
SIGTERM Ukončení (linuxový příkaz kill)
76
Tabulka signálů POSIX
Signál
Význam
SIGHUP
problém
SIGQUIT
opuštění terminálu
SIGKILL
zabití procesu (nelze odchytit)
SIGUSR1
signál definovaný uživatelem
SIGUSR2
další signál definovaný uživatelem
SIGPIPE
nefungující roura (např. když z ní nikdo nečte)
SIGALARM budík
SIGCHLD
dceřiný proces se změnil (např. se zastavil nebo skončil)
SIGCONT
pokračovat (byl-li proces pozastaven)
SIGSTOP
zastavit provádění (nelze odchytit)
SIGTSTP
zastavit provádění (signál z klávesnice)
SIGTTIN
čtení procesu spuštěného na pozadí (ze standardního vstupu)
SIGTTOU
proces na pozadí se pokouší zapisovat
Funkce signal()
Pokud některý ze signálů SIGHUP – SIGALARM není v programu odchycen, dojde k ukončení běhu
programu. K odchytávání signálů, respektive k přiřazení signálům funkce slouží funkce signal(). Zde je její
deklarace:
void (*signal(int signum, void (*handler)(int)))(int);
Funkce signal má jako první argument (signum) signál, kterému je přiřazena funkce handler. Funkce
handler() nemá žádnou návratovou hodnotu a musí mít jeden argument typu int, kterým je předávána
hodnota signálu, který způsobil volání funkce. Jedna funkce totiž může být přiřazena libovolnému množství
signálů. Funkce signal vrací ukazatel na funkci, která byla signálu dosud přiřazena, SIG_ERR v případě
chyby, nebo jednu z následujících hodnot:
Návratové hodnoty funkce signal
Makro
Význam
SIG_IGN ignoruj signál
SIG_DFL implicitní chování funkce
Tyto hodnoty lze použít ve funkci signal jako druhý argument (tedy místo funkce obsluhující signál). V
příkladu bude odchytáván signál SIGFPE po dělení nulou. Nejdříve je vytvořena globální proměnná
chyceni_signalu, ve které bude započítáváno, kolikrát byl signál vyvolán. Po skončení funkce, obsluhující
signál, se program vrátí do místa ve kterém byl přerušen. V našem případě to znamená, že se pokusí
znovu dělit nulou.
1:
2:
4:
5:
7:
9:
/*------------------------------------------------*/
/* signal1.c
*/
#include <stdio.h>
#include <signal.h>
static int chyceni_signalu;
int a = 5, b = 0;
77
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
32:
33:
35:
37:
38:
39:
40:
41:
43:
44:
45:
47:
48:
50:
void zpracuj_signal(int cislo_signalu)
{
chyceni_signalu++;
switch (cislo_signalu) {
case SIGFPE:
printf
("Delit nulou se nevyplaci! a= %i, b= %i, chyceni_signalu= %i\n",
a, b, chyceni_signalu);
break;
default:
printf("Neocekavany signal!\n");
break;
}
if (chyceni_signalu >= 3) {
printf("Uz toho mam dost!\n");
(void) signal(SIGFPE, SIG_DFL);
} else {
(void) signal(SIGFPE, zpracuj_signal);
}
}
int main(void)
{
void (*ukazatel_na_fci) (int);
ukazatel_na_fci = signal(SIGFPE, zpracuj_signal);
if (ukazatel_na_fci == SIG_DFL) {
printf
("To je poprve, co nastavuji zpracovani signalu pro SIGFPE\n");
}
printf("Zaciname: \n");
printf("Deleni: %i/%i=%i\n", a, b, a / b);
printf("Tento text se jiz nevytiskne.");
return 0;
}
/*------------------------------------------------*/
Výstup v prostředí Linux: To je poprve, co nastavuji zpracovani signalu pro SIGFPE
Zaciname:
Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 1
Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 2
Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 3
Uz toho mam dost!
Výjimka matematického koprocesoru (SIGFPE)
Převzato z manuálových stránek k funkci signal() : Podle normy POSIX není definováno chování procesu
poté, co ignoruje signál SIGFPE, SIGILL, nebo SIGSEGV, který nebyl generován funkcemi kill nebo raise.
Celočíselné dělení nulou dává nedefinovaný výsledek a na některých architekturách generuje signál
SIGFPE. Ignorování tohoto signálu může způsobit zacyklení procesu.
Pokud by byl v programu uveden výraz ve kterém by bylo dělení nulou přímo a ne přes proměnné jako v
předchozím příkladě, pak by to překladač odhalil a buď by program nepřeložil, nebo by za výraz dosadil
nulu. V takovém případě by za běhu programu k dělení nulou nedošlo.
Dnes platí, že obslužná funkce zůstává stále zaregistrována. Kvůli tomuto historickému zmatení se
doporučuje místo funkce signal() používat funkci sigaction().
78
Funkce sigaction()
Funkce signal() volá funkci sigaction(). Funkce sigaction() je o něco složitější, protože má více možností
jak ovlivnit chování signálů. Zde bude ukázáno, jak tuto funkci použít namísto signal().
Funkce sigaction() odpovídá normě POSIX. To znamená, že je zaručeno, že bude definována v Unixech,
ale ve Windows pravděpodobně k dispozici nebude. Ve windows si musíme vystačit s funkcí signal() (viz
výše).
Deklarace funkce vypadá takto:
int sigaction(int signum, const struct sigaction *act, struct sigaction
*oldact);
Návratovou hodnotou je 0 v případě úspěchu a -1 v případě chyby (v programu, ač se to správně nedělá,
bude návratová hodnota ignorována). První argument je signál, který chceme odchytávat, druhý argument
je struktura, která nastavuje odchytávání signálu a do třetího argumentu se uloží staré nastavení pro daný
signál signum.
Definice struktury sigaction ( má stejné jméno jako funkce sigaction()).
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
První argument struktury sigaction je ukazatel na obslužnou funkci. Druhý argument ovlivňuje zachytávání
signálů během volání obslužné funkce a pro nás je nezajímavý. Třetí argument, sa_flags, je množina
příznaků, která ovlivňuje obsluhu signálu. Například SA_RESTART způsobí, že po zpracování signálu je
znovu nastavena naše obsluha signálu a nikoliv implicitní akce. Poslední argument, sa_restorer, je
zastaralý a již se nepoužívá.
Příklad sigaction.c dělá to samé, co příklad signal1.c
1:
2:
4:
5:
7:
9:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
/*------------------------------------------------*/
/* sigaction.c
*/
#include <stdio.h>
#include <signal.h>
static int chyceni_signalu;
int a = 5, b = 0;
void zpracuj_signal(int cislo_signalu)
{
chyceni_signalu++;
struct sigaction sa = { zpracuj_signal, 0, SA_RESTART };
switch (cislo_signalu) {
case SIGFPE:
printf
("Delit nulou se nevyplaci! a= %i, b= %i, chyceni_signalu= %i\n",
a, b, chyceni_signalu);
break;
default:
printf("Neocekavany signal!\n");
break;
}
79
25:
26:
27:
28:
29:
30:
32:
33:
35:
36:
38:
39:
40:
41:
42:
44:
45:
46:
48:
49:
if (chyceni_signalu >= 3) {
printf("Uz toho mam dost!\n");
sa.sa_handler = SIG_DFL;
}
(void) sigaction(SIGFPE, &sa, NULL);
}
int main(void)
{
void (*ukazatel_na_fci) (int);
struct sigaction sb, sa = { zpracuj_signal, 0, SA_RESTART };
(void) sigaction(SIGFPE, &sa, &sb);
if (sb.sa_handler == SIG_DFL) {
printf
("To je poprve, co nastavuji zpracovani signalu pro SIGFPE\n");
}
printf("Zaciname: \n");
printf("Deleni: %i/%i=%i\n", a, b, a / b);
printf("Tento text se jiz nevytiskne.");
return 0;
}
51: /*------------------------------------------------*/
Knihovna errno.h
Mnoho funkcí může z nějakého důvodu selhat. Například funkce která má otevřít soubor buď proto, že
soubor neexistuje, nebo k němu program nemá přístupová práva. Nebo funkce, která má spočítat
logaritmus čísla, dostane jako argument zápornou hodnotu, nebo nulu. K tomu, aby bylo možné zjistit, zda
a k jaké chybě došlo, slouží knihovna <errno.h>.
Proměnná errno
Knihovna <errno.h> definuje jedinou proměnnou, a to extern int errno;
Díky tomu se tak dá při vytváření knihovny nebo předkompilované části programu pracovat s proměnnou, která
ještě nebyla nikde deklarována, takže fakticky v době překladu (knihovny, části programu, ...) ještě nemusí
existovat.
Při startu programu je proměnná errno nastavena na nulu (tj. bezchybný stav). Když proběhne nějaká
funkce, která umí s touto proměnnou pracovat, nastaví ji na nějakou hodnotu. Například 0, když se nic
špatného nestalo. Z toho vyplývá, že pro odhalení chyby v nějaké funkci, je nutno
proměnnou errno testovat ihned po ukončení kritické funkce. (Další funkce může tuto hodnotu přepsat.)
Použití errno se hodí především tam, kde funkce nemůže oznámit chybu svou návratovou hodnotou. Aby
byla práce s touto proměnnou přehlednější, byla definována dvě standardní makra, která reprezentují
možné chyby.
Standardní chyby definované v <errno.h>
EDOM
ERANGE
Označuje chybu v rámci definičního oboru funkce.
Označuje chybu v rámci oboru hodnot funkce.
Příklad využívá proměnnou errno k odhalení a určení chyby ve funkci log z knihovny <math.h>.
1:
2:
4:
5:
/*------------------------------------------------*/
/* errno1.c
*/
#include <stdio.h>
#include <errno.h>
80
6:
8:
9:
10:
11:
13:
14:
15:
17:
18:
20:
21:
23:
24:
26:
27:
29:
30:
31:
33:
34:
35:
36:
37:
38:
40:
#include <math.h>
double logaritmus(double x)
{
double y;
y = log(x);
switch (errno) {
case EDOM:
printf("Doslo k chybe v ramci definicniho oboru funkce!\n"); return 0;
case ERANGE:
printf("Doslo k chybe v ramci oboru hodnot funkce!\n"); return 0;
case 0:
printf("Vse je v poradku. log(%f) = %f.\n", x, y); return y;
default:
printf("Neocekavana chyba\n"); return 0;
}
}
int main(void)
{
double x;
x = logaritmus(2.718281828);
x = logaritmus(-5);
x = logaritmus(9999999999);
/* prilis velke cislo */
x = logaritmus(0);
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
Vse je v poradku. log(2.718282) = 1.000000.
Doslo k chybe v ramci definicniho oboru funkce!
Doslo k chybe v ramci definicniho oboru funkce!
Doslo k chybe v ramci oboru hodnot funkce!
Co může obsahovat errno
Proměnná errno je typu int, takže může obsahovat celá čísla. V následující tabulce jsou uvedena některé
makra převzatá z linuxového manuálu k souboru <errno.h>. Mnohé z těchto maker jsou přístupné jen pod
Unixovými operačními systémy. Přesněji řečeno v systémech podporujících nejen standard ANSI C, ale i
POSIX.1.
Standardní chyby definované v <errno.h>
EDOM
ERANGE
E2BIG
Označuje chybu v rámci definičního oboru funkce.
Označuje chybu v rámci oboru hodnot funkce.
Příliš mnoho argumentů
EACCES
EBADF
ECANCELED
Přístup odmítnut
Špatný deskriptor souboru
Operace přerušena
ECHILD
Žádný podproces
EEXIST
Soubor existuje
EFAULT
Špatná adresa (do paměti)
EFBIG
Soubor je příliš veliký
EINVAL
EIO
Chybný argument
Chyba vstupu/výstupu
81
EISDIR
Je to adresář
EMFILE
Příliš mnoho otevřených souborů
ENAMETOOLONG
ENOENT
ENFILE
Příliš dlouhé jméno souboru
Soubor nebo adresář nebyl nalezen
Příliš mnoho otevřených souborů v systému
ENODEV
Zařízení nebylo nalezeno
ENOEXEC
Chyba formátu spustitelného souboru
ENOMEM
Nedostatek místa (v paměti)
ENOSPC
Nedostatek místa na zařízení (třeba na disku)
ENOTDIR
Není adresářem
ENOTEMPTY
Adresář není prázdný
EPERM
EROFS
Operace není přístupná
Systém je jen pro čtení (neumožňuje zápis
Chyby definované v Linuxu
Chybové hlášení pomocí perror()
Všechna chybová hlášení jsou uložena v proměnné const char *sys_errlist[];. Počet chybových hlášení
(tedy počet řetězců, na které ukazuje pole sys_errlist) je uložen v proměnné typu int, sys_nerr. Poslední
chybové hlášení je tedy v:
sys_errlist[sys_nerr-1];
Proměnné sys_errlist , sys_nerr a funkce perror() jsou definovány v knihovně <stdio.h>. Deklarace :
void perror(const char *s);
Funkce perror pošle nejdříve řetězec s, za něj přidá dvojtečku a poté chybové hlášení do standardního
chybového proudu stderr (to je nejčastěji na obrazovku). Pokud má s hodnotu NULL, pak vypíše jen
chybové hlášení. V případě že k žádné chybě nedošlo (errno == 0), vypíše se něco jako „Úspěch“
(Success). Díky funkci perror() lze příklad errno1.c zjednodušit. Změna je ve funkci logaritmus(), zbytek
zůstane stejný.
1:
2:
4:
5:
6:
8:
9:
10:
11:
13:
14:
16:
18:
19:
21:
22:
24:
25:
26:
28:
/*------------------------------------------------*/
/* errno2.c
*/
#include <stdio.h>
#include <errno.h>
#include <math.h>
double logaritmus(double x)
{
double y;
y = log(x);
switch (errno) {
case 0: perror(NULL);
printf("log(%f) = %f.\n", x, y); return y;
default:
perror("Hlaseni"); return 0;
}
}
int main(void)
{
double x;
x = logaritmus(2.718281828);
82
29:
x = logaritmus(-5);
30:
x = logaritmus(9999999999);
/* prilis velke cislo */
31:
x = logaritmus(0);
32:
return 0;
33: }
35: /*------------------------------------------------*/
Možný výstup z programu: Success
log(2.718282) = 1.000000.
Hlaseni: Numerical argument out of domain
Hlaseni: Illegal seek
Hlaseni: Numerical result out of range
Žádný program by neměl ignorovat návratové hodnoty funkcí. Mezi běžné chyby patří například nedostatek
místa na disku, odmítnuté internetové spojení, nedostatečná přístupová práva atp. Se všemi problémy by
se programy měly umět co nejlépe vyrovnat.
Knihovna string.h II
Knihovna <string.h> obsahuje nejen funkce pracující s řetězci, ale také funkce pracující s pamětí. Tyto
funkce však pracují s daty po bytech. Pokud tedy na pole typu int použijete funkci, která toto pole vyplní
třeba jedničkou, pak to neznamená, že v každém prvku bude jednička, ale v každém bajtu. Pro 16bitové
překladače to znamená, že typ int má velikost dvou bajtů, a číslo 1 se v něm tedy objeví dvakrát!
(0000000100000001bitově = 257decimálně).
Výčet funkcí
int memcmp(const void *s1, const void *s2, size_t n);
Porovná prvních n bajtů pamětí označených ukazateli s1 a s2. Vrací 0 v případě, že jsou všechny
bajty stejné; větší jak 0, když s1 je větší jak s2; menší jak 0, když s1 je menší jak s2.
void *memcpy(void *dest, const void *src, size_t n);
Zkopíruje n bajtů z paměti označené pomocí src do dest. Vrací ukazatel na dest. Paměti
se nesmějí překrývat.
void *memmove(void *dest, const void *src, size_t n);
Zkopíruje n bajtů z paměti označené pomocí src do dest. Vrací ukazatel na dest. Paměti se smějí
překrývat. Pracuje pomaleji než memcpy().
void *memset(void *s1, int c, size_t n);
Vyplní prvních n bajtů paměti označené ukazatelem s1 hodnotou c. Vrací ukazatel s1.
Je potřeba dávat si pozor, aby nemohlo dojít k pokusu o zápis mimo alokovanou paměť. Používání těchto
funkcí je optimální co do rychlosti. Pokud je potřeba zkopírovat strukturu T2 do T1, je jistě nejrychlejší
použít přiřazení T1 = T2;, ale jak je v příkladu ukázáno, při kopírování jen několika částí ze struktury
ležících za sebou, je výhodnější použít memcpy() než přiřazovat jednu položku struktury za druhou.
Obdobně se může memcpy() hodit při kopírování pole struktur.
1:
2:
4:
5:
7:
8:
9:
10:
11:
12:
14:
15:
/*------------------------------------------------*/
/* pamet1.c
*/
#include <stdio.h>
#include <string.h>
void porovnej(const char *s1, const char *s2, const int n)
{
int x;
x = memcmp(s1, s2, n);
printf("%2i: %s %s %s\n", n, s1, x < 0 ? "<" : x > 0 ? ">" : "=", s2);
}
typedef struct {
short int x;
83
16:
17:
18:
20:
21:
22:
23:
24:
25:
26:
27:
29:
30:
31:
32:
33:
35:
36:
38:
39:
char pole[10];
float f;
} Trida;
void tiskni(const Trida * T)
{
unsigned int i;
printf("T = {%3i,{", T->x);
for (i = 0; i < sizeof(T->pole) / sizeof(T->pole[0]); i++)
printf("%i,", T->pole[i]);
printf("\b},{%.40f}}\n", T->f);
}
int main(void)
{
Trida T1, T2;
char text1[] = "1234567890";
char text2[] = "abcdefghij";
porovnej("aaazzzzz", "aabaa", 4);
porovnej("aaaaa", "aabaa", 2);
tiskni((Trida *) memset(&T1, 0, sizeof(T1)));
tiskni((Trida *) memset(&T2, 1, sizeof(T2)));
41:/* Vyraz &T1 + n neukazuje n bajtu za adresu &T1, ale n sizeof(T1)bajtu za
adresu T1. Tedy o n struktur dale. Proto je pouzit k pretypovani ukazatele typ
char, (1 bajt)a tak lze pomoci sizeof posouvat ukazatele po bajtech */
47:
memcpy((char *) &T1 + sizeof(T1.x), (char *) &T2 + sizeof(T1.x),
48:
sizeof(T1) - sizeof(T1.x));
49:
tiskni(&T1);
51:
printf("text1+3: %s\n", (char *) memcpy(text1 + 3, text2 + 6, 3));
52:
printf("text1 : %s\n", text1);
54:
/* chybne pouziti - pameti se prekryvaji */
55:
(void) memcpy(text2 + 1, text2, 6);
56:
printf("text2+1: %s (memcpy)\n", text2);
58:
/* spravne pouziti */
59:
memcpy(text2, "abcdefghij", 10);
60:
(void) memmove(text2 + 1, text2, 6);
61:
printf("text2+1: %s (memmove)\n", text2);
63:
return 0;
64: }
66: /*------------------------------------------------*/
Výstup: 4: aaazzzzz < aabaa
2: aaaaa = aabaa
T = { 0,{0,0,0,0,0,0,0,0,0,0},{0.0000000000000000000000000000}}
T = {257,{1,1,1,1,1,1,1,1,1,1},{0.0000000000000000000000000237}}
T = { 0,{1,1,1,1,1,1,1,1,1,1},{0.0000000000000000000000000237}}
text1+3: ghi7890
text2+1: aabbdefhij (memcpy)
text1 : 123ghi7890
text2+1: aabcdefhij (memmove)
Porovnávání struktur pomocí memcmp() je poněkud sporné co do přenositelnosti kódu. Některé platformy
(ať už softwarové nebo hardwarové) mohou mezi jednotlivé části struktury vkládat bity (z jakéhosi důvodu
to opravdu dělají, zvláště překladače C++). Tyto bity pak mohou mít různé hodnoty i pro struktury, jejichž
obsah je stejný. Z toho důvodu může funkce memcmp() vyhodnotit stejné struktury jako různé. Jediným
řešením tohoto problému je porovnávání všech složek struktur hezky jednu po druhé.
Funkce sprintf(), snprintf() a sscanf()
Tyto funkce jsou sice ze souboru <stdio.h>, jelikož však pracují také s pamětí jsou zde na závěr zařazeny.
Jejich deklarace jsou:
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format,...);
84
Funkce sprintf() zapíše formátovaný výstup stejně jako funkce printf() s tím rozdílem, že výstup jde do
paměti, kam ukazuje str. Funkce snprintf() navíc zapíše pouze nanejvýš size znaků. Je tedy bezpečnější
co do rizika překročení limitu alokované paměti. Funkce sprintf() by se tedy měla používat jen v případě
jistoty, že formátovaný řetězec nepřekročí délku pole str.
Funkce snprintf() vrátí hodnotu -1 v případě, že se výstup musel kvůli limitu délky size zkrátit. V případě
úspěchu vracejí obě funkce počet úspěšně zapsaných znaků do paměti. Na rozdíl od funkcí, které
převádějí řetězce na čísla (např. atof()), se opačný převod provádí právě pomocí funkce snprintf().
1:
2:
4:
5:
7:
8:
9:
10:
11:
12:
13:
15:
/*------------------------------------------------*/
/* snpr1.c
*/
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[50];
snprintf(str, 5, "%f", 3.8);
printf("Retezec %s je dlouhy %i znaku.\n", str, strlen(str));
return 0;
}
/*------------------------------------------------*/
Výstup z programu: Retezec 3.80 je dlouhy 4 znaku.
Řetězec je dlouhý o jeden znak méně, než byl zadaný limit pro funkci snprintf(). Ten poslední znak je totiž
vyhrazen pro označení konce řetězce '\0'.
Funkce sscanf()
Funkce sscanf() je též ze souboru stdio.h a dělá totéž co scanf, jenom místo standardního vstupu čte data
z paměti, kam ukazuje str. Deklarace funkce je následující:
int sscanf(const char *str, const char *format, ...);
Knihovna stdio.h
Funkce getchar(), gets(), putchar() a puts()
Funkce getchar() načte ze standardního vstupu jeden znak a vrátí jej jako typ int. V případě chyby vrací
speciální hodnotu EOF (v případě uzavřeného/chybného standardního vstupu Funkce gets() načte ze
standardního vstupu řetězec do textového pole a vrátí ukazatel na toto pole. Načítá znaky, dokud nenarazí
na konec řádku ('\0') nebo EOF. Tato funkce může překročit velikost pole paměti, do které řetězec ukládá a
způsobit tak havárii programu! Návratovou hodnotou je ukazatel na paměť, kam řetězec ukládá (tedy
návratová hodnota je rovna argumentu funkce). Funkce putchar() vytiskne jeden znak (jenž je
argumentem funkce) do standardního výstupu. Vrací zapsaný znak (tedy to samé jako je argument), nebo
EOF v případě chyby. Funkce puts() vytiskne řetězec, jenž je jejím argumentem, do standardního výstupu.
V případě úspěchu vrací nezáporné číslo, jinak EOF. Zároveň vytiskne po řetězci ještě znak '\n' (nová
řádka).
1:
2:
4:
5:
7:
8:
9:
10:
/*------------------------------------------------*/
/* gets1.c
*/
#include <stdio.h>
#include <string.h>
int main(void)
{
char text[80];
printf("Zadejte libovolny text: ");
85
12:
(void) gets(text);
13:
puts(text);
14:
printf("Zadejte libovolny text: ");
15:
scanf("%s", text);
16:
puts(text);
18:
return 0;
19: }
21: /*------------------------------------------------*/
Možný výstup:
Zadejte libovolny text: Mam 25000 Euro.
Mam 25000 Euro.
Zadejte libovolny text: Mam 25000 Euro.
Mam
Zatímco funkce gets() načte řetězec až do znaku nového řádku, funkce scanf() načítá řetězec k prvnímu
„bílému znaku“ (mezera, tabulátor, nový řádek).
Výčet funkcí ze stdio.h
FILE *fopen(const char *path, const char *mode);
Otevře souborový proud.
FILE *freopen(const char *path, const char *mode, FILE *stream);
Otevře soubor se jménem path a přesměruje do něj souborový proud stream. Používá se pro
přesměrování standardního vstupu stdio, nebo výstupů stdout a stderr.
int remove(const char *pathname);
Smaže soubor pathname.
int rename(const char *oldpath, const char *newpath);
Přejmenuje soubor oldpath na soubor newpath.
int fileno(FILE *stream);
Vrací deskriptor souboru datového proudu stream.
int fflush(FILE *stream);
Slouží k vyprázdnění datového proudu stream. Vrací 0 v případě úspěchu, jinak EOF.
int fclose( FILE *stream);
Uzavře (a vyprázdní) datový proud stream.
int feof(FILE *stream);
Vrací kladnou nenulovou hodnotu v případě dosažení konce souboru stream, jinak 0.
int ferror(FILE *stream);
V případě chyby souboru stream vrací kladnou nenulovou hodnotu, jinak 0.
void clearerr(FILE *stream);
Nuluje indikátor konce souboru a chyby pro souborový proud stream.
int fseek(FILE *stream, long offset, int whence);
Posune kurzor v souboru stream na pozici vzdálenou offset od místa whence.
long ftell( FILE *stream);
Vrátí aktuální pozici kurzoru souboru stream.
void rewind(FILE *stream);
Posune kurzor na začátek souboru stream.
int fgetpos(FILE *stream, fpos_t *pos);
Uloží aktuální pozici kurzoru do proměnné pos.
int fsetpos(FILE *stream, fpos_t *pos);
Obnoví pozici kurzoru z proměnné pos.
int fgetc(FILE *stream);
Načte znak ze souborového proudu stream.
char *fgets(char *s, int size, FILE *stream);
Načte řetězec délky size ze souborového proudu stream.
int getc(FILE *stream);
Načte znak ze souborového proudu stream.
int getchar(void);
Načte a vrátí jeden znak ze standardního vstupu.
char *gets(char *s);
Načte ze standardního vstupu řetězec do textového pole s a vrátí ukazatel na toto pole.
86
int ungetc(int c, FILE *stream);
Vrátí znak c do proudu stream, je-li to možné. Více jak jedno vrácení znaku, před dalším čtením
proudu, není garantováno. V případě úspěchu vrací znak c, jinak EOF.
int printf(const char *format, ...);
Slouží k formátovanému výstupu do stdout.
int fprintf(FILE *stream, const char *format, ...);
Slouží k formátovanému výstupu do souborového proudu stream.
int sprintf(char *str, const char *format, ...);
Zapíše formátovaný výstup do paměti, kam ukazuje str.
int snprintf(char *str, size_t size, const char *format,...);
Zapíše formátovaný výstup do paměti, kam ukazuje str, nejvýše size znaků.
int scanf(const char *format, ...);
Čte data ze standardního vstupu.
int fscanf(FILE *stream, const char *format, ...);
Čte data ze souborového proudu stream.
int sscanf(const char *str, const char *format, ...);
Načítá formátovaný řetězec z paměti, kam ukazuje str.
int fputc(int c, FILE *stream);
Tiskne znak c do souborového proud stream.
int fputs(const char *s1, FILE *stream);
Tiskne řetězec s1 do souborového proud stream.
int putc(int c, FILE *stream);
Tiskne řetězec c do souborového proud stream. Rozdíl mezi fputc() a putc() je zanedbatelný. Lépe
je používat fputc().
int putchar(int c);
Vytiskne znak c do standardního výstupu.
int puts(const char *s1);
Vytiskne řetězec s1 a po něm ještě znak '\n' (nová řádka).
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
Načte nmemb dat o velikosti size ze souboru stream do paměti kam ukazuje ptr. Návratovou
hodnotou je počet správně načtených položek.
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
Zapíše nmemb dat o velikosti size do souboru stream z paměti kam ukazuje ptr. Návratovou
hodnotou je počet správně zapsaných položek.
void perror(const char *s1);
Vypíše chybové hlášení na základě proměnné errno za řetězec s1 a dvojtečku, nebo jen chybové
hlášení, pokud má s1 hodnotu NULL.
Práce se soubory (a <stdio.h>)
O vstupu a výstupu
Funkce printf() a scanf() jsou typickými představiteli knihovny <stdio.h> (standard input output).
Standardní vstup a výstup se považují také za soubory (s drobnými rozdíly, například „skutečné“ soubory
mají své jméno a jsou někde uloženy ...). Na rozdíl od ostatních souborů (např. textových, binárních a
dalších speciálních souborů) jsou standardní vstup a výstupy otevřeny a programu přístupny automaticky
při spuštění programu.
Deklarace standardního vstupu, výstupu a chybového výstupu
FILE *stdin;
Standardní vstup (typicky klávesnice)
FILE *stdout; Standardní výstup (typicky obrazovka)
FILE *stderr; Standardní chybový výstup (typicky obrazovka)
87
Z tabulky je patrné, že se jedná o ukazatele na strukturu FILE. Ta je deklarovaná v souboru <stdio.h> a
slouží k práci se soubory. Tato struktura je argumentem spousty funkcí ze souboru <stdio.h>.
Přístup k souborům
Existují dva základní způsoby jazyka C, jak přistupovat k souborům. Buďto pomocí souborového proudu,
nebo pomocí přímého volání.
Souborový proud
K souborovému proudu se přistupuje pomocí struktury FILE. Díky normě ANSI je jisté, že používané
funkce budou podporovány všemi („dobrými“) překladači. Proto je převážně používán tento způsob.
Přímé volání
Přímé volání má přímou vazbu na systém. Tím je zaručena maximální rychlost přístupu k souborům, ale
také implementační závislost. Pro funkce s přímým voláním je nutno použít knihovny <io.h>, <sys/stat.h> a
<fcntl.h>. K souboru se na rozlíl struktury FILE přistupuje pomocí manipulačního čísla tzv. deskriptoru
souboru. Jedná se o datový typ int. Přímé volání není součástí normy ASCII, ale je definována normou
POSIX.
Textový a binární přístup k souborům
Existuje ještě jedno dělení přístupu k souborům a to na textový a binární. V textovém souboru je uložen
pouze text. To umožňuje zjednodušit některé souborové operace a zlepšit tak přenositelnost mezi různými
operačními systémy. Problém textových souborů spočívá ve znaku '\n'. Je to označení konce řádku. V
DOSu je konec řádku dán dvěma bajty 13 a 10, zatímco v Linuxu pouze jedním (10). V jiných systémech je
to pro změnu 10 a 13 (v tomto pořadí). Při textovém přístupu k souboru se načítají a zapisují textové
řetězce. Přitom není nutno starat se o to, jakým způsobem je znak '\n' interpretován. Nový řádek je vnitřně
reprezentován jedním znakem, který je vždy roven '\n' (při čtení/zápisu souboru dochází k automatické
konverzi). Při binárním přístupu k souboru se čtou i zapisují data bajt po bajtu. V takovém případě, pokud
má být soubor textový, je nutné zapsat znak '\n' v závislosti na OS. Binární přístup umožňuje tedy větší
kontrolu nad zpracovávanými daty, ale práce s takovými soubory může být o něco náročnější.
Limity
Soubor <stdio.h> obsahuje i tyto makra. Pro přenositelnost programů je využití těchto maker nezbytné:
FOPEN_MAX udává maximální počet otevřených souborů. Maximum otevřených souborů omezuje
operační systém, mnohdy podle aktuální konfigurace systému.
FILENAME_MAX udává maximální délku jména souboru. V systému MS-DOS je známé omezení 8+3, tj. 8
znaků, tečka a tři znaky (přípona).
Datové proudy
Otevření datového proudu
FILE *fopen(const char *path, const char *mode);
Funkce fopen() vrací ukazatel na strukturu FILE. Pokud se proud nepodaří otevřít, vrací hodnotu NULL. Z
jakého důvodu se nepodařilo proud uzavřít lze zjistit pomocí proměnné errno definované v
souboru <errno.h>. První argument path je název souboru, který se má otevřít (samotný název pro práci s
88
aktuálním adresářem, nebo s celou cestou - "C:\TEXTY\RECEPTY\neco.txt). Druhým argumentem
je mode. Je to také textový řetězec, který může obsahovat některý z řetězců podle následující tabulky:
Argumenty mode pro funkci fopen()
význam
řetězec
Pozice v
souboru
r
Otevře soubor pro čtení.
začátek
r+
Otevře soubor pro čtení a zápis (podobně jako "rw")
začátek
w
Existující soubor ořízne na nulovou délku, jinak vytvoří nový a otevře
proud pro zápis
začátek
w+
Jako "w", ale proud otevře i pro čtení.
začátek
a
Otevře soubor pro čtení. Pokud ještě neexistuje, je vytvořen.
konec
a+
Otevře soubor pro čtení a zápis. Pokud soubor neexistuje, je vytvořen.
konec
t
Textový režim.
-
b
Binární režim.
-
Vždy by měl být uveden druh přístupu k souboru, tj. zda bude v textovém nebo binárním režimu (pokud
není určen, je defaultně nastaven textový režim), a to kvůli přenositelnosti kódu. Argument mode by tedy
mohl být např. "rt" nebo "rb", nebo také "rwt". Z logiky věci vyplývá, že nelze použít zároveň textový i
binární. V Linuxu není rozdíl mezi textovými a binárními soubory, v jiných OS rozdíl je.
Příklady použití funkce fopen():
FILE *soubor;
soubor = fopen("nazev.txt","rt");
Soubor "nazev.txt" se otevře pro čtení (v textovém režimu). Pokud se to nepodaří (soubor
neexistuje nebo k němu nejsou přístupová práva), funkce fopen vrátí NULL.
soubor = fopen("nazev.txt","wb");
Soubor "nazev.txt" se zkrátí na nulovou délku. Pokud neexistuje, vytvoří se. Pokud se to nezdaří,
funkce vrátí NULL. Pokud ano, je soubor otevřen pro zápis v binárním módu.
if ((soubor = fopen("nazev.txt","rt")) == NULL)
printf("soubor nelze otevrit!");
Možný způsob kontroly existence souboru.
Uzavření a vyprázdnění datového proudu
K tomu slouží funkce fclose().
int fclose(FILE *stream);
Při uzavření datového proudu stream je tento proud vyprázdněn z vyrovnávací paměti na disk (nebo třeba
na standardní výstup, pokud je stream stdin). Pak již není možné se souborem pracovat bez jeho
znovuotevření pomocí fopen(). Počet možných otevřených souborů v jeden okamžik je omezen, proto je
dobré soubory, které se již nepoužívají, co nejdříve zavřít. Datový proud je automaticky uzavřen po
ukončení programu. V případě havárie programu (před řádným uzavřením souboru) se však může
(zpravidla) stát, že změny v souboru jsou ztraceny. To je také důvod k co nejrychlejšímu uzavření
souboru. Funkce fclose() vrací nulu v případě úspěchu, jinak EOF a proměnnou černo nastaví na indikátor
chyby. Např. EBADF znamená, že proud stream nebyl otevřeným datovým proudem.
K vyprázdnění vyrovnávací paměťi před uzavřením souboru slouží funkce fflush().
89
int fflush(FILE *stream);
Argument stream je samozřejmě datovým proudem, který chcete vyprázdnit. Pokud má stream hodnotu
NULL, jsou vyprázdněny všechny datové proudy, které jsou v programu otevřené. Funkce fflush() vrací 0 v
případě úspěchu, jinak EOF a proměnnou errno nastaví na příslušnou chybu. Ovšem ani
funkce fclose() ani fflush() nezajistí, že budou data skutečně fyzicky na disku. Mezi programem a
hardwarem je totiž ještě operační systém, který také využívá vyrovnávací paměti. O tomhle je třeba vědět
při psaní nějakých víceuživatelských programů (třeba databáze), kde k jednomu souboru přistupuje několik
uživatelů najednou (třeba i ze vzdálených počítačů). V Unixu k reálnému zapsání dat na disk slouží
funkce fsync().
V příkladu je procvičování otevření a uzavření souboru.
1:
2:
4:
5:
6:
8:
10:
11:
12:
14:
15:
16:
17:
18:
19:
20:
21:
23:
25:
26:
27:
29:
30
32:
36:
37:
38:
39:
42:
43:
45:
48:
49:
50:
51:
52:
53:
54:
55:
56:
59:
62:
63:
64:
65:
66:
67:
68:
69:
/*------------------------------------------------*/
/* fopen1.c
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define NAZEV "pokus.txt"
#ifndef ENOENT
#define ENOENT 2
#endif
/* vraci 0 pokud soubor existuje,
jinak cislo chyby */
int SouborExistuje(char *nazev)
{
FILE *soubor;
if ((soubor = fopen(nazev, "rt")) != NULL) {
fclose(soubor);
return 0;
/* soubor existuje, jinak by se nesel otevrit */
}
if (errno == ENOENT) {
return errno;
/* soubor neexistuje */
}
return errno; /*soubor mozna existuje, ale nepodarilo se jej otevrit*/
}
int main(void)
{
FILE *soubor;
char text[255];
printf("Soubor %s %sexistuje.\n", NAZEV,
!SouborExistuje(NAZEV) ? "" : "ne");
/*otevre se soubor pro cteni a pokud se to zdari, hned se zase zavre*/
if ((soubor = fopen(NAZEV, "rt")) == NULL) {
strcpy(text, "Otevreni \"rt\" souboru ");
strcat(text, NAZEV);
perror(text);
} else {
printf("Soubor %s se podarilo otevrit\n", NAZEV);
if (fclose(soubor))
perror("Zavirani souboru");
}
/*otevre a zavre se pro aktualizaci, pokud neexistuje, bude vytvoren.*/
if ((soubor = fopen(NAZEV, "at")) == NULL) {
strcpy(text, "Otevreni \"at\" souboru ");
strcat(text, NAZEV);
perror(text);
} else {
printf("Soubor %s se podarilo otevrit\n", NAZEV);
if (fclose(soubor))
perror(NULL);
90
70:
}
72:
return 0;
73: }
75: /*------------------------------------------------*/
Možný výstup 1 spuštění:
Soubor pokus.txt neexistuje.
Otevreni "rt" souboru pokus.txt: No such file or directory
Soubor pokus.txt se podarilo otevřít
Možný výstup 2 spuštění:
Soubor pokus.txt existuje.
Soubor pokus.txt se podarilo otevrit
Soubor pokus.txt se podarilo otevrit
V aktuálním adresáři, by teď měl být prázdný soubor pokus.txt.
Práce s textovými soubory
Zápis do textového souboru
Pro zápis slouží funkce fprintf(). Narozdíl od funkce printf() má navíc argument stream, který určuje
souborový proud. Pokud tímto souborovým proudem bude stdout, pak se chování funkce nebude lišit od
funkce printf(). Další možností je zápis do standardního chybového proudu stderr, nebo do souborového
proudu otevřeného funkcí fopen().
int
fprintf(FILE *stream, const char *format, ...);
Kromě toho je možné také použít funkce fputs() pro tisk řetězce, fputc() a putc() pro tisk znaku. Jejich
deklarace jsou následující:
int fputs(const char *s, FILE *stream);
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
Rozdíl mezi fputc() a putc() je v tom, že fputc() je funkce, zatímco putc() je implementován jako makro (proto je
lepší používat fputc() u kterého se nestane, že by byl stream vyhodnocován vícekrát).
Funkce fputc() a putc() vracejí zapsaný znak, nebo EOF v případě chyby. Funkce fputs() nezáporné číslo v
případě úspěchu, při chybě EOF.
V následujícím příkladu nejsou, pro větší jednoduchost, kontrolovány návratové hodnoty funkcí (nemělo by
se na to však zapomínat). Program bude zapisovat na konec souboru text zadaný od uživatele.
1:
2:
4:
5:
7:
9:
10:
11:
12:
14:
15:
16:
17:
18:
19:
20:
/*------------------------------------------------*/
/* fprintf1.c
*/
#include <stdio.h>
#include <string.h>
#define NAZEV "textak.txt"
int main(void)
{
FILE *soubor;
char text[255];
soubor = fopen(NAZEV, "a+");
/* soubor se otevre pro aktualizaci,
neexistujici soubor se vytvori */
do {
fputs("Zadejte slovo, ktere chcete zapsat do souboru\n"
"a stisknete ENTER, nebo \"q\" pro ukonceni: ", stdout);
scanf("%254s", text);
if (!strcmp(text, "q"))
91
21:
break;
22:
printf("Zapisuji >> %s <<\n", text);
23:
fprintf(soubor, ">> %s <<\n", text);
24:
} while (1);
26:
fclose(soubor);
28:
return 0;
29: }
31: /*------------------------------------------------*/
Možný výstup:
Zadejte slovo, ktere chcete
a stisknete ENTER, nebo "q"
Zapisuji >> jedna <<
Zadejte slovo, ktere chcete
a stisknete ENTER, nebo "q"
Zapisuji >> dve <<
Zadejte slovo, ktere chcete
a stisknete ENTER, nebo "q"
Zapisuji >> honza <<
Zadejte slovo, ktere chcete
a stisknete ENTER, nebo "q"
Zadejte slovo, ktere chcete
a stisknete ENTER, nebo "q"
zapsat do souboru
pro ukonceni: jedna
zapsat do souboru
pro ukonceni: dve
zapsat do souboru
pro ukonceni: honza jde
zapsat do souboru
pro ukonceni: Zapisuji >> jde <<
zapsat do souboru
pro ukonceni: q
Čtení z textového souboru
Pro čtení slouží funkce fscanf(). Opět je rozdíl od funkce scanf() v argumentu stream, který určuje
souborový proud. Bude-li tímto proudem stdin, je funkce shodná s scanf().
int fscanf(FILE *stream, const char *format, ...);
Kromě této funkce je možné také použít funkce fgets() pro načtení řetězce, fgetc() a getc() pro načtení
znaku. Jejich deklarace jsou následující:
char *fgets(char *s, int size, FILE *stream);
int fgetc(FILE *stream);
int getc(FILE *stream);
Rozdíl mezi fgetc() a getc() je pouze v implementaci těchto funkcí překladačem. Lépe je používat fgetc().
V příkladu je otevřen nový textový soubor pro zápis. Po zápisu je soubor uzavřen, následně otevřen pro
čtení, a jeho obsah načten a vypísán.
1:
2:
4:
5:
6:
8:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
22:
/*------------------------------------------------*/
/* fscanf1.c
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define NAZEV "textak.txt"
int main(void)
{
FILE *soubor;
int i;
float x, y, z;
char text[255];
/* nejdrive do souboru neco zapiseme */
if ((soubor = fopen(NAZEV, "w")) == NULL) {
fprintf(stderr, "Chyba zapisu do souboru %s\n", NAZEV);
return errno;
}
fputs("Seznam zlomku:\n", soubor);
92
23:
for (i = 1; i <= 5; i++)
24:
fprintf(soubor, "%f %f %f\n", 100.0, (float) i, 100.0 / i);
25:
fclose(soubor);
26:
/* a ted to zpatky nacteme */
27:
if ((soubor = fopen(NAZEV, "r")) == NULL) {
28:
fprintf(stderr, "Chyba cteni souboru %s\n", NAZEV);
29:
return errno;
30:
}
32:
/* prvni radek nacteme znak po znaku */
33:
do {
34:
i = fgetc(soubor);
35:
fputc(i, stdout);
36:
} while (i != '\n');
38:
/* druhy radek (prvni radek s cisly) nacteme naraz */
39:
fgets(text, sizeof(text) - 1, soubor);
40:
fputs(text, stdout);
42:
/* zbytek nacteme po cislech */
43:
for (i = 1; i <= 4; i++) {
44:
fscanf(soubor, "%f %f %f", &x, &y, &z);
45:
printf("%f/%f = %f\n", x, y, z);
46:
}
48:
fclose(soubor);
49:
return 0;
50: }
52: /*------------------------------------------------*/
Výstup z programu:
Seznam zlomku:
100.000000 1.000000
100.000000/2.000000
100.000000/3.000000
100.000000/4.000000
100.000000/5.000000
100.000000
= 50.000000
= 33.333332
= 25.000000
= 20.000000
Kontrola chyb (feof(), ferror(), clearerr())
Tyto funkce pracují s datovým proudem stream a je jedno, jestli jde o textový nebo binární přístup.
int feof(FILE *stream);
void clearerr(FILE *stream);
int ferror(FILE *stream);
Funkce feof() zjišťuje konec souboru. Po dosažení konce vrací kladnou hodnotu (1) jinak 0. Funkce
ferror() testuje indikátory chyb. Pokud nedošlo k chybě, vrací 0, jinak kladnou hodnotu. Funkce clearerr()
dokáže jako jediná odstranit indikátor konce souboru a indikátory chyb v datovém proudu. To může být
užitečné například ve chvíli, kdy se dostaneme na konec souboru, ale pak se přesuneme na jeho začátek,
nebo obnovení zápisu do proudu v případě uvolnění místa na zaplněném disku.
Program v následujícím příkladu otevře soubor cisla.txt a sečte všechna čísla, v něm zapsaná.
1:
2:
4:
5:
6:
8:
10:
11:
12:
13:
14:
/*------------------------------------------------*/
/* feof1.c
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define NAZEV "cisla.txt"
int zavri_soubor(FILE * soubor)
{
if (fclose(soubor) == EOF) {
fprintf(stderr, "%s %i", __FILE__, __LINE__);
perror(NULL);
93
15:
16:
17:
18:
20:
21:
22:
23:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
44:
45:
46:
47:
49:
return errno;
}
return 0;
}
int main(void)
{
FILE *soubor;
float f, suma;
if ((soubor = fopen(NAZEV, "rt")) == NULL) {
fprintf(stderr, "%s %i:", __FILE__, __LINE__);
perror(NULL);
return errno;
}
suma = 0.0;
do {
if (fscanf(soubor, "%f", &f) == 1) {
/* ma se nacist 1 cislo */
printf("%.1f ", f);
suma += f;
} else if (feof(soubor))
/* dosahli jsme konce souboru */
break;
/*preskoci retezec, který neni cislo*/
else if(fscanf(soubor,"%*s")!= 0){
fprintf(stderr, "%s %i:", __FILE__, __LINE__);
perror(NULL);
return zavri_soubor(soubor);
}
}while (1);
(void) zavri_soubor(soubor);
printf("\nSuma = %.3f\n", suma);
return 0;
}
/*------------------------------------------------*/
Výhoda použití perror() je v tom, že lze pomocí knihovny <locale.h> nastavit jazyk, jakým na nás bude program
mluvit.
Reakce, pokud cisla.txt nebude existovat. kod.c 26:No such file or direktory
Nebo když nejsou přístupová práva ke čtení souboru: kod.c 26:Permission denied
Soubor cisla.txt existuje a má následující obsah:
Pak je výstup:
text 11.257 te123xt 20.447
18.3 text 30 text ? 20
11.3 20.4 18.3 30.0 20.0
Suma = 100.004
Práce s binárními soubory a pohyb v souborech
Čtení a zápis binárního souboru
Otevření a uzavření binárního souboru je stejné jako textového (místo příznaku t se napíše b).
Zápis pomocí fwrite()
Funkce fwrite() slouží k zápisu dat do souborového proudu. Její deklarace je následující:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
Prvním argumentem je ukazatel ptr, který ukazuje na začátek místa v paměti, odkud se budou brát data
pro zápis do souboru. Může to být ukazatel na strukturu, řetězec, nebo číslo atp. Druhý argument size je
94
velikost položky, kterou budeme zapisovat v bajtech (může to být např. sizeof(struktura)). Třetím
argumentem je nmemb, což je počet položek, které zapíšeme. Celkem se tedy zapíše nmemb * size
bajtů. A čtvrtým argumentem je datový proud stream, do kterého se data zapisují. Návratovou hodnotou je
počet zapsaných položek, v ideálním případě je to rovno nmemb. V případě chyby je nastavena globální
proměnná errno.
Čtení pomocí fread()
Funkce fread() slouží ke čtení dat ze souborového proudu. Její deklarace je následující:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
Funkce fread() načte nmemb dat o velikosti size ze souboru stream do paměti kam ukazuje ptr.
Návratovou hodnotou je počet správně načtených položek, v ideálním případě nmemb. Pokud se nenačtou
všechny požadované položky, pak došlo k chybě, nebo se funkce dostala na konec souboru. To lze zjistit
pomocí funkcí feof() a ferror().
Příklad čtení a zápisu
Program zapíše do souboru strukturu data, pak ji znovu načte a vypíše.
1:
2:
4:
5:
7:
8:
9:
10:
12:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
28:
29:
30:
31:
32:
33:
35:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
/*------------------------------------------------*/
/* binar.c
*/
#include <errno.h>
#include <stdio.h>
typedef struct {
float cisla[5];
char text[100];
} data;
#define NAZEV "bsoub.dat"
int main(void)
{
FILE *soubor;
/* pole obsahuje 5 struktur typu data */
data pole[] = { {{1.1, 1.2, 1.3, 1.4, 1.5}, "prvni struktura"},
{{2.1, 2.2, 2.3, 2.4, 2.5}, "druha struktura"},
{{3.1, 3.2, 3.3, 3.4, 3.5}, "treti struktura"},
{{4.1, 4.2, 4.3, 4.4, 4.5}, "ctvrta struktura"},
{{5.1, 5.2, 5.3, 5.4, 5.5}, "pata struktura"}
};
data xdata;
size_t navrat;
unsigned int i;
if ((soubor = fopen(NAZEV, "wb")) == NULL) {
/* vite co znamena "w" ? */
fprintf(stderr, "%s %i:", __FILE__, __LINE__);
perror(NULL);
return errno;
}
/* zapis dat do souboru - nejdriv prvnich tri a pak zbylych dvou */
navrat = fwrite(pole, sizeof(data), 3, soubor);
if (navrat != 3) {
fprintf(stderr, "%s %i:", __FILE__, __LINE__);
perror(NULL);
if (EOF != fclose(soubor))
fprintf(stderr, "S jistotou bylo zapsano %i polozek\n",
navrat);
else
perror(NULL);
return errno;
95
47:
}
49:
navrat = fwrite(pole + 3, sizeof(data) * 2, 1, soubor);
50:
if (navrat != 1) {
51:
fprintf(stderr, "%s %i:", __FILE__, __LINE__);
52:
perror(NULL);
53:
fclose(soubor);
54:
fprintf(stderr, "Neni znam pocet zapsanych polozek\n");
55:
return errno;
56:
}
58:
/* soubor pred ctenim uzavreme a pak zase otevreme */
59:
if (fclose(soubor) == EOF)
60:
perror(NULL);
62:
if ((soubor = fopen(NAZEV, "rb")) == NULL) {
63:
fprintf(stderr, "%s %i:", __FILE__, __LINE__);
64:
perror(NULL);
65:
return errno;
66:
}
68:
/* nyni data nacteme a vypiseme */
69:
do {
70:
navrat = fread(&xdata, 1, sizeof(xdata), soubor);
71:
if (navrat != sizeof(xdata)) {
72:
if (feof(soubor)) {
73:
printf("Konec souboru!\n");
74:
break;
75:
} else {
76:
perror(NULL);
77:
fprintf(stderr, "Bylo nacteno pouze %i bajtu "
78:
"z jedne polozky spravne\n", navrat);
79:
break;
80:
}
81:
}
83:
/* vypasni jedne struktury */
84:
for (i = 0; i < sizeof(xdata.cisla) / sizeof(xdata.cisla[0]); i++)
85:
printf("%3.1f ", xdata.cisla[i]);
86:
printf("%s\n", xdata.text);
88:
} while (!feof(soubor));
90:
if (fclose(soubor) == EOF)
91:
perror(NULL);
93:
return 0;
94: }
96: /*------------------------------------------------*/
Výstup z programu:
1.1 1.2 1.3 1.4
2.1 2.2 2.3 2.4
3.1 3.2 3.3 3.4
4.1 4.2 4.3 4.4
5.1 5.2 5.3 5.4
Konec souboru!
1.5
2.5
3.5
4.5
5.5
prvni struktura
druha struktura
treti struktura
ctvrta struktura
pata struktura
Po přečtení páté struktury ještě funkce feof() nevratcí hodnotu „true“, ale vrací ji až po pokusu načíst
strukturu šestou, která již v souboru neexistuje. Příznak konce souboru je tedy nastaven až poté, co se
pokoušíme načíst více dat, než je v souboru a ne ve chvíli, kdy přečteme poslední existující data ze
souboru!
Pozice v souboru
Po otevření souboru pro čtení ukazuje souborový indikátor pozice (dále jen kurzor) na začátek souboru
(tedy na „nultý“ bajt). Po použití libovolné funkce na přečtení určitého počtu bajtů bude kurzor automaticky
ukazovat na bajt následující atd. K „ručnímu“ nastavení kurzoru na požadovanou pozici slouží následující
funkce.
96
Funkce je fseek().
int fseek(FILE *stream, long offset, int whence);
Posouvá kurzor do vzdálenosti offset (tato hodnota může být i záporná!) od místa whence. Místo whence
může být SEEK_SET (relativní vzdálenost od začátku souboru), SEEK_CUR (relativní vzdálenost od
aktuální pozice) nebo SEEK_END (relativní vzdálenost od konce souboru). Funkce vrací v případě
úspěchu nulu.
Další funkce jsou ftell() a rewind().
long ftell(FILE *stream);
void rewind(FILE *stream);
Funkce rewind() posune kurzor na začátek souboru stream. Je to totéž, jako (void) fseek(stream, 0L,
SEEK_SET). Funkce ftell() vrací hodnotu aktuální pozice kurzoru v souboru stream.
Alternativou k předešlým funkcím mohou být funkce fgetpos() a fsetpos(). Tyto funkce pracují s objektem
typu fpos_t, do kterého funkce fgetpos() ukládá hodnotu aktuální pozice kurzoru a fsetpos() nastavuje
pozici kurzoru podle této proměnné.
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
Všechny tyto funkce jsou vhodné pro používání jak v
textovém, tak binárním přístupu k souboru.
Zjištění velikosti souboru
Nejlepším způsobem je přesunout kurzor na konec souboru a pak funkcí ftell() zjistit aktuální pozici
kurzoru. Pro jednoduchost nebudou v příkladu ošetřeny žádné chyby.
1:
2:
4:
7:
8:
9:
10:
11:
12:
14:
15:
16:
18:
19:
20:
21:
22:
24:
25:
26:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
39:
/*------------------------------------------------*/
/* ftell1.c
*/
#include <stdio.h>
int main(void)
{
FILE *soubor;
unsigned int i;
char pole[100];
soubor = fopen("pokus.txt", "w+t");
/* nejdrive zapiseme do souboru nekolik znaku 'a' */
for (i = 0; i < 50; i++)
fputc('a', soubor);
/* posuneme kurzor za prvnich 5 bajtu */
fseek(soubor, 5L, SEEK_SET);
/* zapiseme 5 znaku '-' */
for (i = 0; i < 5; i++)
fputc('-', soubor);
/* posuneme se na konec souboru a zjistime jeho velikost */
fseek(soubor, 0L, SEEK_END);
printf("Velikost souboru je %lu bajtu.\n", ftell(soubor));
/* presuneme se opet na zacatek souboru a precteme jeho obsah */
fseek(soubor, 0L, SEEK_SET);
while (!feof(soubor)) {
fgets(pole, sizeof(pole) / sizeof(char) - 1, soubor);
printf("%s", pole);
}
printf("\n");
fclose(soubor);
return 0;
}
/*------------------------------------------------*/
97
Výstup z programu:
Velikost souboru je 50 bajtu.
aaaaa-----aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Adresářová struktura
Různé operační systémy používají rozličné způsoby uchovávání dat. Například v Unixech existují ke
každému souboru a adresáři přístupová práva, která je nutno při vytváření a mazání respektovat, zatímco
například v DOSu nikoliv. Následující funkce se proto liší systém od systému, překladač od překladače.
Příklad na konci kapitoly, byl odladěn v operačním systému Linux, v DOSovém překladači Borland C a
windowsovém Dev-C++.
Aktuální adresář
Ke zjištění aktuálního adresáře slouží funkce getcwd().
char *getcwd(char *buf,size_t size);
První argument buf je ukazatel na textové pole, kam se uloží název aktuálního adresáře. Druhý argument
tsize je maximální velikost, která se do pole může uložit. V případě chyby (např. jméno aktuálního
adresáře je větší než size) vrátí getcwd() ukazatel NULL.
Vytvoření adresáře
K vytvoření adresáře se používá funkce mkdir
int mkdir(const char *pathname); /* DOSová verze */
int mkdir(const char *pathname, mode_t mode); /* LINUXová verze */
Argument pathname je jméno adresáře, který se má vytvořit. Argument mode v deklaraci pro Unixy určuje
přístupová práva. Přesněji řečeno, přístupová práva k souboru budou nastavena jako (mode & ~umask).
Návratovou hodnotou je 0 v případě, že vše proběhlo bez chyby. Při chybě -1 a je nastavena globální
proměnná errno na číslo chyby.
Přístupová práva
Přístupová práva k souborům a adresářům se nastavují pomocí funkce chmod() definované v hlavičkovém
souboru <sys/stat.h>, <io.h>, př. jiném (záleží na prostředí, ve kterém je zdrojový kód překládán).
int chmod(const char *path, mode_t mode);
První argument je cesta k souboru nebo adresáři, jehož práva měníme. Druhý argument nastavuje
přístupová práva. Funkce vrací 0 v případě úspěchu, jinak -1 a nastavuje proměnnou errno. Parametr
mode lze nastavit pomocí maker definovaných v <sys/stat.h>, s přihlédnutím na to, která makra jsou pro
systém dostupná. Ve Windows to může být např. S_IREAD | S_IWRITE pro právo čtení a zápisu.
Změna aktuálního adresáře
Lze změnit pomocí funkce chdir(). Změna se projeví jen v programu, který tuto funkci spustil. (V DOSu
zůstane platná i po ukončení programu.
int chdir(const char *path);
Argumentem funkce je adresář, do kterého se máme přesunout. Návratová hodnota je v případě úspěchu
0, jinak -1 a je nastavena proměnná errno.
98
Mazání souborů a adresářů
Dle normy ANSI C je to funkce remove(), ale většinou je nahrazena dvěma jinými: Funkcí unlink() pro
mazání souborů a funkcí rmdir() pro mazání prázdných adresářů. Funkce remove() volá funkci unlink()
pro soubory a funkci rmdir() pro adresáře.
int remove(const char *pathname);
int unlink(const char *pathname);
int rmdir(const char *pathname);
Argument pathname je jméno souboru resp. adresáře, který se má smazat. Návratové hodnoty jsou v
případě úspěchu 0, jinak -1 a je nastavena proměnná errno.
Přejmenování a přesunutí souborů
K tomu slouží funkce rename(). Pokud bude nové jméno souboru v jiném adresáři než staré jméno, pak se
soubor přesune. K tomu však dojde jedině v případě, že staré a nové jméno je na jednom disku (diskovém
oddíle). Pokud bude nové jméno jméno již existujícího souboru, bude tento soubor přepsán! Deklarace
funkce vypadá následovně:
int rename (const char *oldpath, const char *newpath);
Návratová hodnota v případě úspěchu je 0, jinak -1 a je nastavena proměnná errno.
Příklad
1:
2:
4:
5:
6:
7:
8:
9:
10:
11:
12:
14:
15:
17:
18:
19:
20:
21:
23:
24:
25:
26:
27:
28:
29:
31:
32:
33:
34:
35:
37:
38:
41:
/*------------------------------------------------*/
/* adresar.c
*/
#include <stdio.h>
#include <errno.h>
#ifdef unix
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#else
#include <dir.h>
#endif
#define ADRESAR "DATADIR"
#define SOUBOR "ahoj.txt"
#ifdef unix
#define MKDIR(X) mkdir(X,0777)
#else
#define MKDIR(X) mkdir(X)
#endif
int errkonec(void)
{
perror("CHYBA");
fprintf(stderr, "Stisknete ENTER pro ukonceni ...\n");
(void) getchar();
return errno;
}
int main(void)
{
char text[255];
FILE *soubor;
char c;
getcwd(text, sizeof(text) / sizeof(text[0]));
printf("Vytvarim v adresari %s podadresar %s\n", text, ADRESAR);
if (MKDIR(ADRESAR) != 0) {
99
42:
return errkonec();
43:
}
45:
printf("Vstupuji do adresare %s\n", ADRESAR);
46:
if (chdir(ADRESAR) != 0) {
47:
return errkonec();
48:
}
50:
printf("Vytvarim soubor %s\n", SOUBOR);
52:
if (!(soubor = fopen(SOUBOR, "wr")))
53:
return errkonec();
55:
fprintf(soubor, "Ahoj");
56:
fclose(soubor);
57:
printf("Mam zase vse smazat? [y/n] ");
58:
c = getchar();
59:
if (c == 'n') {
60:
fputs("Koncim.", stdin);
61:
return 0;
62:
} else if (c != 'y') {
63:
fputs("Nechapu, koncim.", stdin);
64:
return 0;
65:
}
67:
/* mazani souboru a adresare */
68:
printf("Mazu soubor %s\n", SOUBOR);
69:
if (unlink(SOUBOR) != 0) {
70:
return errkonec();
71:
}
73:
printf("Opoustim adresar %s\n", ADRESAR);
74:
/* prechod v adresarove strukture o adresar vyse */
75:
if (chdir("..") != 0) {
76:
return errkonec();
77:
}
79:
printf("Mazu adresar %s\n", ADRESAR);
80:
if (rmdir(ADRESAR) != 0) {
81:
return errkonec();
82:
}
84:
return 0;
85: }
87: /*------------------------------------------------*/
Možný výstup:
Vytvarim v adresari /home/rimmer/public_html/rimmer/c podadresar DATADIR
Vstupuji do adresare DATADIR
Vytvarim soubor ahoj.txt
Mam zase vse smazat? [y/n] y
Mazu soubor ahoj.txt
Opoustim adresar DATADIR
Mazu adresar DATADIR
Přímý přístup na disk
Tento způsob práce se soubory není v normě ANSI C (ale je součástí normy POSIX), není tedy zaručena
přenositelnost. Výhodou oproti datovým proudům je rychlost. Funkce pracující s přímým přístupem na disk
se chovají velice podobně jako funkce pracující s datovými proudy. Funkce jsou deklarovány v
standardních knihovnách <sys/types.h>, <sys/stat.h>, <fcntl.h> a <unistd.h>.
100
Deskriptor souboru
Deskriptor souboru je kladné celé číslo, které slouží k identifikaci souboru v rámci programu. Někdy se mu
také říká manipulační číslo. Toto číslo využívají všechny funkce pracující s přímým přístupem na disk.
Pokud je již datový proud otevřen, pak k získání jeho deskriptoru lze použít funkci fileno(). Je deklarována
v souboru <stdio.h>.
int fileno(FILE *stream);
Otevření a uzavření souboru
Pro práci se souborem pomocí přímého přístupu na disk, je potřeba jej nejdříve „otevřít“ funkcí open().
Návratovou hodnotou je potřebný deskriptor souboru nebo -1 v případě chyby. Zde jsou dvě možné verze
deklarace.
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Prvním argumentem je pathname (jméno souboru), druhým je flags, neboli příznak otevření souboru.
Příznaky flags pro open()
Příznak
Otevření pro
O_RDONLY čtení
O_WRONLY zápis
čtení i zápis
O_RDWR
Příznak se kombinuje pomocí bitového součtu | s některým(i) příznaky z následující tabulky. (Kromě těchto
vypsaných existují další implementačně závislé.
Další příznaky flags pro funkci open()
Příznak
Význam
O_CREAT
Pokud soubor neexistuje, vytvoří se.
O_EXCL
Používá se spolu s volbou O_CREAT. V případě, že soubor existuje, otevírání selže a
funkce open() vrátí chybu.
O_TRUNC
Pokud soubor existuje a je nastaven pro zápis (O_WRONLY nebo O_RDWR) je
zkrácen na nulovou délku
O_APPEND Zápis bude prováděn na konec souboru.
V systému MS-DOS jsou v souboru <fcntl.h> definovány ještě dva důležité příznaky, které určují zda
chceme pracovat v textovém či binárním režimu.
#define O_TEXT
#define O_BINARY
0x4000
0x8000
/* CR-LF translation */
/* no translation
*/
Druhá deklarace open() má navíc argument mode, který určuje přístupová práva k nově vytvářenému
souboru (jinak nemá smysl). Přístupová práva v Unixech jsou pak nastavena na hodnotu mode & ~umask.
Tedy obdobně jako při vytváření adresářů. Jaká přístupová práva lze použít je závislé na OS, respektive
souborovém systému. V DOSu je možné nastavit S_IWRITE a S_IREAD, v Unixu jsou jiná přístupová
práva, např. S_IRUSR a S_IWURS. Přístupová práva se opět kombinují pomocí bitového součtu |.
101
Dále je možné k otevření souboru s přímým přístupem na disk použít funkci creat(). Dělá to samé jako
funkce open s nastaveným argumentem flags na O_CREAT|O_WRONLY|O_TRUNC.
int creat(const char *pathname, mode_t mode);
K uzavření souboru se používá funkce close(), jejímž jediným argumentem je jeho deskriptor.
int close(int fd);
Návratovou hodnotou je 0 nebo -1 v případě neúspěchu. Chceme-li mít jistotu, že po zavření souboru jsou
data na disku, je nutno nejprve použít funkci sync().
Čtení, zápis a posun
Ze souboru čteme pomocí funkce read(), do souboru zapisujeme pomocí funkce write().
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, void *buf, size_t count);
Argument fd je deskriptor otevřeného souboru, argument buf je ukazatel do paměti, kde jsou zapisovaná
data, nebo kam načtená data ukládáme (argument je třeba přetypovat). Argument count je počet
načítaných (zapisovaných) bajtů. Návratovou hodnotou je počet skutečně načtených (zapsaných) bajtů,
nebo, v případě neúspěchu, -1. Opět - pokus načtení (zápisu) více bajtů, než je velikost paměti buf skončí
běhovou chybou, a dále- jistotu vyprázdnění vyrovnávací paměti můžeme mít až po použití funkce fflush().
Pro pohyb v souboru slouží funkce lseek().
off_t lseek(int fildes, off_t offset, int whence);
Funkce posune ukazatel deskriptoru souboru fildes o vzdálenost off_t bajtů od místa určeným argumentem
whence. Tento argument může být SEEK_SET (posun od začátku), SEEK_CUR (posun od aktuální
pozice) nebo SEEK_END (od konce souboru). Pokud bude ukazatel posunut za konec souboru, pak při
jakémkoliv zápisu bude vzniklá mezera (mezi původním koncem souboru a nově zapsanými daty) vyplněna
nulami. Návratová hodnota je v případě úspěchu pozice na kterou se deskriptor přesunul, jinak (off_t) -1.
Následující dva programy dělají to samé. Jeden využívá přímý přístup na disk, druhý datové proudy.
1:
2:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
16:
18:
19:
20:
21:
22:
23:
26:
/*------------------------------------------------*/
/* zapis1.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef unix
#include <unistd.h>
#else
#include <io.h>
#endif
#define POCET_CYKLU 1000000u
#ifdef unix
#define OPEN open("jmeno.txt",O_RDWR|O_CREAT|O_TRUNC,\
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
#else
#define OPEN open("jmeno.txt",O_RDWR|O_CREAT|O_TRUNC|O_TEXT)
#endif
int main(void)
102
27:
28:
29:
30:
31:
32:
34:
35:
36:
37:
38:
40:
41:
42:
43:
44:
45:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
63:
64:
65:
67:
{
int id;
unsigned long i;
char SNO[] = "Soubor nelze otevrit\n";
char TEXT1[] = "Tenhle text budeme zapisovat do souboru\n";
char TEXT2[] = "-------------- prepsano ---------------\n";
if ((id = OPEN) <= 0) {
/* fprintf by bylo vhodnejsi, ale nemohl jsem si to odpustit :-} */
write(fileno(stderr), SNO, strlen(SNO));
exit(1);
}
for (i = 0L; i < POCET_CYKLU * 10u; i++) {
if (write(id, TEXT1, strlen(TEXT1)) == -1) {
fprintf(stderr, "Data nelze zapisovat");
exit(1);
}
}
lseek(id, 0, SEEK_SET);
for (i = 0L; i < POCET_CYKLU; i++) {
if (write(id, TEXT2, strlen(TEXT2)) == -1) {
fprintf(stderr, "Data nelze zapisovat");
exit(1);
}
#ifdef unix
#define clrf 0
#else
#define clrf 1
#endif
lseek(id, (strlen(TEXT1) + clrf) * 9, SEEK_CUR);
#undef clrf
}
close(id);
/* spravne by bylo kontrolovat navratovou hodnotu */
return 0;
}
/*------------------------------------------------*/
V programu je definováno makro clrf v závislosti na operačním systému a jeho hodnota se přičítá k délce textu.
Je to proto, protože v DOSu (a Windows) je znak konec řádku '\n' do souboru zapisován pomocí dvou bajtů,
kdežto v Unixu pomocí jednoho. Funkce strlen() znak '\n' počítá vždy jako jeden znak. Zbytek už je otázka malé
násobilky.
Druhý program využívá datové proudy.
1:
2:
4:
5:
6:
8:
10:
12:
13:
14:
15:
16:
17:
18:
20:
21:
22:
23:
25:
/*------------------------------------------------*/
/* zapis2.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define POCET_CYKLU 1000000u
#define OPEN fopen("jmeno.txt","w+")
int main(void)
{
FILE *id;
unsigned long i;
char SNO[] = "Soubor nelze otevrit\n";
char TEXT1[] = "Tenhle text budeme zapisovat do souboru\n";
char TEXT2[] = "-------------- prepsano ---------------\n";
if ((id = OPEN) == NULL) {
fprintf(stderr, "%s", SNO);
exit(1);
}
for (i = 0L; i < POCET_CYKLU * 10u; i++) {
103
26:
27:
28:
29:
30:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
47:
48:
49:
51:
if (fwrite(TEXT1, strlen(TEXT1), 1, id) != 1) {
fprintf(stderr, "Data nelze zapisovat");
exit(1);
}
}
fseek(id, 0, SEEK_SET);
for (i = 0L; i < (POCET_CYKLU); i++) {
if (fwrite(TEXT2, strlen(TEXT2), 1, id) != 1) {
fprintf(stderr, "Data nelze zapisovat");
exit(1);
}
#ifdef unix
#define clrf 0
#else
#define clrf 1
#endif
fseek(id, (strlen(TEXT1) + clrf) * 9, SEEK_CUR);
#undef clrf
}
fclose(id);
return 0;
}
/*------------------------------------------------*/
Po spuštění obou programů vznikne na disku soubor velikosti 381Mb. Otázka je, zda zrychlení programu
pomocí přímého přístupu na disk stojí za všechny ty komplikace (nižší komfort při programování a nižší
přenositelnost kódu).
Spouštění programů
Nejprve zdrojový kód programu mudrc, který bude v dalších programech spouštěn.
1: /*------------------------------------------------*/
2: /* mudrc.c
*/
3: #include <stdio.h>
5: int main(int argc, char *argv[])
6: {
7:
int i;
8:
printf("\n***************************\n");
9:
printf("* mudrc rika:
*\n");
10:
switch (argc) {
11:
case 1:
12:
printf("* Kdo to rusi me kruhy?
*\n");
13:
break;
14:
default:
15:
printf("* Argumenty:
*\n");
16:
/* Prvni argument je nazev programu (preskocime ho).*/
19:
for (i = 1; i < argc; i++) {
20:
printf("* %2i) %-19s *\n", i, argv[i]);
21:
}
22:
break;
23:
}
24:
printf("***************************\n\n");
25:
return 0;
26: }
28: /*------------------------------------------------*/
Pokud nejsou programu mudrc nepředány žádné argumenty, vypíše jen hlášku „Kdo to rusi me kruhy?“,
jinak vypíše zadané argumenty.
104
Funkce system()
Funkce system() je jedinná, z funkcí zde zmiňovaných, definována v normě ANSI C a zaručuje tedy
největší kompatibilitu. Návratovou hodnotou je 0 v případě úspěchu, jinak -1.
int system(const char * string);
Má pouze jediný argument (textový řetězec). Je to příkaz, který se má vykonat. Příkazem může být cokoliv,
co lze zadat na příkazovou řádku. Funkce system() spustí příkazový interpret a tento příkaz mu předá.
Příkazový interpret příkaz vykoná a ukončí se tj. vrátí řízení programu. To, jaký příkazový interpret se
spustí je ovlivněno operačním systémem. V DOSu (Windows) půjde nejpravděpodobněji o command.com
MS-DOSu, v Linuxu může jít o bash, tcsh, csh atp. Spustit příkaz samotného interpretu je jednoduché.
Stačí zadat jeho jméno (a za ním parametry). Například command.com obsahuje příkaz pause, který
způsobí pozastavení vykonávání příkazového interpretu (a ve svém důsledku pozastavení programu, který
tento příkaz pomocí system() spustil).
system("pause");
Dalším příkladem může být příkaz DIR, u kterého budeme chtít vypsat jen textové soubory.
system("dir *.txt");
Dále například i přesměrování výstupu do souboru.
system("dir *.txt > vystup.log");
Při spouštění programů je třeba zajistit, aby příkazový interpret věděl, kde se program daného jména
nachází. Nejjednodušším řešením je uvést plné jméno programu. Jinak příkazový interpret hledá v
aktuálním adresáři, nebo adresářích určených v systémové proměnné PATH (v DOSu se inicializace této
proměnné nachází v souboru c:\autoexec.bat). Další informace o návratovém kódu a funkci system() jsou
uvedeny v kapitole o knihovně <stdlib.h>, kde je tato funkce deklarována. V následujícím příkladu je
spouštěn program mudrc. Je umístěn ve stejném adresáři, jako program system2 a v proměnné PATH je
uveden i aktuální adresář (tj. tečka).
1:
2:
3:
4:
6:
7:
8:
10:
11:
12:
13:
14:
15:
16:
18:
19:
21:
/*------------------------------------------------*/
/* system2.c
*/
#include <stdlib.h>
#include <stdio.h>
#ifdef unix
#include <sys/wait.h>
#endif
int main(void)
{
printf("system2: system(\"mudrc\");\n");
system("mudrc");
printf("system2: system(\"mudrc Jak se vede?\");\n");
system("mudrc Jak se vede?");
printf("system2: Konec programu.\n");
return 0;
}
/*------------------------------------------------*/
Výstup: system2: system("mudrc");
***************************
***************************
* mudrc rika:
*
* mudrc rika:
*
* Argumenty:
*
* Kdo to rusi me kruhy?
*
* 1) Jak
*
***************************
* 2) se
*
system2: system("mudrc Jak se vede?");
* 3) vede?
*
***************************
system2: Konec programu.
105
Při spuštění programu system2 a přesměrování jeho výstupu do souboru se stala zvláštní věc. V souboru
byly na začátku výstupy ze spuštěných programů mudrc a až na konci výstupy (z funkcí printf()) programu
system2. Došlo k tomu zřejmě tím, že spuštěný program mudrc měl ve chvíli svého vykonávání vyšší
prioritu. Toto podivné chování lze brát jako další nevýhodu funkce system() oproti dále uvedeným funkcím,
a i na takovéto věci si musí programátor dávat pozor.
Poznámka: Zabránit tomuto podivnému chování lze použitím funkce fflush(stdout).
Funkce exec*
Funkce exec() nahradí aktuální proces novým. Načte zadaný program do paměti a ten, který funkci
spustil skončí (v případě úspěchu). Pokud se program funkcí exec() nepodaří spustit, aktuální proces
poběží dál, funkce vrátí -1 a globální proměnná errno se nastaví na hodnotu chyby. Funkce exec()
nespouští příkazový interpret, ale jen daný program (rychlé a účinné). Funkcí exec() existuje celá
rodina, avšak nejsou součástí normy ANSI C. Z toho důvodu jejich deklaraci lze v různých systémech najít
v různých hlavičkových souborech (<unistd.h>, <process.h>, <stdlib.h>), (nebo vůbec).
int
int
int
int
int
execl (const char *path, const char *arg, ...);
execlp (const char *file, const char *arg, ...);
execle (const char *path, const char *arg, ..., char *const envp[]);
execv (const char *path, char *const argv[]);
execvp (const char *file, char *const argv[]);
Všechny předchozí funkce jsou (většinou) implementovány pomocí následující funkce:
int execve (const char *file, char *const argv[], char *const envp[]);
U všech funkcí je prvním argumentem název programu, který se má spustit. Funkce execlp() a execvp() se
liší tím, že při hledání programu prohledávají adresáře určené systémovou proměnnou PATH (pokud není
program zadán s absolutní nebo relativní cestou). Dalšími argumenty funkcí exec jsou argumenty
předávané spouštěnému programu. Prvním předávaným argumentem je jméno spouštěného programu a
poslední argument musí být ukazatel NULL. Funkce execle() a execve() umožňují nastavit nové systémové
proměnné pro spouštěný program (pomocí argumentů envp). Rozdíl mezi funkcemi, které mají v
názvu l nebo v, je jen ve způsobu předávání argumentů.
1:
2:
4:
5:
6:
7:
8:
9:
10:
11:
13:
14:
15:
16:
17:
19:
21:
22:
23:
24:
26:
27:
28:
29:
/*------------------------------------------------*/
/* exec1.c
*/
#include <stdlib.h>
#include <stdio.h>
#ifndef unix
#include <process.h>
#else
#include <unistd.h>
#endif
#include <errno.h>
int main(void)
{
char *argv[] = { "mudrc", "prvni", "druhy", "treti", NULL };
char *env[] = { "PATH=.", NULL };
int navrat;
printf("exec1: execl(\"nesmysl\",\"nesmysl\",\"argument1\",NULL);\n");
navrat = execl("nesmysl", "nesmysl", "argument1", NULL);
/* kdyby nesmysl existoval, nasledujici kod by jiz neprobehl */
fprintf(stderr, "Navrat execl = %2i\n", navrat);
perror("CHYBA EXECL");
printf("exec1: execve(\"mudrc\", argv, env);\n");
execve("mudrc", argv, env);
/*pokud se podari spustit program mudrc, nasledujici kod se neprovede */
fprintf(stderr, "Navrat execl = %2i\n", navrat);
106
30:
perror("CHYBA EXECVE");
32:
return 0;
33: }
35: /*------------------------------------------------*/
Výstup z programu:
exec1: execl("nesmysl","nesmysl","argument1",NULL);
Navrat execl = -1
CHYBA EXECL: No such file or directory
exec1: execve("mudrc", argv, env);
***************************
* mudrc rika:
*
* Argumenty:
*
* 1) prvni
*
* 2) druhy
*
* 3) treti
*
***************************
Funkce spawn*
Rodina funkcí spawn* se chová stejně jako exec* s tím rozdílem, že aktuální proces nenahradí a po svém
skončení se pokračuje v jeho vykonávání. Dostupnost těchto funkcí je však ještě menší, než funkcí exec*.
V Linuxu nepřeložitelné. Jsou tu ale k dispozici funkce fork() a vfork(), jejichž použití je však náročnější.
1: /*------------------------------------------------*/
2: /* spawn1.c
*/
3: #include <stdlib.h>
4: #include <stdio.h>
5: #include <process.h>
6: #include <errno.h>
8: int main(void)
9: {
11:
printf
("spawn1: spawnl(\"mudrc\",\"mudrc\",\"prvni\",\"druhy\",\"treti\",NULL);\n");
13:
if (spawnl(P_WAIT, "mudrc", "mudrc", "prvni", "druhy", "treti", NULL)
14:
== -1)
15:
perror("CHYBA SPAWNL");
17:
printf("spawn1: konec.\n");
18:
return 0;
19: }
21: /*------------------------------------------------*/
Výstup:
spawn1: spawnl("mudrc","mudrc","prvni","druhy","treti",NULL);
***************************
* mudrc rika:
*
* Argumenty:
*
* 1) prvni
*
* 2) druhy
*
* 3) treti
*
***************************
spawn1: konec.
Algoritmy třídění
Funkce qsort()
Je nejjednodušším řešením jak setřídit data v programovacím jazyku C.
107
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *,
const void *));
Prvním argumentem funkce qsort() je „prázdný“ ukazatel na pole, které se bude třídit. (Jelikož to bude pole
typu int, přetypujeme ukazatel na ukazatel na typ int.) Druhým argumentem je počet položek (rozměr) pole,
třetím argumentem je velikost jedné položky (v tomto případě počet bajtů typu int) a posledním
argumentem je ukazatel na funkci (compar), podle které se budou porovnávat dva prvky z pole. Tato
porovnávací funkce musí mít dva argumenty – ukazatele, kterými budou předány dvě hodnoty z pole na
porovnání. Návratovou hodnotou této porovnávací funkce je typ int, podle kterého funkce qsort() pozná,
který z porovnávaných prvků je větší. V případě, že větší je první prvek, musí vrátit porovnávací funkce
kladnou hodnotu (1), pokud je menší vrátí hodnotu zápornou (-1) a pokud se rovnají, vrátí 0. Pokud budou
návratové hodnoty v porovnávací funkci obráceny, dojde k setřídění od největšího po nejmenší.
Není zde uveden celý příklad s výstupem, jen podstatná část.
1:
3:
4:
5:
6:
8:
9:
10:
int pole[POLOZEK];
int vetsi(int *a, int *b)
{
return *a - *b;
}
/* nezapomente nejdrive pole inicializovat nejakymi hodnotami */
qsort((void *) pole, POLOZEK, sizeof(pole[0]),
(int (*)(const void *, const void *)) vetsi);
Buble sort
Algoritmus bublinkového třídění je poměrně jednoduchý ale ne moc efektivní. Prochází se několikrát celé
pole (odzadu do předu) a porovnávají se sousední prvky. Pokud nejsou prvky správně seřazeny za sebou,
jsou prohozeny. Takto nejmenší prvky „probublávají“ směrem k začátku (nebo obráceně). Po prvním
průchodu je zajištěno, že nejmenší prvek probublal na začátek pole a tak s ním v dalším cyklu už
nemusíme počítat.
1:
2:
4:
5:
6:
8:
10:
11:
12:
13:
15:
16:
17:
18:
19:
20:
21:
23:
24:
25:
26:
28:
29:
31:
32:
34:
35:
37:
/*------------------------------------------------*/
/* bubble.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROZMER 10
void vypis_pole(short int cykl, unsigned int pole[])
{
unsigned short int i;
printf("Prvky pole (%2i):\n", cykl);
/* schvalne, co by se stalo, kdyby byl ROZMER lichy ? */
for (i = 0; i < ROZMER / 2; i++)
printf("%2u: %u\t\t%2u: %u\n", i + 1, pole[i], i + ROZMER / 2 + 1,
pole[i + ROZMER / 2]);
/* spravna odpoved zni .... chvilka napeti ...
* posledni prvek z pole by se nevypsal.
*/
}
int main(void)
{
unsigned int cykl, bublani;
unsigned int pole[ROZMER], pom;
/* nejdrive inicializujeme pole nahodnymi hodnotami a vypiseme jej */
srand((unsigned int) time(NULL));
for (cykl = 0; cykl < ROZMER; cykl++)
pole[cykl] = (rand() % 100) + 1;
printf("Inicializace: \n");
vypis_pole(-1, pole);
/* ted pole setridime algoritmem bubble sort */
108
39:
for (cykl = 1; cykl < ROZMER; cykl++) {
40:
for (bublani = ROZMER - 1; bublani >= cykl; bublani--) {
41:
printf(".");
42:
if (pole[bublani - 1] > pole[bublani]) {
43:
/* mensi cislo probubla o jeden prvek smerem k zacatku */
44:
pom = pole[bublani - 1];
45:
pole[bublani - 1] = pole[bublani];
46:
pole[bublani] = pom;
47:
}
48:
}
49:
/* vypiseme mezivysledek, kde "cykl" nejmensich cisel
50:
* uz mame zarucene na zacatku pole */
51:
printf("\n");
52:
vypis_pole(cykl, pole);
53:
}
55:
return 0;
56: }
58: /*------------------------------------------------*/
Možný výstup z programu:
Inicializace:
Prvky pole (-1):
1: 26
6:
2: 87
7:
3: 81
8:
4: 9
9:
5: 17
10:
.........
Prvky pole ( 1):
1: 6
6:
2: 26
7:
3: 87
8:
4: 81
9:
5: 9
10:
........
Prvky pole ( 2):
1: 6
6:
2: 7
7:
3: 26
8:
4: 87
9:
5: 81
10:
.......
Prvky pole ( 3):
1: 6
6:
2: 7
7:
3: 9
8:
4: 26
9:
5: 87
10:
64
53
6
23
7
17
64
53
7
23
9
17
64
53
23
81
17
23
64
53
....
Prvky pole ( 6):
1: 6
6: 26
2: 7
7: 53
3: 9
8: 87
4: 17
9: 81
5: 23
10: 64
...
Prvky pole ( 7):
1: 6
6: 26
2: 7
7: 53
3: 9
8: 64
4: 17
9: 87
5: 23
10: 81
/* zde je inicializované a
zatím nesetříděné, náhodně
vygenerované pole */
/* po prvním "buble" cyklu nám probublalo na začátek
nejmenší číslo 6. Všimněte si, že se nejdříve
snažila probublat sedmička. Ostatní čísla se jen
posunula o prvek níže. */
......
Prvky pole ( 4):
1: 6
6: 87
2: 7
7: 81
3: 9
8: 23
4: 17
9: 53
5: 26
10: 64
.....
Prvky pole ( 5):
1: 6
6: 26
2: 7
7: 87
3: 9
8: 81
4: 17
9: 53
5: 23
10: 64
/* Pole je seřazeno, ale buble-sort
o tom neví. Proto pokračuje ve třídění. */
..
Prvky pole ( 8):
1: 6
6: 26
2: 7
7: 53
3: 9
8: 64
4: 17
9: 81
5: 23
10: 87
.
Prvky pole ( 9):
1: 6
6: 26
4: 17
9: 81
2: 7
7: 53
5: 23
10: 87
3: 9
8: 64
109
Tučně zvýrazněné jsou místa v poli, kde je zaručeno, že je pole seřazeno. (Proto se vnitřní průchozí cyklus 'for'
zkracuje.)
Algoritmus bubble sort má pevně daný počet operací, který závisí jen na rozměru pole. I kdyby bylo pole
už seřazeno, bubble-sort by provedl stejný počet operací. Tomu lze jednoduše zabránit tak, že se bude
kontrolovat, zda se při „bubble“ cyklu nějaké prvky zaměnili. Pokud ne, algoritmus se ukončí.
Quick sort
Algoritmus quick sort (nebo též „řazení dělením“) je jeden z nejefektivnějších. Princip je následující.
Řazená pole se rozdělí na dvě části tak, aby v jedné části pole byla všechna čísla menší, než čísla v druhé
části. Aby se to dobře programovalo, vezme se jeden prvek z pole (třeba prostřední), tzv. pivot a třídí se
pole tak, že čísla z první části jsou menší a čísla z druhé části jsou větší než pivot. Na oba úseky se
použije rekurzivně stejný algoritmus, a tak se pole rozděluje na menší a menší úseky, až je rozdělené na
úseky délky 1, čímž je pole setříděno.
1: /*------------------------------------------------*/
2: /* quick.c
*/
4: #include <stdio.h>
5: #include <stdlib.h>
6: #include <time.h>
/* funkci quicks budeme volat rekurzivne,
8: #define ROZMER 10
vzdy na levou a pravou cast */
13: void vypis_usek(const unsigned int *pole, const char *text,
14:
const unsigned int zacatek, const unsigned int konec)
16: {
unsigned int i;
17:
printf("%-10s", text);
18:
for (i = 0; i < ROZMER; i++) {
19:
if (i < zacatek || i > konec) {
20:
printf("-- ");
21:
} else {
22:
printf("%2u ", pole[i]);
23:
}
24:
}
25:
printf("\n");
26: }
28: void quicks(unsigned int *pole, const unsigned int levy_usek,
29:
const unsigned int pravy_usek)
30: {
31:
unsigned int levy, pravy;
32:
unsigned int pivot, pom;
34:
levy = levy_usek;
35:
pravy = pravy_usek;
37:
pivot = pole[(levy + pravy) / 2];
/* vybereme pivota */
38:
printf("pivot: pole[%u] = %u\n", (levy + pravy) / 2, pivot);
39:
vypis_usek(pole, "Pred:", levy_usek, pravy_usek);
41:
do {
42:
while (pole[levy] < pivot)
43:
levy++;
44:
while (pivot < pole[pravy])
45:
pravy--;
47:
if (levy <= pravy) {
48:
pom = pole[levy];
49:
pole[levy] = pole[pravy];
50:
pole[pravy] = pom;
51:
levy++;
52:
pravy--;
53:
}
55:
} while (levy <= pravy);
57:
vypis_usek(pole, "Po:", levy_usek, pravy_usek);
58: /*vsechna cisla od "levy_usek" k "pravy" jsou <= od "levy" po pravy_usek*/
60:
if (levy_usek < pravy)
110
61:
62:
63:
65:
67:
69:
70:
72:
74:
75:
76:
77:
79:
80:
82:
83:
84:
85:
86:
88:
89:
91:
quicks(pole, levy_usek, pravy);
if (levy < pravy_usek)
quicks(pole, levy, pravy_usek);
/* razeni leve casti */
/* razeni prave casti */
}
int main(void)
{
unsigned int cykl;
unsigned int pole[] = { 74, 43, 94, 6, 89, 76, 88, 33, 52, 59 };
printf("%-10s", "Start:");
for (cykl = 0; cykl < ROZMER; cykl++) {
printf("%2u ", pole[cykl]);
}
printf("\n");
/* ted pole setridime algoritmem quick sort */
quicks(pole, 0, ROZMER - 1);
printf("%-10s", "Konec:");
for (cykl = 0; cykl < ROZMER; cykl++) {
printf("%2u ", pole[cykl]);
}
printf("\n");
return 0;
}
/*------------------------------------------------*/
Start:
pivot:
Pred:
Po:
pivot:
Pred:
Po:
pivot:
Pred:
Po:
pivot:
Pred:
Po:
pivot:
Pred:
Po:
pivot:
Pred:
Po:
pivot:
Pred:
Po:
pivot:
Pred:
Po:
pivot:
Pred:
Po:
Konec:
74 43 94 6
pole[4] = 89
74 43 94 6
74 43 59 6
pole[3] = 6
74 43 59 6
6 43 59 74
pole[4] = 52
-- 43 59 74
-- 43 33 52
pole[2] = 33
-- 43 33 52
-- 33 43 52
pole[2] = 43
-- -- 43 52
-- -- 43 52
pole[5] = 76
-- -- -- --- -- -- -pole[4] = 74
-- -- -- --- -- -- -pole[6] = 88
-- -- -- --- -- -- -pole[8] = 89
-- -- -- --- -- -- -6 33 43 52
89 76 88 33 52 59
89 76 88 33 52 59
52 76 88 33 89 94
52 76 88 33 -- -52 76 88 33 -- -52 76 88 33 -- -74 76 88 59 -- --- -- -- -- -- --- -- -- -- -- --- -- -- -- -- --- -- -- -- -- -74 76 88 59 -- -74 59 88 76 -- -74 59 -- -- -- -59 74 -- -- -- --- -- 88 76 -- --- -- 76 88 -- --- -- -- -- 89 94
-- -- -- -- 89 94
59 74 76 88 89
111
OBSAH
Programování v jazyku C/C++ ............................................................................................................. 1
Programovací jazyk, zdrojový kód a překladač .................................................................................... 1
Přenositelnost ........................................................................................................................................ 1
Trocha historie C a C++........................................................................................................................ 1
Vývojová prostředí a překladače ......................................................................................................... 2
Windows API........................................................................................................................................ 2
Překladač pro Linux .............................................................................................................................. 2
Code::Blocks......................................................................................................................................... 2
Bloodshed Dev-C++ a wxDev-C++...................................................................................................... 2
DJGPP................................................................................................................................................... 2
Visual Studio......................................................................................................................................... 2
Visual C++ Express Edition.................................................................................................................. 3
Borland C++ Builder............................................................................................................................. 3
Borland C++ Builder Compiler ............................................................................................................ 3
Eclipse................................................................................................................................................... 3
NetBeans ............................................................................................................................................... 3
Anjuta.................................................................................................................................................... 3
Sun Studio ............................................................................................................................................. 3
Struktura programu ................................................................................................................................ 3
První program ....................................................................................................................................... 3
Komentáře ............................................................................................................................................. 4
Knihovny............................................................................................................................................... 4
Funkce ................................................................................................................................................... 4
Funkce printf() ...................................................................................................................................... 5
Datové typy, bloky, deklarace a definice ............................................................................................ 5
Základní datové typy............................................................................................................................. 5
Boolovské datové typy.......................................................................................................................... 6
Bloky, deklarace a definice................................................................................................................... 6
Ukazatele............................................................................................................................................... 7
NULL .................................................................................................................................................... 8
Klíčová slova, konstanty, řetězce ........................................................................................................ 8
Klíčová slova......................................................................................................................................... 8
Konstanty .............................................................................................................................................. 8
Řetězce .................................................................................................................................................. 9
Escape sekvence.................................................................................................................................. 11
Standardní vstup a výstup .................................................................................................................. 12
Funkce printf() .................................................................................................................................... 12
Funkce scanf()..................................................................................................................................... 15
Práce s typy dat .................................................................................................................................... 17
ASCII tabulka ..................................................................................................................................... 17
Přetypování ......................................................................................................................................... 18
Chyby přetypování.......................................................................................................................... 18
Pole a ukazatele ................................................................................................................................... 19
Datové pole a řetězce .......................................................................................................................... 19
Vícerozměrná pole .............................................................................................................................. 21
Ukazatele a pole .................................................................................................................................. 21
Ukazatele na ukazatele.................................................................................................................... 22
Operátory a výrazy............................................................................................................................... 23
Unární operátory ................................................................................................................................. 23
112
Priorita operátorů ................................................................................................................................ 23
Binární operátory ................................................................................................................................ 23
Podmíněný operátor ............................................................................................................................ 24
Použití NULL.................................................................................................................................. 25
Preprocesor........................................................................................................................................... 26
Standardní makra ................................................................................................................................ 26
Definice maker.................................................................................................................................... 26
Makra s argumenty.......................................................................................................................... 28
Operátory # a ##.................................................................................................................................. 28
Podmíněný překlad ............................................................................................................................. 29
Užití knihovny..................................................................................................................................... 29
Ostatní direktivy.................................................................................................................................. 30
#pragma........................................................................................................................................... 30
#line................................................................................................................................................. 30
Podmínky a cykly.................................................................................................................................. 31
Podmínka if-else ................................................................................................................................. 31
Přepínač switch ................................................................................................................................... 32
Skok goto ............................................................................................................................................ 33
Cyklus for............................................................................................................................................ 33
Cykly do a while ................................................................................................................................. 34
Funkce.................................................................................................................................................... 34
Definice funkce ................................................................................................................................... 35
Deklarace funkce................................................................................................................................. 35
Rekurzivní funkce ............................................................................................................................... 36
Proměnlivý počet argumentů .............................................................................................................. 37
Argumenty příkazové řádky................................................................................................................ 38
Lokální, globální a statické proměnné ................................................................................................ 40
Uživatelské knihovny.......................................................................................................................... 41
Vytváření typů......................................................................................................................................... 42
Struktury.............................................................................................................................................. 42
Definování typu pomocí typedef......................................................................................................... 45
Výčtový typ enum............................................................................................................................... 47
Typ union ............................................................................................................................................ 48
Funkce rand() a srand() ................................................................................................................... 48
Ukazatele na funkce ............................................................................................................................ 49
Přehled deklarací................................................................................................................................. 52
Dynamická alokace paměti ..................................................................................................................... 57
Získání a uvolnění paměti pomocí malloc() a free()........................................................................... 57
Získání paměti pro struktury pomocí calloc()..................................................................................... 59
Změna velikosti získané paměti pomocí realloc() .............................................................................. 62
Knihovna string.h.................................................................................................................................... 63
Výčet funkcí ........................................................................................................................................ 63
Příklad použití funkcí.......................................................................................................................... 63
Knihovna time.h...................................................................................................................................... 64
Uchovávání času ................................................................................................................................. 64
Výčet funkcí ........................................................................................................................................ 65
Příklad použití funkcí I (sportka) ........................................................................................................ 66
Příklad použití funkcí II ...................................................................................................................... 68
Knihovna math.h..................................................................................................................................... 69
Výčet funkcí ........................................................................................................................................ 69
Knihovna stdlib.h .................................................................................................................................... 71
Výčet funkcí (výčet není úplný).......................................................................................................... 71
113
Užitečná makra ................................................................................................................................... 72
Poznámky k funkcím .......................................................................................................................... 72
Knihovny stdarg.h, limits.h a signal.h .................................................................................................... 75
Knihovna stdarg.h ............................................................................................................................... 75
Knihovna limits.h................................................................................................................................ 75
Knihovna signal.h ............................................................................................................................... 76
Funkce signal()................................................................................................................................ 77
Funkce sigaction()........................................................................................................................... 79
Knihovna errno.h .................................................................................................................................... 80
Proměnná errno ................................................................................................................................... 80
Co může obsahovat errno.................................................................................................................... 81
Chybové hlášení pomocí perror() ....................................................................................................... 82
Knihovna string.h II ................................................................................................................................ 83
Výčet funkcí ........................................................................................................................................ 83
Funkce sprintf(), snprintf() a sscanf() ................................................................................................. 84
Funkce sscanf() ............................................................................................................................... 85
Knihovna stdio.h ..................................................................................................................................... 85
Funkce getchar(), gets(), putchar() a puts()......................................................................................... 85
Výčet funkcí ze stdio.h........................................................................................................................ 86
Práce se soubory (a <stdio.h>)................................................................................................................ 87
O vstupu a výstupu.............................................................................................................................. 87
Přístup k souborům ............................................................................................................................. 88
Souborový proud............................................................................................................................. 88
Přímé volání .................................................................................................................................... 88
Textový a binární přístup k souborům ................................................................................................ 88
Limity.................................................................................................................................................. 88
Datové proudy......................................................................................................................................... 88
Otevření datového proudu................................................................................................................... 88
Uzavření a vyprázdnění datového proudu .......................................................................................... 89
Práce s textovými soubory ...................................................................................................................... 91
Zápis do textového souboru ................................................................................................................ 91
Čtení z textového souboru................................................................................................................... 92
Kontrola chyb (feof(), ferror(), clearerr()) .......................................................................................... 93
Práce s binárními soubory a pohyb v souborech..................................................................................... 94
Čtení a zápis binárního souboru.......................................................................................................... 94
Zápis pomocí fwrite()...................................................................................................................... 94
Čtení pomocí fread() ....................................................................................................................... 95
Příklad čtení a zápisu ...................................................................................................................... 95
Pozice v souboru ................................................................................................................................. 96
Zjištění velikosti souboru.................................................................................................................... 97
Adresářová struktura............................................................................................................................ 98
Aktuální adresář .................................................................................................................................. 98
Vytvoření adresáře .............................................................................................................................. 98
Přístupová práva.................................................................................................................................. 98
Změna aktuálního adresáře ................................................................................................................. 98
Mazání souborů a adresářů ................................................................................................................. 99
Přejmenování a přesunutí souborů...................................................................................................... 99
Příklad ................................................................................................................................................. 99
Přímý přístup na disk ......................................................................................................................... 100
Deskriptor souboru............................................................................................................................ 101
Otevření a uzavření souboru ............................................................................................................. 101
Čtení, zápis a posun .......................................................................................................................... 102
114
Spouštění programů .......................................................................................................................... 104
Funkce system() ................................................................................................................................ 105
Funkce exec*..................................................................................................................................... 106
Funkce spawn*.................................................................................................................................. 107
Algoritmy třídění.................................................................................................................................. 107
Funkce qsort() ................................................................................................................................... 107
Buble sort .......................................................................................................................................... 108
Quick sort.......................................................................................................................................... 110
Obsah................................................................................................................................................... 112
115

Podobné dokumenty

Ročníkový projekt - Model sanace turonské zvodně

Ročníkový projekt - Model sanace turonské zvodně prostředí. Rozsáhlý sanační program, prováděný firmou DIAMO s. p., si klade za cíl minimalizovat tento dopad produkce uranu na životní prostředí. Proces čištění oblasti je velmi náročný a to jak ča...

Více

Programový balík Crypta v1.3

Programový balík Crypta v1.3 Nakonec pomocí tlačítka “Generuj” spustíte generování dvojice RSA klíčů a tvorbu žádosti o certifikát. Nyní se vám objeví okno s emblémem České pošty, kde budete tak hlouho tisknout různé klávesy n...

Více

info@hudy / číslo 4 / 2005

info@hudy / číslo 4 / 2005 Jedná se o materiálovou novinkou pro výrobu funkčního prádla – 100% přírodní vlna Merino. Nejedná se o klasickou vlněnou přízi, která se používala dříve. Mnozí z nás jistě pamatují ponožky a svetry...

Více

Neinduktivní generace elektrického proudu v tokamaku

Neinduktivní generace elektrického proudu v tokamaku z anglického Lower Hybrid Current Drive). Takové vlny efektivně předávají svůj impulz, a tedy i energii, elektronům v plazmatu, což vede k elektrickému proudu v toroidálním směru. Vedle samotného f...

Více

Knihovny funkcí Chyby v CRT

Knihovny funkcí Chyby v CRT znaménkové celé číslo d nebo i e reálné číslo v exp. tvaru E reálné číslo v exp. tvaru f reálné číslo dekadicky g kratší z %e nebo %f G kratší z %E nebo %f o znaménkové celé číslo v osmičkové soust...

Více

rukovet programatora v os unix

rukovet programatora v os unix Knihovnı́ funkce zjišt’ujı́cı́ hodnotu proměnné prostředı́ (často jı́ bývá proměnná shellu). Parametrem je řetězec se jménem hledané proměnné. poznámka : Proměnné prostředı́ jsou...

Více

Praktická cvičení algoritmů - Informatika Ostravská Univerzita

Praktická cvičení algoritmů - Informatika Ostravská Univerzita ve dvojkové soustavě, záporná čísla se ukládají v doplňkovém kódu. Nejvyšší bit slouží k uložení znaménka. Pokud je tento bit roven nule, je číslo kladné, pokud je roven 1, znamená to, že číslo je ...

Více