Vyšší odborná škola, Jihlava, Tolstého 16 Obor Počítačové systémy ALGORITMY A PROGRAMOVÁNÍ V JAZYCE C/C++ 2. díl Ing. David Matoušek Mgr. Antonín Přibyl 3. vydání leden 2005
2 Algoritmy a programování v jazyce C/C++
1 Úvod... 6 2 Práce se soubory v jazyce C... 7 2.1 Základní funkce... 7 2.1.1 Otevření souboru fopen... 8 2.1.2 Zavření souboru fclose... 8 2.1.3 Test konce souboru feof... 9 2.2 Textové soubory... 9 2.2.1 Čtení a zápis do souboru getc, putc, fgets, fputs, fprintf, fscanf... 9 2.2.2 Jednoduché příklady... 10 2.3 Binární soubory... 15 2.3.1 Blokové čtení a zápis dat fread, fwrite... 15 2.3.2 Posun ukazovátka v souboru fseek... 15 2.3.3 Zjistění aktuální pozice ukazovátka ftell... 16 2.4 Další užitečné funkce pro práci se soubory... 20 2.4.1 Vyprázdnění souborových bufferů fflush... 20 2.4.2 Přesun ukazovátka souboru na začátek rewind... 20 2.4.3 Přesměrování souboru freopen... 20 2.4.4 Vytvoření dočasného souboru tmpfile... 20 2.4.5 Smazání souboru remove... 21 2.4.6 Zjištění poslední chyby při práci se souborem ferror... 21 2.4.7 Smazání chyby clearerr... 21 2.5 Spolupráce s operačním systémem... 22 2.5.1 Návratová hodnota funkce main... 22 2.5.2 Parametry funkce main... 22 3 Úvod do programovacího jazyka C++... 24 3.1 Základní rozdíly mezi C a C++... 24 3.1.1 Zjednodušený zápis komentáře... 24 3.1.2 Umístění deklarací v programu na libovolném místě... 24 3.2 Typ bool... 24 3.3 Klíčová slova jazyka C++... 24 3.4 Funkce v C++... 25 3.4.1 Přetížení funkcí (Function Overloading)... 25 3.4.2 Implicitní parametry funkcí... 25 3.4.3 Parametry funkce volané odkazem... 26 3.5 Zjednodušená deklarace datových typů... 28 3.6 Operátory jazyka C++... 28 3.6.1 Přetížené operátory (Overloading Operators)... 28 3.6.2 Nové operátory jazyka C++... 28 3.6.3 Vkládací operátor << a extrakční operátor >>... 29 3.6.4 Ukazatelový selektor >... 30 3.6.5 Operátor příslušnosti ::... 31 3.6.6 Operátory pro dynamickou správu paměti... 32 3.7 Závěrečné shrnutí... 33 4 Základy OOP v jazyce C++... 35 4.1 Definice třídy... 36 4.2 Konstruktor... 37 4.2.1 Přetížení konstruktoru... 38 4.2.2 Konstruktor s implicitními parametry... 40 4.3 Destruktor... 40 Obsah 3
4.4 Vložené (inline) metody... 42 4.5 Ukazatel this... 43 4.6 Dynamická alokace instancí... 43 4.7 Závěrečné shrnutí... 43 5 Dědičnost, polymorfismus, virtuální metody a abstraktní třídy... 45 5.1 Jednoduchá dědičnost... 45 5.1.1 Odvození přístupových úrovní... 47 5.1.2 Konstruktor, destruktor a dědičnost... 47 5.2 Polymorfismus a virtuální metody... 47 5.2.1 Základní pohled na virtuální metody... 48 5.2.2 Podrobnější výklad virtuálních metod... 49 5.2.3 Implementace pozdní vazby... 49 5.2.4 RTTI dynamická identifikace typů... 50 5.2.5 Operátor pro bezpečnější přetypování... 50 5.2.6 Abstraktní třída... 51 5.2.7 Polymorfismus a parametry volané odkazem... 51 5.3 Vícenásobná dědičnost... 56 5.3.1 Problémy při odvození z více bází... 56 5.3.2 Řešením je virtuální bázová třída... 57 5.4 Závěrečné shrnutí... 57 6 Další rysy OOP v programovacím jazyce C++... 58 6.1 Výjimky... 58 6.1.1 catch... 58 6.1.2 try... 58 6.1.3 throw... 59 6.2 Konstantní členy... 59 6.2.1 Konstantní atributy... 60 6.2.2 Konstantní metody... 60 6.3 Statické členy... 62 6.3.1 Statické atributy... 62 6.3.2 Statické metody... 62 6.4 Spřátelené třídy a funkce... 65 6.5 Vložené datové typy... 66 7 Proudová knihovna... 67 7.1 Bázový proud ios... 67 7.1.1 Stavy datového proudu... 67 7.1.2 Formátování... 67 7.2 Výstup ostream... 70 7.3 Vstup istream... 71 7.4 Souborové proudy... 71 7.4.1 Vstupní souborový proud ifstream... 72 7.4.2 Výstupní souborový proud ofstream... 72 7.4.3 Vstupně/výstupní souborový proud fstream... 72 7.5 Řetězcové proudy... 73 7.5.1 Vstupní řetězcový proud istrstream... 73 7.5.2 Výstupní řetězcový proud ostrstream... 73 7.5.3 Vstupně/výstupní řetězcový proud strstream... 73 8 Programování pod Linuxem... 77 8.1 Základní pojmy... 77 8.1.1 Unix... 77 4 Algoritmy a programování v jazyce C/C++
8.1.2 Linux... 77 8.1.3 GNU... 77 8.2 Souborový systém, spouštění programů... 78 8.3 Editory... 79 8.4 Kompilace programu... 79 8.5 Ladění programu... 80 8.6 Knihovny... 80 8.6.1 Statické knihovny... 80 8.6.2 Dynamické knihovny... 81 8.7 Standardy... 81 9 Neprobrané partie z C++... 84 Obsah 5
1 Úvod Tento učební text navazuje na skriptum Algoritmy a programování v jazyku C/C++ 1. díl. Kromě souborů, které se ještě převážně týkají programování v jazyce C, jsou především probírány možnosti programování v jazyce C++. Po uvedení základních odlišností mezi jazyky C a C++ jsou vysvětleny základy objektově orientovaného programování. Poté výklad přechází k náročnějším pojmům, jako je dědičnost, polymorfismus, virtuální metody a abstraktní třídy. Dále je věnována velká pozornost proudové knihovně. Na závěr jsou uvedeny a vysvětleny základní principy programování v unixových operačních systémech. Autoři si text rozdělili takto: Mgr. Antonín Přibyl je autorem kapitoly 8, ostatní text a formátování proved Ing. David Matoušek. Autoři, leden 2005 6 Algoritmy a programování v jazyce C/C++
2 Práce se soubory v jazyce C Každý program (zapsaný v libovolném programovacím jazyce) potřebuje nutně zpracovávat vstup a výstup. Byl by jistě skvělý program, který dokáže provést rychlý výpočet Fourierovy transformace, ale jak bychom jej mohli používat bez vstupu a výstupu? Program bez vstupu neumožňuje žádné ovládání (například z klávesnice) a program bez výstupu zase nedokáže zobrazit žádné výsledky (například na obrazovce). Možnostem použití standardního vstupu (klávesnice) a standardního výstupu (obrazovky) jsme se věnovali již dříve. Nyní si ukážeme, že kromě těchto standardních prostředků lze používat i tzv. soubory. Soubor je nejčastěji definován jako uspořádaná množina dat uložená mimo operační paměť. Ve většině případů jsou soubory uloženy na disku a pak také používáme pojem diskový soubor. Výhodou souborů je skutečnost, že data v nich uložená zůstávají k dispozici i po ukončení programu nebo vypnutí počítače (nevymažou se, protože nejsou v paměti, ale na disku). Takže program může své výsledky trvale uložit. Jako klasický příklad programu, který nutně potřebuje používat diskové soubory, lze uvést textový editor. Bylo by jistě nešikovné, kdybychom mohli editovat text, měli možnost jej vytisknout na tiskárně, ale už nemohli daný text uložit do souboru. Znamenalo by to, že k textu se nelze nikdy později vrátit a provést nějaká rozšíření či úpravy. Místo toho musí být celý text napsán znovu. Editor bez možnosti práce se soubory se chová jako obyčejný psací stroj. Ten také neumožňuje výsledky práce uchovat jinak, než v tištěné podobě bez možnosti pozdějších úprav. Pomocí knihovních funkcí jazyka C můžeme se soubory pracovat buď pomocí funkcí odpovídajících volání služeb operačního systému nebo používat obecnější (a také jednodušší) způsob založený na tzv. proudech. Druhý z uvedených způsobů má výhodu právě v tom, že nesouvisí přímo s operačním systémem. Tak tedy lze vytvářet programy, které jsou implementačně nezávislé a tedy snadno přenositelné mezi různými operačními systémy (UNIX, DOS, Windows). To je hlavní důvod, proč se tomuto způsobu ovládání souborů budeme věnovat. Odpovídající funkce jsou definovány v hlavičkovém souboru stdio.h. Z hlediska programování rozeznáváme dva typy souborů: textové soubory data jsou uložena v čitelné podobě, binární soubory data jsou uložena jako sekvence bajtů, tedy úsporněji. 2.1 Základní funkce Základní funkce pro práci se soubory jsou použitelné jak pro textové, tak i pro binární soubory. První věcí, pokud chceme pracovat se soubory na úrovni proudů, je definice proměnné typu ukazatel na FILE (jedná se o strukturu, její definice je však implementačně závislá a proto ji nebudeme dále rozebírat): FILE *vstup; 2. Práce se soubory v jazyce C 7
2.1.1 Otevření souboru fopen Každý soubor, se kterým chceme pracovat, je třeba nejdříve otevřít pomocí funkce fopen. Hlavička: FILE *fopen(const char *FileName, const char *Mode); Parametry: FileName je řetězec jména souboru, který chceme otevřít, Mode je řetězec, který definuje způsob otevření, viz tab. 2.1. Tab. 2.1 Význam řetězce Mode Mode "r"nebo "rt" "w" nebo "wt" "a" nebo "at" "rb", "wb", "ab" "r+"nebo "r+t" "w+" nebo "w+t" "a+" nebo "a+t" "r+b", "w+b", "a+b" Význam otevře daný soubor pro čtení (textový režim) vytvoří daný soubor pro zápis (textový režim) otevře daný soubor pro připojení na konec (textový režim) to samé pro binární režim otevře daný soubor pro čtení i zápis (textový režim) vytvoří daný soubor pro čtení i zápis (textový režim) otevře daný soubor pro připojení na konec (textový režim) to samé pro binární režim Několik poznámek (platí pro textové i binární soubory): pokud je soubor otevírán podle "r" nebo "r+", musí existovat, pokud je soubor otevírán pro zápis "w" nebo "w+" a existuje, bude přepsán (pokud neexistuje, vytvoří se), pokud je soubor otevírán pro připojení na konec "a" nebo "a+" a neexistuje, bude vytvořen. Návratová hodnota: Pokud funkce fopen proběhla bez chyby, vrací ukazatel na FILE. Návratová hodnota se tedy přiřazuje do zavedeného ukazatele (přes něj voláme další funkce). V případě, že soubor není možno otevřít, vrací NULL. Zde jsou dva příklady: a) Otevře soubor POKUS.TXT umístěný v aktuálním adresáři pro čtení v textovém režimu: FILE *f; f=fopen("pokus.txt","r"); b) Otevře soubor POKUS.TXT umístěný v adresáři PROGRAMY disku C pro zápis v textovém režimu (připomeňme, že znak \ musí být v zápisu řetězce zdvojen, protože se jedná o řídicí znak jazyka C): FILE *f; f=fopen("c:\\programy\\pokus.txt","w"); 2.1.2 Zavření souboru fclose Soubor zavíráme funkcí fclose, zde je její hlavička: int fclose(file *File); 8 Algoritmy a programování v jazyce C/C++
Parametr: File je soubor (jako FILE*), který chceme zavřít. Návratová hodnota: V případě úspěchu vrací funkce fclose hodnotu 0, jinak EOF. Příklad (zavřeme dříve otevřený soubor): fclose(f); Poznámka: Po ukončení operací nad souborem je třeba soubor vždy zavřít. Toto pravidlo platí hlavně pro soubory, do kterých bylo zapisováno. Zavření souboru totiž zajistí vyprázdnění souborových bufferů a tím skutečné zapsání všech dat do souboru!!! Viz také kapitolu 2.4.1. 2.1.3 Test konce souboru feof Většina programů čte soubor od začátku do konce, například po jednotlivých znacích. Pro ukončení cyklu, který realizuje takový algoritmus, je třeba zjistit, zda již nebylo dosaženo konce souboru. Pokud je třeba testovat konec souboru, je nejvýhodnější použít makro feof. Zde je jeho hlavička: int feof(file *File); Parametr: File je soubor (jako FILE*), který testujeme na konec. Návratová hodnota: Makro feof vrací nulovou hodnotu (false), pokud jsme ještě nedosáhli konce souboru. Jinak vrací nenulovou hodnotu (true). 2.2 Textové soubory Textové soubory jsou tvořeny posloupností znaků členěných do řádků různé délky. Konec řádku je proveden jedním (případně oběma) z těchto znaků (níže uvedené funkce ošetřují všechny možnosti): '\r' carriage return (návrat vozíku), dekadicky 13 (0x0D), '\n' life feed (posun o řádek), dekadicky 10 (0x0A). Zpracování textového souboru je sekvenční (od začátku směrem ke konci). Zpracovávají se jednotlivé znaky, celé skupiny znaků nebo celé řádky. 2.2.1 Čtení a zápis do souboru getc, putc, fgets, fputs, fprintf, fscanf Pokud chceme číst/zapisovat do souboru, je třeba jej otevřít voláním funkce fopen. Jinak se jedná o poměrně jednoduchou akci, protože uvedené funkce pracují podobně, jako funkce operující nad standardním vstupem a výstupem. 2. Práce se soubory v jazyce C 9
Tab. 2.2 Porovnání funkcí pro práci se standardním vstupem a výstupem s funkcemi pro práci se soubory Standardní vstup/výstup getchar čte znak ze standardního vstupu putchar zapisuje znak na standardní výstup gets čte řetězec znaků ze standarního vstupu puts zapíše řetězec znaků na standardní výstup printf formátovaný výstup scanf formátovaný vstup Soubory getc čte znak ze souboru putc zapisuje znak do souboru fgets čte řetězec znaků ze souboru fputs zapíše řetězec znaků do souboru fprintf formátovaný výstup do souboru fscanf formátovaný vstup ze souboru Stačí tedy uvést hlavičky nových funkcí: int getc(file *f); int putc(char c, FILE *f); char *fgets(char *s, int n 1, FILE *f); int fputs(const char *s, FILE *f); int fprintf(file *f, const char* format, arg1, arg2,...,argn); int fscanf(file *f, const char* format, adr1, adr2,...,adrn); Každý překladač definuje standardní vstup a výstup jako proměnné typu FILE*. V závislosti na typu překladače bývá definováno několik standardních souborů dle tab. 2.3 (minimálně však první tři!). Tab. 2.3 Standardní soubory Identifikátor stdin stdout stderr stdaux stdprn Význam standardní vstup (obvykle klávesnice) standardní výstup (obvykle obrazovka) výpis chybových hlášení (obvykle obrazovka) sériové rozhraní paralelní rozhraní Nyní by mělo být jasné, že tyto dva zápisy způsobí stejnou akci: printf("ahoj...\n"); fprintf(stdout,"ahoj...\n"); 2.2.2 Jednoduché příklady V této kapitole je uvedeno několik základních příkladů, řazení je od jednodušších ke složitějším. Příklad 2.02.01: Program čte obsah souboru TEXT.TXT po jednom znaku a znaky vypisuje na obrazovce. Testuje se úspěšnost otevření souboru. Program končí, když je funkcí getc přečten znak EOF (konec souboru). Všimněte si, že proměnná c je typu int a ne char, jak by se dalo předpokládat. Je to z toho důvodu, že hodnota EOF (celočíselná) se při konverzi na typ char zobrazí jako platný 1 Čtení je ukončeno, pokud se přečte n-1 znaků (vtipné omezení délky načteného řetězce). 10 Algoritmy a programování v jazyce C/C++
znak a nikoli jako informace o konci souboru. Pokud zavedeme proměnnou c typu char, musíme pro testování konce souboru použít makro feof! #include <stdio.h> void main() FILE* f; int c; if(f=fopen("text.txt","r"),f==null) fprintf(stderr,"soubor nelze otevrit\n"); else while(c=getc(f),c!=eof) putchar(c); fclose(f); Příklad 2.02.02: Program čte obsah souboru MAIN.C po řádcích a ty zapisuje do souboru SEZNAM.TXT, v novém souboru je každý řádek očíslován. Řádky se načítají funkcí fgets (maximální délka řádku je 199 znaků). Konec souboru je testován makrem feof. Zvláštností je výpis chyb do standardního souboru stderr a vracení chybových kódů 1 (nelze otevřít vstupní soubor) a 2 (nelze vytvořit výstupní soubor). Více viz kapitolu 2.5. #include <stdio.h> int main() FILE *fr, *fw; char radek[200]; int pocet=0; if(fr=fopen("main.c","r"),fr==null) fprintf(stderr,"soubor MAIN.C nelze otevrit\n"); return 1; if(fw=fopen("seznam.txt","w"),fw==null) fprintf(stderr,"soubor SEZNAM.TXT nelze vytvorit\n"); return 2; while(fgets(radek,200,fr),!feof(fr)) fprintf(fw,"%04d: %s",++pocet,radek); fclose(fr); fclose(fw); printf("dobehlo uspesne"); return 0; 2. Práce se soubory v jazyce C 11
Příklad 2.02.03: Program vytiskne čísla 0 až 9 do souboru POKUS.DAT, každé číslo bude na samostatném řádku. #include <stdio.h> void main() FILE* f; int i; const char* jmeno="pokus.dat"; if(f=fopen(jmeno,"w"),f==null) fprintf(stderr,"soubor %s nelze otevrit\n",jmeno); else for(i=0;i<10;i++) fprintf(f,"%d\n",i); fclose(f); Příklad 2.02.04: Program čte celá čísla ze souboru POKUS.DAT a vypočítá jejich součet a aritmetický průměr. Algoritmus čtení je navržen tak, že není předem zadán počet čísel. #include <stdio.h> void main() FILE* f; int i,soucet=0,pocet=0; float prumer; const char* jmeno="pokus.dat"; if(f=fopen(jmeno,"r"),f==null) fprintf(stderr,"soubor %s nelze otevrit\n",jmeno); else while(!feof(f)) if(fscanf(f,"%d",&i)==1 1 ) pocet++; soucet+=i; prumer= 2 pocet?(float)soucet/pocet:0; fclose(f); printf("součet=%d\narit. prumer=%f\n",soucet,prumer); 1 Jednoduchý test, zda se podařilo přečíst celé číslo ze souboru (kdyby řádek neobsahoval zápis čísla, ale jen obyčejný řetězec ). Rovněž lze psát: if(fscanf(f,"%d",&i)) 2 Použit ternární operátor (aby se zabránilo možnému dělení nulou) a přetypování na float (aby bylo dělení reálné). 12 Algoritmy a programování v jazyce C/C++
Příklad 2.02.05: Mějme soubor PRIJMENI.TXT, který obsahuje příjmení osob (na každém řádku je jedno příjmení, maximální délka 30 znaků). Program načte obsah tohoto souboru a zjistí počet řádků, podle toho dynamicky naalokuje pole řetězců pro načtení všech příjmení. Načte jednotlivé řádky. Potom v tomto poli provede seřazení metodou Bubble Sort a výsledek uloží do souboru SERAZENO.TXT. Při vlastní realizaci řazení nedochází k přesouvání řetězců v paměti, ale pouze k výměně adres jednotlivých řetězců v poli Radky! Takže se nepoužívá funkce strcpy (na kopírování řetězců). Připomeňme, že řetězce musíme porovnávat pomocí funkce strcmp! #include <stdio.h> #include <malloc.h> //pro Dev C++, jinak bude alloc.h #include <string.h> #include <conio.h> int main() //vstupni a vystupni soubory: FILE *fr, *fw; //pocet radku: int N=0; //maximalni delka radku+1: const int MAX=31; //pole obsahujici adresy retezcu z jednotlivych radku: char **Radek=NULL; //pomocny ukazatel: char *p; //pro cteni jednoho znaku: char c; //pro cykly: int i,d; //otevre vstupni soubor (spocita radky): if(fr=fopen("prijmeni.txt","r"),fr==null) fprintf(stderr,"soubor PRIJMENI.TXT nelze otevrit\n"); return 1; //hleda '\n' a podle toho pocita radky: while(c=getc(fr),!feof(fr)) if(c= ='\n') N++; //zavre soubor: fclose(fr); //otevre vystupni soubor: if(fw=fopen("serazeno.txt","w"),fw==null) fprintf(stderr,"soubor SERAZENO.TXT nelze vytvorit\n"); return 2; //alokace pole radku: Radek=(char**)malloc(N*sizeof(char*)); 2. Práce se soubory v jazyce C 13
if(radek==null) fprintf(stderr,"chyba pri alokaci pameti\n"); return 3; //alokace jednotlivych radku: for(i=0;i<n;i++) Radek[i]=(char*)malloc(MAX*sizeof(char)); if(radek[i]= =NULL) fprintf(stderr,"chyba pri alokaci pameti\n"); free(radek); return 3; //otevre vstupni soubor pro cteni radku: if(fr=fopen("prijmeni.txt","r"),fr==null) fprintf(stderr,"soubor PRIJMENI.TXT nelze otevrit\n"); return 1; //nacte radky: for(i=0;i<n;i++) fgets(radek[i],max,fr); //zavre soubor: fclose(fr); //razeni metodou BubbleSort: d=n; do c=0; for(i=0;i<d-1;i++) if(strcmp(radek[i],radek[i+1])>0) //vymeni ukazatele, pokud je spatne poradi: p=radek[i]; Radek[i]=Radek[i+1]; Radek[i+1]=p; c=1; d--; while(c!=0); //zapise data: for(i=0;i<n;i++) fputs(radek[i],fw); //zavre soubor: fclose(fw); //uvolni pamet: for(i=0;i<n;i++) free(radek[i]); free(radek); 14 Algoritmy a programování v jazyce C/C++
printf("dobehlo uspesne"); getch(); return 0; 2.3 Binární soubory Binární soubory jsou tvořeny posloupností bajtů vyjadřující data v binární podobě, obvykle se jedná o skupiny položek stejného typu (například několik struktur stejného typu zapsaných za sebou). S jednotlivými položkami lze pak pracovat buď sekvenčně (procházet je od začátku do konce) nebo v libovolném pořadí (protože jsou položky stejného typu, lze součinem velikosti položky a pořadového čísla zjistit pozici v souboru). Po otevření souboru (pokud soubor neotevřeme v režimu připojení na konec) je vnitřní ukazovátko v souboru nastaveno na první položku, po každé operaci čtení/zápisu se ukazovátko posune na další položku. Funkcí fseek se pak lze posouvat na libovolné místo. 2.3.1 Blokové čtení a zápis dat fread, fwrite Pro čtení/zápis binárních souborů se používají funkce fread a fwrite. Zde jsou jejich hlavičky: int fread(void *buffer, int size, int n, FILE *f); int fwrite(const void *buffer, int size, int n, FILE *f); Parametry obou funkcí: buffer adresa vyrovnávací paměti (odsud se data čtou při použití fwrite nebo se sem zapisují při použití fread), size velikost jedné položky v bajtech, n počet současně čtených/zapisovaných položek, f soubor, nad nímž se provádí operace. Návratová hodnota: Pokud proběhlo volání funkce bez chyby, vrací počet skutečně zapsaných/přečtených položek (ne bajtů!). 2.3.2 Posun ukazovátka v souboru fseek Pokud chceme položky souboru číst/zapisovat v libovolném pořadí (někdy se také říká v náhodném pořadí), použijeme k přestavení ukazovátka v souboru funkci fseek. Hlavička: int fseek(file *f, long posuv, int odkud); Tato funkce přesune ukazovátko souboru f o tolik bajtů, kolik je specifikováno posuv. Místo odkud se posouvá je dáno odkud (viz tab. 2.4). Při úspěšném posunutí vrací 0. Tab. 2.4 Možné hodnoty parametru odkud odkud SEEK_CUR SEEK_END SEEK_SET Význam aktuální pozice v souboru konec souboru začátek souboru 2. Práce se soubory v jazyce C 15
Příklad použití (umístí ukazovátko na předposlední bajt souboru): int fseek(f,-1,seek_end); Příklad použití (umístí ukazovátko na začátek, vhodné pro opakování čtení; pro tento případ má smysl používat funkci fseek i pro textové soubory; také lze použít funkci rewind, viz kapitolu 2.4.2): int fseek(f,0,seek_begin); 2.3.3 Zjistění aktuální pozice ukazovátka ftell Funkce ftell vrací aktuální pozici ukazovátka souboru (při chybě pak 1), hlavička: long ftell(file *f); Příklad: Níže je uvedena funkce fsize, která zjistí délku otevřeného souboru. K tomu jsou použity funkce ftell a fseek. long fsize(file *f) long puvodni_pozice, delka; //schování původní pozice 1 : puvodni_pozice=ftell(f); //přesun na konec souboru: fseek(f,0,seek_end); //ukazovátko je délka: delka=ftell(f); //přesun zpět: fseek(f,puvodni_pozice,seek_set); return delka; Příklad 2.02.06: Program čte obsah textového souboru OSOBY.TXT po řádcích. Každý řádek obsahuje jméno, příjmení a osobní číslo (vše odděleno libovolným počtem mezer nebo tabulátorů). Dané údaje jsou načteny do struktury typu TOsoba a uloženy v binární formě do souboru OSOBY.DAT. Nakonec je vypsána délka obou soborů (použita funkce fsize). Struktura TOsoba dovoluje načítat jména dlouhá 11 znaků, příjmení dlouhá 15 znaků a osobní číslo jako celé číslo se znaménkem. Z textového souboru se údaje načítají funkcí fscanf a v binární podobě pak ukládají funkcí fwrite. Příklad vstupního souboru je uveden níže. Vstupní soubor měl délku 217 bajtů, výstupní soubor pak 192 bajtů (jedná se o 6 záznamů délky 32 bajtů, takže 6 32 = 192). Příklad souboru OSOBY.TXT: Jaroslav Jankovsky 10001 Jaroslav Venclovsky 10002 Miroslav Vapenik 10003 Ondrej Chalupsky 10004 1 Pak lze tuto funkci použít i nad souborem, v němž jsme se již pohybovali. 16 Algoritmy a programování v jazyce C/C++
Josef Jarosovsky 10005 Frantisek Jandacek 10006 Vlastní program: #include <stdio.h> //struktura pro ukladani udaju do bin. souboru: typedef struct char Jmeno[12]; char Prijmeni[16]; int OsCislo; TOsoba; //zjistuje velikost otevreneho souboru: long fsize(file *f) long puvodni_pozice, delka; puvodni_pozice=ftell(f); fseek(f,0,seek_end); delka=ftell(f); fseek(f,puvodni_pozice,seek_set); return delka; int main() //vstupni a vystupni soubory: FILE *fr, *fw; //promenna pro nacteni/ulozeni udaju: TOsoba o; //otevre vstupni textovy soubor: if(fr=fopen("osoby.txt","rt"),fr==null) fprintf(stderr,"soubor OSOBY.TXT nepodarilo otevrit\n"); return 1; //otevre vystupni binarni soubor: if(fw=fopen("osoby.dat","wb"),fw==null) fprintf(stderr,"soubor OSOBY.DAT nepodarilo vytvorit\n"); return 2; //cteni radku z textoveho souboru a zapis v binarni forme: do //vyplni strukturu o nulami: memset(&o,0,sizeof(o)); //není nezbytné //cte jmeno, prijmeni a osobni cislo: if(fscanf(fr,"%11s",&o.jmeno)!=1) break; if(fscanf(fr,"%15s",&o.prijmeni)!=1) break; if(fscanf(fr,"%d",&o.oscislo)!=1) break; 2. Práce se soubory v jazyce C 17
//zapise binarne: fwrite(&o,sizeof(o),1,fw); while(!feof(fr)); //vypise delky souboru: printf("delka souboru OSOBY.TXT=%d B\n",fsize(fr)); printf("delka souboru OSOBY.DAT=%d B\n",fsize(fw)); //zavre soubory: fclose(fr); fclose(fw); return 0; Hexadecimální zobrazení souboru OSOBY.DAT: Jednotlivé bajty Jako znaky 4A 61 72 6F 73 6C 61 76 00 00 00 00 4A 61 6E 6B Jaroslav...Jank 6F 76 73 6B 79 00 00 00 00 00 00 00 11 27 00 00 ovsky...'.. 4A 61 72 6F 73 6C 61 76 00 00 00 00 56 65 6E 63 Jaroslav...Venc 6C 6F 76 73 6B 79 00 00 00 00 00 00 12 27 00 00 lovsky...'.. 4D 69 72 6F 73 6C 61 76 00 00 00 00 56 61 70 65 Miroslav...Vape 6E 69 6B 00 00 00 00 00 00 00 00 00 13 27 00 00 nik...'.. 4F 6E 64 72 65 6A 00 00 00 00 00 00 43 68 61 6C Ondrej...Chal 75 70 73 6B 79 00 00 00 00 00 00 00 14 27 00 00 upsky...'.. 4A 6F 73 65 66 00 00 00 00 00 00 00 4A 61 72 6F Josef...Jaro 73 6F 76 73 6B 79 00 00 00 00 00 00 15 27 00 00 sovsky...'.. 46 72 61 6E 74 69 73 65 6B 00 00 00 4A 61 6E 64 Frantisek...Jand 61 63 65 6B 00 00 00 00 00 00 00 00 16 27 00 00 acek...'.. Příklad 2.02.07: Program přečte obsah binárního souboru OSOBY.DAT (z předchozího příkladu) pozpátku a vypíše jej na obrazovce. Tento příklad demonstruje používání libovolného přístupu k datům uloženým v binárním souboru. Přesuny jsou realizovány funkcí fseek. Před každým čtením je třeba pozici v souboru přesunout o tolik délek struktury TOsoba, kolikátý záznam čteme. Takže pro první záznam od konce musí být vůči konci pozice sizeof(tosoba), pro druhý dvojnásobek a tak dál. V programu se používá proměnná p, která se před každým čtení sníží o délku struktury TOsoba. Zajímavé je použití výsledku funkce printf (vrací počet vypsaných znaků). Takto jsme elegantně realizovali podtržení řádku s hlavičkou vypisovaných údajů (viz dále). #include <stdio.h> //struktura pro nacteni dat z bin. souboru: typedef struct char Jmeno[12]; char Prijmeni[16]; int OsCislo; TOsoba; 18 Algoritmy a programování v jazyce C/C++
int main() //vstupni soubor: FILE *fr; //promenna pro nacteni udaju: TOsoba o; //pozice pro cteni od konce: int p=0; //pocet znaku vypsanych v zahlavi: int x; //otevre vstupni binarni soubor: if(fr=fopen("osoby.dat","rb"),fr==null) fprintf(stderr,"soubor OSOBY.DAT nelze otevrit\n"); return 2; //tisk hlavicky: x=printf("%-16s %-11s %7s","Jmeno","Prijmeni","OsCislo"); printf("\n"); //radek pomlcek: for(;x>0;x--) printf("-"); printf("\n"); //presun na konec souboru: fseek(fr,0,seek_end); //presun o zaznam zpet, cteni a vypis: while(p-=sizeof(tosoba),fseek(fr,p,seek_end)==0) if(fread(&o,sizeof(tosoba),1,fr)==1) printf("%-16s %-11s %07d\n", o.jmeno,o.prijmeni,o.oscislo); //zavre soubor: fclose(fr); return 0; Na obrazovce se objeví: Jmeno Prijmeni OsCislo ------------------------------------ Frantisek Jandacek 0010006 Josef Jarosovsky 0010005 Ondrej Chalupsky 0010004 Miroslav Vapenik 0010003 Jaroslav Venclovsky 0010002 Jaroslav Jankovsky 0010001 2. Práce se soubory v jazyce C 19
2.4 Další užitečné funkce pro práci se soubory V této kapitole si uvedeme několik dalších funkcí definovaných v hlavičkovém souboru stdio.h. Opět se jedná o funkce obecně použitelné, nezávislé na operačním systému (také se jedná o funkce schválené standardizací ANSI C). 2.4.1 Vyprázdnění souborových bufferů fflush Operační systémy pracují s diskovými sobory tak, že k nim přistupují skrze tzv. buffery (vyrovnávací paměti). To jim umožní najednou načíst/zapsat více bajtů a tak urychlit práci se souborem. Z toho vyplývá, že pokud požadujeme například zápis jednoho znaku, neprovede se tato operace do souboru okamžitě, ale znak se uloží do bufferu. Až je celý buffer zaplněn, zapíšou se všechny znaky najednou. Pokud tedy není zapsán dostatečný počet znaků, nedojde k jejich faktickému uložení, ale stále zůstávají v bufferu. Funkce fflush vyprázdní buffer souboru (tím zajistí zápis dat na disk). Tuto funkci je vhodné použít v případě, že chceme k nově vytvořeným datům přistoupit dříve, než soubor zavřeme (typicky v režimu současného čtení a zápisu). Stejnou akci provede funkce fclose při zavírání souboru, proto je tak důležité zavírat soubory! Hlavička: int fflush(file *f); Návratová hodnota: Funkce vrací 0 po úspěšném provedení a EOF při chybě. 2.4.2 Přesun ukazovátka souboru na začátek rewind Funkce rewind přesune ukazovátko souboru na začátek, což je operace ekvivalentní použití fseek(f,0,seek_set). Hlavička: void rewind(file *File); 2.4.3 Přesměrování souboru freopen Funkce freopen umožňuje přesměrovat otevřený soubor do jiného souboru. Tuto funkci nejčastěji používáme pro přesměrování některého ze standardních souborů (stdout, stdin, stderr) do diskového souboru. Takže například místo výpisu dat na obrazovce, dostaneme stejný výstup do diskového souboru. Hlavička: FILE *freopen( const char *FileName,const char *Mode,FILE *File); Parametry: FileName řetězec názvu souboru, do kterého proběhne přesměrování, Mode způsob otevření souboru dle tab. 2.1, FILE soubor, který chceme přesměrovat. Návratová hodnota: Pokud funkce freopen proběhla bez chyby, vrací platný ukazatel na přesměrovaný soubor. V případě chyby vrací NULL. 2.4.4 Vytvoření dočasného souboru tmpfile Funkce tmpfile vytvoří dočasný soubor, který otevře v režimu "w+b". Jméno je souboru přiděleno dynamicky, takový soubor se automaticky smaže při zavření. Hlavička: FILE *tmpfile(); 20 Algoritmy a programování v jazyce C/C++
Návratová hodnota: Vrací ukazatel na strukturu FILE pro dočasný soubor. V případě chyby vrací NULL. 2.4.5 Smazání souboru remove Funkce remove smaže soubor určený jeho jménem. Hlavička: int remove(const char* FileName); Návratová hodnota: Funkce vrací 0 po úspěšném provedení a 1 při chybě. 2.4.6 Zjištění poslední chyby při práci se souborem ferror Funkce ferror detekuje chybu při práci se souborem. Hlavička: int ferror(file *File); Návratová hodnota: Funkce vrací 0, pokud k žádné chybě nedošlo. Jinak vrací kód chyby (závislé na operačním systému). 2.4.7 Smazání chyby clearerr Funkce clearerr nuluje příznak chyby při práci se souborem, takže funkce ferror po volání této funkce vrací vždy hodnotu 0. Hlavička: void clearerr(file *File); Příklad 2.02.08: Program přečte obsah binárního souboru OSOBY.DAT pozpátku a vypíše jej na obrazovce nebo uloží do souboru OBRACENE.TXT. Jedná se o rozšíření příkladu 2.02.07. Výstup do souboru je řešen přes přesměrování standardního souboru stdout. Uživatel má možnost zvolit si, zda výpis proběhne na obrazovku nebo do souboru (v druhém případě proběhne přesměrování funkcí freopen). #include <stdio.h> #include <ctype.h> //struktura pro nacteni z bin. souboru: typedef struct char Jmeno[12]; char Prijmeni[16]; int OsCislo; TOsoba; int main() //vstupni soubor: FILE *fr; //promenna pro nacteni udaju: TOsoba o; //pozice pro cteni od konce: int p=0; //pocet znaku vypsanych v zahlavi: int x; 2. Práce se soubory v jazyce C 21
//volba vypisu: int c; //otevre vstupni binarni soubor: if(fr=fopen("osoby.dat","rb"),fr==null) fprintf(stderr,"soubor OSOBY.DAT nelze otevrit\n"); return 2; //kam bude vystup? printf("provest tisk na obrazovku? (A/N)\n"); c=toupper(getchar()); if(c= ='N') freopen("obracene.txt","wt",stdout); //tisk hlavicky: x=printf("%-16s %-11s %7s","Jmeno","Prijmeni","OsCislo"); printf("\n"); //radek pomlcek: for(;x>0;x--) printf("-"); printf("\n"); //presun o zaznam zpet, cteni a vypis: while(p-=sizeof(tosoba),fseek(fr,p,seek_end)==0) if(fread(&o,sizeof(tosoba),1,fr)==1) printf("%-16s %-11s %07d\n", o.jmeno,o.prijmeni,o.oscislo); //zavre soubory: fclose(fr); fclose(stdout); return 0; 2.5 Spolupráce s operačním systémem Na závěr kapitoly pojednávající o souborech si ještě vysvětlíme, jak může program spolupracovat s operačním systémem. Vše se skrývá v hlavičce funkce main (existují i další varianty): int main(int argc, char** argv); 2.5.1 Návratová hodnota funkce main Návratová hodnota typu int je vracena operačnímu systému jako informace o běhu programu. Je obvyklé, že příkazem return 0 indikujeme úspěšné dokončení programu. Pokud dojde k chybě, můžeme jinými hodnotami návratových kódů signalizovat konkrétní chybu. Této možnosti jsme již využili v příkladu 2.02.02. 2.5.2 Parametry funkce main Zajímavější jsou parametry funkce main. Jedná se vlastně o text zadaný uživatelem jako parametry programu. Jednotlivé řetězce oddělené mezerami se uvažují samostatně. 22 Algoritmy a programování v jazyce C/C++
Počet jednotlivých řetězců je k dispozici v parametru argc (argument count) a řetězce samotné pak v poli řetězců argv (argument values). argv[0] vždy obsahuje plnou cestu ke spuštěnému programu, argv[1] je první řetězec zadaný jako parametr programu, atd. Pokud například napíšeme: PROGRAM.EXE VYSTUP.TXT VSTUP.TXT bude platit: argc = 3 argv[0] = "C:\\ADRESAR\\PROGRAM.EXE" argv[1] = "VYSTUP.TXT" argv[2] = "VSTUP.TXT" Vidíme tedy, že takto lze například vytvořit program, který si jméno vstupního a výstupního souboru přečte z parametrů programu. Jména souborů nebudou zadána v programu natvrdo (bez možnosti změny) ani nebude vyžadováno zadávání jmen při spuštění programu. Praktický příklad je uveden v kapitole 7.5.3. 2. Práce se soubory v jazyce C 23
3 Úvod do programovacího jazyka C++ Programovací jazyk C++ je objektovým rozšířením jazyka C. Z toho důvodu je zřejmé, že zachovává zpětnou kompatibilitu k programovacímu jazyku C. Program napsaný v jazyce C lze bez vážnějších problémů přeložit i na překladači jazyka C++ (překladač C++ pouze více dbá na čistotu kódu, např. souhlas datových typů). Není tedy nutné používat dva kompilátory: jeden pro C a druhý pro C++. Název C++ sám dosti napovídá o povaze tohoto programovacího jazyka. V syntaxi jazyka C značí zápis: C++ zvýšení proměnné C o 1. C++ je tedy, už podle označení, vylepšením jazyka C. Vylepšením se myslí skutečnost, že jsou v jazyce C++ zavedeny prostředky pro OOP (objektově orientované programování). 3.1 Základní rozdíly mezi C a C++ V této kapitole popíšeme základní rozdíly mezi jazyky C a C++. 3.1.1 Zjednodušený zápis komentáře V jazyce C se komentář může zapisovat pouze v této formě: /*Toto je komentář*/ V jazyce C++ je možno zapisovat komentář zkráceně, musí se však celý vejít na řádek: //Toto je kratší komentář, celý se vejde na řádek 3.1.2 Umístění deklarací v programu na libovolném místě V jazyce C je nutno deklarovat proměnné na začátku bloku. V jazyce C++ může být deklarace proměnné provedena až v místě bezprostřední potřeby. Takže je zcela přípustný například tento zápis: for(int i=0; i<100; i++) printf("%i",i); Proměnná i je deklarována těsně před svým použitím. Nadále je však nutno dodržet základní pravidlo: deklarace předchází použití. 3.2 Typ bool Novinkou v C++ je zavedení typu logická proměnná. V jazyce C platí, že logické výrazy vrací celé číslo (0 nepravda, 1 pravda). Dále platí, že celočíselný výraz hodnoty 0 je chápán jako nepravdivý. Pokud je hodnota výrazu různá od 0, chápe se jako pravda. Tyto zvyklosti jsou v jazyce C++ z větší části zachovány. Pouze logické výrazy mají jako výsledek typ bool a proměnná tohoto typu nabývá hodnoty false (0, nepravda) a true (1, pravda). 3.3 Klíčová slova jazyka C++ V C++ je samozřejmě možno používat klíčová slova z jazyka C. Kromě toho jsou definována nová klíčová slova. Na tomto místě uvedeme jen ta, která budeme používat: class, 24 Algoritmy a programování v jazyce C/C++
public, protected, private, inline, virtual, operator, friend, catch, try, throw, template, dynamic_cast, namespace, using. 3.4 Funkce v C++ Funkce se v C++ deklarují prakticky stejným způsobem jako v jazyce C. Kromě toho je však možno používat tzv. přetížené funkce a funkce s implicitními parametry. Rovněž je možno zjednodušit některé zápisy. 3.4.1 Přetížení funkcí (Function Overloading) Jazyk C++ umožňuje deklarovat několik funkcí stejného jména, různého prototypu. V místě volání funkce musí překladač rozhodnout (podle počtu a typu dosazených aktuálních parametrů, případně podle typu návratové hodnoty), kterou ze stejně pojmenovaných funkcí zavolá. void fce(int i) printf("\n1. funkce: i=%i",i); void fce(int i, double j) printf("\n2. funkce: i=%i, j=%lf",i,j); //volání funkce: fce(5); //volá se 1. funkce fce(5,1.75);//volá se 2. funkce fce(5,1); //volá se 2. funkce, 1 se přetypuje na double 1.0 3.4.2 Implicitní parametry funkcí Funkce může být deklarována tak, že některé parametry mají zadány implicitní hodnotu. To umožňuje volání funkce s menším počtem parametrů, než je uvedeno. Neuvedené parametry se nastaví podle implicitního přiřazení. Implicitní parametry je možno umísťovat pouze na konci seznamu deklarací parametrů: void test(int x, int y=3) printf("%i %i",x,y); //funkci test lze pak volat: test(1,2); //vytiskne: 1 2 test(0); //vytiskne: 0 3 Přetížení funkce je možno používat současně s implicitními parametry funkce: void fce(int i=1) printf("\n1. funkce: i=%i",i); 3. Úvod do programovacího jazyka C++ 25
void fce(int i, double j=0) printf("\n2. funkce: i=%i, j=%lf",i,j); //volání funkce: fce(); //volá se 1. funkce fce(5,1.75);//volá se 2. funkce fce(5,1); //volá se 2. funkce, 1 se přetypuje na double 1.0 fce(5);//překladač neumí rozhodnout, kterou funkci má zavolat 3.4.3 Parametry funkce volané odkazem Základním rysem programovacího jazyka C je skutečnost, že jsou implementovány pouze funkce (tedy procedury nejsou zavedeny). Navíc jsou parametry do funkcí předávány vždy voláním hodnotou. To způsobuje, že funkce nemůže parametry měnit a předávat je jako výsledky. Například následující program nemůže, přes všechna naše očekávání, změnit hodnotu globální proměnné x a vytiskne postupně: x=2 i=3 x=2 #include <stdio.h> void test(int i) i=3; printf("i=%i\n",i); void main() int x=2; printf("x=%i\n",x); test(x); printf("x=%i\n",x); Připomeňme, že pokud chceme v jazyce C naprogramovat funkci, která má umožnit změnu parametrů, je třeba předávat ukazatele na dané proměnné. Říkáme, že parametry jsou volány přes ukazatel. Toto je modifikace předchozího programu, která již změní hodnotu proměnné x: #include <stdio.h> void test(int *i) *i=3; printf("*i=%i\n",*i); 26 Algoritmy a programování v jazyce C/C++
void main() int x=2; printf("x=%i\n",x); test(&x); printf("x=%i\n",x); Tento program tedy postupně vytiskne: x=2 *i=3 x=3 Je zřejmé, že toto řešení je poněkud těžkopádné. Protože uvnitř funkce se na danou proměnnou musíme odkazovat pomocí operátoru dereference (*) a jako parametr funkce musíme předat adresu proměnné x (použít operátor reference &). Aby se programovací jazyk C++ přiblížil zvyklostem z jiných vyšších programovacích jazyků, je možno používat tzv. referenci (pozor, nezaměňovat s operátorem reference). Reference je speciální typ ukazatele, který umožňuje stejný způsob zápisu, jako pro běžné proměnné. Reference se deklaruje pomocí operátoru reference (&). V našem případě je možno funkci test napsat takto, zde se jedná o parametry volané odkazem: #include <stdio.h> void test(int& i) i=3; printf("i=%i\n",i); void main() int x=2; printf("x=%i\n",x); test(x); printf("x=%i\n",x); //i je reference //pozor, zde již není & Program postupně vypíše: x=2 i=3 x=3 Všimněme si, že při použití reference není nutno v těle funkce přistoupit k proměnné přes operátor dereference (*) a i volání funkce probíhá normálně, není třeba zapisovat operátor reference (&) pro předání adresy objektu. 3. Úvod do programovacího jazyka C++ 27
3.5 Zjednodušená deklarace datových typů V C++ není nutno používat operátor typedef pro deklaraci uživatelsky definovaných typů odvozených ze: struct, union, enum, class 1. Tyto dva zápisy jsou korektní, pouze ten vpravo je kratší: typedef struct CLOVEK struct CLOVEK float vaha; float vaha; int vyska; int vyska; ; ; 3.6 Operátory jazyka C++ V jazyce C++ je samozřejmě možno používat operátory jazyka C. Kromě toho jsou implementovány nové operátory a také je možno provádět přetěžování. 3.6.1 Přetížené operátory (Overloading Operators) V jazyce C++ je dovoleno přetížit standardní operátory jazyka C, tzn. přiřadit operátorům jazyka C kromě jejich hlavního významu i nějaký jiný (nový) význam. Přetížení se provádí pomocí tzv. operátorových funkcí. Přetížení je možno zajistit pro nově zavedený datový typ. Význam operátorů pro datové typy jazyka (myšleno například int), nelze změnit. Příklad ukazuje přetížení operátoru + tak, aby bylo možno snadno provádět součet dvou komplexních čísel: struct complex float re, im; ; complex operator+(complex a1, complex a2) complex temporary; temporary.re=a1.re+a2.re; temporary.im=a1.im+a2.im; return temporary; //pak lze psát: complex a,b,c; a.re=1; a.im=2; b.re=-1; b.im=1; c=a+b; //použití přetíženého operátoru + printf("\n%f %f",c.re,c.im); 3.6.2 Nové operátory jazyka C++ Programovací jazyk C++ definuje některé nové operátory, jako nové operátory lze označit i operátory << a >>, které jsou v jazyce C++ přetíženy (mají dvě funkce). Jako nový označíme rovněž operátor >, který se v jazyce C nepoužívá příliš často. Viz tab. 3.1. 1 Viz kapitolu 4. 28 Algoritmy a programování v jazyce C/C++
Tab. 3.1 Nové operátory jazyka C++ Operátor Popis << vkládací operátor (insertor), posuv vlevo >> extrakční operátor (extraktor), posuv vpravo > ukazatelový selektor :: operátor příslušnosti (scope resolution) new, new[ ] vytvoření dynamického objektu delete, delete[ ] zrušení dynamického objektu Nyní uvedeme význam a použití těchto nových operátorů: 3.6.3 Vkládací operátor << a extrakční operátor >> Tyto dva operátory již známe ze standardního jazyka C. Slouží pro bitový posuv doleva nebo doprava. V C++ jsou tyto operátory přetíženy a mají i jiný, snad ještě mnohem důležitější, význam. Tyto dva operátory značně usnadňují vstup a výstup pro základní datové typy. Uvědomme si, že ve standardním C je nutno používat funkci scanf pro vstup z klávesnice a funkci printf pro výstup na obrazovku. Navíc je nutno znát konverze pro všechny datové typy. S použitím operátorů << a >> se tyto operace značně zjednodušší. Vstup nebo výstup pak probíhá přes tzv. v/v proudy (I/O streams). Standardní vstupní proud (vstup z klávesnice) se jmenuje cin, standardní výstupní proud (výstup na obrazovku) se jmenuje cout. Aby bylo možno operátory používat v tomto novém významu, je třeba do zdrojového textu přidat hlavičkový soubor iostream.h direktivou #include: #include <iostream.h> Vkládací operátor (<<) vkládá prvky do proudu, syntaxe je: proud<<prvek Například konstrukce: cout<<"ahoj, jak se máš?"; vloží do standardního výstupního proudu (tzn. vypíše na obrazovku) řetězec: Ahoj, jak se máš? Extrakční operátor (>>) čte prvky z proudu, syntaxe je: proud>>prvek Například konstrukce: char z; cin>>z; načte ze standardního vstupního proudu (tzn. z klávesnice) jeden znak a uloží jej do proměnné z. 3. Úvod do programovacího jazyka C++ 29
Zde je trochu lepší příklad použití obou operátorů (zasláním speciálního znaku endl se odřádkuje provede se přechod na nový řádek; všimněte si, že operátory << a >> lze zřetězovat): #include <iostream.h> void main() int i=2; char c='a'; float f; cin>>f; cout<<"reálné číslo je: "<<f<<endl; cout<<"celé číslo je: "<<i<<endl; cout<<"písmeno je: "<<c<<endl; Je zřejmé, že zápis pomocí těchto nových operátorů je jednodušší (hlavně není třeba znát konverze pro datové typy), než při programování v jazyce C. Samozřejmě, že tyto jednou již přetížené operátory je možno přetížit opětovně. Například tato konstrukce umožňuje přetížení insertoru (<<) tak, aby uměl vypsat položky struktury complex: #include <iostream.h> struct complex float re, im; ; ostream& operator <<(ostream& str, complex c) str<<"reálná část:"<<c.re<<", imaginarní část:"<<c.im; return(str); void main() complex a; a.re=2; a.im=-3; cout<<a; Další informace o použití proudů budou uvedeny v kapitole 7. 3.6.4 Ukazatelový selektor > Měli bychom se předně opravit, tento operátor je definován i ve standardním jazyce C, jenže tam se prakticky nepoužívá. 30 Algoritmy a programování v jazyce C/C++
Ukazatelový selektor slouží pro zjednodušení zápisu v případě, že chceme přistoupit k položkám struktury, na kterou máme ukazatel. Význam tohoto operátoru snadno nahlédneme z následujícího příkladu: #include <stdio.h> void main() struct CLOVEK int vyska; float vaha; ; typedef CLOVEK *PCLOVEK; CLOVEK Novak; PCLOVEK Ukazatel; Novak.vyska=189; Novak.vaha=85; Ukazatel=&Novak; printf("novák je vysoký: %i cm\n",(*ukazatel).vyska); printf("novák váží: %f kg\n",(*ukazatel).vaha); Všimněme si, že k položkám struktury se přistupuje jednoduše, pomocí selektoru. Tak například přiřazení do položky vyska proměnné Novak se provede takto: Novak.vyska=189; Pokud chceme k dané struktuře přistoupit pomocí ukazatele na ni, musíme použít operátor dereference (*). Protože je priorita operátoru dereference nižší, než priorita operátoru selekce (.), musí se výraz uzávorkovat: printf("novák váží: %f kg\n",(*ukazatel).vaha); Tento zápis je dosti složitý, proto je v C implementován ukazatelový selektor >, jehož použití má stejný efekt jako: (*)., takže poslední dva řádky lze zapsat přehledněji: printf("novák je vysoký: %i cm\n",ukazatel->vyska); printf("novák váží: %f kg\n",ukazatel->vaha); 3.6.5 Operátor příslušnosti :: Operátor příslušnosti umožňuje například přistoupit ke globálnímu objektu, který je zastíněn lokální deklarací. Další význam vyplyne v kapitole 4.1. 3. Úvod do programovacího jazyka C++ 31
Například ve funkci test je globální proměnná x zastíněna lokální deklarací, takže se vypíše 3 a ne 0: #include <stdio.h> int x=0; void test() int x=3; //definice globální proměnné x //zastínění globálního x printf("\n%i",x); void main() test(); Pokud chceme ve funkci test přistoupit ke globální proměnné x musíme použít operátor ::, například (teď se vypíše globální x=0, lokální x=3): #include <stdio.h> int x=0; void test() int x=3; printf("\nglobální x=%i, lokální x=%i",::x,x); void main() test(); 3.6.6 Operátory pro dynamickou správu paměti Dynamickou správou paměti rozumíme dynamické vytváření a rušení datových objektů. Vytvoření dynamického objektu (new) Vzpomeňme si, že ve standardním jazyce C je možno vytvářet dynamické objekty pomocí funkce malloc. Tyto dynamické objekty se ruší pomocí funkce free. Vytvoření dynamické proměnné tímto způsobem je dosti složité. V jazyce C++ je k dispozici operátor new, s jehož použitím je vytvoření dynamické proměnné hračkou. Pokud chceme vytvořit dynamické pole, které obsahuje 1000 položek typu int napíšeme: 32 Algoritmy a programování v jazyce C/C++
#include <iostream.h> #include <process.h> void main() int *p; p=new int[1000]; if(p==null) exit(-1); else cout<<"ok, pole vytvořeno...";.. delete[] p; //uvolnění pole z paměti, když už jej nepotřebujeme Poznamenejme, že stejně snadno lze vytvářet i jednoduché dynamické proměnné a dokonce jim i přiřadit počáteční hodnotu. Například tento zápis: #include <iostream.h> void main() int *p=new int(10); //vlastně volání konstruktoru. *p=10; cout<<*p;. delete p; //uvolnění proměnné z paměti, když už ji nepotřebujeme nejdříve vytvoří dynamickou proměnnou typu int s počáteční hodnotou 10, ukazatel na ni je p. Obsah této dynamické proměnné je možno měnit pomocí ukazatele na ni, samozřejmě je možno obsah dynamické proměnné vypsat. Zrušení dynamického objektu (delete) Pro zrušení dynamického objektu vytvořeného pomocí operátoru new se používá operátor delete nebo delete[ ]. Operátor delete uvolňuje z paměti jednoduché dynamické objekty (všechny proměnné vyjma polí). Operátor delete[ ] uvolňuje z paměti dynamická pole. Operátor delete resp. delete[ ] byl již použit ve dříve uvedených příkladech. 3.7 Závěrečné shrnutí Nakonec si shrneme dříve uvedené poznatky formou tabulek. 3. Úvod do programovacího jazyka C++ 33
Při inicializaci přiřaď ukazateli hodnotu 0 nebo NULL. Nikdy nesmíš dynamický objekt zrušit více než jednou. Nastav ukazatel na NULL nebo 0, pokud jsi na něm aplikoval operátor delete. Pro přístup k dynamické proměnné musíš použít operátor dereference (*). Pro přístup k položkám dynamické proměnné typu struct, union, class je výhodné použít ukazatelový selektor ( >). FUNKCE A JEJICH PARAMETRY Funkce může mít libovolný počet parametrů, tedy i nulový. Funkce může být přetížena, definována více než jednou. Pak se v místě použití zavolá ta varianta, jejíž prototyp nejvíce odpovídá daným parametrům. Funkce může používat implicitní parametry, které se však musí uvést na konci seznamu parametrů. Pokud bude při volání funkce implicitní parametr vynechán, dosadí se implicitní hodnota. Funkce může mít návratovou hodnotu (vracet výsledek), nebo nemusí (je definována jako void), pak se v podstatě jedná o proceduru. Pokud má funkce měnit hodnoty parametrů, musíme předat adresu proměnné nebo použít referenci. OPERÁTORY JAZYKA C++ V jazyce C++ je možno používat operátory jazyka C. V C++ je možno přetížit standardní operátory, tzn. přisoudit jim více než jeden význam. Insertor << je určen pro usnadnění výstupu. Extraktor >> je určen pro usnadnění vstupu. Ukazatelový selektor > zjednodušuje přístup k položkám struktury pomocí ukazatele. Operátor příslušnosti :: umožní například zpřístupnění globální proměnné, která je zastíněna lokální deklarací. Jeho použití v C++ je však obecnější. Pro dynamickou správu paměti se používají nové operátory new a delete. 34 Algoritmy a programování v jazyce C/C++
4 Základy OOP v jazyce C++ Objektově orientované programování (OOP) se často definuje, jako metoda tvorby programů, která věrně napodobuje způsob, jakým zacházíme s objekty reálného světa. Jiný pohled zase oceňuje skutečnost, že při OOP jsou spojována data s funkcemi, které nad nimi můžeme vyvolávat. Mějme například matici. Pak je jisté, že budeme chtít používat operace jako součet a součin matic, stanovení determinantu nebo inverzní matice. Určitě bude snazší, když bude zřejmé, které funkce můžeme nad maticí používat. Představa, že musíme studovat manuály funkcí a zjistit, které funkce můžeme použít, je dosti úmorná. Tento způsob může snadno vést k nefunkčnímu programu. Spojení dat s použitelnými funkcemi se nazývá zapouzdření a vede k vyšší bezpečnosti programování. Datům v tomto modelu pak obvykle říkáme atributy (datové položky, vlastnosti), použitelným funkcím metody (členské funkce). Dalším pojmem je instance (objekt). Jedná se o proměnnou objektového typu. Jako základní objektový typ se používá tzv. třída class. Z hlediska C++ lze však jako objektové typy chápat i strukturu nebo sjednocení (struct, union). Jednou ze základních vlastností tříd je dědičnost. Což je schopnost odvodit ze stávající třídy (báze, předka) novou třídu (potomka, následníka). Nová třída přijme všechny vlastnosti bázové třídy a může je dále rozšířit. V odvozené třídě tedy zůstávají k dispozici metody a atributy bázové třídy, navíc je možno přidávat další. Tímto způsobem lze například definovat základní třídu hierarchie, která poskytuje základní možnosti (například třídu TSeznam, představující spojový dynamický seznam) a odvodit od ní specializované třídy (například třídu TSetridenySeznam, která již prvky zatřiďuje ve vzestupném pořadí). Vzhledem k náročnosti tohoto tématu je dědičnosti věnována kapitola 5. Velmi důležitým pojmem je úroveň přístupu k položkám třídy. Je totiž možno zajistit, že ne všechny atributy nebo metody jsou dostupné zvenčí (takto chráníme instanci třídy před možnými, často asi nechtěnými, chybami ze strany uživatele): public veřejná položka, tyto atributy a metody jsou dostupné kdekoli; ne jenom v samotné třídě, ale i zvenku, private soukromá položka, tyto atributy a metody jsou dostupné pouze v samotné třídě, nelze k nim přistupovat zvenku; tato úroveň je ve třídě implicitní (pokud nestanovíme jinak, jsou všechny metody a atributy privátní vně nepřístupné), protected chráněná položka, tyto atributy a metody jsou dostupné pouze v samotné třídě, případně ve třídách, které z ní odvodíme; nejsou dostupné zvenku. Platí vlastně jednoduchá (nebo chcete-li logická) pravidla: atributy jsou nejčastěji ukrývány před vnějším okolím. Proto se obvykle zařazují do úrovně private. Pokud však předpokládáme, že z dané třídy odvodíme následníka, bude vhodné mít daný atribut k dispozici. Pak je šikovnější, zařadit jej do úrovně protected. metody jsou obvykle zařazovány do úrovně public, jinak bychom je nemohli volat. Existují ale jistě metody, které nesmí mít neznalý uživatel k dispozici a v tomto případě můžeme použít úrovně private nebo protected. 4. Základy OOP v jazyce C++ 35