Introduzione al linguaggio C++
Download
1 / 97

Introduzione al linguaggio C++ 5 lezioni Lunedì, Giovedì ore 12.00-13.30 Alessandro Lonardo - PowerPoint PPT Presentation


  • 87 Views
  • Uploaded on

Introduzione al linguaggio C++ 5 lezioni Lunedì, Giovedì ore 12.00-13.30 Alessandro Lonardo [email protected] Davide Rossetti [email protected] Pagina WEB http://apegate.roma1.infn.it/ ~lonardo Testi base: -Bjarne Stroustrup, Il Linguaggio C++,

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about ' Introduzione al linguaggio C++ 5 lezioni Lunedì, Giovedì ore 12.00-13.30 Alessandro Lonardo ' - hedva


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript

Introduzione al linguaggio C++

5 lezioni

Lunedì, Giovedì ore 12.00-13.30

Alessandro Lonardo

[email protected]

Davide Rossetti

[email protected]

Pagina WEB

http://apegate.roma1.infn.it/~lonardo

Testi base:

-Bjarne Stroustrup, Il Linguaggio C++,

Addison-Wesley.

-Brian W. Kernighan, Dennis M. Ritchie,

Il Linguaggio C, Jackson libri.


  • Programma del corso

  • Lezione 1

  • 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


  • Lezione 5

  • Overload di Operatori

  • Template

  • La Standard Template Library

  • argomenti non trattati a lezione


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);

//...

}


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;


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()

{

//implementazione

}


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.


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);

};


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.


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

}

}


  • - quadrati.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à.


-Il concetto di forma più generale: quadrati.

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;

public:

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.


Esempio di uso: funzione che prende un vettore di quadrati.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à


Dichiarazioni quadrati.

-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=3.1415926535897932385

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.


-le seguenti sono solo dichiarazioni: quadrati.

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;


Oggetto quadrati.: 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


Tipi quadrati.

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


-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


Conversione tra i tipi di memoria,

-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


-Un altro modo di introdurre un tipo derivato è la definizione di una struttura:

struct Point

{

int x;

int y;

};

Point a,b,c;


Puntatori definizione di una struttura:

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'

p

c

'y'

null

c

p

&c

'y'

c

p

c2

&c

'y'

'y'

&c: indirizzo di c.

*p: dereferenziazione di p, accesso all'oggetto puntato.

-avrei potuto scrivere: char *p = &'y'?


no! ottengo: definizione di una struttura:non-lvalue in unary '&'.

Naturalmente posso dichiarare un puntatore a puntatore:

p

c2

c

char** pp = &p;

&c

'y'

'y'

pp

&p

**pp = 'z';

p

c

c2

&c

'z'

'y'

pp

&p

cout << c << '\t' << c2 << endl;

in output:

z y


-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.


-In realtà esistono anche ovunquepuntatori 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 (*funp) (int, int);

int i = 1, j = 2, k, l;

funp = &max;

k = (*funp)(i, j);

funp = &min;

l = (*funp)(i, j);

Quanto valgono k ed l?


array ovunque

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'};


-solo per i vettori di ovunquechar 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


  • Strutture ovunque

  • 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;

  • //...


- ovunqueil 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.


Esempio: ovunque

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!


  • typedef ovunque

  • -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);


reference ovunque

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.


costanti senza nome ovunque

-costanti intere:

decimali 0 137 12 3 1

ottali 0 064 0237

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:

0.0 1.37 2. 1.3e10 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 '_'


costanti con nome ovunque

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).


enum ovunque

-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


-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


union piacere:

-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 = 0xA3458543;

//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?


word = a3458543 byte[0] = 43 halfword[1] = a345 piacere:

-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;


campi di bit piacere:

-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


operatori piacere:

  • -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:

    • 4 4 4 3


-relazionali: piacere:

> >= < <=

== !=

-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?


-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 //11110000

-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);


-assegnamento (semplice e composto): bit:

= assegnamento

*= /= %= += -=

<<= >>= &= |= ^=

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;


  • [] bit: 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


  • associatività bit:

  • -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


costrutti bit:

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;

else

max = b;


switch-case bit:

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.


es. bit:

enum Animale {CANE, GATTO, TOPO};

Animale bu;

//...

switch(bu)

{

case CANE:

cout << “BAU!" << endl;

break;

case GATTO:

cout << “MIAO!” << endl;

break;

case TOPO:

cout << “SQUIT!” << endl;

break;

default:

cout << “Un minollo?” << endl;

break;

}


