NPRG041Programování v C++
Programování v C
David BednárekFilip Zavoral
Vývoj◦ 1970-3 první verze C, společný vývoj s UNIXem, jádro v C◦ 1978 Kerninghan, Ritchie: The C Programming Language◦ 1983 Rick Mascitti: poprvé název C++◦ 1985 Stroustrup: The C++ Programming Language◦ 1998 ISO/ANSI norma C++◦ 1999 ISO/ANSI norma C - 'C99'◦ 2011+ C++11, C++14, C++17
zásadní rozšíření jazyka - lambda, r-values, generic prog., ...
Proč C v 21. století◦ jádra OS, drivery, knihovny◦ GPU, many-core◦ embedded devices, 'pračky'◦ údržba sw
C vs. C++
... neboli co v C není
OOP◦ classes, inheritance, member functions, constructors and
destructors, virtual functions, access control, pointers to members, static members
Syntax◦ templates, smart pointers, exceptions, references, overloading,
default arguments, namespaces, new/delete, casting, friend, inline, RTTI, auto, lambda, rvalues, ..., ..., ...
Libraries◦ containers, iterators & algorithms◦ strings◦ streams
C++ jako ostrá nadmnožina C
bool
struct & enum tags
implicitní konverze - enum, void *
main - return 0;
Odlišnosti C a C++
struct S { int x;};
S s;
struct S { int x;};
struct S s;
typedef struct S_ { int x;} S;
S s;
bool b = true; typedef enum bool_ { FALSE, TRUE} bool;bool b = TRUE;int b2 = a > 1;_Bool b = TRUE; C99
enum E { NULA };enum E e = NULA;int x = e;int* p = malloc(sizeof( int));
enum E { NULA };E e = NULA;int x = (int)e;int* p = (int*)malloc(sizeof(int));
int main(){ ...}
int main(){ ... return 0;}
C ⊈ C++
Pole a ukazatele, aritmetika ukazatelů
T a[n]; pole prvků typu T T *p = &a[i]; ukazatel p ukazuje na prvek pole p+j; p+=j; ++ppřičtení - posun o j prvků v rámci pole p-j odečtení - posun zpět
int a[5]; int *p; a[2] = 20; p = &a[2]; a[0] = *p - 15; ++p; *p = 30;
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
stejně jako iterátory
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
◦ 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ů
Řetězec - pole znaků (char) zakončené nulou◦ konvence, knihovny
"Ahoj"
X proměnná 'X' znaková konstanta - celočíselná hodnota "X" řetězec - ukazatel
Řetězce
'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, ...)
char s1[] = "Uno"; const char *s2 = "Due";
puts( "Uno");
Řetězcové proměnné a konstanty
'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 };
Délka řetězce – různé 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 ukazatel
více inkrementací prázdné tělo
nezapomenout na ';' !!
složitější podmínka:test nenulovostiinkrementace
ukazatelewhile(a!=0)
while(a)podmínka je splněnapokud je nenulová
rozdíl ukazatelů = počet prvků mezi nimi
pozor na ± 1 !
Ř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ístafunkce 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 \0ptr
neinicializovaný !!!
Naprosto chybné řešení◦ Nekontroluje přetečení pole buf◦ Vrací odkaz na lokální proměnnou, která v okamžiku návratu zaniká
Vracení řetězců - lokální proměnná
char * cele_jmeno( const char * jm, const char * prijm){ char buf[ 100]; strcpy( buf, jm); strcat( buf, " "); strcat( buf, prijm); return buf;}
string cele_jmeno( const string& jm, const string& prijm){ return jm + " " + prijm;}
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
Vracení řetězců - statická proměnná
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)) )
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
◦ Většina C knihovenale funguje podobně
Vracení řetězců - parametr
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);}
Vracení řetězců - 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 konstrukcí
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 konstrukcí
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 konstrukcí
použitím typedefse výrazně zpřehlední kód
OOP v Cclass A {public: A() : x_(1) {} int f( int y) { return x_ + y; }private: int x_;};
A* a = new A;a->f(2);
typedef struct A_ { int x_;} A;
int A_init( A* a) { a->x_ = 1; }int A_f( A* a, int y) { return a->x_ + y; }
A* a = malloc( sizeof( A));A_init(a);A_f(a,2);
explicitní this
explicitní prefix nebo možnost kolize
nemožnost přetížení
beztypová alokace
absence ochrany
absencekonstruktorua destruktoru
explicitní inicializace
Pozdní vazba v Cclass A { virtual int f(void) { return 1; }};
class B : public A { virtual int f(void) { return 2; }}
A* a = new B;a->f();
typedef int fnc(void);typedef struct A_ { fnc* f_;} A;void A_init( A* a, fnc* f) { a->f_ = f; }int A_f( A* a) { return (*a->f_)(); }int A_f_body() { return 1; }int B_f_body() { return 2; }
A* a = malloc( sizeof( A));A_init( a, B_f_body);A_f( a);
VMT konstruktor inicializace
VMT
virtual method
'odvozená metoda'
zavolá se B_f
ruční inicializace
Variabilní argumenty
int sum( int num, ...) { int rv = 0; va_list argptr; va_start( argptr, num); for( ; num > 0; num--) { rv += va_arg( argptr, int); } va_end( argptr); return rv;} int answer = sum( 4, 4, 3, 2, 1);
opravdu '...'speciální typ
inicializace
přístup
ukončení
#include <stdarg.h>typedef va_list ????va_start( va_list argptr, last_parm);va_arg( va_list argptr, type);va_end( va_list argptr );
korektnost parametrů musí zajistit uživatel
funkce
Náhrada v C++: Variadic Templates
typ !
Parametry příkazové řádkyC:\> 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
vector<string> arg( argv, argv+argc);
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á funkcep r g . e x e \0- n \0- w \0a . t x t \0b . t x t \0
0
argv
Zpracování 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í 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 ...
Zpracování příkazové řádky
int printf( const char * format, ...);◦ % [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
printf
fprintf( FILE*, sprintf( char*,swprintf( wchar_t*,_snprintf( char*, int n,_scprintf(...
místo width a/nebo precision znak *
hodnota následujícího parametru
width: min. počet míst na výstupuprecision: max. počet zpracovávaných
znaků
#include <stdio.h>printf( "Ahoj %s dnes je %d.%d.", "Babi", 8, 1);
shodu formátu a parametrů musí zajistit programátor
neshoda: nedefinované chování
printf
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:●
Unhandled exception at 0x0041470c :Access violation reading location
0x00000061
#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 souborytyp 'soubor'
(ukazatel na strukturu)otevření souborukonvence dle OS
pozor na '\\' !!!
konstanta v stdio.hrůzná od všech možných dat
mode
soubor ex.
soubor neex.
seek
r R Error 0w Del, W W 0a W W Endr+ R/W Error 0w+ Del, R/W R/W 0a+ 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ý 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
Textové vs. binární 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_ENDint fflush( FILE *fp);
Funkce pro práci se soubory
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ýstupFILE* stand. otevřený na čtení/zápis před vstupem do mainFILE *stdin;FILE *stdout;
getchar() getc(stdin)putchar(c) putc(c, stdout)
FILE* fp = stdout;if( ...) fp = fopen( "...", "r");c = getc( fp);
Souborový vs. standardní v/v
jednotný zápisna std výstup nebo
do souboru
Nikdy nepoužívat!Nelze ohlídat přetečení bufferu
všechny souborové funkce lze použít
i pro std. v/v
<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, freadfwrite, 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, ...
Knihovní funkce C
... a mnoho dalších<assert.h> <errno.h><limits.h> <locale.h>
<stdarg.h> <setjmp.h>
C++ namespace std
#define _ F-->00||F-OO--;int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO(){ _-_-_-_ _-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-__-_-_-_-_-_-_-_-_-_-_-_-_-_-_-__-_-_-_-_-_-_-_-_-_-_-_-_-_-_-__-_-_-_-_-_-_-_-_-_-_-_-_-_-_-__-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_ _-_-_-_}
Co (a proč ) dělá tento program?
Je v C, nikoliv v C++
vlastní C moduly◦ obj, lib, dll/so◦ jak linkovat C a C++ moduly ◦ jak dělat společné C/C++ headery
cizí C knihovny◦ jak z C++ volat C knihovny◦ callback z C knihoven do C++◦ mandlování, volací konvence◦ dynamicky linkované knihovny
Interoperabilita
Překlad více modulů
.c
.h .h
CC .obj Link .exe
.obj.obj.obj .obj.obj.lib
.obj.obj.obj
kompilace jednoho modulu
knihovnyknihovní headeryvlastní headery
.cpp
.c.cpp
další moduly
Vytvoření vlastní knihovny
.c
.h .h
CC .obj Lib .lib
.obj.obj.obj
kompilace jednoho modulu
knihovní headeryvlastní headery
.cpp
.c.cpp
další moduly
C++ exe / C lib
CPPC .obj Link .exe
.obj .lib
zdrojový text / překladač C
exe.cpp
lib.c
lib.h CC Lib
zdrojový text / překladač C++
C++ exe / C lib
CPPC .obj Link .exe
.obj .lib
exe.cpp
lib.c
lib.h CC Lib
error LNK2019: unresolved external symbol ◦ "int __cdecl lib_fnc(int)" (?lib_fnc@@YAHH@Z) ◦ referenced in function _main what the ... ...
hell???
mangling◦ mandlování◦ znetvoření◦ name-decoration
syntaktická a sémantická informace o symbolu◦ zjednoznačnění identifikátoru◦ proměnná / funkce / operator / metoda◦ typy a typové konstrukce parametrů a návratové hodnoty◦ třída, další atributy (const, volatile, ...)◦ volací konvence
formát jednotně nedefinovaný◦ závislý na platformě, překladači, ...◦ obecně nepřenositelné
Mandlování int a;int a( void);int a( int, int);class a {};class a { int a; };class a { int a( int); };
C++ exe / C lib/* pureclib.c */#include "pureclib.h"
int lib_x;
int lib_fnc( int x){ return old_x;}
/* pureclib.h */
#ifndef PURECLIB__H_#define PURECLIB__H_
extern int lib_x;int lib_fnc( int x);
#endif
// cppexe.cpp#include "pureclib.h"
int main(....){ int i = lib_fnc( 1);}
CPPC CC různé překladačerůzné jazyky
různá implementace
_lib_fnc ?lib_fnc@@YAHH@Z
Společné hlavičkové soubory/* pureclib.c */#include "pureclib.h"
int lib_x;
int lib_fnc( int x){ return old_x;}
/* pureclib.h */
#ifndef PURECLIB__H_#define PURECLIB__H_
#ifdef __cplusplusextern "C" {#endif
extern int lib_x;int lib_fnc( int x);
#ifdef __cplusplus}#endif
#endif
// cppexe.cpp#include "pureclib.h"
int main(....){ int i = lib_fnc( 1);}
CPPC CC
symboly C
_lib_fnc _lib_fncCPPC - definovanéCC - nedefinované
způsob implementace volání funkcí◦ registry vs. zásobník◦ zachovávání registrů◦ pořadí předávání parametrů◦ návratová hodnota◦ příprava a úklid zásobníku
konkrétní konvence◦ není součástí normy - rozšíření
__cdecl - default for C and C++, varargs __stdcall - Win32 API functions __fastcall - arguments in registers, faster __thiscall - this __clrcall - C++/CLI, .Net, managed code
Volací konvence
mov eax, 1mov ebx, 2call ?f@@X
mov eax, [ebp+08] mov ebx, [ebp+04]...
f( 1, 2);
C++ callback
/* pureclib.c */#include "pureclib.h"
int lib_cb( int x, int (*cb_fnc)( int)){ return cb_fnc( x);}
/* pureclib.h */
#ifdef __cplusplusextern "C" {#endif
int lib_cb( int x, int (*cb_fnc)( int));
#ifdef __cplusplus}#endif // cppexe.cpp
#include "pureclib.h"
extern "C" int cpp_fnc( int x) { return x+1;}
int main() { lib_cb( i, cpp_fnc);}
CC očekává C funkci
extern "C" určuje i volací konvenci
callbackknihovní kód volá klientskou funkci
CC očekává C funkci
použití funkcí dodaných až za běhu není součástí normy
◦ použití na různých platformách ideově podobné ale nepřenositelné
◦ pomocí preprocesoru lze multiplatformní rozhraní ale netriviální
Windows◦ .dll◦ chová se jako .exe◦ vlastní zásobník, heap, standardní knihovny
Linux / Unix◦ .so◦ chová se jako .lib◦ balíček .o
Dynamicky linkované knihovny
more details:http://www.symantec.com/connect/articles/dynamic-linking-linux-and-windows-part-one...-part-two
Dynamicky linkované knihovny// dll.cpp
extern "C" __declspec(dllexport)int add( int a, int b) { return a + b;}
BOOL APIENTRY DllMain(....) { return TRUE;}
// exe_explicit.cpp HINSTANCE dll = LoadLibrary( TEXT("dll.dll"));if( dll == NULL) return 1;
typedef int dll_fnc(int, int);dll_fnc* add = (dll_fnc*) GetProcAddress( dll, "add");if( add == NULL) { FreeLibrary( dll); return 1;}
int result = add(1, 2);
FreeLibrary( dll);
// exe_import.cpp extern "C" __declspec(dllimport)int add(int a, int b); int result = add(1, 2);
explicit runtime linking
běžné volání
statické slinkovánís dll.lib
jen proxy, kód v .dll
load dll