Scaricare la presentazione
La presentazione è in caricamento. Aspetta per favore
1
Introduzione al linguaggio C++ 5 lezioni
Lunedì, Giovedì ore Alessandro Lonardo Davide Rossetti Pagina WEB Testi base: -Bjarne Stroustrup, Il Linguaggio C++, Addison-Wesley. -Brian W. Kernighan, Dennis M. Ritchie, Il Linguaggio C, Jackson libri.
2
Programma del corso Lezione 1 Lezione 2 Lezione 3 Lezione 4
Paradigmi di programmazione Dichiarazioni Tipi Costanti Lezione 2 Operatori Istruzioni Funzioni Header File Il Preprocessore Le librerie Lezione 3 Classi Interfacce ed implementazioni Caratteristiche delle classi Costruttori e distruttori Lezione 4 Classi derivate Classi astratte Ereditarietà multipla Controllo dell’accesso Memoria dinamica
3
Lezione 5 Overload di Operatori Template La Standard Template Library
argomenti non trattati a lezione
4
Paradigmi di Programmazione
Programmazione Procedurale Si definiscano le procedure desiderate; Si utilizzino gli algoritmi migliori. -Programmatore concentrato sull’algoritmo -Supporto fornito dai linguaggi: funzioni, procedure. (Fortran, Pascal, C...) -Il programma viene suddiviso in funzioni, ogni funzione realizza un algoritmo o una parte di esso. Es. double sqrt(double arg) { //codice per il calcolo della radice quadrata } void some_function() double root2 = sqrt(2.0); //...
5
Programmazione Modulare
Modulo = Dati + Procedure; Si decida quali sono i moduli necessari; Si suddivida il programma in modo che i dati siano nascosti nei Moduli. -Dati nascosti: nomi delle variabili, delle costanti e dei tipi sono resi locali al modulo. -Il linguaggio C consente l’impiego di questo paradigma attraverso Il concetto di unità di compilazione. Es. File stack.h: //dichiarazione della interfaccia per //il modulo stack di caratteri void push(char); char pop(); const int stack_size = 100;
6
File stack.cc (implementazione de modulo):
#include “stack.h” //usa l’interfaccia stack //static significa: simbolo locale //a questo modulo (file) static char v[stack_size]; //lo stack viene inizializzato vuoto static char* p = v; void push(char c) { //implementazione } char pop()
7
Uso del modulo stack di caratteri:
File bubu.cc: #include “stack.h” //usa il modulo stack void some_function() { push(‘y’); char c = pop(); assert(c == ‘y’); } -in questo file non si ha accesso alla struttura interna dello stack, è possibile utilizzare lo stack solo per mezzo delle funzioni esposte nell’interfaccia del modulo. -Il linguaggio C++ estende il supporto del C alla programmazione modulare attraverso l’uso delle classi.
8
Astrazione dei dati Si decida quali tipi si desiderano;
si fornisca un insieme completo di operazioni per ogni tipo. -estende il concetto di modulo al caso in cui siano necessari più oggetti di un certo tipo (come avrei fatto a dichiarare 2 stack?) -I linguaggi che supportano la programmazione modulare permettono l’astrazione dei dati. -Linguaggi come Ada, Java, C++... supportano il paradigma della astrazione dei dati. File complex.h: //dichiarazione del tipo numero complesso class Complex { double re, im; public: Complex(double r, double i) {re = r; im = i;} Complex(double r) {re = r; im = 0;} friend complex operator+(Complex, Complex); friend Complex operator-(Complex, Complex); friend Complex operator-(Complex);//unario friend Complex operator*(Complex, Complex); friend Complex operator/(Complex, Complex); };
9
File complex.cc: //implementazione del tipo complex //... Complex operator+(Complex a1, Complex a2) { return Complex(a1.re+a2.re, a1.im+a2.im); } File bubu.cc: //uso del tipo complex void some_function() Complex a(2.0, 1.0), b(3.14), i(0.0, 1.0), c; c = a*i+b; -L’uso del tipo complex definito dall’utente è del tutto analogo a quello dei tipi predefiniti. -Il tipo di dato astratto è una scatola nera. Il suo comportamento non può essere cambiato, se non ridefinendo il tipo. Questa è una limitazione significativa.
10
Esempio: un sistema grafico che gestisce cerchi, triangoli e quadrati.
-Esistono i seguenti tipi astratti: class Point {//...}; class Color {//...}; enum Kind {circle, triangle, square}; //rappresentazione di una forma class Shape { Point center; Color col; Kind k; public: point where() {return center;} void move(point to) {center = to; draw(); } void draw(); void rotate(int); }; -k è un “campo tipo” utile alle funzioni per determinare il tipo di forma su cui si lavora: void Shape::draw() { switch(k) { case circle: //disegna un cerchio case triangle: //disegna un triangolo case square: //disegna un quadrato }
11
Programmazione Orientata agli Oggetti
-Problemi: draw() (come le altre operazioni) deve conoscere tuttti i tipi di forme su cui si lavora. Se si introduce una nuova forma il codice di draw() dovrà essere modificato. 2. Non è possibile aggiungere nel sistema la gestione di una nuova forma se non si ha accesso al codice sorgente di ogni operazione. 3. Ogni modifica espone il sistema alla introduzione di bug su codice già sviluppato. -La sorgente di tutti questi problemi è la mancata espressione della distinzione tra le proprietà generali di una forma(ha un colore, ha una posizione, si può disegnare...) e le proprietà di una forma particolare (il cerchio ha un raggio, si disegna come un cerchio (!), ...). -L’espressione di questa distinzione in modo utile per la scrittura del codice rappresenta la Programmazione Orientata agli Oggetti -Il supporto che il linguaggio C++ offre a questo paradigma è il meccanismo della ereditarietà.
12
-Il concetto di forma più generale:
class Shape { Point center; Color col; //è sparito il fastidioso Kind public: point where() {return center;} void move(point to) {center = to; draw(); } virtual void draw(); //ora è virtual virtual void rotate(int); //ora è virtual }; -“virtual”: può essere ridefinito in una classe derivata -Una forma particolare: class Circle : public Shape int radius; void draw() {//disegna un cerchio!}; void rotate(int) {}//facile implementazione -La classe Circle è derivata (sottoclasse) dalla classe Shape. -La classe Shape è di base (superclasse) per la classe Circle.
13
Paradigma di programmazione orientata agli oggetti
Esempio di uso: funzione che prende un vettore di size forme e le ruota di angle gradi. void rotate_all(Shape v[], int size, int angle) { int i = 0; while (i<size) v[i].rotate(angle); i = i+1; } -l’elemento v[i]è in principio una forma qualsiasi, l’operazione di rotazione sarà quella che gli compete. -Nella fase di progettazione del software è necessario individuare la massima quantità di elementi in comune tra i tipi del sistema e rappresentare queste similitudini utilizzando classi di base comuni. Paradigma di programmazione orientata agli oggetti Si determini quali classi si desiderano; Si fornisca un insieme completo delle operazioni di ogni classe; Si espliciti ciò che hanno in comune per mezzo della ereditarietà
14
Dichiarazioni -Identificatore C++: sequenza di lettere e cifre, il primo carattere deve essere una lettera (o “underscore”, ‘_’). Non si possono usare keyword. Case sensitive. Buoni identificatori: Hello hello _bubu_ ApeMaia un_identificatore_molto_lungo var1 var2 Non sono accettati: 1var $pippo for lunghezza.massima -Dichiarazione di un identificatore: Prima dell’uso di qualsiasi identificatore bisogna specificare il Suo tipo: Char c; int count = 1; char* name = “ciccio”; Const double pi= float minus(float arg) { return -arg; } -Queste sono anche definizioni di identificatori: definiscono l’entità alla quale il nome si riferisce. Per le variabili è la quantità di memoria allocata, per le funzioni la loro implementazione, per le costanti il loro valore.
15
-le seguenti sono solo dichiarazioni:
extern float sqrt(float arg); extern int err_num; struct user; -Una dichiarazione ha effetto in generale in un sottoinsieme del programma (visibilità). int x; // x globale, visibile in tutto il pr. void f() { int x; //x locale, nasconde x globale x = 1; int x; //locale, nasconde la prec. Locale x = 2; } x = 3; int* p = &x;
16
Oggetto : zona di memoria
lvalue: espressione che fa riferimento ad un oggetto -Un oggetto viene creato all’atto della sua definizione e distrutto quando non è più visibile (anche i locali definiti static) int a = 1; void f() { int b = 1; //inizializzato ad ogni chiamata static int c = a; //ini. una sola volta cout << “ a = “ << a++ << “ b = “ << b++ << “ c = “ << c++ << endl; } int main() while ( a < 4 ) f(); Output: a = 1 b = 1 c = 1 a = 2 b = 1 c = 2 a = 3 b = 1 c = 3
17
Tipi il tipo specifica le operazioni che si possono compire sul dato
e la loro semantica Tipi fondamentali: void Tipi interi: bool char short int int long int Tipi floating point (reali): float double long double Tipi interi senza segno, valori logici, vettori di bit: unsigned char unsigned short int unsigned int unsigned long int Per esplicitare i tipi interi con segno: signed char signed short int signed int signed long int -se il tipo è omesso si assume int
18
-tipi interi e floating point diversi, diversa occupazione di memoria,
velocità di esecuzione... -Il linguaggio definisce solo queste restrizioni: 1==sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long) sizeof(float)<=sizeof(double)<=sizeof(long double) sizeof(I)==sizeof(signed I) == sizeof(unsigned I) Ad esempio, architettura IA32: bool 8 bit char 8 bit short 16 bit int 32 bit long int 32 bit float 32 bit double 64 bit long double 80 bit
19
Conversione tra i tipi -implicita: in generale si possono mescolare liberamente variabili di tipo diverso in una espressione.(non è un bello stile...) int i = 2; float f, g = 2.0; f = i * g - 4; -esplicita: float r = (float) 2; //cast float r = float(2); -promozioni Tipi derivati -definiti a partire da quelli base o user-defined per mezzo degli operatori di dichiarazione: * puntatore & reference [] array Esempio: int* pi; //tipo = puntatore ad int double& d; //tipo = reference a double float v[10]; //tipo = vettore di 10 float
20
-Un altro modo di introdurre un tipo derivato è la definizione di una struttura:
struct Point { int x; int y; }; Point a,b,c;
21
Puntatori puntatore: variabile che contiene l'indirizzo di un'altra variabile -In C++ i puntatori hanno un tipo associato (eccezione void *). c char c = 'y'; char* p; p = &c; char c2 = *p; 'y' c p 'y' null c p 'y' &c c p c2 'y' &c 'y' &c: indirizzo di c. *p: dereferenziazione di p, accesso all'oggetto puntato. -avrei potuto scrivere: char *p = &'y'?
22
no! ottengo: non-lvalue in unary '&'.
Naturalmente posso dichiarare un puntatore a puntatore: c p c2 char** pp = &p; 'y' &c 'y' pp &p **pp = 'z'; c p c2 'z' &c 'y' pp &p cout << c << '\t' << c2 << endl; in output: z y
23
-se p è un puntatore di tipo T* allora *p può comparire ovunque
ci si aspetti un oggetto di tipo T: int i = 4; int* pi = &i; int* pi2; *pi = *pi + 1; //i=5 pi2 = pi; *pi2 = i * 2; //i=10 Quanto vale i? -void*: è il tipo che corrisponde ai puntatori generici, qualsiasi puntatore può essere convertito a void* e poi riconvertito nel suo tipo originale senza perdita di informazione. Questo tipo è utilissimo come parametro di funzioni.
24
-In realtà esistono anche puntatori a funzione
int (*funp) (int, int); è la dichiarazione di un puntatore di nome funp ad una funzione che accetta due parametri di tipo int e restituendo un tipo int come risultato. La dereferenziazione di funp restituisce una funzione. Esempio: //restituisce il massimo tra arg1 e arg2 int max(int arg1, int arg2) {//...} //restituisce il minimo tra arg1 e arg2 int min(int arg1, arg2) {//...} int i = 1, j = 2, k, l; funp = &max; k = (*funp)(i, j); funp = &min; l = (*funp)(i, j); Quanto valgono k ed l?
25
array tipo T, T[size] è un vettore di size elementi di tipo T. -L'indice è compreso tra 0 e size-1. -size deve essere una costante intera; alcune implementazioni del compilatore (ad es. GNU) permettono l'uso di variabili o espressioni intere. float v[3]; //v[0], v[1], v[2] int m[2][3];//2 vettori di 3 interi char* vpc[10];//vettore di 10 punt. a char -Inizializzazione: //v[] ha 6 elementi int v[] = {137, -12, 53, 12943, 21, -20}; float vf[] = {12.2, 0.1, -22.1}; double id[3][3] ={ {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} }; char vc[] = {'c', 'i', 'a', 'o', '\0'};
26
-solo per i vettori di char si può utilizzare una notazione più comoda:
char vocali[] = "aeiou"; //in questo caso il carattere di fine stringa //viene aggiunto automaticamente -boundary checking: no! Il compilatore non controlla la correttezza degli indici degli elementi di array. Si può facilmente ottenere un errore. Puntatori ed array -Il nome di un array può anche essere usato come puntatore al suo primo elemento: int v[10]; int* pi = v; *pi = 0;//equivale a v[0] = 0 pi++; //ora pi punta a v[1] *pi = 1;//v[1] = 1 pi--; //ora pi punta a v[0] pi = pi +5 ; //ora pi punta a v[5] int offset = pi - v; //numero di el. tra i 2 p. aritmetica dei puntatori +, -, ++, --. Da usare con grande cautela, è facile puntare ad aree di memoria sbagliate uscita dal programma con errore, il famigerato segmentation fault
27
Strutture Meccanismo per introdurre tipi di dato costituiti da un insieme di elementi di tipi (anche) diversi. struct Particle { double p[3]; double v[3]; int charge; }; -si possono dichiarare variabili di questo nuovo tipo: Particle p1, p2, p3; -si può accedere ai campi del tipo usando l'operatore . p1.p[0] = p1.p[1] = p1.p[2] = 0.0; p1.v[0] = p1.v[1] = p1.v[2] = 0.0; p1.charge = -1; //poi vedremo che risulta più comodo //utilizzare il costruttore //... p2 = p1;
28
-il nome del tipo risulta utilizzabile anche nella definizione del tipo
stesso: struct Link { Link* prev; Link* succ; }; -ma ciò non significa che si possono dichiarare oggetti del nuovo tipo durante la sua dichiarazione! struct NewType NewType x; //ERRORE IN COMPILAZIONE //... -Come si gestiscono i riferimenti incrociati durante le dichiarazioni? Meccanismo della forward declaration.
29
Esempio: struct List; //dichiarazione non definizione struct Link { Link* prev; Link* succ; List* member_of; }; struct List Link* head; -In generale il nome della struct può essere utilizzato prima della sua definizione quando non è necessario conoscere la sua dimensione. struct Astruct; void f(Astruct); //no problem Astruct a; //Errore! f(a); //Errore!
30
typedef -introduce un nuovo nome per un tipo -comodo per costruire convenzioni proprie: typedef double Mass; typedef double Distance; Mass m1, m2, m3; Distance d1, d2, d3; -esempi abbastanza comuni: typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; -è utile per abbreviare tipi complicati (come i puntatori a funzione): typedef void (*calc_func)(float); calc_func func_table[10]; è certamente più espressivo di: void (* func_table[10])(float);
31
reference nome alternativo di un oggetto -tipo T, T& significa riferimento a T. int i = 1; int& r = i; //r ed i si rif. allo stesso int int x = r; // x = 1 r = 2; // i = 2 -Una reference deve essere sempre inizializzato (a cosa riferirebbe?) -Inizializzazione reference != assegnamento di variabile -Gli operatori applicati ad una reference non agiscono su di essa, ma sull'oggetto a cui si riferisce: int ii = 0; int& rr = ii; rr++; //è ii che viene incrementato, non rr -una reference non può essere modificata dopo l'inizializzazione. -Come vedremo sono utili come parametri di funzioni e nella definizione degli operatori definiti dall'utente.
32
costanti senza nome -costanti intere: decimali ottali esadecimali 0x0 0x3 0x7fff 0xfefe -suffissi U, L, LL: void f(int); void f(unsigned int); void f(long int); void f(long long int); f(3); f(3U); f(3L); f(3LL); -costanti floating point: e10 1.6e-15 -costanti carattere (ASCII, EBCDIC, UNICODE...) 'a' '2' '\n' '\t' si può, ma è meglio evitare (portabilità del codice): '\137' '\x05f' 95 codice ASCII di '_'
33
costanti con nome la keyword const premessa alla dichiarazione di un oggetto lo rende una costante invece di una variabile (deve essere inizializzato): const int bu = 20; bu++; //ERRORE const char* pippo = "abcde"; -Chi è costante il puntatore o l'oggetto puntato? pippo[2] = 'z'; //ERRORE pippo = "ciccio"; ovvero ho dichiarato un puntatore a costante. -Per rendere costante il puntatore si usa l'operatore *const char *const bubu = "yogi"; bubu[3] = 'a'; bubu = "napo"; //ERRORE ovvero ho dichiarato un puntatore costante. -infine: const char *const cp = "fred"; -notare che non si può: const int x = 10; int* pi = &x; //ERRORE, potrei modificare x const int* pic = &x; //no problem -vantaggi per il compilatore usando const (e ovviamente per l'utente).
34
enum -un nome simbolico per ogni costante: enum { PICCOLO, MEDIO, GRANDE }; equivale a: const int PICCOLO = 0; const int MEDIO = 1; const int GRANDE = 2; -è possibile assegnare un nome, facendo diventare l'enum un nuovo tipo: enum Verdure { RAPE, BROCCOLI, CIPOLLE }; //... Verdure cose_da_comprare; cose_da_comprare = RAPE; int j = BROCCOLI; Verdure da_preparare = 2; //ERRORE!! Verdure da_preparare = Verdure(2); //OK
35
-in realtà gli enumeratori si possono inizializzare a piacere:
enum Colors { red = 2, green, blue = green + 1, grey = blue * 2 }; //... cout << grey << ' ' << blue << ' ' << green << ' ' << red << endl;; Cosa ottengo in uscita? enum <---> switch
36
union -definisce piu` modi di vedere lo stesso oggetto: // nell’ipotesi sizeof(int)==4 union MultipleAccess { int word_value; unsigned short halfword_values[2]; unsigned char byte_values[4]; }; -come si usa ? MultipleAccess value; value.word_value = 0xA ; //cosi` accedo ai bytes: unsigned char first_byte = value.byte_values[0]; //cosi` alle parole di 16 bit (half word): unsigned short second_halfword = value.halfword_values[1]; cout << hex << value.word_value << ' ' << (int) first_byte << ' ' << second_halfword << endl; -cosa ottengo in uscita?
37
word = a3458543 byte[0] = 43 halfword[1] = a345
-E' utilissimo quando si abbia necessita di risparmiare memoria (lo stesso spazio occupa oggetti diversi in momenti diversi): enum EntryType { STRING, INT}; union EntryValue { char* string_val; int int_val; }; struct Entry char* name; EntryType type; EntryValue value; //... Entry a[10]; a[0].name = "Pippo"; a[0].type = STRING; a[0].value.string_val = "Amico di Topolino"; a[1].name = "Targa di Paperino"; a[1].type = INT; a[1].value.int_val = 313;
38
campi di bit -modo per inserire oggetti di dimensioni ridotte in una sola word (economizzando lo spazio occupato). struct { unsigned int sign : 1; unsigned int exponent: 8; unsigned int fraction0: 7; unsigned int fraction1: 16; } number; -i campi si comportano come degli interi (di dimensione ridotta) -Tutti i dettagli (come avviene l'allocazione dei campi in memoria...) dipendono dalla macchina. -Tipo di dato con cui è facile scrivere codice non portabile
39
operatori -aritmetici (tipi interi e floating point): + - * / % resto della div. int (modulo) ++ -- pre e post incremento/decremento - + unari -esempio: int i, j, inc_i, j_inc; i = j = 3; inc_i = ++i; j_inc = j++; cout << i << '\t' << j << '\t' << inc_i << '\t' << j_inc << endl; ottengo:
40
-relazionali: > >= < <= == != -logici: && AND || OR ! NOT le espressioni formate con questi operatori vengono valutate da sin. a destra, bloccandosi non appena si determina il risultato. Attenzione! int ciao() { cout << "Ciao" << endl; return 1; } //... int i = 10; unsigned booleano = (i == 10) || (ciao() == 1); Verremo salutati?
41
-bit a bit (tipi interi), utili per lavorare con vettori di bit:
& AND | OR ^ XOR << shift a sinistra >> shift a destra (logico/aritmetico) ~ complemento ad uno -mascherare (azzerare) insiemi di bit: AND n = n & 0xF0 // -accendere insiemi di bit: OR n = n | 0x1; //dispari -moltiplicare per potenze di 2 (x = y * 2 z) x = y << z; -dividere per potenze di 2 (x = y / 2 z) x = y >> z; - mascherare il bit meno significativo: n = n & (~0x1);
42
-assegnamento (semplice e composto):
*= /= %= += -= <<= >>= &= |= ^= es: a *= 2; ---> a = a * 2; -vari: . selezione elemento object.member -> selezione elemento pointer->member es: struct Color { int r,g,b; }; //... Color c; Color* pc = &c; c.r = pc->r;
43
[] indicizzazione pointer[expr]
() chiamata di funzione expr(expr_list) () costruzione valore type(expr_list) & indirizzo di &lvalue * dereferenziazione *expr new crea un oggetto new type delete distrugge un oggetto delete pointer sizeof dimensioni del tipo sizeof type sizeof dimensioni oggetto sizeof expr :: scope resolution class_name::member ?: espressione condiz. expr?expr:expr , virgola expr, expr
44
associatività -unari e assegnamento associativi a destra: a = b = c ---> a = ( b = c ) *p++ ---> *(p++) //non (*p)++ -tutti gli altri sono associativi a sinistra precedenza degli operatori: manuale di riferimento! -esiste la forma funzionale di quasi tutti gli operatori visti: double n1 = 1.33; double n2 = .3E-2; double result; result = operator+(n1,n2); -e` come se il compilatore avesse predefinite e utilizzato le funzioni speciali: double operator +(const double &d1, const double &d2); idem per: int operator <(const int &n1, const int &n2); int operator ~(const int &n1); int operator >>=(const int &n1, const int &n2); -nessuno usa questa forma, di solito, ma servono per l’overloading nei tipi definiti dall'utente
45
costrutti if-else permette di esprimere una decisione if(espressione) istruzione_1 else istruzione_2 -per istruzione si intende anche un blocco di istruzioni (sequenza di dichiarazioni ed istruzioni tra parentesi graffe), che a sua volta può contenere altri blocchi... es. if( a > b ) max = a; max = b;
46
switch-case permette di operare delle scelte multiple controllando se una espressione assume un certo valore in un insieme di costanti intere switch (espressione) { case const_expr1 : istruzioni case const_expr2 : istruzioni ... default : istruzioni } -Risulta conveniente (e migliora la leggibilità del codice) usare degli enum come valori possibili per i case.
47
es. enum Animale {CANE, GATTO, TOPO}; Animale bu; //... switch(bu) { case CANE: cout << “BAU!" << endl; break; case GATTO: cout << “MIAO!” << endl; case TOPO: cout << “SQUIT!” << endl; default: cout << “Un minollo?” << endl; }
48
while permette di eseguire iterativamente una istruzione (o blocco) while (espressione) istruzione espressione viene valutata, se il suo valore != 0 allora viene eseguita istruzione ed espressione viene valutata di nuovo. Il ciclo si interrompe quando espressione diventa falsa (uguale a 0). -istruzione a seconda del valore di espressione puo` anche non esser mai eseguita. es. while(i == 0 && j < 100) { //... if (ww) break; //esci dal while if (kk) continue;//riparti dalla iterazione succ. v1[j] = v2[j] + v3[j++];//attenzione }
49
do-while controlla la condizione di uscita al termine di ogni iterazione do istruzione while (espressione); -e` eseguito almeno una volta : int k = 0; ... { … k++; } while(k < 100);
50
for struttura iterativa alternativa allo while for (espr1; espr2; espr3) istruzione equivale a: espr1; while (espr2) { espr3; } -in molti casi è piu' comodo da usare for( solo_la_prima_volta; all_inizio_di_ogni_ciclo; alla_fine_di_ogni_ciclo) // in qualunque momento posso: // uscire dal ciclo con break // oppure andare direttamente // alla iterazione succ. con continue
51
-esempio, trovare il numero di bit ad 1 della variabile x
unsigned int x, tmp; unsigned char nbit; //... for(nbit = 0, tmp = x; tmp != 0; tmp >>= 1) if( (tmp & 0x1) != 0) nbit++; l'operatore virgola!
52
-esiste anche il goto! goto identificatore; identificatore : istruzione -è meglio evitarlo! Ma in alcuni casi può servire (codice generato automaticamente, applicazioni real-time...) for (i = 0; i < n ; i++) for (j = 0; j < m; j++) if (a[i] == b[j]) goto trovato;//salta alla label trovato //non ha trovato elementi comuni //... trovato: //trovato un elemento in comune
53
parte di un programma che svolge un determinato compito
funzioni parte di un programma che svolge un determinato compito -dichiarazione: si specificano il nome della funzione, il tipo ed il numero dei parametri in ingresso, il tipo del valore di ritorno int lsh(const int& op1, const int& op2); nella dichiarazione i nomi degli argomenti in ingresso sono utili per aggiungere informazione sulla semantica della funzione, ma viene ignorato dal compilatore: extern char* strcpy(char* to, const char* from); -definizione: tipo-ritornato nome-funzione(dich. args) { dichiarazioni ed istruzione } -rispetto alla dichiarazione ho aggiunto il corpo della funzione extern int min(int op1, int op2);//dichiar. int min(int op1, int op2) //definizione int min = op1 < op2? op1 : op2; return min;
54
inline -specificando che una funzione è inline si dice al compilatore di espandere il codice di una funzione ad ogni sua chiamata piuttosto di effettuare una chiamata vera e propria (efficienza/memoria). ...ora calma e sangue freddo... inline int fatt(int i) { return i < 2 ? 1 : i*fatt(i-1); } ricorsione: funzione che richiama se stessa -quale è la sequenza di chiamate? int res = fatt(5); fatt(5) -> 5 * fatt(4) = 120 fatt(4) -> 4 * fatt(3) = 24 fatt(3) -> 3 * fatt(2) = 6 fatt(2) -> 2 * fatt(1) = 2 fatt(1) -> 1 la mente si confonde...allora poi si cita sempre la famosa frase: l'iterazione è umana, la ricorsione è divina!
55
parametri -formali , attuali: double sqrt(double d) { //... } //... double x, res; res = sqrt(x); x:attuale d:formale -type checking, conversioni -passaggio di parametri per valore e per riferimento void f(int val, int& ref) { val++; //inc una copia locale del par. val ref++; //inc il par. ref } -i maestri sconsigliano l'uso esteso del passaggio by ref (io no) Comunque il passaggio per riferimento è essenziale per ottenere un codice efficiente quando si definiscono funzioni che accettano parametri in ingresso di grandi dimesioni (si evita la copia del par.) void f(const TipoGrande& arg) { //... }
56
-parametri array: un argomento T[] viene convertito in un T* nel passaggio, quindi l'array non può essere passato per valore. float dot(float v1[], float v2[], int dim) { float res = 0.0; for(int i = 0; i < dim; i++) res += v1[i] * v2[i]; //equivalente a: // for(int i = 0; i < dim; i++) // res += *v1++ * *v2++; return res; } -per gli array multidimensionali è necessario specificare tutte le dimensioni tranne la prima: //ERRORE float* mul(float m1[][], float m2[][], int d1, int d2, int d3, int d4 ); //OK float* f(float m1[][100], float m2[][200], int d1, int d2);
57
overload stesso nome per operazioni diverse su tipi diversi void print(int); void print(char *); attenzione: non si può ridefinire il tipo ritornato! -parametri di default: void print(int val, int base = 10) //... print(16); print(16, 10); print(16, 2); -i parametri opzionali vanno messi per ultimi! -numero non specificato di parametri: int printf(const char* ...); è possibile farlo, ma l'utilità è veramente rara --> Manuale!
58
Preprocessore realizza la prima fase, separata dalle altre, della compilazione trasformando il codice sorgente -principalmente: #include "nomefile" sostituisce la linea con il contenuto di nomefile (cerca nomefile nella stessa dir, se non è specificato un path completo) #include <nomefile> come sopra, però nomefile viene cercato nelle directory standard di inclusione: /usr/include, /usr/local/include -tipicamente i file che vengono inclusi sono header file (.h) questi contengono tipicamente: definizione di tipi, struct Color {int r,g,b;} template (vedremo) dichiarazione di funzioni, variabili, costanti dichiarazioe di nomi, struct Token #include, #define ...
59
#define nome testo da sostituire
sostituisce ad un identificatore una stringa arbitraria. #define MAX 100 //... int v[MAX]; diventa: int v[100]; Si usa per definire macro: #define min(a,b) a<b?a:b int c = min(1, x); int c = 1<x?1:x; -evitare l'uso eccessivo delle macro! Il C++ offre costrutti alternativi: const, inline, template
60
inclusione condizionale
permette di inserire selettivamente parti di codice -codice sorgente che compila correttamente su diverse architetture: #ifdef UNIX #include "unix.h" #else #include "msdos.h" #endif -definire in modo selettivo le macro #ifdef VERBOSE #define message(m) cerr << m; #define message(m) -proteggere da inclusioni multiple: #ifndef _HEADER_H_ #define _HEADER_H- //contenuto del file header.h
61
varie #warning “this header is version 1.2” #error “I compile only from v1.2 on” #pragma optimization(on) # : string-ification enum Colors { red, green, blue, yellow }; #define FILL_LIST(NAME) { NAME, #NAME } struct { Colors c; const char *name; } color_list[] = { FILL_LIST(red), FILL_LIST(green), //... { 0, NULL } }; -nomi predefiniti: __LINE__ costante int, numero corrente del codice sorgente __FILE__ stringa, nome del file sotto compilazione __DATE__ stringa, data della compilazione __TIME__ stringa, ora della compilazione
62
editor esecuzione Preprocesor cpp Assembler as C++ Compiler Linker ln
catena di compilazione editor .cc, .h Preprocesor cpp header file di sistema .h codice espanso .ii Assembler as C++ Compiler .s assemby .o altri file oggetto .o exe esecuzione Linker ln lib?.a .so librerie dinamiche
63
hello.cc (C++) #include <iostream.h> void main() { cout << "Ciao!!!" << endl; }
64
hello.ii (Output del Preprocessore) //qua sopra ci sono mooolte righe
class _IO_ostream_withassign : public ostream { public: _IO_ostream_withassign& operator=(ostream&); _IO_ostream_withassign& operator=(_IO_ostream_withassign& r { return operator= (static_cast<ostream&> (rhs)); } }; extern _IO_istream_withassign cin; extern _IO_ostream_withassign cout, cerr; extern _IO_ostream_withassign clog; extern istream& lock(istream& ins); extern istream& unlock(istream& ins); extern ostream& lock(ostream& outs); extern ostream& unlock(ostream& outs); struct Iostream_init { } ; inline ios& dec(ios& i) { i.setf(ios::dec, ios::dec|ios::hex|ios::oct); return i; } inline ios& hex(ios& i) { i.setf(ios::hex, ios::dec|ios::hex|ios::oct); return i; } inline ios& oct(ios& i) { i.setf(ios::oct, ios::dec|ios::hex|ios::oct); return i; } } # 1 "hello.cc" 2 void main() { cout << "Ciao!!!" << endl;
65
hello.s (Assembly) .file "hello.cc" gcc2_compiled.:
___gnu_compiled_cplusplus: .def ___terminate; .scl 2; type 32; .endef .def ___sjthrow; .scl 2; type 32; .endef .def ___main; scl 2; type 32; .endef .text LC0: .ascii "Ciao!!!\0" .align 4 .globl _main .def _main; .scl 2; type 32; .endef _main: pushl %ebp movl %esp,%ebp subl $8,%esp call ___main addl $-8,%esp pushl $LC0 pushl $_cout call ___ls__7ostreamPCc addl $16,%esp addl $-12,%esp pushl %eax call _endl__FR7ostream movl %ebp,%esp xorl %eax,%eax popl %ebp ret .def _endl__FR7ostream; scl 2; type 32; .endef .def ___ls__7ostreamPCc; .scl 3; type 32; .endef
66
Il file eseguibile (hello.exe)
1<C0><EB>^A<90><89><EC>]<C3>U<89><E5><8B>;^U^D <D2>t,<C7>^E<AC> <E8> <A5><8B>^B<8B><8D>U<C0><89>P^\<89><CA><8B>^A<83><C <C6>B^R^D< <89><E5><83><EC>^TS<8B><89><F6><83><C4><F4>S<E8><F <EC>^TS<8B><8B>^C<8A>P^R<84><D2>t <80><CA>^B <C9>t^V<8B>^P<8B>B^D9u^L<83><C4><F4>Q<E8> ]<E8><89><EC>]<C3><89><F6>U<89><E5><83><EC>^PV<8B> <FF><FF><FF><EB>Q<89><F6><8B>H^D<85><C9>t^V<8B>^P< <FF>t <8B>C^D^O<B6>^P<EB>^C<90><89><C2><83><FA>< <8D>e<E8>[^<89><EC>]<C3>U<89><E5><83><EC>^LWVS<8B> <8A>P^R<84><D2>t <80><CA>^B<88>P^R<EB>]<90>
67
...che però si può disassemblare:
hello.exe: file format pei-i386 Disassembly of section .text: <_mainCRTStartup>: 401000: push %ebp 401001: e mov %esp,%ebp 401003: ec sub $0x18,%esp 401006: d cmpl $0x0,0x412000 40100d: je <_mainCRTStar 40100f: cc int3 401010: d9 7d fe fnstcw 0xfffffffe(%ebp) 401013: f b7 45 fe movzwl 0xfffffffe(%ebp),%eax 401017: c0 f0 ff ff and $0xfffff0c0,%eax 40101c: fe mov %ax,0xfffffffe(%ebp) 401020: f b7 45 fe movzwl 0xfffffffe(%ebp),%eax 401024: d 3f or $0x33f,%eax 401029: fe mov %ax,0xfffffffe(%ebp) 40102d: d9 6d fe fldcw 0xfffffffe(%ebp) 401030: c4 f add $0xfffffff4,%esp 401033: c push $0x40104c 401038: e8 1b da call 40ea58 <_cygwin_crt0> 40103d: ec mov %ebp,%esp 40103f: d pop %ebp 401040: c ret 401041: nop 401042: nop 401043: nop <.text>: 401044: inc %ebx 401045: f imul $0x212121,0x6f(%ecx),%esp c <_main>: 40104c: push %ebp 40104d: e mov %esp,%ebp 40104f: ec sub $0x8,%esp 401052: e8 4d da call 40eaa4 <___main> 401057: c4 f add $0xfffffff8,%esp 40105a: d push $0x4027d0 40105f: c4 f add $0xfffffff8,%esp 401062: push $0x401044 401067: push $0x412024 40106c: e8 b call <___ls__7os 401071: c add $0x10,%esp 401074: c mov %eax,%eax 401076: push %eax
68
librerie un insieme di file oggetto (.o) ottenuti compilando i corrispondenti file sorgente (.c, .cc) accompagnati da uno o più header file (.h) con le dichiarazioni per l'uso dei file .o -supponiamo di voler scrivere una libreria per la crittografia DES, un possibile header file: #ifndef _DESCRYPT_H_ #define _DESCRYPT_H_ //necessario per utilizzare librerie C in C++ //istruisce il linker sul modo di chiamata (ABI) extern "C" { void encrypt(char *block, int edflag); void setkey(char *key); char* crypt(const char *key, const char *salt); } #endif -supponiamo di aver definito queste funzioni nei rispettivi file C: encrypt.c setkey.c crypt.c -ad esempio lavorando su un sistema UNIX, la seguente serie di comandi genera la libreria descrypt.a: $ cc -c encrypt.c setkey.c crypt.c $ ar cr descrypt.a encrypt.o setkey.o crypt.o $ ranlib descrypt.a
69
posso ispezionare il contenuto di una libreria (implementazioni vuote
$ nm -s descrypt.a Archive index: _encrypt in encrypt.o _setkey in setkey.o _crypt in crypt.o encrypt.o: b .bss d .data t .text t ___gnu_compiled_c T _encrypt t gcc2_compiled. setkey.o: T _setkey crypt.o: T _crypt
70
-uso della libreria: nella mia applicazione (secure_link.cc) includerò il file header che corrisponde alla libreria e farò chiamate alle funzioni là definite #include "descrypt.h" //... setkey(sessionkey); mycrypt = crypt(sessionkey, sugar); -per compilare la mia applicazione: $ c++ secure_link.cc descrypt.a -o secure_link in questo modo il linker estrae i file .o dalla libreria e li collega con il file secure_link.o garantendo che venga fornita la definizione delle funzioni di libreria richiamate in secure_link.cc -librerie dinamiche (.so sotto UNIX, .dll sotto WINDOWS): vengono incluse al momento della esecuzione, dimensioni ridotte degli eseguibili. -riutilizzo del codice
71
classi una classe (class) è un tipo definito dall'utente e le struct allora? anche loro! //definisco il tipo struct Date { int day, month, year; }; //definisco le operazioni sul tipo void set_date(date*, int, int, int); void get_date(date*, int&, int&, int&); void tomorrow_date(date*); void yesterday_date(date*); void print_date(const date*); -è un pò scomodo, non c'è un legame tra le funzioni ed il tipo (se non nei parametri e nel nome scelto opportunamente) -una cosa che ancora non vi avevo detto a proposito delle struct: struct Date { int day, month, year; //dichiarazione metodi (o funzioni proprie) void set(int, int, int); void get(int&, int&, int&); void tomorrow(); void yesterday(); void print(); };
72
-i metodi possono essere richiamati solo per una variabile del
tipo che gli compete: Date today, xmas; //... today.set(24, 7, 2001); xmas.set(25, 12, 2001); today.tomorrow(); xmas.print(); today.print(); -definizione di un metodo: void Date::tomorrow() { if(++day > 28) //trentagiornihanovembrecon... } Date:: è necessario, potrei aver dichiarato il metodo void tomorrow() anche per un altra struct -rimane l'imbarazzante capacità di modificare lo stato interno del tipo Date manipolando direttamente i suoi campi (e non per mezzo delle operazioni implementate dai metodi): today.day = today.month = today.year = -13; e allora entrano in gioco le classi! l'operatore di scope resolution!
73
class Date { int day, month, year; public: void set(int, int, int); void get(int&, int&, int&); void tomorrow(); void yesterday(); void print(); }; -i nomi contenuti nella parte privata possono essere manipolati solo dai metodi della classe -la parte pubblica è anche detta interfaccia agli oggetti della classe -in tutte le parti possono essere presenti sia dati(attributi) che funzioni(metodi) Oggetto = istanza di una classe. Identificato dal nome, definisce uno stato che è rappresentato dal valore dei suoi attributi a un certo istante di tempo. int i; int è la classe (il tipo) i è il nome dell'oggetto(variabile) parte privata parte pubblica
74
encapsulation: nascondere tutti i dettagli di un oggetto che non
contribuiscono in maniera essenziale alle sue caratteristiche (esposte tramite l'interfaccia) -una struct è una classe in cui tutti i membri sono pubblici. void Date::print() { cout << day << '/' << month << '/' << year; } va tutto bene, Date::print è un metodo della classe Date ed ha accesso alla sua parte privata. void print_date(Date day) cout << day.day << '/' << day.month << '/' << day.year; non va bene, la funzione print_date non può leggere gli attributi privati di un oggetto della classe Date
75
dichiarazione di una classe:
// in particle.h class Particle { private: // chi lo può chiamare ? void SetMass(double m); protected: Vector q; //attributi Vector p; //o variabili membro int charge; double mass; public: Particle(const Vector& q, //un costruttore const Vector& p, int charge, double mass); ~Particle(); //un distruttore double GetMass() const //un metodo { return this->mass; } };
76
-protected: meno privata di private, vedremo parlando di classi
derivate. -costruttore: metodo per l'inizializzazione degli oggetti, ha lo stesso nome della classe -distruttore: metodo richiamato quando un oggetto esce dallo scope in cui è stato dichiarato, la memoria da esso occupata viene liberata. Il distruttore per la classe T si chiama ~T() -metodo const: può leggere ma non modificare l'oggetto per cui viene richiamata -this: nome sempre disponibile nei metodi, per una classe T è di tipo T* e rappresenta il puntatore all'oggetto di invocazione -inlining: defininendo metodi nella dichiarazione della classe questi saranno automaticamente considerate dal compilatore come funzioni inline (comodo per metodi "piccoli" richiamati di frequente)
77
-implementazione della classe:
// in particle.cc Particle::Particle(const Vector& q, const Vector& p, int c, double mass) { m_q = q; m_p = p; m_charge = c; m_mass = mass; } void Particle::SetMass(double mass) this->m_mass = mass; // this-> e` opzionale -this e` una varibile sempre disponibile dentro l’implementazione di ogni metodo di una classe; e` di tipo NOME_CLASS* in questo caso Particle* . Più precisamente: Particle* const this E` come se ogni metodo avesse un ulteriore parametro nascosto che serve per accedere ai membri del nostro oggetto. -utile per le classi contenitore: alberi, liste...
78
Interfaccia-Implementazione
Classe = scatola nera su cui si agisce per mezzo di un certo insieme di operazioni (interfaccia) Fintanto che l'interfaccia è fissata, la effettiva realizzazione della classe (implementazione) può subire dei cambiamenti senza che l'utente della classe se ne accorga. -static: questo è uno dei nomi più sovraccaricati di significati nel mondo dei linguaggi di programmazione (lo abbiamo già incontrato). Quando lo si usa nella dichiarazione di un membro di una classe significa: membro comune a tutti gli oggetti della classe class Elettrone { static double massa; static int carica; protected: Vector q; Vector p; }; //... double Elettrone::mass = e-31; int Elettrone::carica = -1; -anche i metodi possono essere dichiarati static
79
Un esempio file geom.h: class Mat_3_3; //forward declaration //realizza un vettore 3D class Vett_3 { protected: double v[3]; public: Vett_3(double x = 0.0, double y = 0.0, double z = 0.0) { v[0] = x; v[1] = y; v[2] = z; } void Stampa(); //modulo del vettore double Mod(); //moltiplicazione per matrice 3x3 Vett_3 Molt(const Mat_3_3& ); //overload dell'operatore somma //è parte dell'interfaccia, //anche se non è un metodo friend Vett_3 operator*(const double, const Vett_3&); friend Vett_3 operator+(const Vett_3& , };
80
//realizza una matrice 3x3
class Mat_3_3 { double m[3][3]; public: Mat_3_3(double m00 = 0.0, double m01 = 0.0, double m02 = 0.0, double m10 = 0.0, double m11 = 0.0, double m12 = 0.0, double m20 = 0.0, double m21 = 0.0, double m22 = 0.0); void Stampa(); //è parte dell'interfaccia, //anche se non è un metodo di Mat_3_3 friend Vett_3 Vett_3::Molt(const Mat_3_3& m); };
81
-friend: una funzione (anche come nel nostro esempio degli
operatori e delle funzioni membro) che ha accesso alla parte privata di una classe. Dal momento che può manipolare liberamente gli oggetti di una classe è opportunamente inserita nella dichiarazione della classe (nella interfaccia). class A { friend class B; //... }; -il senso della dichiarazione è che tutti i metodi di B sono funzioni friend di A. -gli operatori sono spesso dichiarati friend L'operatore visto prima: friend Vett_3 operator*(const double, const Vett_3&); nella sua definizione avrà libero accesso alla parte privata degli oggetti della classe Vett_3. L'operatore accetta const Vett_3& come secondo parametro perchè non deve modificarlo ma allo stesso tempo il compilatore non devrà fare una copia del parametro.
82
-file geom.cc; #include <math.h> #include <iostream.h> #include "geom.h" //metodi ed operatori di Vett_3 void Vett_3::Stampa() { cout << "(" << v[0] << '\t' << v[1] << '\t' << v[2] << ")" << endl; } double Vett_3::Mod() { double ris = sqrt( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] ); return ris; } Vett_3 Vett_3::Molt(const Mat_3_3& mat) Vett_3 ris; for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) ris.v[i] += mat.m[i][j] * v[i];
83
//quelli che seguono non sono metodi di Vett_3
Vett_3 operator*(const double op1, const Vett_3& op2) { Vett_3 ris; for(int i = 0; i < 3; i++) ris.v[i] = op1 * op2.v[i]; //!!!Friend!!! return ris; } Vett_3 operator+(const Vett_3& op1, const Vett_3& op2) ris.v[i] = op1.v[i] + op2.v[i];//idem
84
//metodi di Mat_3_3 Mat_3_3::Mat_3_3(double m00 = 0.0, double m01 = 0.0, double m02 = 0.0, double m10 = 0.0, double m11 = 0.0, double m12 = 0.0, double m20 = 0.0, double m21 = 0.0, double m22 = 0.0) { m[0][0] = m00; m[0][1] = m01; m[0][2] = m02; m[1][0] = m10; m[1][1] = m11; m[1][2] = m12; m[2][0] = m20; m[2][1] = m21; m[2][2] = m22; } void Mat_3_3::Stampa() for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) cout << m[i][j] << 't'; cout << endl;
85
#include <iostream.h>
#include "geom.h" #define PI void main() { Vett_3 x(0.0, 3.0, 4.0); cout << "X:\t"; x.Stampa(); cout << "Modulo(X): " << x.Mod() << endl; Vett_3 y(0.0, -5.0, 3.0); Vett_3 z = 2.0 * (x + y); cout << "Z:\t"; z.Stampa(); double theta = PI/4.0; double phi = PI/4.0; Mat_3_3 Mat(cos(phi), -sin(phi), 0.0, sin(phi), cos(phi), 0.0, 0.0 , , ); cout << "Mat:" << endl; Mat.Stampa(); Vett_3 z_phi = z.Molt(Mat); cout << "z_phi:\t"; z_phi.Stampa();
86
-ottengo: $ ./geotest.exe X: ( ) Modulo(X): 5 Z: ( ) Mat: z_phi: ( )
87
-supponiamo di voler introdurre un nuovo tipo di dato che rappresenti
un versore in 3 dimensioni. Potrei dichiararlo nella seguente maniera: class Vers_3 { protected: double v[3]; public: Vers_3(double x = 0.0, double y = 0.0, double z = 0.0); void Stampa(); //modulo del versore, si spera = 1.0 double Mod(); //moltiplicazione per matrice 3x3 Vers_3 Molt(const Mat_3_3& ); friend Vers_3 operator*(const double, const Vers_3&); friend Vers_3 operator+(const Vers_3& , }; Somiglia tantissimo alla dichiarazione del Vett_3: i dati sono gli stessi, le operazioni pure, anche se alcune sono modificate nel comportamento: Costruttore, Mod()...
88
-rendo esplicito quello che le classi Vett._3 e Vers_3 hanno in
comune utilizzando il meccanismo della derivazione di classi -file geom.h: class Vers_3 : public Vett_3 { public: //crea un versore Vers_3(double x, double y, double v); //converte in Vett_3 in un Vers_3 Vers_3(const Vett_3& v3); //crea un versore da coordinate sferiche Vers_3(double theta, double phi); //lo sto ridefinendo rispetto alla classe base void Stampa(); }; Vers_3 specializza la classe Vett_3 al concetto di versore. Vers_3 è una classe derivata publicamente da Vett_3. Eredita tutti i dati e le funzioni di Vett_3 dichiarate public o protected. Vett_3 è una classe di base per Vers_3. -importante: è possibile assegnare ad un puntatore a Vett_3 un puntatore ad un oggetto Vers_3 senza dover fare il cast esplicito (conversione). Utilizzando i puntatori si può trattare un oggetto di una classe derivata come se fosse un oggetto della classe di base.
89
-file geom.cc: Vers_3::Vers_3(double x = 0.0, double y = 0.0, double z = 0.0) : Vett_3(x, y, z) { double inv_mod = 1.0/Mod(); v[0] = inv_mod * v[0]; v[1] = inv_mod * v[1]; v[2] = inv_mod * v[2]; } Vers_3::Vers_3(const Vett_3& v3) : Vett_3(v3) Vers_3::Vers_3(double theta, double phi) v[0] = sin(theta) * cos(phi); v[1] = sin(theta) * sin(phi); v[2] = cos(theta); void Vers_3::Stampa() { cout << "<" << v[0] << '\t' << v[1] << '\t' << v[2] << ">" << endl; }
90
costrutore: ho richiamato il costuttore della classe di base nel
costruttore della classe derivata. Sto trattando la classe di base come un oggetto proprio della classe derivata. -ordine di costruzione: classe di base, elementi, classe derivata -ordine di distruzione: classe derivata, elementi, classe di base -Nei costruttori ho richiamato il metodo Mod(), chi era l'oggetto d'invocazione?
91
-uso: #include <iostream.h> #include "geom.h" #define PI void main() { Vers_3 versore(PI/4.0, PI/4.0); cout << "versore(PI/4.0, PI/4.0):" << endl; versore.Stampa();//quale Stampa() chiama? double theta = PI/4.0; double phi = PI/4.0; Mat_3_3 Mat(cos(phi), -sin(phi), 0.0, sin(phi), cos(phi), 0.0, 0.0 , , ); //ma come, molt non era un metodo di Vett_3 //che restituiva un Vett_3??? Vers_3 ris = versore.molt(Mat); cout << "...dopo una rotazione di PI/4.0 lungo l'asse Z:" << endl; ris.Stampa(); }
92
-ottengo: $ ./geotest.exe versore(PI/4.0, PI/4.0): < > ...dopo una rotazione di PI/4.0 lungo l'asse Z: < e > è stato richiamato Vers_3::Stampa(), come probabilmente ci aspettavamo. Ma se avessi scritto: //... Vers_3 ris = versore.Molt(Mat); Vett_3* pv = &ris;//si può fare! cout << "...dopo una rotazione di PI/4.0 lungo l'asse Z:" << endl; pv->Stampa(); -avrei ottenuto: ( e ) brutto!
93
il C++ prevede la soluzione di questo problema tramite la keyword
virtual Basta aggiungere questa parola chiave nella dichiarazione dei metodi della classe di base che si vogliono ridefinire nelle classi derivate: class Vett_3 { protected: double v[3]; public: //... virtual void Stampa(); }; con questa modifica otterremo: $ ./geotest.exe versore(PI/4.0, PI/4.0): < > ...dopo una rotazione di PI/4.0 lungo l'asse Z: < e > polimorfismo!
94
variabile utente public ok protected private controllo dell'accesso
-private: il nome può essere usato solo dai metodi e dalle funzioni amiche della classe. -protected: il nome può essere usato solo dai metodi e dalle funzioni amiche della classe e delle classi derivate -public: il nome è utilizzabile da qualunque funzione variabile utente public ok protected private
95
classe base nella classe derivata
Nell'esempio abbiamo visto una derivazione public. In realtà (anche se si usa raramente) si può fare anche una derivazione protected o private --> Manuale! classe base nella classe derivata variabile derivazione public derivazione protected derivazione private public public protected private protected protected protected private private
96
Memoria dinamica operatori New e Delete -finora abbiamo creato oggetti automatici (sullo stack) o a livello file: char *bubu; int ciccio; ... void funzione() { int anni = 0; //... -ma si puo` fare allocazione dinamica: // chiama costruttore Vers_3* pv = new Vers_3(theta,phi); pv->Stampa(); delete pv; // qui viene chiamato // il distruttore
97
-gli oggetti dinamici hanno vita fino alla distruzione esplicita con
delete -si possono creare dinamicamente tutti i tipi , base, composti o classi: int *index = new int(1123); double *num = new double; *num = 12.34; int j = *index; delete index; delete num; -pure vettori, ma attenti ai costruttori con parametri e al delete: int* vec = new int[100]; Triangle* t1 = new Triangle[20]; //no params vec[33] = 23; //… delete [] vec;
Presentazioni simili
© 2024 SlidePlayer.it Inc.
All rights reserved.