while bit:

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

}


  • do-while bit:

  • controlla la condizione di uscita al termine

  • di ogni iterazione

  • do

  • istruzione

  • while (espressione);

  • -e` eseguito almeno una volta :

  • int k = 0;

  • ...

  • do

  • {

  • k++;

  • } while(k < 100);


for bit:

struttura iterativa alternativa allo while

for (espr1; espr2; espr3)

istruzione

equivale a:

espr1;

while (espr2)

{

istruzione

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 conbreak

// oppure andare direttamente

// alla iterazione succ. con continue

}


-esempio, trovare il numero di bit ad 1 della variabile bit: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!


-esiste anche il bit: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

//...


funzioni bit:

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;

}


inline bit:

-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!


parametri bit:

-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) { //... }


- bit: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);


overload bit:

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);

16 16 10000

-i parametri opzionali vanno messi per ultimi!

-numero non specificato di parametri:

int printf(const char* ...);

è possibile farlo, ma l'utilità è veramente rara --> Manuale!


  • Preprocessore bit:

  • 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

  • ...


#define bit:nometestodasostituire

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);

//...

diventa:

//...

int c = 1<x?1:x;

//...

-evitare l'uso eccessivo delle macro!

Il C++ offre costrutti alternativi: const, inline, template


inclusione condizionale bit:

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;

#else

#define message(m)

#endif

-proteggere da inclusioni multiple:

#ifndef _HEADER_H_

#define _HEADER_H-

//contenuto del file header.h

#endif


varie bit:

#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


catena di compilazione bit:

editor

.cc, .h

Preprocesor

cpp

header file

di sistema

.h

codice espanso

.ii

Assembler

as

C++

Compiler

.s

assemby

.o

altri file

oggetto

.o

esecuzione

exe

Linker

ln

lib?.a

.so

librerie dinamiche


hello.cc (C++) bit:

#include <iostream.h>

void main()

{

cout << "Ciao!!!" << endl;

}


hello.ii (Output del Preprocessore) bit:

//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;

}


hello.s (Assembly) bit:

.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


Il file eseguibile (hello.exe) bit:

^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@

<83><EC>^X<83>=^@ A^@^@t^A<CC><D9>}<FE>^O<B7>E<FE>

<B7>E<FE>^M?^C^@^@f<89>E<FE><D9>m<FE><83><C4><F4>h

<C3><90><90><90>Ciao!!!^@U<89><E5><83><EC<E8>M<DA>

<C4><F8>hD^[email protected]^@h$ A^@<E8><B3>^R^@^@<83><C4>^P<89><

1<C0><EB>^A<90><89><EC>]<C3>U<89><E5><8B>;^U^D A^@

<D2>t,<C7>^E<AC> A^@^@0A^@<C7>^E( A^@`0A^@<C7>^Eh

A^@<EB>*<89><F6><C7>^E<AC> A^@$!A^@<C7>^E( A^@<84>

<E8> A^@<E4>!A^@<A1>^D A^@<89>^U^D A^@<89><EC>]<C3

:<95>^@^@<89><C3><83>}^L^@tJ<8B><83><C0<8B><89>^B<

<C7>^@^@^@^@<C7>@^D^@^@^@^@<C7>^@^@^@^@f<C7>@^P ^@

<C7>@^X^@^@^@^@<8B>C^D<8D>S^D<89>E<E0><C7>E<E4>^@^

<84>^[email protected]^@<89><8D>M<E0><89>

<8B><8B>^B<C7>@^\<C4>%A^@<83>}^L^@u1<8D>}<C0><BE><

<A5><8B>^B<8B><8D>U<C0><89>P^\<89><CA><8B>^A<83><C

<D0>f<89>E<C8><8B><8B>^Q<83>}^P^@u <C6>B^R^D<

<8B>E^P<89>^B<8B>E^T<89>B^D<C7>^@^@^@^@f<C7>B^P ^@

<C7>B^X^@^@^@^@<8B><C7>B^D^@^@^@^@<8B>C^D<8B>^@<89

<89><E5><83><EC>^TS<8B><89><F6><83><C4><F4>S<E8><F

<FA><FF>t^L<A1><E0>@A^@<F6>D^P^u<E1><8B>]<E8><89><

<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>

^T^@^@<83><C4>^P<8B>^C<83><C4><F4><8B>^@P<E8><8E>A

<8B>E^L<88>^P<C7>C^D^A^@^@^@<EB>^N<90><8B>^C<80>H^

]<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<

<96>^S^@^@<83><C4>^P<8B>^F<8B>^X<8B>9C^Dr^N<83><C4

<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>

^D9u^L<83><C4><F4>Q<E8>^V^S^@^@<83><C4>^P<8B>^C<8B

<E8><93>^^^@^@<89>C^D<EB>&<89><F6><8B>^C<80>H^R^C<

^@<83><C4>^P<83><F8><FF>t<E4><FF>C^D;E^Pu<E4><8D>e


...che però si può disassemblare: bit:

hello.exe: file format pei-i386

Disassembly of section .text:

00401000 <_mainCRTStartup>:

401000: 55 push %ebp

401001: 89 e5 mov %esp,%ebp

401003: 83 ec 18 sub $0x18,%esp

401006: 83 3d 00 20 41 00 00 cmpl $0x0,0x412000

40100d: 74 01 je 401010 <_mainCRTStar

40100f: cc int3

401010: d9 7d fe fnstcw 0xfffffffe(%ebp)

401013: 0f b7 45 fe movzwl 0xfffffffe(%ebp),%eax

