PRG029
Programování v C a C++ (LS 2005/06)
RNDr. Filip Zavoral, Ph.D.Katedra softwarového inženýrství
http://ulita.ms.mff.cuni.cz -> Výuka
21.04.2023 04:24
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 5.3.2005 Ověřte platnost e-mailu – Hladání studenta -> Změna údajů
Účast na cvičeních ’Pokročilí’ programátoři – 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
Pravidla pro budoucí neúspěšné
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 na některé
cvičenía dohodnout se s cvičícím na konkrétních podmínkách
PRG025 - Programování v C a C++ PRG035 - OOP LS 1. roč ZS 2. roč
Obsah předmětu
C++
C
C++
Nejdůležitější: vlastní praxeNa přednáškách se nikdo nikdy programovat
nenaučil
C
Obsah přednášky
Kurz jazyka 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
Obsah cvičení
Střídavě v S7 a v laboratoři SW2 Začínáte tam, kde to máte napsané v rozvrhu 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 program a ono to řeklo 'Váš program provedl neplatnou instrukci a bude ukončen '. Co mám dělat?
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++ 101 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é
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++ 2003 TR1 (2005) C++0x
ISO/IEC 9899: Programming languages - C (1999)
WWW http://mindview.net/Books
Eckel: Thinking in C++ ... and more http://www.parashift.com/c++-faq-lite
v češtině: Moderní programování v
C++
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++
Pascal vs. C a C++
Programování I, II 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é........
Pascal vs. C a C++
Programování v C a 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án
o
Historie
1970-3 první verze C, společný vývoj s UNIXem1973 přepsání jádra UNIXu do C1978 Kerninghan, Ritchie: The C Programming Language1980 standardy – ANSI X3J11, od r. 1999 ISO 98991980 AT&T - "C with Classes"1983 poprvé název C++ (Rick Mascitti)1985 Stroustrup: The C++ Programming Language1989 ANSI X3J16 norma C++2003 nejnovější ISO/ANSI norma C++2005 C++ 2003 TR1
rozšíření knihoven (částečně podporováno GCC 4.0) založeno na knihovně Boost
2007-9 plán nové normy C++
Normy se vyvíjí, aktuální překladače o několik let zpětImplementace novinek často nekorektní nebo neefektivní
Part I - C
Cpřesněji společná část C a C++ se zdůrazněním
odlišností
hello.c
#include <stdio.h>#include <conio.h>
int main( int argc, char ** argv){ printf( "Hello\n"); getch(); return 0;}
hello.c
#include <stdio.h>#include <conio.h>
int main( int argc, char ** argv){ printf( "Hello\n"); getch(); return 0;}
tělo funkce
hlavička funkce
příkaz
deklarace knihovních funkcí
direktiva preprocesoru
vložení souboru
formální parametr
y
název funkce
typ návratové hodnoty
skutečné parametr
yvolání funkce
bez parametr
ů
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
Překlad jednoho modulu a sestavení
.cpp
.h
CC .obj Link .exe
.obj.obj.obj.obj.obj.lib
kompilacespojování(linkování
)
knihovnystandardní i
jiné
knihovní headery
objektový modul
(přeložený kód)
spustitelný
program
Překlad více modulů – oddělený překlad
.c
.h .h
CC .obj Link .exe
.obj.obj.obj.obj.obj.lib
.obj.obj.obj
kompilace jednoho modulu
knihovnyknihovní headery
vlastní headery
.cpp
.c.cpp
další moduly
Modul - ucelená funkčí jednotkamodul.cpp - implementacemodul.h - definice rozhraní
Oddělený překlad - dělení na moduly
fotbal.cpp
fotbal.h
hriste.cpp hrac.cpp mic.cpp
hriste.h hrac.h mic.h
rozdělení projektu do modulů
a vytváření headerů je umění,
nedá se to naučit na přednášce
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
Integrované vývojové prostředí
.c
.h .h
CC Link .exe
.obj.obj.obj.obj.obj.lib
.obj.obj.obj.c
.cpp
debugger
projekt
editor
make
.c
.h .h
CC Link .exe
.obj.obj.obj.obj.obj.lib
.obj.obj.obj.c
.cpp
makemakefile
Program a modul
Program v C++ nemá (vnořovanou) blokovou strukturuNeexistuje "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
Příklad - malá násobilka
#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); return 0;}
inicializovaná
celočíselná proměnná
neinicializovaná proměnná
hlavička funkce, formální parametr
deklarace knihovních funkcí, ...
konec cyklu
začátek cyklu
kontrola parametrů
okanžitý návrat z funkce
2 * 7 = 14
++i
konec, OK
konec, OK
hlavní program
ignorování návratové hodnoty volání funkce
se skutečným
parametremThe END
Funkce
Základní programová jednotka je funkceNeexistují vnořené funkceZačátek programu – funkce main
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){ ...}
int fce2( int a){ int fce1( int x, int y) { return x+y; }
return fce1( 2*a+17);}
vnořené
funkce nelze!
začátek program
u
argumenty z příkazové řádky
později
Funkce - návratový typ
Typ funkce = typ návratové hodnotyHodnota se vrací pomocí klíčového slova return
Speciální typ void - 'prázdný' typ ekvivalent procedury Pascalu
int fce1( int x, int y){ return x+y;}
void fce2( char* text){ printf( text);}
void fce3( char* text){ if( ! text) return; printf( text);}
návrat celočíselné hodnoty
návrat z funkce
Parametry funkce
Pevný počet, pevný typ možnost proměnného počtu parametrů - printf
Parametry jsou odděleny čárkouU každého parametru musí být uveden jeho typFunkce bez parametrů - void
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{ ...}
každý parametr musí
mít typ
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");}
Předávání parametrů funkci
Všechny parametry se předávají hodnotou'Výstupní' parametry pouze přes ukazatele nebo reference
vymění se jen lokální proměnné
funkce
předají se pouze hodnoty (1, 2) nikoliv 'proměnné'
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;}
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 !!!
int fce( void){ int x, y; int z = 0; return z + x;}
deklarace celočíselný
ch proměnnýc
hdeklarace
s inicializací
náhodná hodnota !!!
int fce2( void){ int i = 0; while( i < 100) { int j = i + 1; i = j * i; } // j jiz neexistuje return i;}
hlavní blok
funkce
vnořený blok
deklarace proměnné
ve vnořeném blokuinicializace se
provádí při každém průběhu
po ukončení bloku již proměnná
neexistuje
Globální proměnné
Definice mimo tělo funkceViditelné v jakékoliv funkciMožná inicializace – při startu programuPoužívat s rozvahou!!
pouze pro sdílená dataint g = 1;
void fce( int x){ int z = 2; g = g + z * x;}
int main( void){ fce( 2); fce( 3); return g;}
globální proměnná
formální parametr
skutečný parametr
lokální proměnná
co vrátí main ??
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
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
Podmíněný příkaz
if (výraz) příkazif (výraz) příkaz else příkaz
if( a > 1) printf( "OK");
if( a > 1) b = 0; printf( "OK");
if( a > 1) b = 0; printf( "OK");else printf( "nee");
if( a > 1){ b = 0; printf( "OK");}else{ printf( "nee");}
if( a > 1) printf( "OK");else printf( "nee");
syntakticky správně, ale dělá
něco jiného
syntakticky špatně
if( a > 1) { b = 0; printf( "OK");} else { printf( "nee");}
Vnořené podmíněné příkazy
if( a > 1) { if( b > 0) printf( "OK");} else { printf( "nee");}
Syntakticky správně, ale nepřehledné
Na pohled nejasné párování
if( a > 1) { if( b > 0) printf( "OK"); else printf( "nee");}
U vnořených podmínek
vždy používat { }
if( a > 1) if( b > 0) printf( "OK");else printf( "nee");
Vícenásobné větvení – konstrukce switch
switch (výraz) { case konstanta: posloupnost příkazů break; case konstanta: case konstanta: posloupnost příkazů break; default: posloupnost příkazů}
switch( opcode) {case 0: // no op break;case 10: add(); break;case 11:case 12: cmp(opcode); break;default: error(opcode); break;}
switch( ch) {case '0'..'9': x += ch - '0'; break;case 'A'..'F': x += ch - 'A'; break;}
switch( opcode) {case 0: // no opcase 10: ...
zapomenutý break syntakticky
OK,ale dělá něco
jiného
interval nelze
celočíselný výraz
ukončení větve
pokud výraz není roven žádné z
konstant
více návěští pro jednu
větev
Cyklus – konstrukce while a do-while
while (výraz) příkaz podmínka na začátkudo příkaz while (výraz) ; podmínka na konci
while( a > 1) { fce( a); a = a / 2;}
while( a > 1) a = a / 2;
do { fce( a); a = a / 2;} while( a > 1);
tělo se vždy alespoň jednou
provede
Pozor! cyklus pokračuje pokud je podmínka
platná
for (výraz1 ; výraz2 ; výraz3 ) příkaz
je ekvivalentem
výraz1;while (výraz2 ) { příkaz výraz3 ;}
Cyklus – konstrukce for
i=0;while( i<=9) { fce( i); i=i+1;}
inicializace
for( i=0; i<=9; i=i+1) { fce( i);}
podmínka inkrement
tělo cyklu
FOR I := 0 TO 9 DO FCE(I)
ekvivalent v jiném jazyce
for( init(a); i<9 && a[i] >0; a[i++]=0) { fce( i);}
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;}
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
Ukončení cyklu - break, continue
for(;;) { errc = fce(); if( errc < 0) break; jinafce();}
n = 1;while( n<1000) { val = get(); if( val == 0) continue; n = n * val;}
Nepoužívat! .... pokud k tomu není dobrý důvodKdyž už, tak pouze dopředu (skok dozadu = cyklus)
Dobrý důvod: výskok z vícenásobně vnořených cyklů
Goto
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();
nelze break -ukončil by pouze vnitřní cyklus, vnější cyklus by
pokračoval
labelnávěští
Celočíselné typy
typ 8 bit 16 bit
32 bit 64 bit
char 8 8 8 8
short 8 / 16
16 16 16 / 32
int 16 16 32 32 / 64
long 16 32 32 32 / 64
long long
- - 64 64
size_t 32 64
ptrdiff_t 32 64
wchar_t 16/32 16/32
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
-2GB .. +2GB
velikost objektů
rozdíl ukazatelů
rozšířená znaková sada
1 byte
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 0 .. 127'a' < 'ž' ?
signed char -128 .. 127unsigned char 0 .. 255 - '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
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 };
Výčtový typ
C++: samostatný typ - nelze
( C: celočíselné konstanty - OK )
test bitů
hodnoty doplní překladač (od 0)
explicitní hodnoty
lze použít jako celočíselnou hodnotu
(ale většinou to nemá rozumný smysl)
'Reálné' typy
float double long double
malá přesnost -
nepoužívat!standard pro
'reálné' výpočty
zvýšená přesnost
double x = 1;double y = x / 3;if( x == 3 * y) printf( "Presne");else printf( "Nepresne");
Pozor!Reálné výpočty
jsou vždy nepřesné
raději nepoužívatpouze pro fyzikální
nebo numerické veličiny
double zustatek = 15.60;
long zustatek_v_halerich = 1560;
pro přesné hodnoty
používejte přesné
typy
Celočíselné konverze
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
vždy když je použit menší typ
než int
při nestejných operandech
moudro: prostě se to zkonvertuje na ten větší
Přehled operátorů, asociativita a priorita
postfi
x
++ --( )[ ]-> .::
post-in/de-krementacevolání funkceindexpřístup k položce strukturykvalifikace identifikátoru
pre
fix
++ --! ~+ -& *sizeof( )newdelete
pre-in/de-krementacebooleovská a bitová negaceunární +/-reference, dereferenceměření velikostipřetypovánídynamická alokacedynamická dealokace
L .* ->* dereference member-ptru
L * / % multiplikativní operátory
L + - aditivní operátory
L << >>
bitové posuny
L < <=> >=
uspořádání
L == != rovnosti
L & bitové AND
L ^ bitové XOR
L | bitové OR
L && booleovské AND
L || booleovské OR
L ? : podmíněný výraz
P =*= /= %= +=-= &=^= |= <<= >>=
přiřazeníkombinované přiřazení
L , sekvence
Základní aritmetické operátory
+ - * / % podle typu operandů automatická konverze na větší typ % - modulo
x / y 1 x / b 1.666
x % y 2 x % b Error
a / b 1.666 a / y 1.666
a % b Error a % y Error
int x=5, y=3;
double a=5, b=3; modulo je pouze celočíselná
operaceceločíselné dělení
reálné dělení
Bitové a logické operátory
& | ^ ~ - bitové operace AND, OR, XOR, NOT&& || ! - logické operace AND, OR, NOT
5 & 3 1 5 && 3 1
5 | 3 7 5 || 3 1
5 ^ 3 6 5 ^^ 3 Error
5 ^ 15 10
5 & 0 0 5 && 0 0
5 | 0 5 5 || 0 1
neexistuje
5 = 01012
3 = 00112
1 = 00012
7 = 01112
9 = 10012
15 = 11112
10 = 10102
oba op. 0
alespoň jeden
operand 0
alespoň jedenoperand = 0
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í !
int x[10]; // pole 0..9
if( i < 10 && x[i] != 0) y = y / x[i];
test mezí pole předpřístupem k prvku
pole
if( x==y && *x++) ...
Pozor! operátory s vedlejšími efekty se
nemusí provést !
if( x = 1) ...
POZOR!!! Přiřazení!(zde hodnota vždy =
1)
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;
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
pokud si lze vybrat, preferujte
preinkrement
Podmíněný výraz, sekvence
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);
x = (tmp = y, y = y + 1, tmp);
ekvivalentx = y++;
ternární operátor
operátor sekvence
('zapomnění')
i = 0;p[ i++] = i++;
Pravidla vyhodnocování
a( b,c)vedlejší efekty parametrů jsou vyhodnoceny před zavoláním fcea && b je-li a nulový, b se nevyhodnocujea || b je-li a nenulový, b se nevyhodnocujea ? b : c po vyhodnocení a se vyhodnotí buď b nebo ca , 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;
Fungující triky vs. chyby
Fungující triky 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' )
nevím, jestli se provede
není definováno pořadí
while( p && p->v < v)
p = p->next;
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 * xconst 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 & xconst 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)
Pole
Deklarace: t x[n] - pole x o n prvcích (0..n-1) typu tIndexy 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;
int x[5];for( i=1; i <=5; ++i) x[i] = i;
0 1 2 3 4 ???
? 1 2 3 4 5
Přepis náhodného místa v paměti !
Nepředvídatelné následky !!
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
poleo souřadnicích m a n
čárka = operátor sekvence
význam:m-prvkové pole typu(n-prvkové pole typu
t)
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 } };
Struktury
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;
definice struktury
položky struktury
struktura, ukazatel na strukturu, pole
struktur
přístup k položkámx.y
(*px).y px->y
definice proměnné typu struktura s
inicializací
Béda
Trávníček
1980
0
Pojmenování struktur a výčtových typů
C++ 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; struct STR * next;};
struct STR * p;
struct STR { int a, b; STR * next;};
STR * p;
C++ - samostatný typ
C - pouze tag
Ukazatele
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á
1někde bydlí
je nějak velká (typ)
má hodnot
u
Ukazatele
Deklarace:t x – proměnná x typu tt *p - ukazatel p na typ t
Operátor reference: p = &xOperátor dereference: x = *p
Neinicializovaný ukazatel vs. nulový ukazatel C++: 0 C: #define NULL 0
17x:
p:
proměnná ukazatel na ni
ukazatel hodnota na kterou ukazuje
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
1x: :px
3y: :py
?
? 5
jaký zde bude obsah x a y?
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;
typicky 'segmentation fault'váš program bude ukončen
pro pokus o porušení ochrany paměti
1x: :px
3y: :py
0: 7
umožní test
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řístup k hodnotě proměnné přes
ukazatel
1x: :px
4y: :pyPozor na prioritu!
*py++ *(py++)
1x: :px
3y: :py
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;
1x: :px
1y: :py
9x: :px
1y: :pyjaký zde bude obsah x a y?
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 polep - j odečtení - posun zpět
int a[5];int *p;
a[2] = 20;p = &a[2];
a[0] = *p - 15;p++;*p = 30;
0 1 2 3 4
? ? 20 ? ?
5 ? 20 30 ?
p
p
reference na prvek pole
inkrementace ukazatele - posun na další prvek
a
reference na prvek pole
Pole a ukazatele, aritmetika ukazatelů
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]
5 ? 20 30 40
ppřičtení čísla k ukazateli posun o n
prvků
identifikátor pole je ukazatelna svůj nultý prvek
indexování pole (ukazatele) je jen jiný zápis přístupu přes
ukazatel
Pole a ukazatele, aritmetika ukazatelů
int a[5];int *p;
identifikátor pole je konstantní nelze do něj přiřazovat
0 10 20 30 40
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;
nekompatibilní typynestejná úroveň
indirekce
p[i] *(p+i) *(i+p) i[p]
Řetězce
Řetězec je pole znaků (char) zakončené nulou konvence, knihovny
"Ahoj"
X proměnná'X' znaková konstanta - celočíselná hodnota"X" řetězec - ukazatel
'A' 'h' 'o' 'j' '\0'
Každý řetězec musí být vždy
ukončen nulou
'A' 'h' 'o' 'j' '\0'
vždy myslet na koncovou nulu !
pozor na uvozovky a apostrofy !
char buffer[4];strcpy( buffer, "Ahoj");
'\0' = 0
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
Řetězcové proměnné a konstanty
static char s1[] = "Uno";
const char *s2 = "Due";
puts( "Uno");
'U' 'n' 'o' '\0'
'D' 'u' 'e' '\0'
s1:
s2:
Inicializovaná proměnná typu ukazatel
s2++ se přesune na další znak
Inicializované pole (konstantní ukazatel)
s1++ nelze!
anonymní globální proměnná
const char[]
ekvivalent globální proměnné typu const char[ ], inicializované obsahem konstanty
... = { 'U', 'n', 'o', 0 };
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í
Ř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);
A h o j \0 ? ? ?pole:
počet znaků(bez koncové
nuly)
char pole[8] = "Ahoj";x = strlen( pole); // 4
skutečný počet znaků (4)nikoliv velikost pole
deklarace řetězcových funkcí
inicializované pole typu char
Délka řetězce – přes index
int strlen ( const char* s){ int i = 0; while ( s[i] != '\0') { i++; } return i;}
přístup přes index
Délka řetězce – přes ukazatel
int i = 0;while ( *s != '\0') { i++; s++;}return i;
int strlen ( const char* s){ int i = 0; while ( s[i] != '\0') { i++; } return i;}
přístup přes ukazatel
Délka řetězce – cyklus for
int i = 0;while ( *s != '\0') { i++; s++;}return i;
int strlen ( const char* s){ int i = 0; while ( s[i] != '\0') { i++; } return i;}
for( i=0; *s != '\0'; i++) s++;
podmínka for cyklu může být
libovolná
Délka řetězce – for bez těla
int i = 0;while ( *s != '\0') { i++; s++;}return i;
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++) ;
více inkrementací prázdné tělo
nezapomenout na ';' !!
Délka řetězce – podmínka s vedlejším efektem
int i = 0;while ( *s != '\0') { i++; s++;}return i;
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++;
složitější podmínka:test nenulovostiinkrementace
ukazatele
Délka řetězce – nenulovost výrazu v podmínce
int i = 0;while ( *s != '\0') { i++; s++;}return i;
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++) i++;
while(a!=0) while(a)
podmínka je splněnapokud je nenulová
Délka řetězce – rozdíl ukazatelů
int i = 0;while ( *s != '\0') { i++; s++;}return i;
int strlen ( const char* s){ int i = 0; while ( s[i] != '\0') { i++; } return i;}
char *p = s;while (*p++) ;return p-s-1;
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++) i++;
rozdíl ukazatelů = počet prvků mezi
nimi pozor na ± 1 !
Délka řetězce – různé způsoby implementace
int i = 0;while ( *s != '\0') { i++; s++;}return i;
int strlen ( const char* s){ int i = 0; while ( s[i] != '\0') { i++; } return i;}
char *p = s;while (*p++) ;return p-s-1;
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++) i++;
přístup přes index
přístup přes ukazatel
podmínka for cyklu může být
libovolná
více inkrementací prázdné tělo
nezapomenout na ';' !!
složitější podmínka:test nenulovostiinkrementace
ukazatele
while(a!=0) while(a)
podmínka je splněnapokud je nenulová
rozdíl ukazatelů = počet prvků mezi
nimi pozor na ± 1 !
Řetězce - kopírování
char* strcpy( char* d, const char* s);
zkopíruje obsah sdo místa začínajího od
dchar buf[8];char pozdrav[] = "Ahoj";strcpy( buf, pozdrav);
A h o j \0pozdrav
A h o j \0 ? ? ?buf
kopíruje pouze do koncové '\0'
Řetězce – chyby při kopírování
D o b r y d e n \0pozdrav
buf
char buf[6];char pozdrav[] = "Dobry den";strcpy( buf, pozdrav);
D o b r y d e n \0
vždy pozor na dostatek místa
funkce nic nekontroluje !!!váš program provedl...
char *ptr;char pozdrav[] = "Ahoj";strcpy( ptr, pozdrav);
kopírování na neinicializovaný ukazatel !!!
váš program provedl...
A h o j \0pozdrav
ptr?
A h o j \0
ptr neinicializovaný !!!
Řetězce – zřetězení, vyhledávání
char* strcat( char* d, const char* s); připojí s za dchar* strchr( const char* s1, int c); vyhledá první pozici c v s1char* 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");
A h o j \0
? ? ? ? ?po strcpy
A h o j B a b i \0
?po strcat
buf
bp
A h o j B a b i \0
?pozor na dostatek
místa !
Řetězce – porovnávání a další funkce
int strcmp( const char* s1, const char* s2);s1 < s2 -1s1 = s2 0s1 > 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
co znamená 's1 < s2'? A B C E
= = =
A B C D E
výsledek podle prvního rozdílného
znaku
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;}
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)) )
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);}
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;}
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 korektnostivýsledku
kontrola velikosti polepozor na mezeru a konec!
kopírování jednotlivých částí
návrat výsledné délky
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
čtení deklarací: od identifikátoru doprava, až to nepůjde, tak
doleva
typicky se nepoužívápole = ukazatel na 1.
prvek
int*(*pf[10])(void);
int*(*maso(int*(*p1)(void),int*(*p2)(void)))(void);
Kombinace typových kostrukcí - příklad
co to je za maso ???
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);
Kombinace typových kostrukcí - typedef
použitím typedef se může výrazně
zpřehlednit kód
Parametry příkazové řádky
C:\> myprog.exe -n -w a.txt b.txt
0
m y p r o g . e x e \0
- n \0
- w \0
a . t x t \0
b . t x t \0
argv
5argc
int main( int argc, char** argv)
pole řetězců(ukazatelů na char)
Počet parametrůvčetně názvu programu !
= počet ukazatelů v argv
Zpracování příkazové řádky – výpis parametrů
int main( int argc, char** argv){ while( *argv) { printf( "%s\n", *argv); ++argv; }}
C:\> myprog.exe -n -w a.txt b.txt
myprog.exe-n-wa.txtb.txt
výstup řetězce a
odřádkováníposun na další
parametr
Zpracování příkazové řádky
int main( int argc, char** argv){ while( *argv) { printf( "%s\n", *argv); ++argv; }}
m y p r o g . e x e \0
- n \0
- w \0
a . t x t \0
b . t x t \0
argv
0
argvtyp: char**
**argv argv[0][0]
typ: char
argv[4][1]
*argv argv[0]
typ: char*
argv[4]
C:\> myprog.exe -n -w a.txt b.txt
Zpracování příkazové řádky
int main( int argc, char** argv){ while( *argv) { printf( "%s\n", *argv); ++argv; }}
m y p r o g . e x e \0
- n \0
- w \0
a . t x t \0
b . t x t \0
argv
0
argv++ **argv
Zpracování příkazové řádky
int main( int argc, char** argv){ while( *argv) { printf( "%s\n", *argv); ++argv; }}
m y p r o g . e x e \0
- n \0
- w \0
a . t x t \0
b . t x t \0
argv
0
*argv == 0
Zpracování parametrů příkazové řádky
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
usage: myprog [-n] [-w] fileA fileB
nastavení přepínače
zbývající parametry
výkonná funkce
p r g . e x e \0
- n \0
- w \0
a . t x t \0
b . t x t \0
0
argv
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;}
číselný parametr
usage: myprog [-n] [-w] [-x123] [-ffilename]
fileA fileB
řetězcový parametr
- x 1 2 3 \0
- f f i l ...
argv
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;}
-ffile
usage: myprog [-n] [-w] [-x123] [-f filename]
fileA fileB
-f file
- f \0
argv- f f i l ...
f i l e n ...
Dynamická alokace paměti
C: standardní knihovny <alloc.h>, <stdlib.h>
void* malloc( size_t size); void free( void* p);
#include <alloc.h>
ELEM * p;p = malloc( sizeof( ELEM));if ( !p ) { /* chyba */ }free( p);
int n = 100;p = malloc( n * sizeof( ELEM));if ( !p ) { /* chyba */ }free( p);
C++ nutnost přetypováníp = (ELEM*)malloc( sizeof( ELEM));
vždy ověřit !!!váš program
provedl...
C++: součást jazyka new T new T[ n] delete p delete[] p
ELEM * p;p = new ELEM;delete p;
int n = 100;p = new ELEM[ n];delete[] p;
mechanismus výjimek
Jeden objekt
Pole objekt
ů
Pole objekt
ů
Jeden objekt
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]);
Velikost pole určena za běhu programu
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]);
spočítá potřebnou velikost
ze vstupních parametrů
pozor na koncovou '\0'
nutný test na úspěšnost
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
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;}
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); }
size
####
####
velikost bloku
kontrolní výplň
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
R0R1...
SP
Organizace paměti procesu – kódový segment
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
R0R1...
SP
int fce( int x) { ... }
{ int (*fp)( int); fp = fce; ... (*fp)(17); fp(17)
alternativní syntaxe
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
R0R1...
SP
int bigpole[ 1000];char retezec[] = "CHAIN";
{ int* p = bigpole; const char* s = "ahoj"; ...
inicializované pole
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
R0R1...
SP
{ char* s; s = new char[128]; ...
nemixovat
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
R0R1...
SP
{ char pole[100]; char s[] = "Ahoj"; int x = 1 + 2 * 3; ...
inicializace při každém
průchodu
Statické proměnné
static int x;
int fce( int a){ static int y = 0; return y += a;}
globální proměnná neviditelná z jiných
modulů
de facto globální (!) proměnná neviditelná z
jiných funkcí
inicializace:C: před vstupem do mainC++: před prvním průchodem
C++: lepší řešení - namespace
C++: raději skrýt do třídy
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?
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); ....}
program pokus
pokus\0 buf
p
D
Z
H
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); ....}
program pokus
pokus\0 buf
p
D
Z
H
s
p
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); ....}
program pokus
pokus\0 buf
p
D
Z
H
s
p
#pokus\0
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); ....}
program pokus
pokus\0 buf
p
D
Z
H
#pokus\0
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); ....}
program pokus
pokus\0 buf
p
D
Z
H
s
p
#pokus\0
Organizace paměti procesu – příklad
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); ....}
program pokus
pokus\0 buf
p
D
Z
H
s
p
#pokus\0
##pokus\0
Organizace paměti procesu – příklad
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); ....}
program pokus
pokus\0 buf
p
D
Z
H
#pokus\0
##pokus\0
Organizace paměti - ukazatele na funkcetypedef 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( fce[i], vysledek, pocet, zac, krok); zobraz( vysledek, ....); } return 0;}
C
cos { ...
sin { ...
log { ...
fce
D
vysled.
Z
f
vysl
Souborový vstup a výstup
struktura definovaná
v <stdio.h>
Neznámý obsah
Pro knihovní funkce
FILE *
FILE
deskriptor souboru -deklaruje
programátor
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)
OS
soubor'na síti'
#include <stdio.h>
FILE* fp;int c;
if( !(fp = fopen("c:\\f.txt", "r")))error();
while( (c = getc( fp)) != EOF)putchar( c);
fclose( fp);
Práce se soubory
typ 'soubor'(ukazatel na strukturu)
otevření souborupojmenovávací konvence
dle OS
čtení ze soubor
u
zavření souboru
pozor na '\\' !!!
Otevření souboru
FILE* fopen( const char* fname, const char* mode);
r open file for readingw truncate to zero length or create file for writinga append; open or create file for writing at end-of-filer+ open file for update (reading and writing)w+ truncate to zero length or create file for updatea+ append; open or create file for update, writing at end-of-file
rb binary file... mod
esoubor ex.
soubor neex.
seek
r R Error 0
w Del, W W 0
a W W End
r+ R/W Error 0
w+ Del, R/W R/W 0
a+ R/W R/W End
+: vždy čtení i zápis
a: otevřít na konci
r: soubor musí existovat
w: soubor se smaže
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
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);
Práce se soubory - příklad
#include <stdio.h>
const int buflen = 1024;
int main(int argc, char ** argv){ FILE *f; char s[buflen]; int n; if (argc <= 1) return 1; f = fopen(argv[1], "r"); if (!f) return 2;
while ((n = fread(s, 1, buflen, f)) > 0) { fwrite(s, 1, n, stdout); }
fclose(f); return 0;}
catkopírování
obsahu souboru na standardní
výstup
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], "r");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 koneczjištění pozice
Pozor! long
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);
jednotný zápisna std výstup nebo
do souboru
Nepoužívat!Nelze ohlídat přetečení
bufferu
všechny souborové
funkce lze použíti pro std. v/v
Knihovní funkce C
<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, ungetcFILE, 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, ...
... a mnoho mnoho dalších
C++ - vše v prostoru jmen std
printf
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
fprintf( FILE*, sprintf( char*,swprintf( wchar_t*,_snprintf( char*, int n,_scprintf(...
printf(":%c:%6d:%08X:%7.3f:%7.3f:\n", 'a', 17, 1234, -1234.56, -1234.5678);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:▫▫-1234.560:▫▫-1234.568::ahoj:▫▫ahoj:ahojbabi:ahoj▫▫:aho▫▫▫:aho::▫▫▫▫17:aho▫▫▫::A::78263451:
místo width a/nebo precision znak *hodnota následujícího parametru
Unhandled exception at 0x0041470c :Access violation reading location
0x00000061
width: min. počet míst na výstupuprecision: max. počet zpracovávaných znaků
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 makraraději vůbec nepoužívat, lépe inline
funkcetest na (ne)definovanost symbolu
obrana před vícenásobným #includeviz Spojování modulů - #ifndef
test konstantního výrazu - #if sizeof( int) == 4
'ouvozovkování' - #abc "abc"
spojení identifikátorů - a##b ab
x.c
double A() { return B( 7);}
Spojování modulů – problém hlaviček funkcí
error:Undefined 'B'
y.c
double B() { return 3.14;
}
x.cdouble B();double A() { return B();}
Spojování modulů – externí deklarace
externí deklarace
y.c
double B() { return 3.14;
}
Spojování modulů – nekonzistence
C: nedefinované chování
C++: linker error
x.cdouble B();double A() { return B();}
y.c
int B( int q) { return q+1; }
x.obj
import Bexport A
y.obj
export Bapp.exe
nekonzistence funkce B
(počet a typy parametrů, návratová hodnota)
Spojování modulů – header
x.c#include "y.h"double A() { return B( 7);}
y.c
int B( int q) { return q+1; }
y.h
int B( int q);
int B( int q);double A() { return B( 7);}
preprocesor
možná nekonzistence
Spojování modulů – řešení
x.c#include "y.h"double A() { return B( 7);}
y.c#include "y.h"double B() { return 3.14;
}
y.h
int B( int q);
int B( int q);double A() { return B( 7);}
int B( int q);double B() { return 3.14;
}
error:Redefinition of 'B'
y.obj
export c
Spojování modulů – definice dat
x.c#include "y.h"double A() {}
y.c#include "y.h"int c;
y.h
int c;
int c;double A() {}
int c;int c;
x.obj
export c export A
linker error: Duplicate symbol 'c'
y.obj
export c
Deklarace vs. definice
x.c#include "y.h"double A() {}
y.c#include "y.h"int c;
y.h
extern int c;
extern int c;double A() {}
extern int c;int c;
x.objimport c export A
Deklarace
Definice
Deklarace
Definice
Spojování modulů - typy
x.c#include "y.h"double A() { return C; }
y.c#include "y.h"
enum T C;
y.henum T { P, Q};extern 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;
Definice nového typu Deklarace proměnné tohoto typu
Spojování modulů - duplicita typů
x.c#include "y.h"#include "z.h"double A() { return C+D; }
t.henum T { P, Q};
enum T { P, Q};extern enum T C;
enum T { P, Q};extern enum T D;
double A() { return C + D; }
y.h#include "t.h"extern enum T C;
z.h#include "t.h"extern enum T D;
error:Type redefinition: 'T'
Přes y.h a z.hje t.h vložen dvakrát
Spojování modulů - #ifndef
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
#ifndef T_H_#define T_H_enum T { P, Q};#endifextern enum T C;
#ifndef T_H_#define T_H_enum T { P, Q};#endifextern enum T D;
y.h#include "t.h"extern enum T C;
z.h#include "t.h"extern enum T D;
symbol již definován
nepřekládá se
není-li symbol definován ...
definice nového symbolu (makra)
vlastní headery obkládejte ifndefy
Uživatelské knihovny
Uživatelské
.h
Standardní
.h
KompilátorUživatelské
.c
Přeložené
.obj
Linker Spustitelný soubor
.exe
Standardní
.obj
Standardní .lib
Knihovní
.obj
Knihovna .lib
Knihovní
.c
KompilátorLibrarian
Privátní
.h
Veřejné
.h
Dynamicky linkované knihovny
Uživatelské
.h
Standardní
.h
KompilátorUživatelské
.c
Přeložené
.obj
Linker Spustitelný soubor
.exe
Standardní
.obj
Standardní .lib
Stub
.obj
Stub .lib
Knihovní
.c
KompilátorLinker
Privátní
.h
Veřejné
.h
Knihovna
.dll
Loader
Programování není 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ů'
Softwarové inženýrstvíInformační systémy
Programování ≈ výroba software
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í
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
#ifdef _MSC_VER // Microsoft typedef __int64 INT64; const char delimiter = '\\'; #else typedef long long INT64; #ifdef UNIX const char delimiter = '/'; #else const char delimiter = '\\'; #endif#endif
int x; char * p = (char *)&x;struct { char a; int b; } S; fwrite( &S, 1, sizeof( S), fp);
Ideál: Program přenositelný bez úpravy zdrojového textu
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 I/O zařízeními není možná 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)
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í Knihovny mohou změnit logiku systémových volání
Soubory: Buffering, překlad znakových sad, statefull/stateless
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")
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
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
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
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á ihnedDereference neinicializovaného ukazatele způsobí pád při odevzdání zápočťáku
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)
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 */
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)...
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);
101 mouder
Organizační záležitosti1. Kompilujte bez varování2. Používejte automatizovaný systém kompilace3. Používejte systém pro správu verzí4. Investujte do čtení kóduDesign Style5. Jedna entita jedna zodpovědnost6. Korektnost, jednoduchost a čitelnost především7. Používejte škálovatelné algoritmy a konstrukce8. Neoptimizujte předčasně9. Neoptimizute pozdě10. Minimalizujte lokální a sdílená data11. Skrývejte informace12. Naučte se správně využívat vlákna13. Každý zdroj by měl patřit nějakému objektuCoding Style14. Kompilační chyby jsou lepší než běhové15. Používejte const16. Vyhýbejte se makrům17. Vyhýbejte se magickým číslům18. Deklarujte proměnné s nejmenším možným rozsahem platnosti19. 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 moduly22. Minimalizujte závislosti, vyhýbejte se cyklickým závislostem23. Hlavičkové soubory by měly být samostatné24. Používejte ochranu vícenásobného vkládání #include
Sutter, Alexandrescu:C++ 101 programovacích
technik (C++ Coding Standards)
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++
Part II - C++
C++
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)
Třída a objekt
Třída (class) 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();
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
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í
BA
x y u
Dědičnost - reusabilitaA 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
Dědičnost - reusabilita
B A x y u
pb pa
A x y
pa
class A {public: int x, y;};
int f( A * pa){ return pa->x * pa->y;}
class B : public A {public: int u;};
int g( B * pb){ return pb->u * f( pb);}
volání kódu pracujícího s předkempřípadné konverze zajístí kompilátor automaticky
Dědičnost - polymorfismus
C A t n u
seznam
B A t n x y
B A t n x y
enum Typ { T_B, T_C };class A {public: Typ t; A * n;};A * seznam;
class B : public A {public: int x, y;};
class C : public A {public: int u;};
seznam objektů se společným předkem
Dědičnost - polymorfismus
C A t n u
pb pa
seznam
B A t n x y
B A t n x y
enum Typ { T_B, T_C };class A {public: Typ t; A * n;};A * seznam;
class B : public A {public: int x, y;};
class C : public A {public: int u;};
A * pa = seznam->n->n;
if ( pa->t == T_B ){ B * pb = (B *)pa;}
odkaz na objekt odvozené třídyzodpovědnost programátora za korektnost
Násobná dědičnost
B A
u
x y
C A x z
B A x yC A x z
D
A xclass 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čnostA * ap = (B *)dp; // zjednoznačněnídp = (D *)ap; // explicitní přetypování
Virtuální dědičnost
B A
u
xy
C A xz
B A xyC
zD
A xclass 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
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
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 relationshipjezevčík JE pespes JE zvíře
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ě...
KompoziceSkládání velkých objektů z malýchC++: Třída s datovými položkami
class Letadlo { ... Motor motor[N];};
class Letadlo : public Motor {};
class Letadlo : public Motor, public Motor {};
class Tlacitko { ... Icon ico;};
class ZamackavaciTlacitko{ ... Icon icoOn; Icon icoOff;};
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... Kompozice?
Ne: Nerodí se zároveň
Tlačítko Start není potomkem Windows
DelegacePřevedení funkčnosti na jiný objektC++: Ukazatel
class JM1 { ... Myslivec* mysl; Jezevcik* jez;};
class JM1 : public Myslivec, public Jezevcik {};
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
Společný abstraktní předek
class Obratlovec {};class Ryba : public Obratlovec {};class Savec : public Obratlovec {};class Obojzivelnik : public Obratlovec {};
class Mlok : public Ryba, public Savec {};
class Mlok : virtual public Ryba, virtual public Motor {};
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í
Potomka lze přiřadit do předka (platí i pro ukazatele)Předka NELZE přiřadit do potomka (platí i pro ukazatele)
Kompatibilita předka a potomka
pes umí jíst, brouk neumí štěkat
azorclass zvire {};class pes : public zvire {};zvire pytlik, *pz;pes azor, *pp;
pytlik = azor;*pz = &azor;
azor = pytlik;*pp = &pytlik;
stav
žaludek
pytlik
žaludek
stav
žaludek žaludek
pytlikazor
???nelze
!
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();
Metoda (member function)
this->x
Každá metoda dostane 'tajný' parametr this - ukazatel na
objekt
A:: => skrytýparametr A* this
Metoda (member function) = funkce uvnitř třídy
implementace (tělo) metody
deklarace (hlavička) metody
volání metody
chráněné položky
přístupné pouze
metodám třídy
veřejné položky přístupné komukoliv
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)
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;
}
int A::f()
{
return x * y;
}
class A {
public:
int x, y;
int f();
};
Tělo vně třídy Ekvivalent externí deklarace a definice globální
funkce Tělo v .cpp souboru
.h .cpp
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í
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
class A {private: int x, y;public: int f() { return x * y; }};
class B : public A {private: int z;public: 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();
Metody základních a odvozených tříd
automatická konverze na ukazatel na předka
volání metody základní třídy
A::f()
volání metody odvozené třídy
class A {private: int x, y;public: int f() { return x * y; }};
class B : public A {private: int z;public: int f() { return z; }};
B ob;A * pa = & ob;B * pb = & ob;pa->f(); // A::fpb->f(); // B::fpb->A::f(); // A::fpb->B::f(); // B::fpa->A::f(); // A::f// pa->B::f(); nelze
Zakrývání metod – compile time binding
volání odpovídající metody podle typu
objektu
stejná metoda jako předka - zakrývání
kvalifikované volání – explicitně určím která metoda
se má volatnelze (takhle) kvalifikovaně
volat funkce odvozených tříd
Implicitní parametry
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)
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ě
Volá se stále stejná funkce int fce( int, int,
int)
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);
Přetěžování funkcí
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 je definována svým identifikátorem a počtem a typem parametrů
Funkce se stejným identifikátorem ale
různým počtem parametrů
Správná funkce podle počtu a typů
skutečných parametrů
Funkce se stejným počtem
ale různým typem parametrů
Přetěžování funkcí
int min( int a, int b){ return a < b ? a : b; }
double min( double a, double b){ return a < b ? a : 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); // chybaf( 1.0, 2.0); // chyba
přesná shoda
levnější varianta
přesná shoda
obě varianty stejně drahé
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
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;
void f( int & u);
int & g();
Reference
int data;
int * ukazatel = & data;
* ukazatel = 2; // data = 2
int data2;
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
Reference
Novinky související 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 x;
return x;
}
int * p = 0;
int & r = * p;
if ( ! & r ) // ...
class bod{private: int x, y;
public: … bod operator+( const bod&); bod operator=( const bod&);};
bod a, b, c;c = a + b;
Přetěžování operátorů
přetížení operátoru +
a + b a.operator+(b)
a = b a.operator=(b)
c.operator=( a.operator+( b));c.assign( a.add( 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);}
Těla metod
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,
např.: ++a nemusí být ekvivalentní a=a+1 a[b] nemusí být ekvivalentní *(a+b) ani b[a]
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ů Pro výběr správné varianty platí stejná pravidla, jako pro přetížené funkce
Předefinování operátorů se provádí definováním metody se speciálním jménem operatorxxx ve třídě (prvního operandu), pro kterou má být operátor definován
Některé operátory je možno definovat i jako globální funkce s týmž speciálním jménem
Operátory, které jsou metodami, jsou s výjimkou operátoru přiřazení dědičné a smějí být virtuálníclass bod
{ bod operator+( const bod&);};
Bod& operator+( const bod&, const bod&);
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::operator xxx( C) A B::operator xxx( const C &) A B::operator xxx( const C &) const
A operator xxx( B, C) A operator xxx( B &, C &) A operator xxx( const B &, const C &)
A B::operator []( C)A B::operator []( C &) A B::operator []( const C &) const
není zde operator=speciální metoda
třídy
speciální operátory -> a
() později
Přetěžování operátorů - unární operátory
unární operátory + - * & ~ ! a prefixové operátory ++ --
lze předefinovat jako globální funkce nebo metody
postfixové operátory ++ a --lze předefinovat jako globální funkce nebo metody
A operator xxx( B) A operator xxx( B &) A operator xxx( const B &)
A B::operator xxx() A B::operator xxx() const
A operator xxx( B, int) A operator xxx( B &, int) A operator xxx( const B &, int)
A B::operator xxx( int) A B::operator xxx( int) const
fiktivní operand typu int
++X
X++
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() {}};
Konstruktor, seznam inicializátorů
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
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
vždy preferujte seznam inicializátorůparametrický konstruktor vs. implicitní konstruktor +
přiřazení
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)
class A { A();}
class A { A(const A&);}
Konstruktory
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
alokace pole - pouze implicitní konstruktor
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())
Konstruktory a dědičnost
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); // errorA* pa = new B; // B::B()// B* pb = new A;// error
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) {}};
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ějepřed konstrukcí celku,
destrukce součástí po destrukci celku
implicitní konstruktor
předka
Vznik a zánik objektů – lokální proměnné
Lokální proměnné
pro lokální objekt je kostruktor vyvolánpř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 jeho destruktor
deklarace objektu může být kdekoliv uvnitř těla funkce nebo složeného příkazu
rozsah platnosti objektu je 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; x = 1; A a; if( x) { B b; x = 2; } if( !x) return; x = 3;}
A::~A
A::~A
A::A
B::B
B::~B
Vznik a zánik objektů – parametry předávané hodnotou
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
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ý 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!
Vznik a zánik objektů – dynamicky alokované objekty
Dynamicky alokované objekty pro dynamickou alokaci slouží operátory new a delete funkce malloc a free s nimi nelze kombinovat
v C++ raději 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ří:
C++ 98 a novější: vyvolá se výjimka std::bad_alloc starší C++: konstruktor se nevyvolává a 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::A()
A::A( const A&)
A *p, **q;p = new A[ 20];q = new A*[ 20];/* ... */delete[] p;delete[] q;
pole ukazatelů
Vznik a zánik objektů – dočasné objekty
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 na tomto objektu použití tohoto 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)
A a, b;a = A();b = A( 1);return A( 2);
konstruktor, kopie, destruktor
konverzní konstruktorz int vyrobí A
použití jako návratové hodnoty
Vznik a zánik objektů – příklad
A f( A y){ return y;}
A p, q;
p = f( q);
kolik konstruktorů a jakých vyvolá zpracování této
řádky?
Vznik a zánik objektů – příklad
A f( A y){ return y;}
A p, q;
p = f( q);
kolik konstruktorů a jakých 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ě?
Vznik a zánik objektů – hloupý kompilátor
A f( A y){ return y;}
A p, q;
p = f( q);
0. pro návrat z funkce se alokuje místo pro dočasnou proměnnou temp2
1. q ypředání hodnoty skutečného parametru do funkcecopy konstruktor
2. y temp1'spočítá' se hodnota výrazu pro return a vytvoří se dočasný objektcopy konstruktor
3. temp1 temp2předání návratové hodnoty ven z funkcecopy konstruktor
4. temp2 ppřiřazení návratové hodnoty funkce proměnnéoperátor přiřazení
Vznik a zánik objektů – chytrý kompilátor
A f( A y){ return y;}
A p, q;
p = f( q);
1. q ypředání hodnoty skutečného parametru do funkcecopy konstruktor
2. y temp'spočítá' se hodnota výrazu pro return a vytvoří se dočasný objektcopy konstruktor
4. temp ppřiřazení návratové hodnoty funkce proměnnéoperátor přiřazení
odlišné chování potomků – pozdní vazba (late binding)
Polymorfismus
najde něco v přírodě
zvíře pes pitbul
člověk
jez jez jez
jez
sní maso sní hodně masa
jde do restaurace
Polymorfismus - motivace
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();
Tohle není polymorfismus !Funkce je známa v době
překladu
'normální' vlastnost třídzakrývání metod
Při překladu je známoze které třídy se volá
metoda
Každá třída má vlastní implementaci (tělo)
metody jez
Polymorfismus – pozdní vazba
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();
Chci pokaždé se zavolat jinou metodu
Rozlišení metody se musí dít za běhu
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 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
chtěl bych, aby se volaly 'správné' metody
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á
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
By
A x
fg
A::f
A::g
A x
fg
B::f
class A {public: int x; virtual int f() { return 1; } virtual int g() { return 2; }};
class B : public A {public: int y; virtual int f() { return 9; }};
Virtuální metody - implementace
class A {public: int x; virtual int f() { return 1; } virtual int g() { return 2; }};
class B : public A {public: 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();
Volání virtuálních metod
class A {public: virtual f();};class B : public A {public: virtual f();};
A a; B b; A * paa = &a;A * pab = &b;B * pbb = &b;// B * pba = &a; nelze!
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
pozd
ní
vazb
a
b B::f
paa
A::fapab
pbb
kvalifi
kované
volá
ní
zakrývání metod
Virtuální metody a konstruktory a destruktory
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
};
nejdřív se zavolá konstruktor
předka
nejdřív se provede kód destruktoru, pak se zavolá
destruktor předka
v konstruktoru a destruktoru se vždy volá metoda
vytvářeného/rušeného objektu
určí se za běhu podle skutečného typu
objektu
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ů.
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 {
public:
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?
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 {
public:
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řídus nevirtuálním destruktorem
je NEDEFINOVÁN
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 {
public:
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
Virtuální destruktory
nejdříve se provede tělo ~B, potom se
zavolá ~A
Má-li být třída předkem určeným pro ukazatele na
dynamicky vytvářené potomky, musí mít virtuální destruktor.
Č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... Takovou třídu nelze instanciovat Lze používat ukazatele na tuto třídu a vyvolávat metody této třídy Často neobsahuje žádné datové položky
B
A
f
A
f B::f
class A {public: virtual int f() = 0;};
class B : public A {public: virtual int f() { return 9; }};
Čistě virtuální metody, abstraktní třída
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;};
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; }};
pure virtual function
abstraktní třídaspolečné rozhraní
Nutný virtuální destruktor
abstraktní třída nelze vytvořit
objektspolečný předek
Statická data
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();
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
nekonstantní statická proměnná
strukturovaná statická konstanta
statická konstanta
Statické metody
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);
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{
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);
}
Přehled druhů metod
metodamember function
virtuální metodavirtual function
čistě virtuální metodapure-virtual function
statická metodastatic member
function
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
zakázáno
register zakázáno zásobník,přednostně v registru
zakázáno
static statická alokace,bez exportu
statická alokace(jediná instance)
statická alokace(jediná instance), export
extern odkaz na globální definici, bez alokace
odkaz na globální definici, bez alokace
zakázáno
funkce globální uvnitř funkce uvnitř třídy
bez spec. export mimo modul zakázáno metoda - implicitní this
virtual zakázáno zakázáno virtuální metoda
static bez exportu mimo modul zakázáno bez this, export
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
private inheritance
public inheritance - Is-A 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
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(); }};
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. classpublic: 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 classpublic: void push( int* p) { s.push(p); } int* pop() { return (int*)s.pop(); }private: Stack s;};
class Slon {};class SlonStack {public: void push( Slon* p) { s.push(p); } Slon* pop() { return (Slon*)s.pop(); }private: Stack s;};
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 {public: void push( Slon* p) {push(p); } Slon* pop() { return (Slon*)Stack::pop(); }};
Spřátelené funkce a třídy - friend
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;}
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é - pokud možno nepoužívat
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
spřátelená třída
spřátelená funkce
Pozor! Nejde o metodu X!
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)
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
Konverzní operátor
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()
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*
Konverzní operátor
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
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!
complx::complx( int _re, int = 0)complx operator +(const complx &,const complx &)
complx::operator int()operator+(int, int)
Prostory jmen (namespaces)
namespace aa { int p; int f1( int x) { return x + p; }}
namespace aa { 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));
zapouzdření identifikátorůprevence kolizí (velké projekty, knihovny)stejné identifikátory v různých prostorech jmenprostor jmen se může opakovaně otevírat a zavíratstandardní knihovny – namespace std
definice prostoru jmen
přístup ze stejného prostoru
definice funkce mimo prostor jmen
direktiva using - rozbalení std
znovuotevření prostoru aa
deklarace using - pouze ident.
Později:argument dependent lookup Koenig lookup
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ů
INxAN DN
dAN INxAN
S
Polymorfní datové struktury - kostra tříd
class Seznam {public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n;};
int main(int argc, char** argv){ Seznam s; s.append( new .... ); s.append( new .... ); s.append( new .... ); s.print(); return 0;}
class AbstractNum {public: virtual void print()=0; virtual ~AbstractNum() {}};
abstraktní předekumí existovat a vytisknout
se
virtuální destruktor!
přidávání dynamicky vytvořených konkrétních
typů
Polymorfní datové struktury - implementace metod
class Seznam {public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n;};
// konstruktorSeznam::Seznam(){ for(int i=0; i<MAX; ++i) pole[i]=0; n=0;}// destruktorSeznam::~Seznam(){ for(int i=0; i<n; ++i) delete pole[i]; n=0;}// tisk seznamuvoid Seznam::print(){ for(int i=0; i<n; ++i) pole[i]->print();}// pridani prvku do seznamuvoid Seznam::append(AbstractNum* p){ if (n<MAX) pole[n++]=p;}
každý prvek ví jak se vytisknout
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;};
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();
konkrétní datové typyimplementují vlastní metody jednotného
rozhraní
kontejner obsahuje různé typy
... a všechny vytiskne
konkrétní datové typyimplementují vlastní metody jednotného
rozhraní
Polymorfní datové struktury - konstruktor const položek
class IntNum : public AbstractNum {public: IntNum(int x_) { x=x_; }private: const int x;};
Požadavek: co když chci zakázat měnit hodnotu prvků
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
Polymorfní datové struktury - přiřazení
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;}
Problém: přiřazení seznamů
Je to korektní kód?
Polymorfní datové struktury - přiřazení
Problém: přiřazení seznamů
Spadne to! Kde?
v destruktoru ~Seznam Proč? Navíc:
memory leaks
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!
problém je v s2 = s; v Seznam není operator=kompilátor si ho vyrobí automatickyokopíruje datové položky a ukazatele !!!destruktor s2 dealokuje prvkydestruktor s znovu odalokuje prvkybloky už ale neexistují !
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 privateznemožní přiřazení
seznamů
stačí pouze deklarace (bez těla)
nikdo ho nemůže zavolat
je to už teď konečně korektní ??
Polymorfní datové struktury - copy konstruktor
Není! copy konstruktor!
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;};
je to už teď konečně korektní ??
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;}
oprator= a copy konstruktorby se měly chovat stejně!
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 ???
Polymorfní datové struktury - kopie prvků
class Seznam {public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); Seznam& operator=(const Seznam&);private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n;};
je to správně ??
Seznam& Seznam::operator=( const Seznam& s){ for(int i=0; i<s.n; ++i) pole[i] = s.pole[i]; n = s.n; return *this}
okopíruji všechny prvky
Polymorfní datové struktury - úklid starého stavu
Je to správně? Není !!
nezruší se předchozí odkazy! memory leaks
je to už teď správně ??
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}
nejdříve zruším všechny prvky cílového kontejneru
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
je to už teď správně ??
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}
dynamická alokace nového prvku
přímá konverze odvozené třídy na AbstractNum&
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
je to už teď správně ??
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}
class AbstractNum {public: virtual void print() {}; virtual ~AbstractNum() {}};
psychicky zdeptaný programátor:
tak tu abstraktnost odstraníme!
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
Polymorfní datové struktury - vytvoření správných typů
INxAN
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ě ??
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
Polymorfní datové struktury - klonování
class AbstractNum {public: virtual AbstractNum* clone() const =0; virtual void print()=0; virtual ~AbstractNum() {}};
je to už teď správně ??
class IntNum : public AbstractNum {public: virtual AbstractNum* clone() const { return new IntNum(*this); } IntNum(int x_) : x(x_) {}private: const int x;};
Seznam& Seznam::operator=( const Seznam& s){ ... for(int i=0; i<s.n; ++i) pole[i] = s.pole[i]->clone(); ...
jednotné rozhranína klonovací funkce
musí být typu AbstractNum*jinak by to nebyla stejná virt.
metoda
IntNum
AbstractNum
případné posunutí ukazatelů řeší automaticky mechanismus virt.
funkcí
INxAN
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!
je to už teď správně ??
Seznam& Seznam::operator=( const Seznam& s){ if( this == &b) return *this; ...
rovnost ukazatelů stejný objekt
this &s
... to be continued
Jazyk 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
OOP kánonické formy tříd, abstraktní datové typy, polymorfní typy counted pointers, mělké vs. hluboké kopie návrhové vzory (design patterns) – zprávy, obálkové třídy, forwarding, ... hlouběji o objektovém návrhu, reusabilitě, efektivitě implementace
101 mouderOrganizational 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 Style5. 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 Style14. 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.
Sutter, Alexandrescu:C++ 101 programovacích
technik (C++ Coding Standards)
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 Inheritance32. 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).
101 mouderConstruction, Destruction, and Copying47. 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 Modules57. 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 Genericity64. 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 Exceptions68. 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.
101 mouderSTL: Containers76. 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: Algorithms83. 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 Safety90. 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.