Stáhnout prezentaci
Prezentace se nahrává, počkejte prosím
1
PRG029 Programování v C a C++ (LS 2006/07)
RNDr. Filip Zavoral, Ph.D. Katedra softwarového inženýrství -> Výuka :18
2
Studijní povinnosti Zápočet není podmínkou složení zkoušky
Požadavky na zápočet SIS – Grupíček – každý musí být v nějaké skupině Platí i pro 'pokročilé', repetenty, externisty a jakékoliv jiné úchylky Zkontrolujte, resp. přihlašte se do volné skupiny Termín Ověřte platnost u – Hladání studenta -> Změna údajů Účast na cvičeních 'Pokročilí' programátoři a 'externisti'– domluvit se s vyučujícím na začátku semestru Během semestru 3 'domácí úkoly' (krátké prográmky) DÚ lze nahradit jedním větším zápočťákem Závěrečný test Odladit krátký program v omezeném čase (v labu) Konkrétní požadavky určuje a jejich plnění hodnotí cvičící Vše nestandardní předem domluvit s cvičícím
3
Pravidla pro budoucí neúspěšné
POZOR!! Letos poslední běh! Zkouška Pokud letos složíte zkoušku se známkou 1 nebo 2 a nedostanete zápočet, bude vám příští rok automaticky uznána Tento mechanismus je implementován zkoušejícími, nikoliv studijním odd. Zápočet Pokud letos splníte zápočet, bude vám příští rok automaticky uznán Pokud nedostanete zápočet, budete příští rok opakovat nesplněné části Podmínky splněné letos se automaticky uznávají V příštím roce se musíte na začátku semestru přihlásit V modulu SIS Nastěnka bude upřesněno jak
4
Obsah předmětu C++ C++ C C LS 1. roč ZS 2. roč
PRG029 - Programování v C PRG032 - OOP LS 1. roč ZS 2. roč C++ C++ C C Nejdůležitější: vlastní praxe Na přednáškách se nikdo nikdy programovat nenaučil
5
Obsah přednášky Kurz jazyka C Úvod do C++
Přesněji: část C++ shodná s C Překlad programů, spojování Základní vlastnosti C, odlišnosti od jiných programovacích jazyků Datové typy, operátory a řídící konstrukce C Pole a ukazatele v jazyce C, práce s řetězci Vstup a výstup, standardní knihovy C Programování není (jen) zápis algoritmů Úvod do C++ Zbývající cca 2/5 semestru Třídy a objekty, dědičnost, virtuální funkce, polymorfismus Povídání o C++ bude plynule pokračovat v 2. ročníku
6
Obsah cvičení V laboratoři SW2 Laboratoř SW2
Základní vlastnosti jazyka C Práce s datovými strukturami, triky Standardní knihovy C Zajímavé a záludné vlastnosti C Cvičení z C++ až v 2. ročníku (pro ty, kteří přežijí) Laboratoř SW2 Microsoft Visual Studio .NET 2005 Praktické programování Ladění programů (!) Prosíím, já jsem napsal správný program a ono to řeklo 'Váš program provedl neplatnou instrukci a bude ukončen '. Co mám dělat?
7
Jak správně C++ používat pro středně pokročilé
Literatura Základní učebnice a popis jazyka Andrew Koenig, Barbara E. Moo: Rozumíme C++ (C++ Accelerated) Miroslav Virius: Programování v C++ (ČVUT 2001) Miroslav Virius: Pasti a propasti jazyka C++ Bruce Eckel: Myslíme v jazyku C++ Thinkinkg in C++ 2nd ed. - volně stáhnutelné . . . C++ In-depth Alexandrescu, Sutter: C programovacích technik (C++ Coding Standards) Meyers: Effective C++ (2nd ed.), More Effective C++, Effective STL Sutter: Exceptional C++, More Exceptional C++, Exceptional C++ Style Josuttis: The C++ Standard Library Josuttis: Object-Oriented Programming in C++ Jak správně C++ používat pro středně pokročilé
8
v češtině: Moderní programování v C++
Literatura Pro velmi pokročilé Alexandrescu: Modern C++ Design Generic Programming and Design Patterns Applied Vandevoorde, Josuttis: C++ Templates Abrahams, Gurtovoy: C++ Template Metaprogramming Normy ISO/IEC 14882, ANSI: Programming languages - C++ (1998, 2003) C TR1 (2005) C++0x ISO/IEC 9899: Programming languages - C (1999) WWW Eckel: Thinking in C and more v češtině: Moderní programování v C++
9
Nevhodná literatura - nepoužívat!
Brian W. Kernighan, Dennis M. Ritchie: The C Programming Language Martin Beran: Učebnice Borland C++ - hrubé chyby Jan Pokorný: Rukověť uživatele Borland C++ - staré, BC 3.1 Vladimír Rudolf: ABC programátora v C++ - neúplné, zastaralé Dirk Louis: C und C++ — Programierung und Referenz - chyby Dalibor Kačmář: Jazyk C — učebnice pro střední školy – chyby Brodský, Skočovský: Operační systém Unix a jazyk C – neúplné, zastaralé Eric Gunnerson: Začínáme programovat v C# – C# není C++
10
Pascal vs. C++ Programování I, II výuka (v) Pascalu
heslo: programování = zápis algoritmů algoritmické myšlení, algoritmizace problému soustředění se na řešení problému formulace algoritmu a jeho zápis v nějakém formalismu (jazyku) základní datové a řídící struktury nedůležité: kontrola vstupů, uživatelské rozhraní, obedněnost, vazba na OS a HW, přenositelnost, optimalizace, udržovatelnost výuka (v) Pascalu dobrý jazyk pro zápis algoritmů nezatěžuje technickými detaily (alokace paměti, vazba na OS, ...) slabá podpora pro kontrolu vstupů, uživatelské
11
'Vše' již bylo naprogramováno
Pascal vs. C a C++ Programování v C++, OOP heslo: programování = vývoj software důležité: kontrola vstupů, uživatelské rozhraní, obedněnost, vazba na OS a HW, přenositelnost, optimalizace, udržovatelnost zvládnutí knihoven a vývojových nástrojů výuka (v) C++ standardní jazyk pro vývoj software další jazyky vycházejí z C++ (Java, C#, ale i PHP, ...) dobrá podpora pro kontrolu vstupů, uživatelské nutnost zvládnout technické detaily (alokace paměti, vazba na OS..) velké množství hotového kódu (knihovny, komponenty, ...) 'Vše' již bylo naprogramováno
12
Historie 1970-3 první verze C, společný vývoj s UNIXem
1973 přepsání jádra UNIXu do C 1978 Kerninghan, Ritchie: The C Programming Language 1980 standardy – ANSI X3J11, od r ISO 9899 1980 AT&T - "C with Classes" 1983 poprvé název C++ (Rick Mascitti) 1985 Stroustrup: The C++ Programming Language 1989 ANSI X3J16 norma C++ 2003 nejnovější ISO/ANSI norma C++ 2005 C TR1 rozšíření knihoven založeno na knihovně Boost plán nové normy C++ Normy se vyvíjí, aktuální překladače o několik let zpět Implementace novinek často nekorektní nebo neefektivní
13
přesněji společná část C a C++ se zdůrazněním odlišností
Part I - C C přesněji společná část C a C++ se zdůrazněním odlišností
14
hello.c #include <stdio.h>
int main( int argc, char ** argv) { printf( "Hello\n"); return 0; }
15
hello.c #include <stdio.h>
direktiva preprocesoru vložení souboru deklarace knihovních funkcí název funkce hlavička funkce #include <stdio.h> int main( int argc, char ** argv) { printf( "Hello\n"); return 0; } typ návratové hodnoty tělo funkce formální parametry příkaz skutečné parametry
16
Struktura programu Program se skládá z modulů
Překládány samostatně kompilátorem Spojovány linkerem Modul z pohledu programátora Soubor s příponou .cpp (.c) Hlavičkové soubory Soubory s příponou .h Deklarují (a někdy i definují) identifikátory používané ve více modulech Vkládány do modulů direktivou include Direktivu zpracovává preprocesor čistě textově Preprocesor je integrován v kompilátoru jako první fáze překladu Modul z pohledu kompilátoru Samostatná jednotka překladu Výsledek práce preprocesoru
17
Překlad jednoho modulu a sestavení
knihovny standardní i jiné knihovní headery .h .obj .obj .obj .obj .obj .lib spustitelný program .cpp CC .obj Link .exe spojování (linkování) objektový modul (přeložený kód) kompilace
18
Překlad více modulů – oddělený překlad
vlastní headery knihovní headery knihovny .h .h .obj .obj .obj .obj .obj .lib .cpp CC .obj Link .exe kompilace jednoho modulu .obj .obj .c .obj .c .cpp další moduly
19
Oddělený překlad - dělení na moduly
Modul - ucelená funkčí jednotka modul.cpp - implementace modul.h - definice rozhraní rozdělení projektu do modulů a vytváření headerů je umění, nedá se to naučit na přednášce fotbal.h hriste.h hrac.h mic.h fotbal.cpp hriste.cpp hrac.cpp mic.cpp
20
Překladače a vývojová prostředí
Windows - překladač součástí integrovaného prostředí MS Visual Studio - Visual C++ (.Net2005) integrovaný make, linker, debugger klikabilní konfigurace další překladače - Borland C++ Builder, Intel, Watcom Unix (Linux) - samostatné programy, příkazová řádka gcc make - pro 'opravdové' programátory pokusy o vývojová prostředí (kDevelop, ...) nepoužívat ! vývoj ukončen
21
Integrované vývojové prostředí
.h .h .obj .obj .obj .obj .obj .lib .c .obj .c CC .obj .obj Link .exe .cpp editor debugger projekt
22
make .h .h .obj .obj .obj .obj .obj .lib .c .obj .c CC .obj .obj Link
.exe .cpp makefile make
23
Program a modul Program v C++ nemá (vnořovanou) blokovou strukturu
Neexistuje "hlavní blok" Běh programu začíná vyvoláním funkce main Před funkcí main běží inicializace běhového prostředí, po ní úklid Funkce main musí mít tuto hlavičku: int main( parametry příkazové řádky) Modul: posloupnost globálních deklarací a definic Deklarace Oznámení překladači, že identifikátor existuje Určení některých vlastností (typ, parametry) Definice Doplnění zbývajících vlastností (kód funkce, obsah struktury) Překladač vygeneruje kód funkce, vyhradí místo pro proměnnou
24
Příklad - malá násobilka
deklarace knihovních funkcí, ... hlavička funkce, formální parametr #include <stdio.h> int vynasob( int c) { int i = 1, v; if( c < 1 || c > 10) return -1; while( i <= 10) { v = i * c; printf( "%d * %d = %d\n", i, c, v); i = i + 1; } return 0; int main() int cislo = 7; vynasob( cislo); neinicializovaná proměnná inicializovaná celočíselná proměnná okanžitý návrat z funkce kontrola parametrů 2 * 7 = 14 začátek cyklu ++i konec cyklu konec, OK hlavní program konec, OK ignorování návratové hodnoty volání funkce se skutečným parametrem The END
25
argumenty z příkazové řádky
Funkce vnořené funkce nelze! Základní programová jednotka je funkce Neexistují vnořené funkce Začátek programu – funkce main int fce2( int a) { int fce1( int x, int y) return x+y; } return fce1( 2*a+17); int fce1( int x, int y) { return x+y; } int fce2( int a) return fce1( 1, 2*a+5); int main( int argc, char** argv) ... začátek programu argumenty z příkazové řádky později
26
návrat celočíselné hodnoty
Funkce - návratový typ Typ funkce = typ návratové hodnoty Hodnota se vrací pomocí klíčového slova return Speciální typ void - 'prázdný' typ ekvivalent procedury Pascalu void fce2( char* text) { printf( text); } void fce3( char* text) if( ! text) return; int fce1( int x, int y) { return x+y; } návrat z funkce návrat celočíselné hodnoty
27
každý parametr musí mít typ
Parametry funkce Pevný počet, pevný typ možnost proměnného počtu parametrů - printf Parametry jsou odděleny čárkou U každého parametru musí být uveden jeho typ Funkce bez parametrů - void každý parametr musí mít typ int fce1( int x, int y, float z) { ... } int f2( double a[5], char* str) int f3( void) int fce1( int x, y) { ... } int fce3
28
Volání funkce Shoda počtu formálních a skutečných parametrů
Kompatibilita typů formálních a skutečných parametrů I funkce bez parametrů musí obsahovat operátor () Návratová hodnota - lze ignorovat int fce1( int x, int y, float z) { ... } int fce3( void) int fce2( int a) { fce1( a, 1, a); fce3(); return 0; } int fce1( int x, int y, float z) { ... } int fce2( int a) { fce1; fce1( a, 1); fce1( a, 1, "ahoj"); }
29
Předávání parametrů funkci
Všechny parametry se předávají hodnotou 'Výstupní' parametry pouze přes ukazatele nebo reference void swap( int x, int y) { int pom; pom = x; x = y; y = pom; } void fce( void) int a = 1; int b = 2; int c = 0; swap( a, b); c = 2 * a + b; vymění se jen lokální proměnné funkce předají se pouze hodnoty (1, 2) nikoliv 'proměnné'
30
Lokální proměnné Definice lokálních proměnných
C: na začátku těla funkce (bloku) C++: kdekoliv v těle funkce Možná inicializace – při každém běhu funkce neinicializovaná proměnná – náhodná hodnota !!! hlavní blok funkce deklarace celočíselných proměnných int fce2( void) { int i = 0; while( i < 100) int j = i + 1; i = j * i; } // j jiz neexistuje return i; vnořený blok int fce( void) { int x, y; int z = 0; return z + x; } deklarace proměnné ve vnořeném bloku deklarace s inicializací inicializace se provádí při každém průběhu náhodná hodnota !!! po ukončení bloku již proměnná neexistuje
31
Globální proměnné Definice mimo tělo funkce
Viditelné v jakékoliv funkci Možná inicializace – při startu programu Používat s rozvahou!! pouze pro sdílená data globální proměnná formální parametr int g = 1; void fce( int x) { int z = 2; g = g + z * x; } int main( void) fce( 2); fce( 3); return g; lokální proměnná co vrátí main ?? skutečný parametr
32
Výraz Norma: "Výraz je posloupnost operací a operátorů specifikující výpočet hodnoty" Přiřazovací 'příkaz' je také výraz jeho hodnotou je přiřazovaná hodnota Výrazy mohou mít vedlejší efekty 1 a+b*sin(x) printf( "Ahoj") q = &b[17]+*++p "retezec" a = b = 0; if( (x = fnc()) != 0) ... užitečné: test přiřazované hodnoty přiřazení je také výraz
33
Příkaz Příkaz je výraz ukončený ';' (středníkem)
složený příkaz - posloupnost příkazů mezi '{' a '}' programová konstrukce { 1; a+b*sin(x); printf( "Ahoj"); q = &b[17]+*++p; "retezec"; } 1; a+b*sin(x); printf( "Ahoj"); q = &b[17]+*++p; "retezec"; složený příkaz
34
syntakticky správně, ale dělá něco jiného
Podmíněný příkaz if (výraz) příkaz if (výraz) příkaz else příkaz if( a > 1) { b = 0; printf( "OK"); } else printf( "nee"); syntakticky správně, ale dělá něco jiného if( a > 1) printf( "OK"); if( a > 1) b = 0; printf( "OK"); if( a > 1) printf( "OK"); else printf( "nee"); if( a > 1) b = 0; printf( "OK"); else printf( "nee"); if( a > 1) { b = 0; printf( "OK"); } else { printf( "nee"); } syntakticky špatně
35
Vnořené podmíněné příkazy
Syntakticky správně, ale nepřehledné Na pohled nejasné párování if( a > 1) if( b > 0) printf( "OK"); else printf( "nee"); if( a > 1) { if( b > 0) printf( "OK"); } else { printf( "nee"); } if( a > 1) { if( b > 0) printf( "OK"); else printf( "nee"); } U vnořených podmínek vždy používat { }
36
Vícenásobné větvení – konstrukce switch
celočíselný výraz switch( opcode) { case 0: // no op break; case 10: add(); case 11: case 12: cmp(opcode); default: error(opcode); } switch( opcode) { case 0: // no op case 10: ... switch (výraz) { case konstanta: posloupnost příkazů break; default: } ukončení větve zapomenutý break syntakticky OK, ale dělá něco jiného switch( ch) { case '0'..'9': x += ch - '0'; break; case 'A'..'F': x += ch - 'A'; } pokud výraz není roven žádné z konstant více návěští pro jednu větev interval nelze
37
Cyklus – konstrukce while a do-while
while (výraz) příkaz podmínka na začátku do příkaz while (výraz) ; podmínka na konci Pozor! cyklus pokračuje pokud je podmínka platná while( a > 1) a = a / 2; tělo se vždy alespoň jednou provede while( a > 1) { fce( a); a = a / 2; } do { fce( a); a = a / 2; } while( a > 1);
38
Cyklus – konstrukce for
inicializace podmínka inkrement tělo cyklu for (výraz1 ; výraz2 ; výraz3 ) příkaz je ekvivalentem výraz1; while (výraz2 ) { příkaz výraz3 ; } ekvivalent v jiném jazyce for( i=0; i<=9; i=i+1) { fce( i); } FOR I := 0 TO 9 DO FCE(I) i=0; while( i<=9) { fce( i); i=i+1; } for v C++: obecnější, širší možnosti použití jako inicializaci, podmínku i inkrement lze libovolný výraz init(a); while( i<9 && a[i] >0) { fce( i); a[i++]=0; } for( init(a); i<9 && a[i] >0; a[i++]=0) { fce( i); }
39
Ukončení cyklu - break, continue
break okamžité ukončení celého cyklu cyklus s podmínkou uprostřed ošetření chybových stavů continue ukončení (jednoho) běhu těla cyklu for(;;) { errc = fce(); if( errc < 0) break; jinafce(); } n = 1; while( n<1000) { val = get(); if( val == 0) continue; n = n * val; }
40
ukončil by pouze vnitřní cyklus, vnější cyklus by pokračoval
Goto Nepoužívat! .... pokud k tomu není dobrý důvod Když už, tak pouze dopředu (skok dozadu = cyklus) Dobrý důvod: výskok z vícenásobně vnořených cyklů nelze break - ukončil by pouze vnitřní cyklus, vnější cyklus by pokračoval for( i=0; i<10; ++i) { for( j=0; j<10; ++j) { if( fnc( i, j) == ERROR) goto konec_obou_cyklu; } konec_obou_cyklu: dalsi_funkce(); label návěští
41
rozšířená znaková sada
Celočíselné typy Základní celočíselné typy jsou znaménkové Pro každý typ existuje unsigned varianta možné využití unsigned: unsigned char, pole bitů, modulová aritmetika pokud není dobrý důvod, unsigned raději nepoužívat char short int long long long typ 8 bit 16 bit 32 bit 64 bit char 8 short 8 / 16 16 16 / 32 int 32 32 / 64 long long long - 64 size_t ptrdiff_t wchar_t 16/32 1 byte -2GB .. +2GB velikost objektů rozdíl ukazatelů rozšířená znaková sada
42
Logické a znakové hodnoty a typy
C: Až do r. 1999: neexistuje typ 'boolean' Porovnání a logické operátory jsou celočíselné výrazy FALSE (nepravda) 0 TRUE (pravda) 1 (libovolná hodnota různá od 0) důsledek: if( x != 0) if( x) if( x == 0) if( !x) C++, C99 celočíselný typ bool (C99: _Bool) hodnoty true (=1), false (=0) char norma neurčuje signed / unsigned (!) korektní porovnání na nerovnost pouze 'a' < 'ž' ? signed char unsigned char 'byte' wchar_t stddef.h: znak rozšířené sady (Unicode) časté použití: test (ne)nulovosti záleží na implementaci většinou char = signed 40 > 200 !!! 200 -56
43
Výčtový typ enum pohlavi { p_muz, p_zena };
pohlavi p = p_muz; int pp = p_zena + 1; pohlavi q = 0; enum flags { f1 = 1, f2 = 2, f3 = 4, f4 = 8 }; if( x & f1) ... enum porty { pop3 = 111, ftp = 21, smtp = 80 }; hodnoty doplní překladač (od 0) lze použít jako celočíselnou hodnotu (ale většinou to nemá rozumný smysl) C++: samostatný typ - nelze ( C: celočíselné konstanty - OK ) test bitů explicitní hodnoty
44
'Reálné' typy float double long double Pozor!
Reálné výpočty jsou vždy nepřesné float double long double zvýšená přesnost double x = 1; double y = x / 3; if( x == 3 * y) printf( "Presne"); else printf( "Nepresne"); malá přesnost - nepoužívat! standard pro 'reálné' výpočty pro přesné hodnoty používejte přesné typy raději nepoužívat pouze pro fyzikální nebo numerické veličiny double zustatek = 15.60; long zustatek_v_halerich = 1560;
45
Celočíselné konverze Automatické konverze (integral promotions)
vždy když je použit menší typ než int Automatické konverze (integral promotions) Výpočty výrazů vždy v šíři alespoň int signed char, unsigned char, signed short signed int unsigned short signed int (pokud je int delší) / unsigned int Automatické konverze u binárních operací signed int unsigned int signed long unsigned long float double long double při nestejných operandech moudro: prostě se to zkonvertuje na ten větší
46
Přehled operátorů, asociativita a priorita
( ) [ ] -> . :: post-in/de-krementace volání funkce index přístup k položce struktury kvalifikace identifikátoru ! ~ + - & * sizeof ( ) new delete pre-in/de-krementace booleovská a bitová negace unární +/- reference, dereference měření velikosti přetypování dynamická alokace dynamická dealokace .* ->* dereference member-ptru * / % multiplikativní operátory + - aditivní operátory << >> bitové posuny < <= > >= uspořádání == != rovnosti & bitové AND ^ bitové XOR | bitové OR && booleovské AND || booleovské OR ? : podmíněný výraz = *= /= %= += -= &= ^= |= <<= >>= přiřazení kombinované přiřazení , sekvence
47
Základní aritmetické operátory
+ - * / % podle typu operandů automatická konverze na větší typ % - modulo int x=5, y=3; double a=5, b=3; modulo je pouze celočíselná operace celočíselné dělení x / y 1 x / b 1.666 x % y 2 x % b Error a / b a / y a % b a % y reálné dělení
48
Bitové a logické operátory
& | ^ ~ - bitové operace AND, OR, XOR, NOT && || ! - logické operace AND, OR, NOT oba op. 0 5 & 3 1 5 && 3 5 | 3 7 5 || 3 5 ^ 3 6 5 ^^ 3 Error 5 ^ 15 10 5 & 0 5 && 0 5 | 0 5 5 || 0 5 = 01012 3 = 00112 1 = 00012 7 = 01112 9 = 10012 15 = 11112 10 = 10102 alespoň jeden operand 0 neexistuje alespoň jeden operand = 0
49
Zkrácené vyhodnocování, relační operátory
a && b - je-li a=0, b se nevyhodnocuje, výsledek = false (0) a || b - je-li a=1, b se nevyhodnocuje, výsledek = true (1) < <= >= > == != výraz typu int (bool) - výsledek vždy 0 nebo 1 (false, true) porovnávání na (ne)rovnost float/double ! porovnání vs. přiřazení ! test mezí pole před přístupem k prvku pole int x[10]; // pole 0..9 if( i < 10 && x[i] != 0) y = y / x[i]; if( x==y && *x++) ... Pozor! operátory s vedlejšími efekty se nemusí provést ! POZOR!!! Přiřazení! (zde hodnota vždy = 1) if( x = 1) ...
50
Přiřazení, inkrementace, bitový posun
= += -= *= /= %= &= |= ^= <<= >>= kombinované přiřazení a op= b a = a op b ++a a = a + 1 , výsledkem je nová hodnota a a++ a = a + 1, výsledkem je stará hodnota a přesněji: a++ (tmp = a, a = a + 1, tmp) << >> bitový posun C++ - časté použití pro jiné účely (streams) - přetěžování i += 2; x[ i+=1] /= 3; pokud si lze vybrat, preferujte preinkrement int sum = 0; int i, x[10]; ... for( i=0; i<9; sum += x[i++]) ; pozor - vždy si uvědomit, zda jde o pre- nebo post- inkrementaci
51
Podmíněný výraz, sekvence
ternární operátor a ? b : c po vyhodnocení podmínky a se vyhodnotí buď b (je-li a != 0) nebo c (je-li a == 0) a , b po úplném vyhodnocení a se vyhodnotí b x = (y>0 ? y : 0); operátor sekvence ('zapomnění') x = (tmp = y, y = y + 1, tmp); ekvivalent x = y++;
52
Pravidla vyhodnocování
a( b,c) vedlejší efekty parametrů jsou vyhodnoceny před zavoláním fce a && b je-li a nulový, b se nevyhodnocuje a || b je-li a nenulový, b se nevyhodnocuje a ? b : c po vyhodnocení a se vyhodnotí buď b nebo c a , b po úplném vyhodnocení a se vyhodnotí b Žádná další pravidla nejsou ! ostatní operátory jsou vyhodnocovány v libovolném pořadí vedlejší efekty se mohou projevit kdykoliv během výpočtu možné výsledky: p[0] = 0; p[1] = 0; p[0] = 1; i = 0; p[ i++] = i++;
53
Fungující triky vs. chyby
Test ukazatele nebo indexu while ( i<MAX && pole[i]<v ) ++pole[i]; Volání funkce s vedlejším efektem while ( (c = getchar()) != EOF && c != '\n' ); Kopie řetězce while ( *a++ = *b++ ); Chyby Vícenásobný výskyt modifikované proměnné p[ i++] = i++; Nadbytečné volání funkce s vedlejším efektem if ( getchar() == 'A' && getchar() == 'B' ) while( p && p->v < v) p = p->next; není definováno pořadí nevím, jestli se provede
54
Typové kostrukce - přehled
A x[ n] pole n prvků typu A, n je konstantní výraz A x[] pole neznámého počtu prvků typu A (pouze v některých kontextech) A * x ukazatel na typ A void * x ukazatel na neurčený typ *x ++x nelze A const * x const A * x ukazatel na konstantní hodnotu typu A x lze *x nelze A * const x konstantní ukazatel na typ A x nelze *x lze A & x C++: reference na typ A A const & x const A & x C++: reference na konstantní hodnotu typu A A x() funkce vracející typ A - C++: bez parametrů, C: bez určení parametrů A x( par) funkce s určenými parametry A x( void) funkce bez parametrů void x( par) funkce bez návratové hodnoty (procedura)
55
Pole Deklarace: t x[n] - pole x o n prvcích (0..n-1) typu t
Indexy polí vždy začínají od 0 ! Při přístupu se nikdy nekontrolují meze !!! Vícerozměrné pole je pole polí Deklarace: t x[m][n]; Přístup: x[m][n] = a; Přepis náhodného místa v paměti ! Nepředvídatelné následky !! int x[5]; for( i=1; i <=5; ++i) x[i] = i; 1 2 3 4 ??? ? 5 význam: m-prvkové pole typu (n-prvkové pole typu t) int x[8][8]; for( i=0; i < 8; ++i) for( j=0; j < 8; ++j) x[ i ] [ j ] = i * j; Pozor! x[m,n] není prvek dvojrozměrného pole o souřadnicích m a n čárka = operátor sekvence
56
Inicializace pole Při deklaraci pole lze obsah pole inicializovat
Nejde o přiřazení, lze pouze při deklaraci Deklarace inicializovaného pole nemusí obsahovat velikost překladač dopočítá z inicializace Při nesouhlasu velkosti pole a počtu inicializátorů velikost > inicializátory: inicializace začátku, obsah zbytku nedefinován velikost < inicializátory: kompilační chyba (Too many initializers) int cisla[] = { 1, 2, 3, 4 }; char* jmena[] = { "Josef", "Karel", "Jana" }; int matice[][3] = { { 11, 12, 13 }, { 21, 22, 23 } };
57
Struktury struct osoba { char jmeno[20]; char prijemni[30];
definice struktury Béda struct osoba { char jmeno[20]; char prijemni[30]; int rok_narozeni; int pohlavi; }; osoba os, *po, zam[20]; osoba beda = { "Béda", "Trávníček", 1980, p_muz }; strcpy( os.jmeno, "Venca"); zam[3].rok_narozeni = 1968; po = &zam[3]; po->pohlavi = p_muz; Trávníček 1980 položky struktury struktura, ukazatel na strukturu, pole struktur definice proměnné typu struktura s inicializací přístup k položkám x.y (*px).y px->y
58
Pojmenování struktur a výčtových typů
Jméno třídy/struktury/unie/výčtového typu je samostatný typ C Jméno (tag) struktury/unie/výčtového typu je možno používat pouze s příslušným prefixem struct/union/enum struct STR { int a, b; STR * next; }; STR * p; struct STR { int a, b; struct STR * next; }; struct STR * p; C++ - samostatný typ C - pouze tag
59
Ukazatele 1 Co to je proměnná?
místo v paměti, typ ( velikost), hodnota hodnota se dá číst a většinou i zapisovat Co to je ukazatel (pointer)? něco, čím si můžu ukazovat na proměnnou (nebo na jiné paměťové místo – pole, položka struktury, dynamická alokace) K čemu jsou ukazatele dobré: zpracování řetězců, dynamické datové struktury, předávání parametrů odkazem, práce s buffery, ... Pro C i C++ je práce s ukazateli typická je nějak velká (typ) má hodnotu 1 někde bydlí
60
Ukazatele p: x: 17 Deklarace: t x – proměnná x typu t
t *p - ukazatel p na typ t Operátor reference: p = &x Operátor dereference: x = *p Neinicializovaný ukazatel vs. nulový ukazatel C++: 0 C: #define NULL 0 x: 17 proměnná ukazatel na ni ukazatel hodnota na kterou ukazuje
61
přepsání náhodného místa v paměti
Ukazatele - příklad int x = 1, y = 3; int * px, * py; *px = 5; py = NULL; *py = 7; if( ! py) etwas(); px = &x; py = &y; (*py)++; px = py; y = x; py = &x; *py = 9; přepsání náhodného místa v paměti ? 5 x: 1 :px y: 3 :py ? jaký zde bude obsah x a y?
62
Ukazatele - příklad x: 1 :px y: 3 :py 0: 7 int x = 1, y = 3;
int * px, * py; *px = 5; py = NULL; *py = 7; if( ! py) etwas(); px = &x; py = &y; (*py)++; px = py; y = x; py = &x; *py = 9; x: 1 :px y: 3 :py umožní test 0: 7 typicky 'segmentation fault' váš program bude ukončen pro pokus o porušení ochrany paměti
63
přístup k hodnotě proměnné přes ukazatel
Ukazatele - příklad x: 1 :px int x = 1, y = 3; int * px, * py; *px = 5; py = NULL; *py = 7; if( ! py) etwas(); px = &x; py = &y; (*py)++; px = py; y = x; py = &x; *py = 9; y: 3 :py x: 1 :px y: 4 :py Pozor na prioritu! *py++ *(py++) přístup k hodnotě proměnné přes ukazatel
64
Ukazatele - příklad x: 1 :px y: 1 :py x: 9 :px y: 1 :py
int x = 1, y = 3; int * px, * py; *px = 5; py = NULL; *py = 7; if( ! py) etwas(); px = &x; py = &y; (*py)++; px = py; y = x; py = &x; *py = 9; x: 1 :px y: 1 :py x: 9 :px jaký zde bude obsah x a y? y: 1 :py
65
Pole a ukazatele, aritmetika ukazatelů
t a[n]; t *p = &a[i]; ukazatel p ukazuje na nějaký prvek pole (stejného typu) p + j přičtení celočíselné hodnoty - posun o j prvků v rámci pole p - j odečtení - posun zpět int a[5]; int *p; a[2] = 20; p = &a[2]; a[0] = *p - 15; ++p; *p = 30; 1 2 3 4 ? 20 a p reference na prvek pole reference na prvek pole 5 ? 20 30 p inkrementace ukazatele - posun na další prvek
66
Pole a ukazatele, aritmetika ukazatelů
5 ? 20 30 40 p = &a[1]; *(p+3) = 40; Operátor [] p[i] *(p+i) &p[i] p+i a &a[0] Automatické konverze pole-ukazatel identifikátor pole se chová jako ukazatel na nultý prvek Pole nelze přiřazovat ani předávat hodnotou ukazatel na nultý prvek p = a je ekvivalentní p = &a[0] p přičtení čísla k ukazateli posun o n prvků indexování pole (ukazatele) je jen jiný zápis přístupu přes ukazatel identifikátor pole je ukazatel na svůj nultý prvek
67
Pole a ukazatele, aritmetika ukazatelů
int a[5]; int *p; 10 20 30 40 nekompatibilní typy nestejná úroveň indirekce p = &a[0]; p = a; *p = a[1]; *(p+2) = a[2] – 1; p = a + 2; p[1] = *(a+1); a[3] = p[2]; *(a+2) = p[-1]; 3[a] = 2[p]; a[4] = p + 1; p = a[0]; p[1] = a; a = p + 2; identifikátor pole je konstantní nelze do něj přiřazovat p[i] *(p+i) *(i+p) i[p]
68
Řetězce Řetězec je pole znaků (char) zakončené nulou "Ahoj" X proměnná
konvence, knihovny "Ahoj" X proměnná 'X' znaková konstanta - celočíselná hodnota "X" řetězec - ukazatel Každý řetězec musí být vždy ukončen nulou 'A' 'h' 'o' 'j' '\0' '\0' = 0 char buffer[4]; strcpy( buffer, "Ahoj"); 'A' 'h' 'o' 'j' '\0' pozor na uvozovky a apostrofy ! vždy myslet na koncovou nulu ! kód znaku v použitém kódování (ASCII, CP1250, ISO8859-2, EBCDIC, ...) když chcete říct mezera, napište mezeru (' ') ať vás ani nenapadne napsat 32 přestože to na VAŠEM počítači funguje
69
Řetězcové proměnné a konstanty
static char s1[] = "Uno"; const char *s2 = "Due"; puts( "Uno"); Inicializované pole (konstantní ukazatel) ++s1 nelze! anonymní globální proměnná const char[] ... = { 'U', 'n', 'o', 0 }; s1: 'U' 'n' 'o' '\0' s2: 'D' 'u' 'e' '\0' Inicializovaná proměnná typu ukazatel s2++ se přesune na další znak ekvivalent globální proměnné typu const char[ ], inicializované obsahem konstanty
70
Předávání řetězců Jazyk C/C++ nedovoluje kopírování pole
není možné pole přiřazovat, předávat jako parametr ani vracet z funkce Předávání řetězců do funkcí Předává se vždy odkazem Parametr typicky typu const char * Vracení řetězců z funkcí Nahrazuje se "návratovým" parametrem Parametr typu char * Je vhodné jej doplnit parametrem typu size_t, určujícím velikost pole ukazatel na pole, které má funkce vyplnit standardní C knihovny to ale většinou nedělají
71
Řetězce – knihovní funkce, délka řetězce
v C neexistují 'řetězcové operace' (C++: třída string) přiřazení, zřetězení, porovnání, podřetězec, ... vše standardní knihovní funkce #include <string.h> size_t strlen( const char* s); deklarace řetězcových funkcí počet znaků (bez koncové nuly) char pole[8] = "Ahoj"; x = strlen( pole); // 4 inicializované pole typu char skutečný počet znaků (4) nikoliv velikost pole pole: A h o j \0 ?
72
Délka řetězce – přes index
přístup přes index int strlen ( const char* s) { int i = 0; while ( s[i] != '\0') { ++i; } return i;
73
Délka řetězce – přes ukazatel
int strlen ( const char* s) { int i = 0; while ( s[i] != '\0') { ++i; } return i; int i = 0; while ( *s != '\0') { ++i; ++s; } return i; přístup přes ukazatel
74
Délka řetězce – cyklus for
podmínka for cyklu může být libovolná int strlen ( const char* s) { int i = 0; while ( s[i] != '\0') { ++i; } return i; for( i=0; *s != '\0'; ++i) ++s; int i = 0; while ( *s != '\0') { ++i; ++s; } return i;
75
Délka řetězce – for bez těla
int strlen ( const char* s) { int i = 0; while ( s[i] != '\0') { ++i; } return i; více inkrementací prázdné tělo nezapomenout na ';' !! for( i=0; *s != '\0'; ++i) ++s; for( i=0; *s != '\0'; ++i, ++s) ; int i = 0; while ( *s != '\0') { ++i; ++s; } return i;
76
Délka řetězce – podmínka s vedlejším efektem
int strlen ( const char* s) { int i = 0; while ( s[i] != '\0') { ++i; } return i; for( i=0; *s != '\0'; ++i) ++s; for( i=0; *s != '\0'; ++i, ++s) ; složitější podmínka: test nenulovosti inkrementace ukazatele, pozor na postfix int i=0; while ( *s++ != '\0') ++i; int i = 0; while ( *s != '\0') { ++i; ++s; } return i;
77
Délka řetězce – nenulovost výrazu v podmínce
int strlen ( const char* s) { int i = 0; while ( s[i] != '\0') { ++i; } return i; for( i=0; *s != '\0'; ++i) ++s; for( i=0; *s != '\0'; ++i, ++s) ; int i=0; while ( *s++ != '\0') ++i; int i = 0; while ( *s != '\0') { ++i; ++s; } return i; while(a!=0) while(a) podmínka je splněna pokud je nenulová int i=0; while ( *s++) ++i;
78
Délka řetězce – rozdíl ukazatelů
int strlen ( const char* s) { int i = 0; while ( s[i] != '\0') { ++i; } return i; for( i=0; *s != '\0'; i++) ++s; for( i=0; *s != '\0'; ++i, ++s) ; int i=0; while ( *s++ != '\0') ++i; int i = 0; while ( *s != '\0') { ++i; ++s; } return i; int i=0; while ( *s++) ++i; rozdíl ukazatelů = počet prvků mezi nimi pozor na ± 1 ! char *p = s; while (*p++) ; return p-s-1;
79
Délka řetězce – různé způsoby implementace
podmínka for cyklu může být libovolná přístup přes index int strlen ( const char* s) { int i = 0; while ( s[i] != '\0') { ++i; } return i; více inkrementací prázdné tělo nezapomenout na ';' !! for( i=0; *s != '\0'; ++i) ++s; for( i=0; *s != '\0'; ++i, ++s) ; složitější podmínka: test nenulovosti inkrementace ukazatele int i=0; while ( *s++ != '\0') ++i; int i = 0; while ( *s != '\0') { ++i; ++s; } return i; while(a!=0) while(a) podmínka je splněna pokud je nenulová int i=0; while ( *s++) ++i; rozdíl ukazatelů = počet prvků mezi nimi pozor na ± 1 ! char *p = s; while (*p++) ; return p-s-1; přístup přes ukazatel
80
Řetězce - kopírování buf pozdrav
char* strcpy( char* d, const char* s); zkopíruje obsah s do místa začínajího od d char buf[8]; char pozdrav[] = "Ahoj"; strcpy( buf, pozdrav); kopíruje pouze do koncové '\0' buf A h o j \0 ? pozdrav A h o j \0
81
Řetězce – chyby při kopírování
vždy pozor na dostatek místa funkce nic nekontroluje !!! váš program provedl... char buf[6]; char pozdrav[] = "Dobry den"; strcpy( buf, pozdrav); buf D o b r y d e n \0 pozdrav D o b r y d e n \0 kopírování na neinicializovaný ukazatel !!! váš program provedl... char *ptr; char pozdrav[] = "Ahoj"; strcpy( ptr, pozdrav); ? ptr A h o j \0 pozdrav ptr neinicializovaný !!! A h o j \0
82
Řetězce – zřetězení, vyhledávání
char* strcat( char* d, const char* s); připojí s za d char* strchr( const char* s1, int c); vyhledá první pozici c v s1 char* strstr( const char* s1, const char* s2); vyhledá podřetězec s2 v s1 char buf[10]; char* bp; strcpy( buf, "Ahoj"); strcat( buf, "Babi"); bp = strstr( buf, "oj"); po strcpy A h o j \0 ? po strcat A h o j B a b i \0 ? pozor na dostatek místa ! buf A h o j B a b i \0 ? bp
83
Řetězce – porovnávání a další funkce
int strcmp( const char* s1, const char* s2); s1 < s2 -1 s1 = s2 0 s1 > s2 +1 lexikografické uspořádání (jako ve slovníku) další řetězcové funkce: strncat, strncmp, strncpy strrchr Find last occurrence of given character in string strspn, strcspn Get length of substring composed / not composed of given characters strpbrk Find first occurrence of character from one string in another string strtok Find next token in string, sequentially truncate string if delimiter is found sprintf Write formatted data to a string memcpy, memset, memcmp, memchr výsledek podle prvního rozdílného znaku co znamená 's1 < s2'? A B C E = D
84
Problém vracení řetězců - lokální proměnná
Naprosto chybné řešení Nekontroluje přetečení pole buf Vrací odkaz na lokální proměnnou, která v okamžiku návratu zaniká char * cele_jmeno( const char * jm, const char * prijm) { char buf[ 100]; strcpy( buf, jm); strcat( buf, " "); strcat( buf, prijm); return buf; }
85
Problém vracení řetězců - statická proměnná
Chybné řešení Nekontroluje přetečení pole buf Používá statickou proměnnou zbytečně zabírá místo i v době, kdy funkce není vyvolána opakovaná volání ji sdílejí: podmínka nikdy nebude splněna, protože strcmp vždy dostane stejné ukazatele na totéž pole buf char * cele_jmeno( const char * jm, const char * prijm) { static char buf[ 100]; strcpy( buf, jm); strcat( buf, " "); strcat( buf, prijm); return buf; } if ( strcmp( cele_jmeno( j1, p1), cele_jmeno( j2, p2)) )
86
Problém vracení řetězců - parametr
Funkční řešení, ale nebezpečné Nekontroluje přetečení pole buf Pokud volající nemá spolehlivý horní odhad velikostí jména a příjmení, nemůže tuto funkci bezpečně volat void cele_jmeno( char * buf, const char * jm, const char * prijm) { strcpy( buf, jm); strcat( buf, " "); strcat( buf, prijm); } void tisk( const char * jm, const char * prijm) { char buf[ 100]; cele_jmeno( buf, jm, prijm); puts( buf); }
87
Problém vracení řetězců - správné a bezpečné řešení
int cele_jmeno( char * buf, size_t bufsize, const char * jm, const char * prijm) { size_t lj = strlen( jm); size_t lp = strlen( prijm); if ( lj + lp + 2 > bufsize ) { /* error */ return -1; } memcpy( buf, jm, lj); buf[ lj] = ' '; memcpy( buf + lj + 1, prijm, lp); buf[ lj + lp + 1] = 0; return lj + lp + 1; } kontrola velikosti pole pozor na mezeru a konec! kopírování jednotlivých částí návrat výsledné délky void tisk( const char * jm, const char * prijm) { enum { N = 100 }; char buf[ N]; if( cele_jmeno( buf, N, jm, prijm) > 0) puts( buf); } max velikost pole kontrola korektnosti výsledku
88
Kombinace typových kostrukcí
A * x[10] pole ukazatelů A (* x)[10] ukazatel na pole A * x() funkce vracející ukazatel A (* x)() ukazatel na funkci A x[10]() pole funkcí - zakázáno A (* x[10])() pole ukazatelů na funkci A x()[10] funkce vracející pole - zakázáno A (* x())[10] funkce vracející ukazatel na pole typicky se nepoužívá pole = ukazatel na 1. prvek čtení deklarací: od identifikátoru doprava, až to nepůjde, tak doleva
89
Kombinace typových kostrukcí - příklad
int*(*pf[10])(void); int*(*maso(int*(*p1)(void),int*(*p2)(void)))(void); co to je za maso ???
90
Kombinace typových kostrukcí - typedef
int*(*pf[10])(void); int*(*maso(int*(*p1)(void),int*(*p2)(void)))(void); typedef int* fce( void); fce * pf [10]; fce * maso( fce* p1, fce* p2); použitím typedef se může výrazně zpřehlednit kód
91
Parametry příkazové řádky
C:\> myprog.exe -n -w a.txt b.txt pole řetězců (ukazatelů na char) int main( int argc, char** argv) argv 5 argc b . t x \0 a . t x \0 Počet parametrů včetně názvu programu ! = počet ukazatelů v argv - w \0 - n \0 m y p r o g . e x \0
92
Zpracování příkazové řádky – výpis parametrů
C:\> myprog.exe -n -w a.txt b.txt int main( int argc, char** argv) { while( *argv) { printf( "%s\n", *argv); ++argv; } myprog.exe -n -w a.txt b.txt výstup řetězce a odřádkování posun na další parametr
93
Zpracování příkazové řádky
int main( int argc, char** argv) { while( *argv) { printf( "%s\n", *argv); ++argv; } C:\> myprog.exe -n -w a.txt b.txt argv[4] argv[4][1] b . t x \0 *argv argv[0] typ: char* a . t x \0 - w \0 argv typ: char** - n \0 m y p r o g . e x \0 argv **argv argv[0][0] typ: char
94
Zpracování příkazové řádky
int main( int argc, char** argv) { while( *argv) { printf( "%s\n", *argv); ++argv; } b . t x \0 a . t x \0 argv++ **argv - w \0 - n \0 m y p r o g . e x \0 argv
95
Zpracování příkazové řádky
int main( int argc, char** argv) { while( *argv) { printf( "%s\n", *argv); ++argv; } b . t x \0 a . t x \0 *argv == 0 - w \0 - n \0 m y p r o g . e x \0 argv
96
Zpracování parametrů příkazové řádky
usage: myprog [-n] [-w] fileA fileB int main( int argc, char** argv) { int n=0, w=0; while( *++argv && **argv=='-') { switch( argv[0][1]) { case 'n': n = 1; break; case 'w': w = 1; break; default: error(); } if( !argv[0] || !argv[1]) error(); doit( argv[0], argv[1], n, w); return 0; options nastavení přepínače zbývající parametry b . t x \0 a . t x \0 - w \0 výkonná funkce - n \0 argv p r g . e x \0
97
Zpracování parametrů příkazové řádky
int main( int argc, char** argv) { int n=0, w=0; int x = 0; char* f = 0; while( *++argv && **argv=='-') { switch( argv[0][1]) { case 'n': n = 1; break; case 'w': w = 1; break; case 'x': x = atoi( argv[0]+2); break; case 'f': f = argv[0]+2; break; default: error(); } if( !argv[0] || !argv[1]) error(); doit( argv[0], argv[1], n, w, x, f); return 0; usage: myprog [-n] [-w] [-x123] [-ffilename] fileA fileB číselný parametr řetězcový parametr - f i l ... - x 1 2 3 \0 argv
98
Zpracování parametrů příkazové řádky
int main( int argc, char** argv) { int n=0, w=0; int x = 0; char* f = 0; while( *++argv && **argv=='-') { switch( argv[0][1]) { case 'n': n = 1; break; case 'w': w = 1; break; case 'x': x = atoi( argv[0]+2); break; case 'f': if( argv[0][2]) f = argv[0]+2; else f = *++argv; break; default: error(); } if( !argv[0] || !argv[1]) error(); doit( argv[0], argv[1], n, w, x, f); return 0; usage: myprog [-n] [-w] [-x123] [-f filename] fileA fileB -ffile -f file f i l e n ... - f \0 - f i l ... argv
99
Dynamická alokace paměti
C: standardní knihovny <alloc.h>, <stdlib.h> void* malloc( size_t size); void free( void* p); C++: součást jazyka new T new T[ n] delete p delete[] p C++ nutnost přetypování p = (ELEM*)malloc( sizeof( ELEM)); mechanismus výjimek #include <alloc.h> ELEM * p; p = malloc( sizeof( ELEM)); if ( !p ) { /* chyba */ } free( p); int n = 100; p = malloc( n * sizeof( ELEM)); ELEM * p; p = new ELEM; delete p; int n = 100; p = new ELEM[ n]; delete[] p; Jeden objekt Jeden objekt Pole objektů Pole objektů vždy ověřit !!! váš program provedl...
100
Velikost pole určena za běhu programu
int main( int argc, char** argv) { char* buf; ... buf = new char[ strlen(argv[1]) + strlen(argv[2]) + 1]; strcpy( buf, argv[1]); strcat( buf, argv[2]); spočítá potřebnou velikost ze vstupních parametrů pozor na koncovou '\0' int main( int argc, char** argv) { char* buf; ... buf = malloc( strlen(argv[1]) + strlen(argv[2]) + 1); if( ! buf) error(); strcpy( buf, argv[1]); strcat( buf, argv[2]); nutný test na úspěšnost
101
Ladicí implementace alokačních funkcí
Obložit každý alokovaný blok prostorem vyplněným značkami Při dealokaci zkontrolovat neporušenost značek a změnit je velikost bloku void * my_malloc( size_t s) { char * p = (char *)malloc( s+sizeof(size_t)+2*AB_SIZE); if ( ! p ) return 0; *(size_t *)p = s; memcpy( p+sizeof(size_t), AB_FILL, AB_SIZE) memcpy( p+sizeof(size_t)+AB_SIZE+s, AB_FILL, AB_SIZE) return p+sizeof(size_t)+AB_SIZE; } size #### void my_free( void * vp) { char * p; if ( ! vp ) ERROR(); p = (char *)vp - (sizeof(size_t)+AB_SIZE); if ( memcmp( p+sizeof(size_t), AB_FILL, AB_SIZE) ) ERROR(); if ( memcmp( p+sizeof(size_t)+AB_SIZE+*(size_t *)p, AB_FILL, AB_SIZE) ) ERROR(); /* ... zmenit znacky ... */ free( p); } #### kontrolní výplň
102
Organizace paměti procesu
Kódový segment Kód programu Datový segment Globální proměnné Heap Dynamicky alokovaná data Zásobník Lokální proměnné a parametry funkcí IP R0 R1 ... SP
103
Organizace paměti procesu – kódový segment
připraven kompilátorem součást spustitelného souboru kód uživatelských i knihovních funkcí obvykle chráněn proti zápisu Datový segment Heap Zásobník IP R0 R1 ... SP int fce( int x) { ... } { int (*fp)( int); fp = fce; ... (*fp)(17); fp(17) alternativní syntaxe
104
Organizace paměti procesu – datový segment
Kódový segment Datový segment připraven kompilátorem součást spustitelného souboru explicitně nebo implicitně (nulami) inicializované globální proměnné řetězcové konstanty data knihoven Heap Zásobník IP R0 R1 ... SP inicializované pole int bigpole[ 1000]; char retezec[] = "CHAIN"; { int* p = bigpole; const char* s = "ahoj"; ...
105
Organizace paměti procesu - heap
Kódový segment Datový segment Heap vytvářen startovacím modulem knihoven dynamicky alokovaná data new/delete malloc/free obsazené bloky různé velikosti + seznam volných bloků Zásobník IP R0 R1 ... SP nemixovat { char* s; s = new char[128]; ...
106
Organizace paměti procesu - zásobník
Kódový segment Datový segment Heap Zásobník připraven operačním systémem lokální proměnné pomocné proměnné generované kompilátorem návratové adresy aktivační záznamy funkcí IP R0 R1 ... SP { char pole[100]; char s[] = "Ahoj"; int x = * 3; ... inicializace při každém průchodu
107
Statické proměnné globální proměnná neviditelná z jiných modulů
C++: lepší řešení - namespace static int x; int fce( int a) { static int y = 0; return y += a; } de facto globální (!) proměnná neviditelná z jiných funkcí C++: raději skrýt do třídy inicializace: C: před vstupem do main C++: před prvním průchodem
108
Organizace paměti procesu – příklad
const int max = 100; char buf[max]; char* prefix( char* s) { char* p = new char[ strlen(s)+2]; *p = '#'; strcpy( p + 1, s); return p; } int main( int argc, char** argv) char* p; strcpy( buf, argv[ argc – 1]); p = prefix( buf); p = prefix( p); .... co je v kterém segmentu?
109
Organizace paměti procesu – příklad
pokus\0 buf const int max = 100; char buf[max]; char* prefix( char* s) { char* p = new char[strlen(s)+2]; *p = '#'; strcpy( p + 1, s); return p; } int main( int argc, char** argv) char* p; strcpy( buf, argv[ argc – 1]); p = prefix( buf); p = prefix( p); .... D p Z program pokus H
110
Organizace paměti procesu – příklad
pokus\0 buf const int max = 100; char buf[max]; char* prefix( char* s) { char* p = new char[strlen(s)+2]; *p = '#'; strcpy( p + 1, s); return p; } int main( int argc, char** argv) char* p; strcpy( buf, argv[ argc – 1]); p = prefix( buf); p = prefix( p); .... D p s p Z program pokus H
111
Organizace paměti procesu – příklad
pokus\0 buf const int max = 100; char buf[max]; char* prefix( char* s) { char* p = new char[strlen(s)+2]; *p = '#'; strcpy( p + 1, s); return p; } int main( int argc, char** argv) char* p; strcpy( buf, argv[ argc – 1]); p = prefix( buf); p = prefix( p); .... D p s p Z #pokus\0 program pokus H
112
Organizace paměti procesu – příklad
pokus\0 buf const int max = 100; char buf[max]; char* prefix( char* s) { char* p = new char[strlen(s)+2]; *p = '#'; strcpy( p + 1, s); return p; } int main( int argc, char** argv) char* p; strcpy( buf, argv[ argc – 1]); p = prefix( buf); p = prefix( p); .... D p Z #pokus\0 program pokus H
113
Organizace paměti procesu – příklad
pokus\0 buf const int max = 100; char buf[max]; char* prefix( char* s) { char* p = new char[strlen(s)+2]; *p = '#'; strcpy( p + 1, s); return p; } int main( int argc, char** argv) char* p; strcpy( buf, argv[ argc – 1]); p = prefix( buf); p = prefix( p); .... D p s p Z #pokus\0 program pokus H
114
Organizace paměti procesu – příklad
pokus\0 buf const int max = 100; char buf[max]; char* prefix( char* s) { char* p = malloc(strlen(s)+2); *p = '#'; strcpy( p + 1, s); return p; } int main( int argc, char** argv) char* p; strcpy( buf, argv[ argc – 1]); p = prefix( buf); p = prefix( p); .... D p s p Z #pokus\0 ##pokus\0 program pokus H
115
Organizace paměti procesu – příklad
pokus\0 buf const int max = 100; char buf[max]; char* prefix( char* s) { char* p = malloc(strlen(s)+2); *p = '#'; strcpy( p + 1, s); return p; } int main( int argc, char** argv) char* p; strcpy( buf, argv[ argc – 1]); p = prefix( buf); p = prefix( p); .... D p Z #pokus\0 ##pokus\0 program pokus H
116
Organizace paměti - ukazatele na funkce
typedef double DoubleFce(double); const int DF_POCET = 4; DoubleFce* fce[DF_POCET]= { cos, sin, log, tan}; double* spocti( double vysl[], DoubleFce* f, int pocet, double hod, double krok) { for( int i = 0; i < pocet; ++i) { vysl[i] = f(hod); hod += krok; } return vysledek; int main() const int pocet=8; const double zac=0.1; const double krok=0.4; double vysledek[ pocet]; for( int i = 0; i < DF_POCET; ++i) { spocti(vysledek, fce[i], pocet, zac, krok); zobraz( vysledek, ....); return 0; cos { ... sin { ... log { ... C fce D vysled. f vysl Z
117
Souborový vstup a výstup
deskriptor souboru - deklaruje programátor struktura definovaná v <stdio.h> soubor 'na síti' FILE * FILE Neznámý obsah Pro knihovní funkce OS Otevření souboru: kontrola existence a práv vytvoření vnitřních knihovních struktur asociace s otevřeným souborem předání deskriptoru souboru (FILE*) soubor (na disku)
118
Práce se soubory typ 'soubor' (ukazatel na strukturu) otevření souboru
pojmenovávací konvence dle OS #include <stdio.h> FILE* fp; int c; if( !(fp = fopen("c:\\f.txt", "r"))) error(); while( (c = getc( fp)) != EOF) putchar( c); fclose( fp); pozor na '\\' !!! čtení ze souboru zavření souboru
119
r: soubor musí existovat
Otevření souboru FILE* fopen( const char* fname, const char* mode); r open file for reading w truncate to zero length or create file for writing a append; open or create file for writing at end-of-file r+ open file for update (reading and writing) w+ truncate to zero length or create file for update a+ append; open or create file for update, writing at end-of-file rb binary file ... r: soubor musí existovat mode soubor ex. soubor neex. seek r R Error w Del, W W a End r+ R/W w+ Del, R/W a+ w: soubor se smaže a: otevřít na konci +: vždy čtení i zápis
120
Textové vs. binární soubory
Textový soubor konverze konců řádek ('\n') na platformově závislou vnější reprezentaci typicky 0x0D 0x0A (Win) nebo 0x0A (Unix) konverze je automatická, programátor se o to nemusí starat vhodné pro ukládání lidsky čitelných dat getc/putc, fgets/fputs, fprintf, ... chování fseek/ftell na '\n' nedefinován - nepoužívat Binární soubor žádné konverze se neprovádí v souboru je přesný binární obraz zapisovaných dat vhodné pro ukládání vnitřních datových struktur lidsky přímo nečitelné typicky fread/fwrite, lze i getc/putc (přístup po bajtech) fseek/ftell OK
121
Funkce pro práci se soubory
FILE* fopen( const char* fname, const char* mode); int fclose( FILE* fp); int fprintf( FILE* fp, const char* format, ...); int getc( FILE* fp); int putc( int c, FILE* fp); char* fgets( char* buffer, size_t limit, FILE* fp); int fputs( const char* buffer, FILE* fp); size_t fread( void* ptr, size_t size, size_t n, FILE* fp); size_t fwrite( const void* ptr, size_t size, size_t n, FILE* fp); long ftell( FILE* fp); int fseek( FILE* fp, long offset, int whence); whence: SEEK_SET, SEEK_CUR, SEEK_END int fflush( FILE *fp);
122
Práce se soubory - příklad
#include <stdio.h> const int buflen = 1024; char buf[buflen]; int main(int argc, char ** argv) { FILE *f; int n; if (argc <= 1) return 1; f = fopen(argv[1], "r"); if (!f) return 2; while ((n = fread(buf, 1, buflen, f)) > 0) { fwrite(buf, 1, n, stdout); } fclose(f); return 0; cat kopírování obsahu souboru na standardní výstup
123
Práce se soubory - příklad
#include <stdio.h> int main( int argc, char** argv) { FILE* f; long l; if( argc < 2) return 8; f = fopen( argv[1], "rb"); if( !f) return 4; fseek( f, 0, SEEK_END); l = ftell( f); fclose( f); printf( "Delka %s je %ld.\n", argv[1], l); return 0; } zjištění skutečné velikosti souboru seek na konec zjištění pozice Pozor! long
124
Souborový vs. standardní v/v
funkce pro práci se standardním vstupem/výstupem int getchar( void); int putchar( int c); int printf( const char* format, ...); char* gets( char* buffer); standardní vstup/výstup FILE* stand. otevřený na čtení/zápis před vstupem do main FILE *stdin; FILE *stdout; getchar() getc(stdin) putchar(c) putc(c, stdout) FILE* fp = stdout; if( ...) fp = fopen( "...", "r"); c = getc( fp); Nepoužívat! Nelze ohlídat přetečení bufferu všechny souborové funkce lze použít i pro std. v/v jednotný zápis na std výstup nebo do souboru
125
C++ - vše v prostoru jmen std
Knihovní funkce C C++ - vše v prostoru jmen std <string.h> <cstring> strlen, strcmp, stricmp, strcpy, strncpy, strcat, strchr, strrchr, strstr, memset, memcmp, memcpy, memchr <stdio.h> <cstdio> getchar, putchar, fopen, tmpfile, fclose, getc, putc, fgets, fputs, fread, fwrite, ftell, fseek, printf, fprintf, vfprintf, fflush, ungetc FILE, stdin, stdout, EOF, SEEK_SET, ... <stdlib.h> <cstdlib> malloc, free, atoi, atof, strtol, qsort, rand, exit <ctype.h> <cctype> isalpha, isdigit, isxdigit, isalnum, isspace, ispunct, iscntrl, islower, isupper, tolower, toupper <math.h> <cmath> abs, floor, sin, sqrt, exp, exp, log, ... <time.h> <ctime> time, gmtime, strftime, asctime, clock, ... pro priste udelat obrazek na fread/fwrite ... a mnoho mnoho dalších
126
printf int printf( const char *, ...); fprintf( FILE*, sprintf( char*,
swprintf( wchar_t*, _snprintf( char*, int n, _scprintf( ... int printf( const char *, ...); % [flags] [width] [.precision] [opt_pref] type %c - char - znak %d - int - decimálně %x - int - šestnáctkově %ld - long %f - double - s desetinnou tečkou %g - double - s exponentem %s - char * - řetězec místo width a/nebo precision znak * hodnota následujícího parametru width: min. počet míst na výstupu precision: max. počet zpracovávaných znaků printf(":%c:%6d:%08X:%7.3f:%7.3f:\n", 'a', 17, 1234, , ); printf(":%s:%6s:%6s:%-6s:%-6.3s:%.3s:\n", "ahoj", "ahoj", "ahojbabi", "ahoj", "ahoj", "ahoj"); printf(":%*d:%-*.*s:\n", 6, 17, 6, 3, "ahoj"); printf(":%c:%c:%d:%s:\n", 0xffeedd41, "ahoj", "ahoj", 'a'); :a:▫▫▫▫17:000004D2:▫▫ :▫▫ : :ahoj:▫▫ahoj:ahojbabi:ahoj▫▫:aho▫▫▫:aho: :▫▫▫▫17:aho▫▫▫: :A:: : Unhandled exception at 0x c : Access violation reading location 0x
127
Direktivy preprocesoru
#include <stdio.h> #include <cstdio> #include <iostream> #include "mymodul.h" #define KZR #define KZR 17 #define KZR( pzr) ((pzr) * 2) #undef #ifdef #ifndef #if #else #endif # ## knihovní headery – C, C/C++, C++ uživatelské headery definice symbolu – viz Spojování modulů - #ifndef definice makra, lépe const int kzr = 17; definice parametrického makra raději vůbec nepoužívat, lépe inline funkce test na (ne)definovanost symbolu obrana před vícenásobným #include viz Spojování modulů - #ifndef test konstantního výrazu - #if sizeof( int) == 4 'ouvozovkování' - #abc "abc" spojení identifikátorů - a##b ab
128
Spojování modulů – problém hlaviček funkcí
x.c double A() { return B( 7); } y.c double B() { return 3.14; } error: Undefined 'B'
129
Spojování modulů – externí deklarace
x.c double B(); double A() { return B(); } y.c double B() { return 3.14; }
130
Spojování modulů – nekonzistence
x.c double B(); double A() { return B(); } y.c int B( int q) { return q+1; } nekonzistence funkce B (počet a typy parametrů, návratová hodnota) x.obj import B export A y.obj export B app.exe C: nedefinované chování C++: linker error
131
Spojování modulů – header
x.c #include "y.h" double A() { return B( 7); } y.h int B( int q); y.c int B( int q) { return q+1; } možná nekonzistence int B( int q); double A() { return B( 7); } preprocesor
132
Spojování modulů – řešení
x.c #include "y.h" double A() { return B( 7); } y.h int B( int q); y.c #include "y.h" double B() { return 3.14; } int B( int q); double A() { return B( 7); } int B( int q); double B() { return 3.14; } error: Redefinition of 'B'
133
Spojování modulů – definice dat
x.c #include "y.h" double A() {} y.h int c; y.c #include "y.h" int c; int c; double A() {} int c; int c; x.obj export c export A y.obj export c linker error: Duplicate symbol 'c'
134
Deklarace vs. definice x.c y.h y.c x.obj y.obj
#include "y.h" double A() {} y.h extern int c; y.c #include "y.h" int c; extern int c; double A() {} extern int c; int c; Definice Definice Deklarace Deklarace x.obj import c export A y.obj export c
135
Spojování modulů - typy
Definice nového typu Deklarace proměnné tohoto typu x.c #include "y.h" double A() { return C; } y.h enum T { P, Q}; extern enum T C; y.c #include "y.h" enum T C; enum T { P, Q}; extern enum T C; double A() { return C; } enum T { P, Q}; extern enum T C; enum T C;
136
Spojování modulů - duplicita typů
Přes y.h a z.h je t.h vložen dvakrát x.c #include "y.h" #include "z.h" double A() { return C+D; } t.h enum T { P, Q}; y.h #include "t.h" extern enum T C; error: Type redefinition: 'T' enum T { P, Q}; extern enum T C; enum T { P, Q}; extern enum T D; double A() { return C + D; } z.h #include "t.h" extern enum T D;
137
Spojování modulů - #ifndef
vlastní headery obkládejte ifndefy x.c #include "y.h" #include "z.h" double A() { return C+D; } t.h #ifndef T_H_ #define T_H_ enum T { P, Q}; #endif není-li symbol definován ... definice nového symbolu (makra) y.h #include "t.h" extern enum T C; #ifndef T_H_ #define T_H_ enum T { P, Q}; #endif extern enum T C; extern enum T D; symbol již definován z.h #include "t.h" extern enum T D; nepřekládá se
138
Odlišnosti mezi platformami
Vlastnosti hardware Velikost adresového prostoru a velikostí ukazatelů Pořadí ukládání vícebajtových hodnot (little/big endian) Dostupné formáty celých a reálných čísel Vlastnosti operačního systému Znaková sadou (ASCII/EBCDIC, Win/ISO), oddělovače řádků Konvence jmen souborů (oddělovače, povolené znaky, délka) Další vlastnosti souborového systému (sémantika delete, links, přístupová práva) Základní služby a konvence OS (environment, registry) Konvence (/usr/bin, .exe, $HOME) Vlastnosti překladače Volba velikosti základních aritmetických typů Způsob zarovnání položek struktur Rozpoznávaná pod-/nad-množinou jazyka Chyby v diagnostice a generovaném kódu Vlastnosti knihoven Dostupnost, pojmenování a sémantika funkcí
139
Přenositelnost mezi platformami
Zákaz konstrukcí závislých na vlastnostech hardware a překladače Užívání základních typů prostředníctvím typedef typedef unsigned long UINT32; Používání funkcí definovaných normou Nelze-li jinak, užívání direktiv #ifdef int x; char * p = (char *)&x; struct { char a; int b; } S; fwrite( &S, 1, sizeof( S), fp); #ifdef _MSC_VER // Microsoft typedef __int64 INT64; const char delimiter = '\\'; #else typedef long long INT64; #ifdef UNIX const char delimiter = '/'; #endif Ideál: Program přenositelný bez úpravy zdrojového textu
140
Vazba programu na operační systém
Proces je izolován od ostatních procesů a jádra OS Virtuální adresový prostor, ochrana paměti Přímá komunikace s jinými procesy by byla možná technikou sdílené paměti Není ovšem všude dostupná a standardizována Použití efektivní ale obtížné a nebezpečné Veškerá komunikace přes systémová volání Systémové volání zajišťuje: Přechod z uživatelského režimu do privilegovaného a zpět Možnost suspendování procesu uvnitř systémového volání Konkrétní technika systémového volání závisí na HW a OS Softwarové přerušení, brány, speciální volání, falešné výjimky Obvykle není možné volání přímo z C/C++ Relativně pomalé (změna kontextu, přeplánování) Množina systémových volání je definována OS Může být přímo zpřístupněna knihovnou (Unix, "io.h") Nemusí být zveřejněna, nebo pouze částečně (Microsoft)
141
Zveřejněné rozhraní operačního systému
"C-API" - zpřístupněno knihovnami Nejtypičtějsí část je standardizována Většina je závislá na OS Knihovní funkce obvykle provádějí více než jedno systémové volání Standardizované rozhraní služeb OS stdio.h - souborový vstup a výstup sjednocení přístupu k souborům a rourám - stdin, stdout, stderr buffering - snížení počtu systémových volání / následky při pádu programu - fflush textový/binární mód - překlad do jednotné formy - '\n' neřeší adresářové služby signal.h, stdlib.h - řízení procesu vyvolání/příjem signálu (Unix-like), ukončení procesu, system("winword my.doc")
142
Vlákna (threads) Pro realizaci serverů i některých GUI aplikací
Je-li třeba souběžně vykonávat více činností Nebo čekat na více událostí různých druhů Proces může mít více vláken (threads) Všechna vlákna žijí uvnitř společného adresového prostoru Každé vlákno má vlastní zásobník (a tedy jiný SP) První vlákno je spuštěno při spuštění procesu Další vlákna vznikají na pokyn již existujících vláken Vlákna běží kvazi-paralelně, na multiprocesorech paralelně (!) Všechny moderní OS vlákna podporují Způsoby implementace se liší Lze je implementovat i na úrovni knihoven bez vědomí OS Norma C ani C++ o vláknech nehovoří Neexistuje přenositelný způsob práce s vlákny Pokusy o unifikaci - nestandardních knihovny Programování s vlákny je obtížnější Nešikovná vzájemná komunikace vláken může zdržovat i zablokovat Využití vláken na multiprocesorech vyžaduje zvláštní opatrnost - Pozor na externí knihovny! Nutnost synchronizace práce se sdílenými datovými strukturami
143
Bezpečné programování
Zapnout všechna varování, která je schopen kompilátor vydat Upravit program do podoby, která nezpůsobí žádné varování V odůvodněných případech lze varování vypnout pomocí #pragma Dodržovat pravidla pro přenositelné a vícevláknové programy A to i když přenositelnost ani vícevláknovost zdánlivě nemá smysl Minimum globálních proměnných, pokud možno pouze konstantní Procedura smí číst či měnit pouze objekty, které jsou přímo či nepřímo určeny jejími parametry Důsledné užívání ochranných prostředků kompilátoru const, private, ... Důsledná chybová diagnostika Test úspěšnosti každého malloc, fopen, ... Testy proti interním chybám a špatným parametrům Ochrana proti užívání odalokovaných bloků Ochrana proti přetečením polí Všechny funkce manipulující s polem dostávají velikost pole jako parametr Žádné strcat, gets a podobné nebezpečné funkce void f( int* p) { assert( p); /*...*/ } free(p); p=0; Největší nepřítel je chyba, která není vždy a ihned smrtelná Dereference nulového ukazatele se pozná ihned Dereference neinicializovaného ukazatele způsobí pád při odevzdání zápočťáku
144
Udržovatelné zdrojové texty
Logické rozdělení do modulů a hlavičkových souborů na nižší úrovni datové struktury a funkce, třídy Jasné oddělení rozhraní od implementace Oddělení obsluhy uživatelského rozhraní od vlastní logiky aplikace "Vstup/výstup" "výpočet" Minimum globálních proměnných ideálně žádné, příp. jedna třída App Komentáře, zejména k rozhraním Indentace, omezená délka řádek Pojmenovávací konvence, logicky zvolené a dlouhé identifikátory Buď my_big_array nebo myBigArray, žádné pom1, pom2 Obvykle typy a konstanty začínají velkými písmeny, proměnné malými Pojmenování všech smysluplných konstant Žádné int x[ 100] ani case 27: Jaké jiné konstanty než smysluplné by měly být ve zdrojových textech? Nepoužívat deprecated features const a = 123; char *s = "abcd"; bool b; b++; správně const int a = 123; const char *s = "abcd"; (bool nelze inkrementovat)
145
Oblíbené chyby – struktura programu
Chybějící nebo přebývající středník: for (i=0; i<n; i++); { ... } funkce(); { ... } Přehozené parametry funkcí nebo části konstrukce: char text[20]; strcpy( "Chyba!", text); for( i=0; i++; i<20)... Nezapomínat na break v konstrukci switch Pozor na define (raději takhle vůbec nepoužívat): #define uint int* uint p1,p2; #define N 100; int delka=N+1; /* rozvine se: int delka=100;+1; */ #define square (x) x*x K čemu patří else? if(podmínka1) if(podmínka2) /* akce */ else /* jiná akce */
146
Oblíbené chyby – výrazy
Celočíselné dělení: x=1/3; Zaokrouhlovací chyby: for (x=0; x!=1.0; x+=0.1)... Odporující si parametry u printf: printf("%d",1.25); Záměna & | a && || a=1; b=2; if(a&b)... Zkrácené vyhodnocování logických výrazů Pozor na záměnu znaků: a=1; if (a<1,5)... Pozor na prioritu operátorů. Závorka navíc nikdy neuškodí. Nespoléhat na pořadí vyhodnocování (následující výstupy nejsou definovány): printf("%d %d",i++,i--); a[i++]=b[i++] může být přeloženo 3 způsoby if (0<x<1)...
147
Oblíbené chyby – ukazatele
Neinicializované proměnné, obzvlášť ukazatele: char *text; strcpy( text, "Chyba!"); Ukazatel na proměnnou, která už neexistuje: char text[20]; strcpy( text, ....); return text; Chybějící nebo naopak přebývající & u parametru předávaného ukazatelem: scanf( "%d %s", i, &text);
148
101 mouder Sutter, Alexandrescu: C++ 101 programovacích technik
(C++ Coding Standards) Organizační záležitosti 1. Kompilujte bez varování 2. Používejte automatizovaný systém kompilace 3. Používejte systém pro správu verzí 4. Investujte do čtení kódu Design Style 5. Jedna entita jedna zodpovědnost 6. Korektnost, jednoduchost a čitelnost především 7. Používejte škálovatelné algoritmy a konstrukce 8. Neoptimizujte předčasně 9. Neoptimizute pozdě 10. Minimalizujte lokální a sdílená data 11. Skrývejte informace 12. Naučte se správně využívat vlákna 13. Každý zdroj by měl patřit nějakému objektu Coding Style 14. Kompilační chyby jsou lepší než běhové 15. Používejte const 16. Vyhýbejte se makrům 17. Vyhýbejte se magickým číslům 18. Deklarujte proměnné s nejmenším možným rozsahem platnosti 19. Inicializujte proměnné 20. Vyhýbejte se dlouhým funkcím a přílišnému větvení 21. Vyhýbejte se inicializačním závislostem mezi moduly 22. Minimalizujte závislosti, vyhýbejte se cyklickým závislostem 23. Hlavičkové soubory by měly být samostatné 24. Používejte ochranu vícenásobného vkládání #include
149
Co (a proč ) dělá tento program?
#define _ F-->00||F-OO--; int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO() { _-_-_-_ _-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_ } Pozor! Je v čistém C, nikoliv v C++
150
C++ Part II - C++ Inheritance, Encapsulation, Polymorphism
Inheritance, Enkapsulace, Polymorfismus Dědičnost, Zapouzdření, ... Polymorfismus
151
Významné vlastnosti jazyka C++
Run-time: vlastnosti C++ s reprezentací za běhu programu dědičnost (inheritance) virtuální metody, polymorfismus, pozdní vazba (virtual functions) násobná dědičnost, virtuální dědičnost (multiple/virtual inheritance) zpracování výjimek (exception handling) typová informace za běhu (RTTI) Syntaxe: vlastnosti C++ bez reprezentace za běhu pomůcky (reference, implicitní parametry, přetěžování funkcí) pro zapomnětlivé (konstruktory, přesnější typová kontrola) proti šťouralům (zapouzdření, ochrana přístupu, prostory jmen) pro estéty (přetěžování funkcí a operátorů, uživatelské konverze) pro zobecnění (šablony) Knihovny proudy (streams) STL (Standard Template Library)
152
Třída a objekt Třída (class) Objekt (instance třídy)
Zobecnění pojmu struktura (struct) Rozdíl mezi class a struct v C++ je nepatrný Užívání class místo struct je pouze konvence Deklarace třídy obsahuje Deklarace datových položek (stejně jako u struktury) Funkce (metody), virtuální / statické funkce metody můžou přistupovat k datovým položkám Definice výčtových konstant a typů včetně vnořených tříd Objekt (instance třídy) Běhová reprezentace jednoho exempláře třídy Reprezentace objektu v paměti obsahuje Datové položky Skryté pomocné položky virtuálních metody, výjimky, RTTI, virtuální dědičnost class A { public: A() : x(0), y(0) {} void Set( int x, int y) { x_ = x; y_ = y; } int Prod() { return x_ * y_); } private: int x_, y_; }; A a; A * pa = &a; a.Set( 1, 2); cout << pa->Prod();
153
Princip a účel dědičnosti
Princip dědičnosti Třída (struktura) může mít jednoho (nebo více) předků relace předek-potomek je orientovaný acyklický graf není povinný společný prapředek pro všechny třídy Účel dědičnosti Lze napsat kód, který pracuje s daty společného předka několika tříd bez závislosti na tom, jaký je typ celého objektu tento kód lze přeložit bez znalosti potomků Reusabilita potomci třídy mají automaticky její datové položky a další vlastnosti a schopnosti Inherit to be reused, not to reuse Polymorfismus lze vytvořit strukturu sdružující objekty různých tříd, pokud mají společného předka
154
Implementace dědičnosti v C++
Je-li třída B (přímým či nepřímým) potomkem třídy A, pak: Paměťová reprezentace objektu typu B obsahuje část se shodným tvarem jako reprezentace samostatného objektu typu A Z každého ukazatele na typ B je možno odvodit ukazatel na část typu A konverze je implicitní, tj. není třeba ji explicitně uvádět ve zdrojovém kódu konverze může vyžadovat jednoduchý výpočet obvykle pouze při násobné dědičnosti přičtení posunutí Z ukazatele na typ A je možno odvodit ukazatel na typ B za těchto okolností: konkrétní objekt, na který ukazuje ukazatel na typ A, je typu B (nebo jeho potomka) zodpovědnost za ověření skutečného typu objektu má programátor konverzi je třeba explicitně vynutit přetypováním konverze může vyžadovat odečtení posunutí B A x y u
155
Dědičnost - reusabilita
x y pa class A { public: int x, y; }; int f( A * pa) { return pa->x * pa->y; } kód nic netušící o případných potomcích
156
Dědičnost - reusabilita
x y pa class A { public: int x, y; }; int f( A * pa) { return pa->x * pa->y; } class B : public A { int u; int g( B * pb) return pb->u * f( pb); B A x y u pb pa volání kódu pracujícího s předkem případné konverze zajístí kompilátor automaticky
157
Dědičnost - polymorfismus
enum Typ { T_B, T_C }; class A { public: Typ t; A * n; }; A * seznam; class B : public A { int x, y; class C : public A { int u; seznam B A t n x y C A t n u B A t n x y seznam objektů se společným předkem
158
Dědičnost - polymorfismus
enum Typ { T_B, T_C }; class A { public: Typ t; A * n; }; A * seznam; class B : public A { int x, y; class C : public A { int u; A * pa = seznam->n->n; if ( pa->t == T_B ) { B * pb = (B *)pa; } seznam B A t n x y C A t n u B A t n x y pb pa odkaz na objekt odvozené třídy zodpovědnost programátora za korektnost
159
Násobná dědičnost class A { int x; }; class B : public A { int y;
class C : public A { int z; } class D : public B, public C { int u; D * dp = ...; // A * ap = dp; chyba: nejednoznačnost A * ap = (B*)dp; // zjednoznačnění dp = (D*)(B*)ap; // explicitní přetypování A x B A x y C A x z D B C A A x y x z u
160
Virtuální dědičnost class A { int x; }; class B : virtual public A
{ int y; class C : virtual public A { int z; } class D : public B, public C { int u; D * dp; A * ap = dp; // OK // dp = (D*)ap; chyba: nelze najít A x B A y x C A z x D B C A y z u x
161
Názvosloví dědičnosti
Předek - potomek Parent - child Převládající názvosloví v češtině Základní - odvozená třída Base - derived class Lepší názvy, užívané normou C++ Univerzální - specializovaná třída Nejlépe odpovídá typickému použití dědičnosti Koliduje s pojmem specializace šablon Nepoužívá se
162
Užití dědičnosti Je-li třída B odvozena z třídy A, pak:
Každý objekt typu B má všechny součásti, vlastnosti a schopnosti typu A Objekt typu B má být použitelný kdekoliv, kde se používá typ A B je specializací A Zděděné součásti nelze odebrat Kluzák nemůže být potomkem motorového letadla Vlastnosti a schopnosti lze skrýt, překrýt či ignorovat Skrývání či ignorování je nesystematické a nebezpečné věznice nemá být potomkem obytného domu Překrývání jiným obsahem téže schopnosti může mít smysl auto na plyn tankuje jinak Is-A relationship jezevčík JE pes pes JE zvíře
163
Nesprávné užití dědičnosti - kompozice
Letadlo není potomkem svého motoru Důkaz: Co když má dva motory... Násobná dědičnost? Ne: Je třeba je odlišit Tlačítko není potomkem své ikony Důkaz: Co když má dvě... class Letadlo : public Motor {}; class Letadlo : public Motor, public Motor {}; class Letadlo { ... Motor motor[N]; }; class ObrazkoveTlacitko { ... Icon ico; }; Kompozice Skládání velkých objektů z malých C++: Třída s datovými položkami class ZamackavaciTlacitko { ... Icon icoOn; Icon icoOff; };
164
Nesprávné užití dědičnosti - delegace
Jezevčík umí vyhnat lišku z nory... Myslivec s jezevčíkem tedy také... Myslivec není potomkem svého jezevčíka Důkaz: Nežere granule... Jiný důkaz - veterinář... Kompozice? Ne: Nerodí se zároveň Tlačítko Start není potomkem Windows class JM1 : public Myslivec, public Jezevcik {}; class JM1 { ... Myslivec* mysl; Jezevcik* jez; }; Různé doby životnosti objektů pozor na platnost referencí ! Delegace Převedení funkčnosti na jiný objekt C++: Ukazatel
165
Nesprávné užití dědičnosti - abstraktní předek
Mlok není potomkem ryby a savce Důkaz: Nemá dvě hlavy... Virtuální dědičnost? Ne: Nekojí Abstraktní předek obratlovec Tlačítko není potomkem obdélníku a textu Abstraktní předek vizuální objekt class Mlok : public Ryba, public Savec {}; class Mlok : virtual public Ryba, virtual public Savec {}; class Obratlovec {}; class Ryba : public Obratlovec {}; class Savec class Obojzivelnik Společný abstraktní předek definuje společné vlastnosti, rozhraní
166
Ideální užití dědičnosti
Objekty: Jednoduchá dědičnost Postupná specializace tříd, reprezentujících objekty Živočich-obratlovec-obojživelník-mlok Rozhraní: Virtuální násobná dědičnost Kombinování protokolů (tříd, reprezentujících rozhraní) Sjednocení množin schopností Fyzikář+Matikář C++ pro oba odlišné účely využívá tytéž mechanismy Některé jazyky tyto účely rozlišují
167
Kompatibilita předka a potomka
Potomka lze přiřadit do předka (platí i pro ukazatele) Předka NELZE přiřadit do potomka (platí i pro ukazatele) pes umí jíst ... jako všechna zvířata brouk neumí štěkat ... nelze ho použít jako psa class Zvire {}; class Pes : public Zvire {}; Zvire pytlik, *pz; Pes azor, *pp; pytlik = azor; pz = &azor; Pozor na polymorfní datové struktury Slicing ! azor pytlik stav žaludek žaludek azor azor = pytlik; pp = &pytlik; pytlik stav žaludek žaludek nelze ! ???
168
Metoda (member function)
Metoda (member function) = funkce uvnitř třídy chráněné položky přístupné pouze metodám třídy veřejné položky přístupné komukoliv Každá metoda dostane 'tajný' parametr this - ukazatel na objekt class A { public: int x, y; }; int f( A * pa) { return pa->x * pa->y; } A oa; A * pa = & oa; int u = f( &oa); int v = f( pa); class A { private: int x, y; public: int f(); }; int A::f() { return x * y; } A oa; A * pa = & oa; int u = oa.f(); int v = pa->f(); deklarace (hlavička) metody A:: => skrytý parametr A* this implementace (tělo) metody this->x volání metody
169
Metoda - umístění funkce uvnitř třídy
Vyvolání na konkrétní instanci třídy Při volání funkce je určen objekt, na kterém je funkce vyvolána Ukazatel na tento objekt je funkci předán jako skrytý parametr Uvnitř funkce je tento objekt přístupný přes ukazatel this Datové položky tohoto objektu jsou přímo přístupné Zapouzdření a přístupová práva Identifikátor funkce je schován uvnitř třídy K přístupu k prvkům třídy stačí krátká jména Funkce může být chráněna proti volání zvenčí (private) Funkce může přistupovat k chráněným prvkům třídy (private)
170
Tělo metody uvnitř a vně třídy
Tělo uvnitř třídy Inline tělo vně třídy Ekvivalent těla uvnitř Kompilátor se pokusí rozvinout tělo Tělo v hlavičkovém souboru class A { public: int x, y; int f() { return x * y; } }; class A { public: int x, y; int f(); }; inline int A::f() { return x * y; } inline: kompilátor se pokusít tělo funkce rozvinout v místě volání inline: kompilátor se pokusít tělo funkce rozvinout v místě volání Tělo vně třídy Ekvivalent externí deklarace a definice globální funkce Tělo v .cpp souboru .h class A { public: int x, y; int f(); }; .cpp int A::f() { return x * y; }
171
Konstantní metody konstantní metoda
nemodifikuje objekt, na kterém je vyvolána lze aplikovat na konstantní objekt class A { public: int x, y; int f() const { return x * y; } }; const A * pa; ... pa->f(); class A { public: int x, y; }; int f( const A * pa) { return x * y; } const A* this const A* this
172
Metody základních a odvozených tříd
class A { private: int x, y; public: int f() { return x * y; } }; class B : public A { int z; int g() { return f() * z; } B ob; A * pa = & ob; B * pb = & ob; int u = pa->f(); int v = pb->f(); int w = pb->g(); A::f() automatická konverze na ukazatel na předka volání metody základní třídy volání metody odvozené třídy
173
Zakrývání metod – compile time binding
class A { private: int x, y; public: int f() { return x * y; } }; class B : public A { int z; int f() { return z; } B ob; A * pa = & ob; B * pb = & ob; pa->f(); // A::f pb->f(); // B::f pb->A::f(); // A::f pb->B::f(); // B::f pa->A::f(); // A::f // pa->B::f(); nelze stejná metoda jako předka - zakrývání volání odpovídající metody podle typu objektu kvalifikované volání – explicitně určím která metoda se má volat nelze (takhle) kvalifikovaně volat funkce odvozených tříd
174
Volá se stále stejná funkce int fce( int, int, int)
Implicitní parametry Některé parametry funkce mohou mít implicitní hodnoty pokud nejsou všechny parametry implicitní – implicitní parametry odzadu Při volání funkce lze implicitní parametry vynechat použije se implicitní hodnota Definují se u hlavičky funkce U těla funkce se neopakují Implicitní hodnoty řeší kompilátor pouze na straně volání Volaná funkce nezjistí, zda jsou hodnoty parametrů určeny explicitně nebo implicitně int fce( int a, int b = 2, int c = 4) { return 2*a + b - c; } fce( 1); // int fce( 1, 2, 4) fce( 1, 5); // int fce( 1, 5, 4) fce( 1, 5, 6); // int fce( 1, 5, 6) Volá se stále stejná funkce int fce( int, int, int)
175
Přetěžování funkcí C++ dovoluje existenci více funkcí téhož jména ve stejné oblasti platnosti Podmínkou je odlišnost v počtu a/nebo typech parametrů Odlišnost typu návratové hodnoty nestačí Při volání funkce se konkrétní varianta určuje takto: Vyberou se vhodné varianty funkce podle počtu a typu skutečných parametrů Určí se ceny typových konverzí parametrů, zjednodušeně: Konverze non-const -> const / typ <-> reference jsou nejlevnější Konverze potomek -> předek / aritmetická konverze na větší typ Uživatelská konverze / ztrátová aritmetická konverze jsou nejdražší Vybere se nejlacinější aplikovatelná varianta Pokud je jich více, kompilátor ohlásí chybu int pocitej( int x); int pocitej( int a, int b); int pocitej( int a, const char* s);
176
Přetěžování funkcí Funkce je definována svým identifikátorem a počtem a typem parametrů int pocitej( int x) { return x + 1; } int pocitej( int a, int b) return 2 * a + b; int pocitej( int a, const char* s) return a + strlen( s); pocitej( 1); // int pocitej( int) pocitej( 1, 2); // int pocitej( int, int) pocitej( 1, "ahoj"); // int pocitej( int, char*) Funkce se stejným identifikátorem ale různým počtem parametrů Funkce se stejným počtem ale různým typem parametrů Správná funkce podle počtu a typů skutečných parametrů
177
obě varianty stejně drahé
Přetěžování funkcí int min( int a, int b) { return a < b ? a : b; } double min( double a, double b) min( 1, 2); // min( int, int) min( 1, 2.0); // min( double, double) min( 1.0, 2); // min( double, double) min( 1.0, 2.0); // min( double, double) void f( int, double); void f( double, int); f( 1, 2); // chyba f( 1.0, 2.0); // chyba přesná shoda levnější varianta přesná shoda obě varianty stejně drahé
178
Přetěžování vs. implicitní parametry
Kdy použít přetěžování a kdy implicitní parametry? Implicitní parametry Stejný kód pro různý počet parametrů Místo chybějících parametrů se dosadí implicitní hodnoty Přetěžování Pro různé počty nebo typy parametrů různý kód
179
Reference C++ definuje vedle deklarátoru ukazatele * deklaráror reference & z hlediska přeloženého kódu jsou oba deklarátory ekvivalentní ukazatel i reference na objekt jsou reprezentovány adresou objektu odlišné je jejich používání ve zdrojovém textu Výraz typu reference má hodnotu objektu, na nějž reference odkazuje tento objekt se určuje při inicializaci reference odkaz na něj trvá po dobu platnosti referenční proměnné (nebo objektu, jehož je reference součástí) identita reference a referencovaného objektu implicitní typová konverze T <=> T & int & ref = data; ref = 17; void f( int & u) { u = 1; } int a = 0; f(a); a: 1 u: ref: 17 :data
180
Reference int data; int * ukazatel = & data;
data2 = * ukazatel; // data2 = data ukazatel = & data2; void f( int * u) { * u = 2; } f( & data); int * g() { return & data; * g() = 3; // data = 3 int data; int & reference = data; reference = 2; // data = 2 int data2; data2 = reference; // data2 = data // NELZE: & reference = & data2; void f( int & u) { u = 2; } f( data); int & g() { return data; g() = 3; // data = 3
181
Reference souvislosti s existencí reference
Inicializaci reference nelze nahradit přiřazením Nelze rozlišit parametry předávané hodnotou a odkazem Návratová hodnota funkce může být l-hodnota Zvýšené nebezpečí nekorektních konstrukcí Nulovou referenci je obtížnější vytvořit i testovat int f( int a, int& b) {...} int x = 1, y = 2; f(x,y); int & f() { int x; return x; } int& f(int& b) { ++b; return b; } int x = 1; f(x) = 2; int * p = 0; int & r = * p; if ( ! & r ) // ...
182
Přetěžování operátorů
class bod { private: int x, y; public: … bod operator+( const bod&); bod& operator=( const bod&); }; bod a, b, c; c = a + b; přetížení operátoru + a + b a.operator+(b) a = b a.operator=(b) bod& bod::operator=( const bod& b) { x = b.x; y = b.y; return *this; } bod bod::operator+( const bod& b) return bod( x+b.x, y+b.y); c.operator=( a.operator+( b)); c.assign( a.add( b)); Těla metod
183
Přetěžování operátorů - pravidla
Většinu operátorů jazyka C++ lze definovat pro uživatelské datové typy Nelze předefinovat tyto operátory: . .* :: ? : sizeof Alespoň jeden z operandů musí být třída nebo výčtový typ nebo reference na ně Nelze předefinovat operace na číselných typech a ukazatelích Předefinováním nelze měnit prioritu a asociativitu operátorů Pro předefinované operátory nemusí platit identity definované pro základní typy ++a nemusí být ekvivalentní a=a+1 a[b] nemusí být ekvivalentní *(a+b) ani b[a] je však velmi doporučeno dodržovat běžnou sémantiku Pro předefinované operátory && a || neplatí pravidla o zkráceném vyhodnocování Typy skutečných operandů nemusejí přesně odpovídat typům formálních parametrů stejná pravidla jako pro přetížené funkce
184
Přetěžování operátorů - pravidla
Dva způsoby definice přetíženého operátoru - symbol příslušného operátoru) metoda se jménem ve třídě prvního operandu globální funkce se jménem globální funkce: např. při vyžadované spolupráci se základními typy Operátory, které jsou metodami, jsou s výjimkou operátoru přiřazení dědičné a mohou být virtuální class bod { bod operator+( const bod&); }; Bod operator+( const bod&, const bod&); class DlouhyInt { ... DlouhyInt operator+( DlouhyInt&); }; DlouhyInt a; a = 1 + a;
185
Přetěžování operátorů - binární operátory
běžné binární operátory: + - * / % << >> < > <= >= <<= >>= ^ & | && || == != += -= *= /= %= ^= &= |= ->* lze předefinovat jako globální funkce nebo metody binární operátor [ ] lze předefinovat pouze metodou A B, C) A B &, C &) A const B &, const C &) A C) A const C &) A const C &) const není zde operator= speciální metoda třídy A B::operator []( C) A B::operator []( C &) A B::operator []( const C &) const speciální operátory -> a () později
186
Přetěžování operátorů - unární operátory
a prefixové operátory ++ -- lze předefinovat jako globální funkce nebo metody postfixové operátory ++ a -- ++X A B) A B &) A const B &) A A const pro některé operátory (např. ++) netypické X++ A B, int) A B &, int) A const B &, int) A int) A int) const fiktivní operand typu int
187
Konstruktory a destruktory
Konstruktor třídy XXX je metoda se jménem XXX Typ návratové hodnoty se neurčuje Konstruktorů může být více, liší se parametry Nesmí být virtuální Konstruktor je volán vždy, když vzniká objekt typu XXX Parametry se zadávají při vzniku objektu Některé z konstruktorů mají speciální význam default constructor, copy constructor Některé z konstruktorů může generovat sám kompilátor Konstruktor nelze vyvolat přímo Destruktor třídy je metoda se jménem ~XXX Nesmí mít parametry ani návratovou hodnotu Může být virtuální Destruktor je volán vždy, když zaniká objekt typu XXX Destruktor může generovat sám kompilátor Destruktor lze vyvolat přímo pouze speciální syntaxí class A { private: int x; public: A() {} A( int x_) {} ~A() {} };
188
Konstruktor, seznam inicializátorů
zavolají se konstruktory složek s parametry dle seznamu inicializátorů volání konstruktorů je podle pořadí v deklaraci třídy, ne podle pořadí inicializátorů na složky, které nejsou v seznamu inicializátorů, se použije implicitní konstruktor nakonec se zavolá tělo konstruktoru povinné použití seznam inicializátorů: inicializace const položek inicializace referencí předávání argumentů konstruktorům předka nebo vloženého objektu class A { private: int x; int y; public: A( int x_, int y_) { x = x_; y = y_; } A( int x_, int y_) : x(x_), y(y_) {} }; seznam inicializátorů položek member initializer list vždy preferujte seznam inicializátorů parametrický konstruktor vs. implicitní konstruktor + přiřazení
189
Implicitní a kopírovací konstruktor
Implicitní konstruktor (bez parametrů, default constructor) Proměnné bez inicializace, dynamická alokace polí Pokud třída nemá žádný konstruktor, kompilátor se jej pokusí vygenerovat Položky, které nejsou třídami, nejsou generovaným konstruktorem inicializovány Generovaný konstruktor volá konstruktor bez parametrů na všechny předky a položky Kopírovací konstruktor (copy constructor) Používán pro předávání parametrů a návratových hodnot Pokud třída kopírovací konstruktor nemá, kompilátor se jej pokusí vygenerovat Položky, které nejsou třídami (POD), jsou kopírovány Na předky a položky se volá kopírovací konstruktor To nemusí jít kvůli ochraně přístupu (konstruktor v sekci private) Kopírovací konstruktor a operátor přiřazení vždy spolu ... podrobněji později, teď jen varování class A { A(); } class A { A(const A&); } A f(A a) { return a; } A b; b = f(b);
190
Konstruktory implicitní konstruktor default constructor
class A { private: int x; int y; public: A() : x(-1), y(-1) {} A( int x_, int y_) : x(x_), y(y_) {} A( const A& a) : x(a.x), y(a.y) {} }; A a1; // A::A() A a2( 1, 2); // A::A(int,int) A a3( a2); // A::A(const A&) A a4 = a2; // A::A(const A&) A* pa; pa = new A; pa = new A( 2,3); pa = new A( a4); pa = new A[ 100]; implicitní konstruktor default constructor copy (kopírovací) konstruktor vytvoří objekt jako kopii jiného různé zápisy použití copy konstruktoru Pro U≠T není ekvivalentní: U u; T t(u); // T::T( U&) T t = u; // T::T( T(u)) nebo // T::T( u.operator T()) alokace pole - pouze implicitní konstruktor
191
Konstruktory a dědičnost
implicitní konstruktor předka class A { private: int x; int y; public: A() : x(-1), y(-1) {} A( int x_, int y_) : x(x_), y(y_) {} A( const A& a) : x(a.x), y(a.y) {} }; class B : public A { private: int z; public: B() : z(-1) {}; B( int x_, int y_, int z_) : A(x_,y_), z(z_) {} B( const B& b) : A(b), z(b.z) {} }; B b( 1, 2, 3); // B::B(int,int,int) A a(b1); // A::A(const A&) // B ba(a); // error A* pa = new B; // B::B() // B* pb = new A;// error neimplicitní konstruktor předka pouze přes seznam inicializátorů dědičnost – na místě předka mohu použít potomka před vstupem do těla konstruktoru potomka se provedou kostruktory předků a položek Konstrukce součástí se děje před konstrukcí celku, destrukce součástí po destrukci celku
192
Vznik a zánik objektů – lokální proměnné
kostruktor je vyvolán při průchodu deklarací při zániku lokálního objektu (opuštění bloku s deklarací jakýmkoli způsobem) je vyvolán destruktor deklarace může být kdekoliv uvnitř funkce rozsah platnosti od místa deklarace po konec složeného příkazu skoky, které by vstupovaly do bloku a obcházely deklaraci, jsou zakázány void f( void) { int x = 1; A a; if( x) { B b; x = 2; } if( !x) return; x = 3; A::A B::B B::~B A::~A A::~A
193
Vznik a zánik objektů – parametry předávané hodnotou
copy konstruktor, konstruktor s jedním parametrem konstruktor je volán na místě volání funkce před jejím zavoláním kompilátor dokáže tento konstruktor vytvořit automaticky destruktor objektu je vyvolán před návratem z funkce void f( A a) { } // A::~A void g() { A a; B b; // class B : public A f( a); // A::A( const A &) f( b); // A::A( const A &) f( 1); // lze pokud ex. A( int) } Na místě předka můžu mít potomka
194
Vznik a zánik objektů – globální proměnné
Globální a statické proměnné pro každý globální objekt je vyvolán konstruktor před vstupem do funkce main pro každý lokální statický objekt je vyvolán konstruktor nejpozději při průchodu deklarací po opuštění main (nebo po zavolání exit) je pro každý globální objekt vyvolán destruktor pořadí vyvolávání je implementačně závislé ! A a( 1), b; A d( a); f() { static A e( 1); } int main() { f(); při vyvolání konstruktoru d nemusí být a inicializováno! možné řešení lazy inicializace
195
Vznik a zánik objektů – dynamicky alokované objekty
pro dynamickou alokaci slouží operátory new a delete funkce malloc a free s nimi nelze kombinovat v C++ nepoužívat! operátor new alokuje pro objekt paměť, vyvolává konstruktor dle parametrů a vrací ukazatel na vytvořený objekt pokud se (nedostatek paměti) alokace nezdaří: vyvolá se výjimka std::bad_alloc (C++ 98 a novější) starší C++: výjimka se nevyvolá, operátor vrací nulový ukazatel vrácení nulového ukazatele při nezdaru lze vynutit: new( nothrow ) A operátor delete vyvolává destruktor objektu a poté dealokuje paměť je odolný proti nulovým ukazatelům parametr delete POUZE alokovaný a doposud neodalokovaný objekt A *p, *q; p = new A; q = new A( *p); /* ... */ delete p; delete q; A *p, **q; p = new A[ 20]; q = new A*[ 20]; /* ... */ delete[] p; delete[] q; A::A() A::A( const A&) pole ukazatelů
196
Vznik a zánik objektů – dočasné objekty
užití jména třídy jako jména funkce v operátoru volání způsobí: vyhrazení místa pro objekt na zásobníku mezi okolními lokálními proměnnými vyvolání konstruktoru s patřičnými parametry použití objektu jako hodnoty v okolním výraze vyvolání destruktoru nejpozději na konci příkazu konstruktor s jedním parametrem je konverzní konstruktor lze použít jako typovou konverzi (function-style cast) konstruktor, kopie, destruktor A a, b; a = A(); b = A( 1); return A( 2); použití jako návratové hodnoty konverzní konstruktor z int vyrobí A
197
Vznik a zánik objektů – příklad
A f( A y) { return y; } A p, q; p = f( q); kolik konstruktorů a jakých a kolik destruktorů vyvolá zpracování této řádky?
198
Vznik a zánik objektů – příklad
A f( A y) { return y; } A p, q; p = f( q); kolik konstruktorů a jakých a kolik destruktorů vyvolá zpracování této řádky? odpověď: to nikdo neví proč? norma C++: kompilátor může ale nemusí vytváření objektů optimalizovat vždy ale platí: pokud se zavolá konstruktor, vždy se zavolá i destruktor jiná otázka: jak to tedy může být v nejhorším a v optimalizovaném případě?
199
Vznik a zánik objektů – hloupý kompilátor
2. y temp1 'spočítá' se hodnota výrazu pro return a vytvoří se dočasný objekt copy konstruktor A f( A y) { return y; } A p, q; p = f( q); 3. temp1 temp2 předání návratové hodnoty ven z funkce copy konstruktor 1. q y předání hodnoty skutečného parametru do funkce copy konstruktor 0. pro návrat z funkce se alokuje místo pro dočasnou proměnnou temp2 4. temp2 p přiřazení návratové hodnoty funkce proměnné operátor přiřazení
200
Vznik a zánik objektů – chytrý kompilátor
2. y temp 'spočítá' se hodnota výrazu pro return a vytvoří se dočasný objekt copy konstruktor A f( A y) { return y; } A p, q; p = f( q); 1. q y předání hodnoty skutečného parametru do funkce copy konstruktor 4. temp p přiřazení návratové hodnoty funkce proměnné operátor přiřazení
201
Polymorfismus zvíře pes pitbul člověk
odlišné chování potomků – pozdní vazba (late binding) zvíře pes pitbul jez jez jez sní maso sní hodně masa najde něco v přírodě člověk jez jde do restaurace
202
Polymorfismus - motivace
Tohle není polymorfismus ! Funkce je známa v době překladu class Zvire { jez() { priroda(); } }; class Pes : public Zvire { jez() { maso(1); } class Pitbul : public Pes { jez() { maso(10); } class Clovek : public Zvire { jez() { hospoda(); } Zvire pytlik; Pes punta; Pitbul zorro; Clovek pepa; pytlik.jez(); // priroda(); punta.jez(); // maso(1); zorro.jez(); // maso(10); pepa.jez(); // hospoda(); 'normální' vlastnost tříd zakrývání metod Při překladu je známo ze které třídy se volá metoda Každá třída má vlastní implementaci (tělo) metody jez
203
Polymorfismus – pozdní vazba
chtěl bych, aby se volaly 'správné' metody Zvire* rodina[4]; rodina[0] = new Clovek; rodina[1] = new Pes; rodina[2] = new Pitbul; rodina[3] = new Zvire; for( int i = 0; i < 4; ++i) rodina[i]->jez(); Pozdní vazba (late binding; virtual call) které tělo bude zavoláno se rozhoduje až za běhu programu podle skutečného typu celého objektu použije se tělo skutečného objektu, na jehož součást ukazuje ukazatel přesněji: použije se tělo z posledního potomka, který definuje tuto funkci a je součástí celého objektu Má smysl pouze u vyvolání na objektu určeném odkazem Chci pokaždé se zavolat jinou metodu Rozlišení metody se musí dít za běhu
204
Virtuální metody - deklarace
class Zvire { virtual jez() { priroda(); } }; class Pes : public Zvire { virtual jez() { maso(1); } class Pitbul : public Pes { virtual jez() { maso(10); } class Clovek : public Zvire { virtual jez() { hospoda(); } magické klíčové slovo virtual každý objekt si s sebou nese informaci kterou virtuální funkci používá
205
Virtuální metody - implementace
Princip implementace každý objekt obsahuje ukazatel na tabulku virtuálních funkcí tabulka ukazatelů na funkce při volání metody se volá tělo funkce z této tabulky class A { public: int x; virtual int f() { return 1; } virtual int g() { return 2; } }; class B : public A { int y; virtual int f() { return 9; } A* paa = new A; A* pab = new B; B* pbb = new B; paa->f(); paa->g(); // 1 2 pab->f(); pab->g(); // 9 2 pbb->f(); pbb->g(); // 9 2 A x f A::f g A::g B A x y f B::f g
206
Virtuální metody - implementace
class A { public: int x; virtual int f() { return 1; } virtual int g() { return 2; } }; class B : public A { int y; virtual int f() { return 9; } A a; B b; A* pa = &b; pa->f(); int A_f( struct A* th) { return 1; } int A_g( struct A* th) { return 2; } struct A { int (*pf)( A*); int (*pg)( A*); int x; int f() { return pf( this); } int g() { return pg( this); } A() { pf = A_f; pg = A_g; } }; int B_f( struct B* th) { return 9; } struct B : public A { int y; B() { pf = (int(*)(A*))B_f; } A a; B b; A* pa = &b; pa->f();
207
Volání virtuálních metod
class A { public: virtual f(); }; class B : public A { A a; B b; A * paa = &a; A * pab = &b; B * pbb = &b; // B * pba = &a; nelze! zakrývání metod a.f(); // A::f b.f(); // B::f paa->f(); // A::f pab->f(); // B::f pbb->f(); // B::f b.A::f(); // A::f b.B::f(); // B::f a.B::f(); // NE! paa->A::f(); // A::f pab->A::f(); // A::f pab->B::f(); // NE! pbb->A::f(); // A::f pbb->B::f(); // B::f pozdní vazba paa kvalifikované volání a A::f pab b B::f pbb
208
Virtuální metody a konstruktory a destruktory
v konstruktoru a destruktoru se vždy volá metoda vytvářeného/rušeného objektu nejdřív se zavolá konstruktor předka class A { public: virtual f(); A() { f(); } // A::f ~A() { f(); } // A::f g() { f(); } // A/B::f }; class B : public A { public: virtual f(); B() { f(); } // A::A B::f ~B() { f(); } // B::f A::~A g() { f(); } // B::f }; určí se za běhu podle skutečného typu objektu nejdřív se provede kód destruktoru, pak se zavolá destruktor předka Konstruktor provádí i vyplnění skrytých položek třídy, zajišťujících funkci virtuálních metod. Toto vyplňování se děje až po konstrukci součástí třídy, což znamená, že v době vyvolání konstruktoru součásti se virtuální funkce vyvolávají tak, jako by tato součást byla celkem. Analogický mechanismus funguje i při destrukci objektů.
209
Dědičnost a destruktory
int x=0; int ya=0; int yb=0; class A { public: virtual f() { x=1; ya=1; } ~A() { f(); } // A::f }; class B : public A { virtual f() { x=2; yb=2; } ~B() { f(); } // B::f A::~A A * pa = new B; delete pa; co udělá tohle? x=1 ya=1 ya=2 ? x=1 ya=1 ya=0 ? něco jiného?
210
Dědičnost a nevirtuální destruktory – POZOR!
int x=0; int ya=0; int yb=0; class A { public: virtual f() { x=1; ya=1; } ~A() { f(); } // A::f }; class B : public A { virtual f() { x=2; yb=2; } ~B() { f(); } // B::f A::~A A * pa = new B; delete pa; co udělá tohle? zformátuje disk pošle zdrojáky mailem zaviruje počítač ... cokoliv! delete objektu odvozené třídy přes ukazatel na základní třídu s nevirtuálním destruktorem je NEDEFINOVÁN
211
Virtuální destruktory
Má-li být třída předkem určeným pro ukazatele na dynamicky vytvářené potomky, musí mít virtuální destruktor. int x=0; int ya=0; int yb=0; class A { public: virtual f() { x=1; ya=1; } virtual ~A() { f(); } // A::f }; class B : public A { virtual f() { x=2; yb=2; } virtual ~B() { f(); } // B::f A::~A A * pa = new B; delete pa; // x=1 ya=1 yb=2 nejdříve se provede tělo ~B, potom se zavolá ~A
212
Čistě virtuální metody, abstraktní třída
Čistě virtuální metoda Deklarována bez definování těla Je možné ji volat Tělo bude doplněno později u potomka Abstraktní třída Třída obsahující nějaké čistě virtuální metody (přímo či v předcích), jejichž tělo ještě nebylo definováno Nelze instanciovat Lze používat ukazatele na tuto třídu a vyvolávat metody této třídy Často neobsahuje žádné datové položky - definice rozhraní class A { public: virtual int f() = 0; }; class B : public A { virtual int f() { return 9; } A f B A f B::f
213
Čistě virtuální metody, abstraktní třída
nelze vytvořit objekt společný předek int armada; class Vojak { public: enum THod { vojin, desatnik, porucik, general }; Vojak( THod hod = vojin) { hodnost=hod; armada++; } virtual void pal() = 0; virtual ~Vojak() { armada--; } private: THod hodnost; }; pure virtual function abstraktní třída společné rozhraní class Samopal {}; class Kalasnikov : public Samopal {}; class Pesak : public Vojak { private: Samopal* sam; public: Pesak( THod hod=vojin) : Vojak( hod) { sam = new Kalasnikov; } virtual void pal() { sam->pal(); } virtual ~Pesak() { delete sam; } }; Nutný virtuální destruktor
214
Statická data Statická data class variables (proměnné třídy)
pouze jedna kopie bez ohledu na počet instancí objekty statická data sdílejí není součástí instancí (objektů) nutnost definice! časté použití: konstanty třídy jednoduché konstanty nemusí mít definici strukturované konstanty mít definici musí např. řetězce potřebuje paměť potřebuje definici statická konstanta class A { private: int x; static int c; static char* dny[]; static const int MAX = 100; public: int f() { return x + c + MAX; } }; int A::c; const char* A::dny[] = { "Po", "Ut", ... }; A a; a.f(); nekonstantní statická proměnná strukturovaná statická konstanta
215
Statické metody Statické metody nedostávají automatický parametr this
protože nepracují s žádnou konkrétní instancí mohou přímo přistupovat pouze k statickým datům k nestatickým datům mohou přistupovat přes specifikovaný objekt mohou přistupovat k private položkám nemohou být virtuální lze je volat jako metody nebo jako globální funkce a.g() A::g() class A { private: int x; static int y; public: int f() { return x + y; } static int g() { return y; } static int h( A* a) { return a->x; } }; A a; a.f(); a.g(); A::g(); A::h(&a);
216
Přehled druhů metod metoda member function virtuální metoda
class A { int f( int a, int b); virtual int f( int a, int b); virtual int f( int a, int b) = 0; static int f( int a, int b); } virtuální metoda virtual function čistě virtuální metoda pure-virtual function statická metoda static member function
217
Ukládací třídy (storage classes)
data globální uvnitř funkce uvnitř třídy bez specifikace statická alokace, export mimo modul = auto součást každé instance třídy auto zakázáno zásobník, případně registr register zásobník, přednostně v registru static statická alokace, bez exportu statická alokace (jediná instance) statická alokace (jediná instance), export extern odkaz na globální definici, bez alokace funkce globální uvnitř funkce uvnitř třídy bez spec. export mimo modul zakázáno metoda - implicitní this virtual virtuální metoda static bez exportu mimo modul bez this, export
218
jediný rozdíl mezi struct a class
Ochrana přístupu Položky tříd jsou z hlediska ochrany přístupu rozděleny na public: veřejně přístupné položky protected: položky přístupné metodám třídy a metodám tříd odvozených private: položky přístupné pouze metodám této třídy Implicitní nastavení class: private struct, union: public Při dědění je možno přístupová práva dále omezit public inheritance: práva zůstávají beze změn private inheritance: všechny položky předka jsou v nové třídě privátní jediný rozdíl mezi struct a class
219
private inheritance public inheritance - Is-A private inheritace
potomek JE vše, co předek private inheritace potomek NENÍ vše, co předek metody předka jsou private ..... co to teda je??? class Osoba { void jist(); }; class Student : public Osoba { void studovat(); }; Osoba o; Student s; s.jist(); // Student JE Osoba // o.studovat(); // Osoba NENÍ Student class Osoba { void jist(); }; class Student : Osoba { void studovat(); }; Osoba o; Student s; // s.jist(); // Student NENÍ Osoba // o.studovat(); // Osoba NENÍ Student
220
private inheritance private inheritace ' is-implemented-in-terms-of '
odvozená třída může využít kód základní třídy dědičnost implementace, nikoliv rozhraní položka třídy (containment, layering) vs. private inheritance vždy, pokud to lze, používejte containment private inheritance používejte pokud to jinak nelze class A { f(); }; class B : private A { public: g() { A::f(); } }; class A { f(); }; class B { private: A a; public: g() { a.f(); } };
221
private inheritance & protected
úkol: obecný zásobník, zásobníky konkrétních typů, ochrana nevýhody: neexistuje ochrana, kdokoliv může přistupovat k Stack a void* class Stack { // implem. class public: Stack(); ~Stack(); void push( void* object); void* pop(); private: struct Node { void* data; Node* next; }; Node* top; Stack( const Stack&); Stack operator=( const Stack&); class IntStack { // interface class public: void push( int* p) { s.push(p); } int* pop() { return (int*)s.pop(); } private: Stack s; }; class Slon {}; class SlonStack { void push( Slon* p) { s.push(p); } Slon* pop() { return (Slon*)s.pop(); }
222
private inheritance & protected
private inheritace, protected constructors & methods výhody: Stack nelze zkonstruovat ani volat jeho metody každý musí použít iterface class class Stack { protected: Stack(); ~Stack(); void push( void* object); void* pop(); private: .... }; class IntStack : private Stack { public: void push( int* p) { Stack::push(p); } int* pop() { return (int*)Stack::pop(); } }; class Slon {}; class SlonStack : private Stack { void push( Slon* p) {push(p); } Slon* pop() { return (Slon*)Stack::pop(); }
223
Spřátelené funkce a třídy - friend
friend funkce a třídy možnost přístupu k private položkám třídy možno použít na třídy nebo funkce nepříliš bezpečné - používat s rozmyslem friend funkce nebo třída závisí na implementaci třídy ... a když už používat, tak jen v dobře odůvodněných případech např. operator overloading, různé úrovně ochrany přístupu class X { private: int x; public: friend class Y; friend int patchX( X& x); }; class Y { int patchX( X& x) { return x.x = 1; } int patchX( X& x) { return x.x = -1; } spřátelená třída spřátelená funkce Pozor! Nejde o metodu X!
224
Symetrický operátor - metoda
class complx { private: int re, im; public: complx( int _re = 0, int _im = 0) { re = _re; im = _im; } complx operator+( const complx& b) const { return complx( re + b.re, im + b.im); } }; complx x(1,2); complx y; y = x + 1; // y = 1 + x; error: binary '+' : no global operator found konverze: konstruktor cmplx(1,0)
225
Symetrický operátor - globální funkce
class complx { private: int re, im; public: complx( int _re = 0, int _im = 0) { re = _re; im = _im; } friend complx operator+( const complx& a, const complx& b) { return complx( a.re + b.re, a.im + b.im); } }; complx x(1,2); complx y; y = x + 1; y = 1 + x; Symetrické operátory je vhodné implementovat jeko friend globální funkce
226
Konverzní operátor uživatelsky definovaná konverze jednoho typu na jiný syntaxe: operator <typ>(); operator int(); operator char*(); nemá žádné parametry konvertovanou hodnotou je objekt, na který je operátor vyvolán nemá návratový typ typ je jednoznačně určen samotným operátorem užitečné např. pro kombinaci C a C++ kódu string vs. char* class complx { private: int re, im; public: complx( int _re = 0, int _im = 0) { re = _re; im = _im; } operator int() { return (re + im) / 2; } }; complx x(3); // complx( 3, 0); int i(x); // complx::operator int()
227
Konverzní operátor Pozor! Konverzní operátory mohou kolidovat s jinými způsoby konverze konverzní konstruktory, integrální promoce, built-in operátory ... používat s rozmyslem! class complx { private: int re, im; public: complx( int _re = 0, int _im = 0) { re = _re; im = _im; } friend complx operator+( const complx& a, const complx& b) { return complx( a.re + b.re, a.im + b.im); } operator int() { return (re + im) / 2; } }; complx x(1,2); x = x + 1; // error: more similar conversions complx::complx( int _re, int = 0) complx operator +(const complx &,const complx &) complx::operator int() operator+(int, int)
228
Prostory jmen (namespaces)
zapouzdření identifikátorů prevence kolizí (velké projekty, knihovny) stejné identifikátory v různých prostorech jmen prostor jmen se může opakovaně otevírat a zavírat standardní knihovny – namespace std Později: argument dependent lookup Koenig lookup definice prostoru jmen namespace aa { int p; int f1( int x) { return x + p; } } void f2( int x, int y); using namespace std; int aa::f2( int x, int y) { cout << p * (x + y); using aa::f2; aa::f1( f2( 5, 6)); přístup ze stejného prostoru znovuotevření prostoru aa direktiva using - rozbalení std definice funkce mimo prostor jmen deklarace using - pouze ident.
229
Příklad - polymorfní datové struktury
Zadání: kontejner obsahující čísla libovolného typu (int, double, řetězec, complex, ...) Technické upřesnění: třída Seznam operace append, print společný předek prvků AbstractNum konkrétní prvky IntNum, DoubleNum, ... stačí jednoduchá implementace polem pole objektů vs. pole odkazů S IN DN IN AN AN AN x d x
230
Polymorfní datové struktury - kostra tříd
class AbstractNum { public: virtual void print()=0; virtual ~AbstractNum() {} }; class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; abstraktní předek umí existovat a vytisknout se virtuální destruktor! int main(int argc, char** argv){ Seznam s; s.append( new .... ); s.print(); return 0; } přidávání dynamicky vytvořených konkrétních typů
231
Polymorfní datové struktury - implementace metod
// konstruktor Seznam::Seznam() { for(int i=0; i<MAX; ++i) pole[i]=0; n=0; } // destruktor Seznam::~Seznam() for(int i=0; i<n; ++i) delete pole[i]; // tisk seznamu void Seznam::print() pole[i]->print(); // pridani prvku do seznamu void Seznam::append(AbstractNum* p) if (n<MAX) pole[n++]=p; class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; každý prvek ví jak se vytisknout
232
Polymorfní datové struktury - konkrétní datové typy
class IntNum : public AbstractNum { public: IntNum(int x_) { x=x_; } virtual ~IntNum() {} virtual void print() { printf("%d ",x); } private: int x; }; konkrétní datové typy implementují vlastní metody jednotného rozhraní konkrétní datové typy implementují vlastní metody jednotného rozhraní class IntDouble : public AbstractNum { public: IntDouble(double d_) { d=d_; } virtual ~IntDouble() {} virtual void print() { printf("%f ",d); } private: double d; }; Seznam s; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); kontejner obsahuje různé typy ... a všechny vytiskne
233
Polymorfní datové struktury - konstruktor const položek
Požadavek: co když chci zakázat měnit hodnotu prvků class IntNum : public AbstractNum { public: IntNum(int x_) { x=x_; } private: const int x; }; compiler error: x must be initialized in constructor base / member initializer list class IntNum : public AbstractNum { public: IntNum(int x_) : x(x_) {} private: const int x; }; seznam inicializátorů používejte všude, kde to lze
234
Polymorfní datové struktury - přiřazení
Problém: přiřazení seznamů int main(int argc, char** argv){ Seznam s, s2; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); s2 = s; return 0; } Je to korektní kód?
235
Polymorfní datové struktury - přiřazení
Problém: přiřazení seznamů Spadne to! Kde? v destruktoru ~Seznam Proč? Navíc: memory leaks problém je v s2 = s; v Seznam není operator= kompilátor si ho vyrobí automaticky okopíruje datové položky a ukazatele !!! destruktor s2 dealokuje prvky destruktor s znovu odalokuje prvky bloky už ale neexistují ! int main(int argc, char** argv){ Seznam s, s2; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); s2 = s; return 0; } Tady!
236
Polymorfní datové struktury - přiřazení
Možné řešení: zakázání přiřazení class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: Seznam& operator=(const Seznam&); enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; operator= v sekci private znemožní přiřazení seznamů stačí pouze deklarace (bez těla) nikdo ho nemůže zavolat je to už teď konečně korektní ??
237
Polymorfní datové struktury - copy konstruktor
Není! copy konstruktor! int main(int argc, char** argv){ Seznam s; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); Seznam s2 = s; return 0; } class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: Seznam& operator=(const Seznam&); Seznam(const Seznam&); enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; oprator= a copy konstruktor by se měly chovat stejně! je to už teď konečně korektní ??
238
Polymorfní datové struktury - přiřazení
Pokud chceme dovolit přiřazení (kopírování), je nutné si ujasnit logiku má nebo nemá se změna projevit i v druhém seznamu? kopie hodnot (alespoň logická) nebo jen kopie datové struktury? typicky: chování takové, jako kdyby se okopírovaly všechny prvky U každé třídy obsahující odkazy na dynamicky alokovaná data buď zakázat přiřazení operator= a copy konstruktor do sekce private nebo nadefinovat kopírování napsat vlastní duplikaci VŽDY napsat hlavičku operatoru = a copy konstruktoru! Seznam a, b; .... b = a; a[1]->x = 999; // b[1]->x ???
239
Polymorfní datové struktury - kopie prvků
Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<s.n; ++i) pole[i] = s.pole[i]; n = s.n; return *this } class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); Seznam& operator=(const Seznam&); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; okopíruji všechny prvky je to správně ??
240
Polymorfní datové struktury - úklid starého stavu
Je to správně? Není !! nezruší se předchozí odkazy! memory leaks nejdříve zruším všechny prvky cílového kontejneru Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) // jako v destruktoru delete pole[i]; for(int i=0; i<s.n; ++i) // jako v copy konstr. pole[i] = s.pole[i]; n = s.n; return *this } je to už teď správně ??
241
Polymorfní datové struktury - generování nových prvků
Je to už teď správně? Není !! okopírují se pouze ukazatele data zůstanou stejná prakticky totéž, jako kdybychom nechali automaticky vygenerovaný operator = musíme vygenerovat nové prvky dynamická alokace nového prvku Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) delete pole[i]; for(int i=0; i<s.n; ++i) pole[i] = new AbstractNum( *s.pole[i]); n = s.n; return *this } přímá konverze odvozené třídy na AbstractNum& je to už teď správně ??
242
Polymorfní datové struktury - zrušení abstraktnosti
Je to už teď správně? Není !! AbstractNum je abstraktní třída nelze instanciovat (vytvořit objekt) neprojde kompilátorem class AbstractNum { public: virtual void print() {}; virtual ~AbstractNum() {} }; psychicky zdeptaný programátor: tak tu abstraktnost odstraníme! Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) delete pole[i]; for(int i=0; i<s.n; ++i) pole[i] = new AbstractNum( *s.pole[i]); n = s.n; return *this } je to už teď správně ??
243
Polymorfní datové struktury - vytvoření správných typů
Je to už teď správně? Není !! vytvoří se pouze část objektu - společný předek mnohem horší chyba než předchozí případ projde kompilátorem, nespadne, ale dělá nesmysly! slicing Co s tím? když je skutečná hodnota IntNum - vytvořit IntNum když je skutečná hodnota DoubleNum - vytvořit doubleNum IN AN x AN class AbstractNum { public: enum T { T_INT, T_DOUBLE, ...}; virtual T get_t() const; virtual void print()=0; virtual ~AbstractNum() {} }; switch( s.pole[i]->get_t()) { case AbstraktNum::T_INT: pole[i] = new IntNum(*s.pole[i]); break; ... je to už teď správně ??
244
Polymorfní datové struktury - vytvoření správných typů
Je to už teď správně? Nooo..... dělá to to, co má, ale ... je to ošklivé - těžko rozšiřitelné přidání nového typu vyžaduje zásah do implementace společného předka! navíc syntaktická chyba předka nelze automaticky konvertovat na potomka new IntNum(*s.pole[i]) - skutečný parametr typu AbstractNum& konverze new IntNum(* (IntNum*) s.pole[i]) new IntNum( (IntNum&) *s.pole[i]) Jak to udělat lépe? využít mechanismus pozdní vazby každý prvek bude umět naklonovat sám sebe rozhraní v AbstractNum, implementace v IntNum, DoubleNum, ... virtuální klonovací metoda
245
Polymorfní datové struktury - klonování
class AbstractNum { public: virtual AbstractNum* clone() const =0; virtual void print()=0; virtual ~AbstractNum() {} }; jednotné rozhraní na klonovací funkce musí být typu AbstractNum* jinak by to nebyla stejná virt. metoda class IntNum : public AbstractNum { public: virtual AbstractNum* clone() const { return new IntNum(*this); } IntNum(int x_) : x(x_) {} private: const int x; }; IN AN x IntNum Seznam& Seznam::operator=( const Seznam& s) { ... for(int i=0; i<s.n; ++i) pole[i] = s.pole[i]->clone(); ... AbstractNum případné posunutí ukazatelů řeší automaticky mechanismus virt. funkcí je to už teď správně ??
246
Polymorfní datové struktury - přiřazení sebe sama
Je to už teď správně? Pořád není !!!! co když někdo provede s = s ? takhle blbě to asi nikdo nenapíše, ale ... Seznam p[100]; p[i] = p[j]; nejprve se zruší všechny prvky ... a pak se kopírují dealokované bloky!!! ani vynulování ukazatelů moc nepomůže neokopírovalo by se nic nutná ochrana! this &s Seznam& Seznam::operator=( const Seznam& s) { if( this == &b) return *this; ... rovnost ukazatelů stejný objekt je to už teď správně ??
247
Polymorfní datové struktury - přiřazení sebe sama
Seznam& Seznam::operator=( const Seznam& s) { if( this == &b) return *this; ... rovnost ukazatelů stejný objekt Je to už teď správně? .... no teď už snad ano ale co když jsou referencované objekty velké? časté kopírování neefektivní optimalizace zachování sémantiky vs. odlišná implementace reference counting, ...
248
101 mouder Sutter, Alexandrescu: C++ 101 programovacích technik
(C++ Coding Standards) Organizational and Policy Issues 0. Don’t sweat the small stuff. (Or: Know what not to standardize.) 1. Compile cleanly at high warning levels. 2. Use an automated build system. 3. Use a version control system. 4. Invest in code reviews. Design Style 5. Give one entity one cohesive responsibility. 6. Correctness, simplicity, and clarity come first. 7. Know when and how to code for scalability. 8. Don’t optimize prematurely. 9. Don’t pessimize prematurely. 10. Minimize global and shared data. 11. Hide information. 12. Know when and how to code for concurrency. 13. Ensure resources are owned by objects. Use explicit RAII and smart pointers. Coding Style 14. Prefer compile- and link-time errors to run-time errors. 15. Use const proactively. 16. Avoid macros. 17. Avoid magic numbers. 18. Declare variables as locally as possible. 19. Always initialize variables. 20. Avoid long functions. Avoid deep nesting. 21. Avoid initialization dependencies across compilation units. 22. Minimize definitional dependencies. Avoid cyclic dependencies. 23. Make header files self-sufficient. 24. Always write internal #include guards. Never write external #include guards.
249
101 mouder Functions and Operators
25. Take parameters appropriately by value, (smart) pointer, or reference. 26. Preserve natural semantics for overloaded operators. 27. Prefer the canonical forms of arithmetic and assignment operators. 28. Prefer the canonical form of ++ and --. Prefer calling the prefix forms. 29. Consider overloading to avoid implicit type conversions. 30. Avoid overloading &&, ||, or , (comma) . 31. Don’t write code that depends on the order of evaluation of function arguments. Class Design and Inheritance 32. Be clear what kind of class you’re writing. 33. Prefer minimal classes to monolithic classes. 34. Prefer composition to inheritance. 35. Avoid inheriting from classes that were not designed to be base classes. 36. Prefer providing abstract interfaces. 37. Public inheritance is substitutability. Inherit, not to reuse, but to be reused. 38. Practice safe overriding. 39. Consider making virtual functions nonpublic, and public functions nonvirtual. 40. Avoid providing implicit conversions. 41. Make data members private, except in behaviorless aggregates (C-style structs). 42. Don’t give away your internals. 43. Pimpl judiciously. 44. Prefer writing nonmember nonfriend functions. 45. Always provide new and delete together. 46. If you provide any class-specific new, provide all of the standard forms (plain, in-place, and nothrow).
250
101 mouder Construction, Destruction, and Copying
47. Define and initialize member variables in the same order. 48. Prefer initialization to assignment in constructors. 49. Avoid calling virtual functions in constructors and destructors. 50. Make base class destructors public and virtual, or protected and nonvirtual. 51. Destructors, deallocation, and swap never fail. 52. Copy and destroy consistently. 53. Explicitly enable or disable copying. 54. Avoid slicing. Consider Clone instead of copying in base classes. 55. Prefer the canonical form of assignment. 56. Whenever it makes sense, provide a no-fail swap (and provide it correctly). Namespaces and Modules 57. Keep a type and its nonmember function interface in the same namespace. 58. Keep types and functions in separate namespaces unless they’re specifically intended to work together. 59. Don’t write namespace usings in a header file or before an #include. 60. Avoid allocating and deallocating memory in different modules. 61. Don’t define entities with linkage in a header file. 62. Don’t allow exceptions to propagate across module boundaries. 63. Use sufficiently portable types in a module’s interface. Templates and Genericity 64. Blend static and dynamic polymorphism judiciously. 65. Customize intentionally and explicitly. 66. Don’t specialize function templates. 67. Don’t write unintentionally nongeneric code. Error Handling and Exceptions 68. Assert liberally to document internal assumptions and invariants. 69. Establish a rational error handling policy, and follow it strictly. 70. Distinguish between errors and non-errors. 71. Design and write error-safe code. 72. Prefer to use exceptions to report errors. 73. Throw by value, catch by reference. 74. Report, handle, and translate errors appropriately. 75. Avoid exception specifications.
251
101 mouder STL: Containers
76. Use vector by default. Otherwise, choose an appropriate container. 77. Use vector and string instead of arrays. 78. Use vector (and string::c_str) to exchange data with non-C++ APIs. 79. Store only values and smart pointers in containers. 80. Prefer push_back to other ways of expanding a sequence. 81. Prefer range operations to single-element operations. 82. Use the accepted idioms to really shrink capacity and really erase elements. STL: Algorithms 83. Use a checked STL implementation. 84. Prefer algorithm calls to handwritten loops. 85. Use the right STL search algorithm. 86. Use the right STL sort algorithm. 87. Make predicates pure functions. 88. Prefer function objects over functions as algorithm and comparer arguments. 89. Write function objects correctly. Type Safety 90. Avoid type switching; prefer polymorphism. 91. Rely on types, not on representations. 92. Avoid using reinterpret_cast. 93. Avoid using static_cast on pointers. 94. Avoid casting away const. 95. Don’t use C-style casts. 96. Don’t memcpy or memcmp non-PODs. 97. Don’t use unions to reinterpret representation. 98. Don’t use varargs (ellipsis). 99. Don’t use invalid objects. Don’t use unsafe functions. 100. Don’t treat arrays polymorphically.
252
... to be continued Jazyk Knihovny OOP
member pointers .* a ->*, true / contractual const, mutable static_cast, dynamic_cast, reinterpret_cast, const_cast funktory výjimky, try, catch, throw, exception safety šablony, explicitní instanciace, parciální specializace, traits & policy classes Koenigovo vyhledávání RTTI, typeid, type_info Knihovny streams (proudy) STL – kontejnery, iterátory, algoritmy auto_ptr, shared_ptr OOP kánonické formy tříd, abstraktní datové typy, polymorfní typy counted pointers, mělké vs. hluboké kopie hlouběji o objektovém návrhu, reusabilitě, efektivitě implementace návrhové vzory (design patterns) – factory, visitor, observer ...
253
... not used
254
Uživatelské knihovny Uživatelské .h Standardní .h Kompilátor
Uživatelské .c Přeložené .obj Linker Spustitelný soubor .exe Standardní .obj Standardní .lib Knihovní .obj Knihovna .lib Knihovní .c Librarian Privátní .h Veřejné .h
255
Dynamicky linkované knihovny
Uživatelské .h Standardní .h Kompilátor Uživatelské .c Přeložené .obj Linker Spustitelný soubor .exe Standardní .obj Standardní .lib Stub .obj Stub .lib Knihovní .c Privátní .h Veřejné .h Knihovna .dll Loader
256
Programování není (jen) zápis algoritmů
Nízkoúrovňové (technické) Běhové prostředí programu Vazba programu na operační systém Přenositelnost mezi platformami Typické chyby a ochrana proti nim Ladění programů debuggerem a bez debuggeru Udržovatelné programy, kultura programování Vysokoúrovňové ('manažerské') Komunikace se 'zákazníkem' - uživatelské požadavky Analýza a specifikace, programátorské zadání Testy - aplikační, integrační, zátěžové Nasazení (deployment) Pilotní provoz, roll-out, konverze dat Dokumentace - vývojová, uživatelská Školení 'běžných uživatelů' Programování ≈ výroba software Softwarové inženýrství Informační systémy
257
Ladění programů debuggerem
Spustit program v ladicím režimu Ladicí režim typicky není pro laděný program identický s normálním Chybný program se může chovat (a často chová) při ladění jinak než finální verze Krokovat a spouštět program Zobrazovat data lokální i globální proměnné, složitější výrazy zásobník volání paměť laděného procesu Odchytit chybující program a zobrazit stav těsně před chybou Nastavovat breakpointy do kódu Mohou být podmíněné Breakpointy na data (změna dat, přístup do paměti, splnění podmínky) Některé typy mohou o několik řádů zpomalit běh programu Nevýhody Ne všude dostupné (kernely, pračky, banky, jaderné elektrárny, ...) Nevhodné pro paralelní nebo dlouhodobý běh
258
Ladění programů bez debuggeru
Ladicí 'tisky' (logy) Záznam důležitých okamžiků běhu programu Co, kdy a kde tisknout Jak tisknout ('monitor', soubor, databáze, pípání ...) Automatické testování Testovací skripty Sady testovacích dat Lokalizace chyby Minimalizace zdrojového textu, kde se chyba vyskytuje Prevence proti zavlečeným chybám Ladicí implementace alokačních funkcí Obložit každý alokovaný blok prostorem vyplněným značkami Při dealokaci zkontrolovat neporušenost značek a změnit je
Podobné prezentace
© 2024 SlidePlayer.cz Inc.
All rights reserved.