401017: 25 c0 f0 ff ff and $0xfffff0c0,%eax

40101c: 66 89 45 fe mov %ax,0xfffffffe(%ebp)

401020: 0f b7 45 fe movzwl 0xfffffffe(%ebp),%eax

401024: 0d 3f 03 00 00 or $0x33f,%eax

401029: 66 89 45 fe mov %ax,0xfffffffe(%ebp)

40102d: d9 6d fe fldcw 0xfffffffe(%ebp)

401030: 83 c4 f4 add $0xfffffff4,%esp

401033: 68 4c 10 40 00 push $0x40104c

401038: e8 1b da 00 00 call 40ea58 <_cygwin_crt0>

40103d: 89 ec mov %ebp,%esp

40103f: 5d pop %ebp

401040: c3 ret

401041: 90 nop

401042: 90 nop

401043: 90 nop

00401044 <.text>:

401044: 43 inc %ebx

401045: 69 61 6f 21 21 21 00 imul $0x212121,0x6f(%ecx),%esp

0040104c <_main>:

40104c: 55 push %ebp

40104d: 89 e5 mov %esp,%ebp

40104f: 83 ec 08 sub $0x8,%esp

401052: e8 4d da 00 00 call 40eaa4 <___main>

401057: 83 c4 f8 add $0xfffffff8,%esp

40105a: 68 d0 27 40 00 push $0x4027d0

40105f: 83 c4 f8 add $0xfffffff8,%esp

401062: 68 44 10 40 00 push $0x401044

401067: 68 24 20 41 00 push $0x412024

40106c: e8 b3 12 00 00 call 402324 <___ls__7os

401071: 83 c4 10 add $0x10,%esp

401074: 89 c0 mov %eax,%eax

401076: 50 push %eax


librerie bit:

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


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:

00000000 b .bss

00000000 d .data

00000000 t .text

00000000 t ___gnu_compiled_c

00000000 T _encrypt

00000000 t gcc2_compiled.

setkey.o:

00000000 b .bss

00000000 d .data

00000000 t .text

00000000 t ___gnu_compiled_c

00000000 T _setkey

00000000 t gcc2_compiled.

crypt.o:

00000000 b .bss

00000000 d .data

00000000 t .text

00000000 t ___gnu_compiled_c

00000000 T _crypt

00000000 t gcc2_compiled.


-uso della libreria: (implementazioni vuote!):

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


classi (implementazioni vuote!):

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();

};


-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

voidtomorrow() 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!


class Date del

{

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


encapsulation del: 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


dichiarazione di una classe: del

// 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; }

};


- delprotected: 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 Tsi 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)


-implementazione della classe: del

// 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...


Interfaccia-Implementazione del

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 = 9.1091e-31;

int Elettrone::carica = -1;

//...

-anche i metodi possono essere dichiarati static


Un esempio del

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& ,

const Vett_3&);

};


//realizza una matrice 3x3 del

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);

};


- delfriend: 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.


-file geom.cc; del

#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];

}

return ris;

}


//quelli che seguono non sono metodi di Vett_3 del

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)

{

Vett_3 ris;

for(int i = 0; i < 3; i++)

ris.v[i] = op1.v[i] + op2.v[i];//idem

return ris;

}


//metodi di Mat_3_3 del

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;

}

}


#include <iostream.h> del

#include "geom.h"

#define PI 3.14159265358979323846

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 , 0.0, 1.0);

cout << "Mat:" << endl;

Mat.Stampa();

Vett_3 z_phi = z.Molt(Mat);

cout << "z_phi:\t";

z_phi.Stampa();


-ottengo: del

$ ./geotest.exe

X: (0 3 4)

Modulo(X): 5

Z: (0 -4 14)

Mat:

0.707107 -0.707107 0

0.707107 0.707107 0

0 0 1

z_phi: (0 -5.65685 14)


-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& ,

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()...


-rendo esplicito quello che le classi rappresentiVett._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_3specializza 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_3dichiarate 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.


-file geom.cc: rappresenti

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)

{

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(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; }


costrutore rappresenti: 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?


-uso: rappresenti

#include <iostream.h>

#include "geom.h"

#define PI 3.14159265358979323846

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 , 0.0, 1.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();

}


-ottengo: rappresenti

$ ./geotest.exe

versore(PI/4.0, PI/4.0):

<0.5 0.5 0.707107>

...dopo una rotazione di PI/4.0 lungo l'asse Z:

<3.92481e-17 0.707107 0.707107>

è 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:

$ ./geotest.exe

versore(PI/4.0, PI/4.0):

<0.5 0.5 0.707107>

...dopo una rotazione di PI/4.0 lungo l'asse Z:

(3.92481e-17 0.707107 0.707107)

brutto!


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):

<0.5 0.5 0.707107>

...dopo una rotazione di PI/4.0 lungo l'asse Z:

<3.92481e-17 0.707107 0.707107>

polimorfismo!


ok keyword

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

protected

private


Nell'esempio abbiamo visto una derivazione keywordpublic.

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


Memoria dinamica keyword

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


  • -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;


ad