Introduzione al C++ e alla programmazione ad oggetti
This presentation is the property of its rightful owner.
Sponsored Links
1 / 203

Introduzione PowerPoint PPT Presentation


  • 141 Views
  • Uploaded on
  • Presentation posted in: General

Introduzione al C++ e alla programmazione ad oggetti Corso Specialistico CNTC Bologna, 19-23 febbraio 2001 Andrea Dell’Acqua e Claudio Grandi. Introduzione. Le due componenti principali dei programmi: Algoritmi : l’insieme delle istruzioni che svolgono un particolare compito

Download Presentation

Introduzione

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

Introduzione al C++ e alla programmazione ad oggettiCorso Specialistico CNTCBologna, 19-23 febbraio 2001Andrea Dell’Acqua e Claudio Grandi


Introduzione

Introduzione

  • Le due componenti principali dei programmi:

    • Algoritmi: l’insieme delle istruzioni che svolgono un particolare compito

    • Dati: ciò su cui gli algoritmi agiscono per produrre una soluzione unica

  • La relazione fra queste componenti definisce il paradigma di programmazione

    • Programmazione procedurale: problemi modellati dagli algoritmi. Dati immagazzinati in aree comuni o passate agli algoritmi

    • Programmazione ad oggetti: problemi modellati dalle relazioni fra tipi di dati astratti (ADT, Abstract Data Types), chiamati generalmente oggetti


Il rapporto dato algoritmo

Il rapporto Dato-Algoritmo

Programmazione Dati Algoritmi

Linguaggio Bits Bits

macchina

Assemblers Symbolic Op-code

Words

Livello di astrazione

Compilatori Variables & Statements

Types

Linguaggi Data Subroutines strutturati structures

Ada (Modula) Abstract Packages

Data Types (Modules)

Object Oriented Objects Objects


Cos un oggetto

Cos’è un oggetto?

  • Né più né meno di quello che potreste trovare scritto in un vocabolario…

    • Un oggetto è un’entità che si possa immaginare dotata di determinate caratteristiche e funzionalità.

  • Lo stato di un oggetto è rappresentato da dati che ne descrivono le caratteristiche in un certo istante

  • Le funzionalità di un oggetto sono le operazioni che può svolgere quando glie lo si richiede (cioè quando riceve un messaggio)

  • Nella nostra vita quotidiana siamo molto più abituati a ragionare per oggetti che non in modo strutturato!


Un esempio

Un esempio...


Introduzione

Soldato


Cos un oggetto1

… cos’è un oggetto:

Funzione

Codice

funzione

Funzione

Codice

funzione

Dato

Dato

Dato

Codice

funzione

Funzione

Un insieme di dati e funzioni:


Incapsulazione

Incapsulazione

  • Netta divisione fra interfaccia e implementazione

  • Da fuori si vede solo l’interfaccia che definisce i messaggi accettati dall’oggetto

  • I dettagli dell’implementazione (dati e codice delle funzioni) sono invisibili dall’esterno

  • Ogni oggetto ha in se tutto ciò che gli serve per rispondere alle chiamate (o deve sapere a chi chiedere…)

  • Il confinamento di informazioni e funzionalità in oggetti permette livelli maggiori di astrazione e semplifica la gestione di sistemi complessi.


Approccio oo

Approccio OO

  • Sono le strutture di dati che svolgono le azioni, non le subroutines

  • Il lavoro è svolto dal server, non dal client

  • “Cos’ è?” “Com’ è fatto?”

     Data Oriented

  • “Cosa può fare per me?”

     Object Oriented


Perch programmare per oggetti

Perché programmare per oggetti?

  • Programmare per oggetti non velocizza l’esecuzione dei programmi...

  • Programmare per oggetti non ottimizza l’uso della memoria...

    E allora perchè programmare per oggetti?

  • Programmare per oggetti facilita la progettazione e il mantenimento di sistemi software molto complessi!


Caratteristiche del software non mantenibile

Caratteristiche del software non mantenibile

  • Rigidità

    • non può essere cambiato con faciltà

    • non può essere stimato l’impatto di una modifica

  • Fragilità

    • una modifica singola causa una cascata di modifiche successive

    • i bachi sorgono in aree concettualmente separate dalle aree dove sono avvenute le modifiche

  • Non riusabilità

    • esistono molte interdipendenze, quindi non è possibile estrarre parti che potrebbero essere comuni


Programmazione ad oggetti

Programmazione ad oggetti

  • La programmazione ad oggetti, attraverso l’incapsulazione, consente di:

    • ridurre la dipendenza del codice di alto livello dalla rappresentazione dei dati

    • riutilizzare del codice di alto livello

    • sviluppare moduli indipendenti l’uno dall’altro

    • avere codice utente che dipende dalle interfacce ma non dall’implementazione


C e object orientation

C++ e Object Orientation

  • Il C++ può essere usato come linguaggio procedurale o per programmazione ad oggetti

  • Object Orientation implementata attraverso il concetto di classe

  • Prima di affrontare il problema della programmazione OO con C++ dobbiamo:

    • capire dove la programmazione procedurale fallisce

    • affrontare la sintassi del C++


Programmazione procedurale

Programmazione procedurale

Idea:

perché non usare una function?

  • Esempio: cinematica relativistica

COMMON /MYDATA/ P1(4), P2(4),

+ P3(4), P4(4)

REAL P1(4), P2(4), P3(4), P4(4)

COSTHETA12 = (P1(1)*P2(1) + P1(2)*P2(2) +

+ P1(3)*P2(3))/...

COSTHETA13 = (P1(1)*P3(1) + P1(2)*P3(2) +

+ P1(3)*P3(3))/...

COSTHETA14 = (P1(1)*P4(1) + P1(2)*P4(2) +

+ P1(3)*P4(3))/...

FUNCTION COSTHETA(P1, P2)

REAL P1(4), P2(4)

COSTHETA = (P1(1)*P2(1) + P1(2)*P2(2) +

+ P1(3)*P2(3))/...

END

COMMON /MYDATA/ P1(4), P2(4),

+ P3(4), P4(4)

REAL P1(4), P2(4), P3(4), P4(4)

COSTHETA12 = COSTHETA(P1, P2)

COSTHETA13 = COSTHETA(P1, P3)

COSTHETA14 = COSTHETA(P1, P4)


Evoluzione del codice

Evoluzione del codice

  • Se cambia il formato del common block?

COMMON /MYDATA/ P1(4), P2(4), P3(4), P4(4)

COMMON /MYDATA/ P(4), E(4), THETA(4), PHI(4)

  • Bisogna cambiare la funzione (gli argomenti sono diversi)

FUNCTION COSTHETA1(THETA1, THETA2,

+ PHI1, PHI2)

COSTHETA1 = SIN(THETA1)*SIN(THETA2) *

+ COS(PHI1-PHI2) + COS(THETA1)*COS(THETA2)

END

  • ...e il codice!

COMMON /MYDATA/ P(4), E(4),

+ THETA(4), PHI(4)

COSTHETA12 = COSTHETA1(THETA(1),THETA(2),

+ PHI(1), PHI(2))

COSTHETA13 = COSTHETA1(THETA(1),THETA(3),

+ PHI(1), PHI(3))

COSTHETA14 = COSTHETA1(THETA(1),THETA(4),

+ PHI(1), PHI(4))


Il concetto di dipendenza

Il concetto di dipendenza

  • Nell’esempio precedente il codice di analisi (“alto livello”) dipende dai dettagli della struttura dati (“basso livello”).

FUNCTION COSTHETA(P1, P2)

REAL P1(4), P2(4)

COSTHETA = (P1(1)*P2(1) + P1(2)*P2(2) +

+ P1(3)*P2(3))/...

END

COSTHETAdipende dalla struttura dei datiP1e P2

COMMON /MYDATA/ P1(4), P2(4),

+ P3(4), P4(4)

COSTHETA12 = COSTHETA(P1, P2)

COSTHETA13 = COSTHETA(P1, P3)

COSTHETA14 = COSTHETA(P1, P4)

Il codice di analisi dipende dalla struttura del common blockMYDATA


Oo riduce le dipendenze

OO riduce le dipendenze!

  • Riduce la dipendenza del codice di alto livello dalla rappresentazione dei datiPermette il riutilizzo del codice di alto livello

  • Nasconde i dettagli di implementazioneSupporta tipi di dati astratti (vedere seguito ...)


Sintassi fortran vs c c

Sintassi: FORTRANvsC/C++

  • Struttura del programma

In C/C++ non è necessario un particolare formato il codice

PROGRAM TESTC esempio di programma

...

END

int main() {// esempio di programma

...

return 0; // fine}

spazi...

IlC/C++è case sensitive

Istruzioni separate da “;”

INTEGER I INTEGER*4 J REAL X REAL*8 D

int i;long j;float x;double d;


Il main program

Il main program

  • Ogni programma in C++, per essere eseguibile, deve contenere una funzione main() da cui l’esecuzione comincerà

  • main() deve avere un tipo (decidere quale è compito del programmatore). Regola generale è che main() ritorni un intero, a significare il return code dell’applicazione

int main()

{

// il piu` semplice programma in C++

return 0;

}


I o lettura e scrittura

I/O: lettura e scrittura

  • Non esiste nel C++ nativo. Si usa: iostream

    • Gli operatori << e >> sono usati per definire la direzione del flusso

    • cin, cout e cerr rappresentano lo standard input, output e error del programma

#include <iostream>

int main(){ return 0;}

#include <iostream> cout << “Hello, world !” << endl;

direttiva al

preprocessore

end of line


Commenti

Commenti

  • Esistono due tipi di commento in C++

    • inline:

    • multiline (come in C):

    • I due tipi possono essere usati indifferentemente, ma si raccomanda di usare l’inline (più semplice e meno ambiguo)

const int Ntries; // questo e` un commento inline

// il resto della linea e’ trattato come un commento

const int Ntries;

/* questo e` un commento multiline:

tutto viene trattato come un commento

fino a quando il commento stesso non

viene chiuso con uno */


Tipi predefiniti in c

Tipi predefiniti in C++

  • Sono definiti una serie di tipi numerici che permettono di rappresentare numeri interi, reali e caratteri

    • char (un solo byte) viene normalmente usato per rappresentare interi inferiori a 256

    • stringhe e numeri complessi sono implementati come tipi derivati

intintero in singola precisione

longintero in doppia precisione

floatreale in singola precisione

doublereale in doppia precisione

long double reale in precisione estesa

unsigned intintero senza segno

unsigned doublereale senza segno in doppia precisione

charcarattere singolo

boolvariabili logiche


Tipi predefiniti in c 2

Tipi predefiniti in C++ (2)

Costanti carattere

Esempi di costanti

‘\a’alert

‘\\’backslash

‘\b’backspace

‘\r’carriage return

‘\”’double quote

‘\f’form feed

‘\t’tab

‘\n’newline

‘\0’carattere nullo

‘\’’single quote

‘\v’vertical tab

‘\101’101 ottale, ‘A’

‘\x041’esadecimale, ‘A’

123 123 0x123 interi costanti,

decimale, ottale,

esadecimale

123l 123uinteri, long,

unsigned

‘A’ ‘1’ ‘\t’ caratteri, tab

3.14f 3.14153.1415Lfloat, double,

long double

300e-2.03e230e-1double, notazione

esponenziale

“Nome”stringa costante

truefalseboolean

Stringhe costanti

“”stringa nulla (‘\0’)

“nome”‘n’ ‘o’ ‘m’ ‘e’ ‘\0’

“una \”stringa\””stampa: una “stringa”

“una stringa \un \ alla fine della linea

su piu` linee”per continuare la stringa


Tipi predefiniti in c 3

Tipi predefiniti in C++ (3)

OS

16 bit

OS

32 bit

OS

64 bit

char[1]

8

8

8

int[1]

16

32

32

bool

16

32

32

short[1]

16

16

16

long[1]

32

32

64

float

32

32

32

double

64

64

64

long double

64

128

128

[1] Può essere unsigned


Identificatori

Identificatori

  • Un identificatore è composto da uno o più caratteri

  • Il primo carattere deve essere una lettera o un underscore. Caratteri successivi possono essere lettere, numeri o underscore

  • Non c’ è un limite in lunghezza, anche se alcuni sistemi si limitano a considerare i primi 31 caratteri

  • Gli identificatori che iniziano con un doppio underscore o con un underscore e una lettera maiuscola sono riservati ad usi di sistema

  • C++ e` case sensitive!

const int Ntries;

double _attempts;

double 2A; // errore!


Keywords

Keywords

  • Alcuni identificatori sono esplicitamente riservati al sistema (hanno un preciso significato in C++) e non possono essere usati

asmelseoperatorthrow

autoenumprivatetrue

boolexplicitprotectedtry

breakexternpublictypedef

casefalseregistertypeid

catchfloatreinterpret_casttypename

charforreturnunion

classfriendshortunsigned

constgotosignedusing

const_castifsizeofvirtual

continueinlinestaticvoid

defaultintstatic_castvolatile

deletelongstructwchar_t

do mutableswitchwhile

doublenamespacetemplate

dynamic_castnewthis

keyword


Const

const

const int N=100;N non puo` essere cambiato

double w[N];N usato come per dimensionare

un vettore

const int vect[5]=le componenti di vect non

{10,20,30,40,50};possono essere cambiate

Esempi di const

  • La keyword const viene utilizzata per dichiarare un oggetto costante

  • In C le costanti vengono normalmente dichiarate usando il preprocessore

    • in questo caso N e` una costante senza tipo ed il preprocessore sostituisce N ovunque lo trovi nel programma, senza rispettare le regole di scope(da evitare)

#define N 100


Dichiarazione

Dichiarazione

  • Le dichiarazioni associano un significato ad un identificatore

  • in C++ ogni cosa deve essere dichiarata per poter essere usata

  • Una dichiarazione è spesso anche una definizione. Per variabili semplici questo consiste nell’associare un valore alla variabile al momento della dichiarazione

const int i;// la variabile i

double max(double r1,double r2);// la funzione max

const double pi=3.1415926;// definizione

double max(double r1, double r2) {// dichiarazionereturn (r1>r2) ? r1: r2; // definizione di max

}


Typedef

typedef

  • L’istruzione typedef viene utilizzata per creare un alias per tipi esistenti

  • typedef NON può essere usato per implementare nuovi tipi, ma solo per definire un alias

typedef int INTEGER;// per i nostalgici del fortran

typedef int BOOLEAN;// usato prima che bool venisse

// implementato

typedef void (*ptr_f)(); // ptr_f e` un puntatore ad una

// procedura (subroutine)

typedef mela frutto;// compila soltanto se mela

// e` gia` stata definita


Enumeratori

Enumeratori

  • In C++ sono supportati tipi definiti dall’utente

enum Color{ red, green, blue};Color screenColor = blue;Color windorColor = red;int n = blue; // validoColor c = 1;// errore

enum Seme{ cuori, picche, quadri, fiori};


Scope

Scope

  • Le variabili possono essere dichiarate e definite quasi ovunque in un programma in C++

  • la visibilità (scope) di una variabile dipende da dove la variabile è stata dichiarata

int func()

{

const int n=50;// function scope

for (int i=0;i<100;i++)// i e` locale

{

double r;// r e` locale

...

}

cout<<“n “<< n <<endl;// OK

cout<<“i “<< i <<endl;// errore! Ma...

cout<<“r “<< r <<endl;// errore!

}


Scope 2

Scope (2)

Scope resolution operator

  • Attenzione! La stessa variabile può essere ri-dichiarata (con visibilità diversa). Questo è da evitare (se possibile) per non rendere il programma oscuro e a rischio di errore!

int i;// file (global) scope

int func()

{

int i=50;// function scope, nasconde

// la i a file scope

for (int i=0;i<100;i++)// block scope. Nasconde

// la i a function scope

{

int i;// questo e` un errore...

...

}

cout<<“i “<< i <<“ “<< ::i <<endl;

...

}


Namespace

namespace

  • Funzioni e variabili definite a global scope sono visibili dappertutto in un programma in C++

    • Per evitare che funzioni diverse (definite in librerie diverse) con lo stesso nome possano interferire (name clash), C++ implementa il concetto di namespace, che introduce un ulteriore, più alto livello di scope

namespace mynames

{

int i;// la mia dichiarazione di i

float max(float, float);// la mia dichiarazione di max

}

float mynames::max(float a, float b)// implementazione della

{// funzione max appartenente

return (a>b) ? a : b;// al namespace mynames

}


Namespace 2

namespace (2)

  • Per utilizzare variabili e funzioni racchiuse in un namespace si può:

    • o accedere all’intero namespace

    • oppure accedere alla singola variabile o funzione

    • oppure dichiarare la singola funzione

using namespace mynames;

...

float r = max (2.1f, 5.3f);

float r = mynames::max (2.1f, 5.3f);

using mynames::max;

...

float r = max (2.1f, 5.3f);


Operatori

Operatori

Espressioni AritmeticheCommento

-i+wpiu` e meno unari

a*ba/bi%2moltiplicazione,

divisione, modulo

a+ba-baddizione e

sottrazione binarie

a=3;assegnazione

Operatori relazionaliFortran

<minore di.LT.

>maggiore di.GT.

<=minore o uguale.LE.

>=maggiore o uguale.GE.

==uguale.EQ.

!=diverso.NE.

!Negazione unaria.NOT.

&&and logico.AND.

||or logico.OR.

bit-wise significato

Auto-incremento Espressione

e decremento

~i; Complemento bit a bit

i&j; AND bit a bit

i|j OR bit a bit

i^j XOR bit a bit

i<<n shift a sinistra di n pos.

i>>n shift a destra di n pos.

k = ++j; j=j+1; k=j;

k = j++; k=j; j=j+1;

k = --j; j=j-1; k=j;

k = j--; k=j; j=j-1;


Espressioni di assegnazione

Espressioni di assegnazione

  • Le espressioni di assegnazione sono valutate da destra a sinistra

  • Le assegnazioni multiple sono permesse

  • alcuni operatori di assegnazione combinano assegnazione ed altri operatori

  • Assegnazioni possono essere fatte all’interno di espressioni aritmetiche

a = j++;

j viene incrementato ed il risultato assegnato ad a

a = b = c = d = 100;

a *= b;// equivale ad a = a*b;

a -= b;// equivale ad a = a-b;

a = b + ( c = 3 );// equivale a c=3; a=b+c;


Statements

Statements

StatementC++commenti

vuoto;

espressionej=j+k;

composto{ . . . . }usato in funzioni, if..Costituisce un blocco

goto goto label;da non usarsi

ifif (p==0)

cerr<<“error”;un solo branch

if-elseif (x==y)

cout<<“the same”;

else

cout<<“different”;due branch

forfor (j=0;j<n;j++)le dichiarazioni sono

a[j]=0;permesse

whilewhile (i != j)0 o piu` iterazioni

i++;

do-whiledo

y=y-1;1 o piu` iterazioni

while (y>0);

breakbreak;esce dal blocco

continuecontinue;prossima iterazione


Statements 2

Statements (2)

StatementC++commenti

switchswitch (s) {

case 1:si deve usare break per

++i;evitare di cadere nei

case 2:casi successivi e

--i;aggiungere un caso di

default:default alla fine della

++j;lista

};

dichiarazioneint i=7;in un blocco, file o

namespace

trytry {. . . .}usato per trattare le

eccezioni

labelerror:

cerr<<“Error!”;usato con goto

return return x*x*x;valore di ritorno di

una funzione


Statement composti

Statement composti

  • Uno statement composto in è costituito da una serie di statement contenuti fra parentesi graffe

  • Usato normalmente per raggruppare istruzioni in un blocco (if, for, while, do-while, etc.)

  • Il corpo di una funzione è sempre uno statement composto

  • La dichiarazione di una variabile può avvenire ovunque all’interno di un blocco, in questo caso lo scope della variabile sarà il blocco stesso

  • Ovunque si possa usare uno statement singolo si può definire un blocco


Introduzione

if

  • Attenzione all’uso di = e ==

  • Nel dubbio, usare sempre un blocco…

  • Attenzione agli else!

if (i=1)// questo e` sempre vero!!!

{. . . .}

if (i != 0)// possibile divisione per 0

a++;// mancano delle {}?

a/=i;

if (i == 0)// possibile divisione per 0

if (a<0)

{

cerr<<“a e` negativo!”;

}

else b=a/i;


While e do while

while e do-while

  • La forma generale di un while è :

  • Lo statement verrà eseguito fino a quando la condizione verrà verificata (true). A seconda del volore della condizione, lo statement verrà eseguito zero o più volte

  • la sintassi di un do-while è invece:

  • Lo statement verrà quindi eseguito almeno una volta

while (condizione)

statement;

do

statement;

while (condizione);


Break e continue

break e continue

  • break e continue sono utilizzati nei loop per saltare alla fine del loop o fuori dal loop stesso

  • break e continue possono solamente essere utilizzati nel corpo di un for, while o do-while. break e` anche usato negli switch

int i,n=0;

int a[100];

cin>>i;// leggo il valore di i

while (1)// loop infinito

{

if (i<0) break;

if (n>=100) continue;

a[n]=i;

n++;

// continue salta qui

}

// break salta qui


Switch

switch

  • Lo switch è uno statement condizionale che generalizza lo if-else

  • lo statement è generalmente composito e consiste di diversi casee, opzionalmente, di un default

switch (condizione)

(statement);

switch (n) {

case 0:

cout<<“ n e` nullo”<<endl; break;

case 1: case 3: case 5: case 7: case 9:

cout<<“ n e` dispari”<<endl; break;

case 2: case 4: case 6: case 8: case 10:

cout<<“ n e` pari”<<endl; break;

default:

cout<<“ n non e` compreso tra 0 e 10”<<endl;

}


Switch 2

switch (2)

  • Non si puo` dichiarare una variabile in uno dei case

  • … ma si puo` creare una variabile locale definendo uno statement composto...

switch (k) {

case 0:

int j=0;// Illegale! Errore!

. . .

case 1:

. . .

}

switch (k) {

case 0:

{

int j=0;// OK, questo compila

. . .

}

case 1:

. . .

}


L operatore

L’operatore ?

  • L’operatore ? e` l’unico esempio di operatore ternario in C++

    • Equivale a:

    • Esempio:

expr1 ? expr2 : expr3;

if(expr1)

expr2;

else

expr3;

double max(double a, double b)

{

double max = (a>b) ? a : b;

return max;

}


Sintassi fortran vs c c1

Sintassi: FORTRANvsC/C++

  • Controllo di flusso del programma

DO I = 1, 10 . . . ENDDO IF (I.EQ.10 .AND. J.GT.4 .OR. X) THEN . . . ENDIF DO WHILE(X .NE. 5) . . . ENDDO

for (i = 1; i <= 10; i++) { . . .}

if (i == 10 && j > 4 || x) { . . .}

while( x != 5 ){ . . .}


Funzioni matematiche

Funzioni matematiche

  • In C++ non esistono funzioni predefinite

int main(){

return 0;}

{ double r, theta, phi;

#include <iostream>cin >> r >> theta >> phi ;

#include <cmath> double x = r * sin( theta ) * sin( phi ); double y = r * sin( theta ) * cos( phi ); double z = r * cos( theta );

cout << x << “, “ << y << “, “ << z << endl;

cmath.hdefinisce sin, cos, ...

  • Potenze: pow(b,exp) (non si può usare ** )


Array

Array

  • Sono supportati gli array di dimensione fissa

int main(){ int x[10]; for ( int i = 0; i < 10, i++ ) x[i] = 0; double m[5][5]; for ( int i = 0; i < 5; i++ ) for ( int j = 0; j < 5; j++ ) m[i][j] = i * j;

return 0;}

  • Inizializzazione:

int x[] = { 1, 2, 3, 4 };char[] t = { ‘C’, ‘i’, ‘a’, ‘o’, ‘\0’ };char[] s = “Ciao”;int m[2][3] = { {11, 12, 13}, {21, 22, 23} };

  • L’indice va da 0 a n-1. Usare un indice maggiore di n-1 può causare un crash.


Esempio con gli arrays

Esempio con gli arrays

  • Moltiplicazione fra matrici:

int main() {

const int DIM=3;

float m[DIM][DIM], m1[DIM][DIM], m2[DIM][DIM]; // Assumiamo che m1 ed m2 vengano riempiti qui...

// Moltiplicazione:

for (int i=0; i<DIM; i++) {

for (int j=0; j<DIM; j++) {

float sum=0;

for (int k=0; k<DIM; k++)

sum += m1[i][k] * m2[k][j];

m[i][j] = sum;

}

}

return 0;}


Puntatori

Puntatori

j

ptr

12

24

  • Riferimento ad una locazione di memoria

int main(){ int j = 12;

return 0;}

int *ptr = &j;

#include <iostream> cout << *ptr << endl;

j = 24; cout << *ptr << endl;

cout << ptr << endl;

24

0x7b03a928

12

indirizzo di memoria


Puntatori1

Puntatori

j

ptr

12

  • Puntatore nullo

#include <iostream>

int main(){ int j = 12; int *ptr = 0; cout << *ptr << endl; // crash ! return 0;}

Segmentation violation (core dumped)


Puntatori e array

Puntatori e array

X[0]

X[1]

X[2]

X[3]

X[4]

1.5

2.5

0.0

3.5

0.0

x

X+3

X+1

  • In C gli array sono trattati come puntatori

int main(){ float x[5]; int j; for (j = 0; j < 5; j++) x[j] = 0; float *ptr = x; *ptr = 1.5; // x[0] = 1.5 *(ptr+1) = 2.5; // x[1] = 2.5 *(ptr+3) = 3.5; // x[3] = 3.5}


Puntatori allocazione dinamica

Puntatori: allocazione dinamica

ptr

12

  • Riferimento ad una locazione di memoria

#include <iostream>

int main(){ int *ptr = new int;

*ptr = 12; cout << *ptr << endl;delete ptr; return 0;}

  • Attenzione:

    • Non usare delete fa accumulare locazioni di memoria inutilizzate (memory leak)

    • Utilizzare puntatori prima del new o dopo il delete causa il crash del programma


Puntatori allocazione dinamica1

Puntatori: allocazione dinamica

ptr

10

11

12

  • Riferimento a più locazioni di memoria

#include <iostream>

int main(){ int *ptr = new int[3];

ptr[0] = 10; ptr[1] = 11; ptr[2] = 12delete [] ptr; return 0;}


New e delete

new e delete

operatore newcommenti

int *i=new int;alloca un intero, returna il puntatore

char *c=new char[100];alloca un array (stringa) di 100

caratteri

int *i=new int(99);alloca un intero e lo inizializza a 99

char *c=new char(‘c’);alloca un carattere inizializzato a c

int *j=new int[n][4];alloca un array di puntatori ad intero

  • Gli operatori newand deletevengono utilizzati per allocazione/deallocazione di memoria dinamica

    • la memoria dinamica (heap), è un’area di memoria libera provvista dal sistema per quegli oggetti la cui durata di vita è sotto il controllo del programmatore

  • new riserva la quantità necessaria di memoria richiesta e ritorna l’indirizzo di quest’area


New e delete 2

new e delete (2)

operatore deletecommenti

delete ptr;distrugge un puntatore ad un oggetto

delete p[i];distrugge l’oggetto p[i]

delete [] p; distrugge ogni oggetto di tipo p

  • L’operatore delete è usato per restituire una certa area di memoria (allocata con new) allo heap

  • Ogni oggetto allocato con new deve essere distrutto con delete se non viene piu` utilizzato, altrimenti l’area di memoria che esso occupata non potra` piu` essere ri-allocata (memory leak)

  • L’argomento di delete è tipicamente un puntatore inizializzato preventivamente con new


New e delete 3

new e delete (3)

  • Attenzione

    • la dimensione dello heap non e` infinita

    • l’allocazione con new può fallire, nel qual caso new restituisce un puntatore nullo o suscita un’eccezione. Nel caso di allocazione di memoria importante bisogna verificare che l’operazione abbia avuto successo prima di usare il puntatore

    • ogni oggetto creato con new deve essere distrutto con delete, ogni oggetto creato con new [] deve essere distrutto con delete [] , queste forme NON sono intercambiabili


Regole di conversione e cast

Regole di conversione e cast

Conversioni implicite

  • char, short e bool vengono promossi ad int

  • Tipi interi che non possono essere rappresentati con un int vengono promossi a unsigned

  • In una espressione di tipo misto, gli operandi di ordine inferiore vengono promossi all’ordine superiore secondo la gerarchia:

    • int<unsigned<long<unsigned long<float<double<long double

  • bool e` un tipo intero, con true che viene promosso a 1 e false a 0

    • In C++ esistono conversioni esplicite ed implicite.

      • Le conversioni implicite (e.g. intfloat) nelle espressioni aritmetiche, nel passare i parametri ad una funzione o nel ritornare un valore da una funzione rendono il meccanismo di conversione molto conveniente ma anche potenzialmente pericoloso (errori a run time)


    Regole di conversione e cast 2

    Regole di conversione e cast (2)

    • Ogni genere di puntatore può essere convertito in un puntatore generico a void

    • Al contrario di quanto avviene in C, un puntatore generico non è compatibile con un puntatore di tipo arbitrario ma richiede un cast esplicito

    • Ogni puntatore puo` essere inizializzato a 0 senza bisogno di un cast esplicito.

    • In C++ usare 0 e non NULL per i puntatori!

    char *ch;

    void *generic_p;

    . . .

    generic_p=ch;// OK, char* va in void*

    ch=generic_p;// OK in C, illegale in C++

    ch=(char *)generic_p; // OK, C e C++ arcaico


    Casting in ansi c

    Casting in ANSI C++

    Castcommenti

    x=(float) i;cast in C++ - notazione C

    x=float(i);cast in C++, notazione funzionale

    x=static_cast<float>(i);ANSI C++ - raccomandato

    i=reinterpret_cast<int>(&x)ANSI C++, non portabile e system

    dependent

    func(const_cast<int>(c_var))dove C_var e` una variabile dichiarata

    const. Usato per eliminare la

    “const-ness” per chiamare func

    • Data la complessità delle operazioni di casting in C++ nuovi operatori di casting sono stati aggiunti a quelli già esistenti in C

    • Esiste anche un dynamic_cast, utilizzato per riconoscere il tipo di un oggetto a run-time (RTTI)


    Funzioni

    Funzioni

    Tipo ritornato

    Parametri

    double max( double a, double b)

    {

    return (a>b) ? a : b;

    }

    Corpo della

    funzione

    • In C++ le funzioni sono caratterizzate da un nome, dal tipo della variabile ritornata e da una lista di parametri (opzionali)

    • La lista dei parametri (anche se vuota) deve essere esplicitata

    • Il valore ritornato deve essere compatibile, a meno di conversione esplicita, con il tipo della funzione

    Valore di ritorno


    Funzioni 2

    Funzioni (2)

    Tipo di dichiarazioneC++ commenti

    funzione double cube(double x) parametri passati

    { return x*x*x; } “by value”

    procedura void pr_square(int i) subroutine, non si

    { cout<<i*I<<endl; } usa return

    senza argomenti void hello () puo` anche essere

    { cout<<“Hello”<<endl; } void hello(void)

    argomenti passati void swap(int& i,int& j) i e j hanno i loro

    per riferimento { int t=i; i=j; j=t; } valori scambiati

    variabile int scanf(const char, …) chiamata con un

    qualsiasi numero

    di argomenti

    inline inline double cube(int x) codice inline

    argomenti di int power(int i, int n=2) il 2do argomento

    default puo` essere tralasciato


    Prototipi delle funzioni

    Prototipi delle funzioni

    main.cc

    max.cc

    #include <iostream>

    double max(double, double);

    int main()

    {

    double m = max(1, 3);

    cout<<“Il massimo e` “<<m<<endl;

    return 0;

    }

    double max (double a, double b)

    {

    return (a>b) ? a : b;

    }

    • Prima di essere usata, una funzione deve essere dichiarata (nel file che la usa)

    • I prototipi rendono le funzioni in C++ “type safe”, nel senso che i valori reali degli argomenti vengono all’occorrenza convertiti nei tipi formali specificati dal prototipo

    Prototipo di max

    (normalmente in max.h)


    Call by reference

    Call-by-Reference

    • L’uso dei riferimenti permette ad una funzione di modificare il valore dei suoi argomenti

      • Per ragioni di efficenza, oggetti di grandi dimensioni (in termini di memoria) vengono normalmente passati “by reference”.

      • Per evitare che possano essere modificati dalla funzione, il riferimento viene definito const

    bool greater(int& i, int& j) { // se i>j scambia i e j

    if (i>j) {

    int temp=i;

    i=j;

    j=temp;

    return true;

    }

    else

    return false;

    }

    Argomenti passati “by reference”

    possono essere modificati dalla

    funzione stessa


    Funzioni inline

    Funzioni inline

    • La keyword inline suggerisce al compilatore che ogni chiamata alla funzione deve essere convertita in codice eseguibile (la definizione della funzione viene sostituita alla chiamata dovunque nell codice)

    • Le funzioni inlinevengono usate per ragioni di efficienza e (per non sovraccaricare il compilatore) devono essere semplici

    • Il compilatore può decidere autonomamente (per esempio se la funzione è troppo lunga) di ignorare la direttiva inline


    Argomenti di default

    Argomenti di default

    main.cc

    pow.cc

    int pow(int , int);

    int main()

    {

    int r=3;

    int a1=pow(3,3); // a1=27

    int a2=pow(3); // a2=9

    return 0;

    }

    int pow (int a, int k=2)

    {

    if (k==2) return a*a;

    else return a*pow(a, k-1);

    }

    • Ad ogni parametro di una funzione può essere assegnato un valore di default. Questo permette di chiamare la funzione tralasciando quei parametri il cui valore di default risulta appropriato

    • Solo ai parametri più a destra nella calling sequence può essere dato un default.

    Argomento di default


    Overloading

    Overloading

    average_array.cc

    double average_array(const int a[], int size)

    {

    int sum=0;

    for (int i=0;i<size;i++) sum+=a[i];

    return double(sum)/size;

    }

    double average_array(const double a[], int size)

    {

    double sum=0;

    for (int i=0;i<size;i++) sum+=a[i];

    return sum/size;

    }

    • Funzioni diverse possono avere lo stesso nome

    • La funzione che viene chiamata è scelta dal compilatore in base al tipo di ritorno ed al numero e tipo degli argomenti


    Overloading 2

    Overloading (2)

    • La lista dei tipi degli argomenti di una funzione è chiamata signature

    • Il tipo ritornato dalla funzione non fa parte della signature, mentre il numero e l’ordine degli argomenti è cruciale

    void print(int i=0) {. . .}// (1)

    void print(int i, double x) {. . .}// (2)

    void print(double y, int i) {. . .}// (3)

    . . .

    print(‘A’); // ‘A’ e` convertito a int, chiama (1)

    print(str[]); // errore! Non e` possibile una conversione

    print(15,9);// errore! Ambiguita` fra (2) e (3)

    print(15,9.);// OK, chiama (2)

    print();// OK, chiama (1) con il default


    L algoritmo di selezione

    L’algoritmo di selezione

    • L’utente può sempre utilizzare una conversione forzata (type cast) per ottenere una corrispondenza

    • Il compilatore segnala tutti i casi in cui esiste ambiguità

    Ricerca della corrispondenza esatta

    Promozioni standard degli argomenti

    int long

    I tentativi del compilatore

    Conversioni standard dei tipi

    int float

    Conversioni definite dall’utente

    traccia int

    Corrispondenza con l’ellipsi (…)


    Funzioni esterne

    Funzioni esterne

    • Si possono chiamare funzioni FORTRAN da C++:

      SUBROUTINE HBOOK1(ID, TITLE, NBIN, MIN, MAX, OPT)

      SUBROUTINE HFILL(ID,X, Y, WEIGHT)

      extern “C” void hbook1_(int&, char*, int&, float&, float&, float&, int);

      extern “C” void hfill_(int&, float&, float&, float&);

      ...

      hbook1_(100, title, ……) // BUS ERROR!!! (il FORTRAN passa

      // sempre “by-reference”

      int id=100;

      hbook1_(id, title, ……)// OK!


    Parametri del programma

    Parametri del programma

    • Dotando main() di una lista di argomenti, è possibile avere accesso ai parametri passati dalla command line:

    • argc è il numero di parametri passati dalla command line (sempre almeno 1, il nome del programma) mentre il vettore di stringhe argv contiene ogni singolo parametro

    #include <iostream.h>

    int main(int argc, char *argv[])

    {

    cout<<“ argc e`: “<<argc<<endl;

    cout<<“ il nome dell’eseguibile e` “<<*argv<<endl;

    for (int i=1; i<argc; i++)

    cout<<“Argomento #”<<i<<“ = “<<*(argv+i)<<endl;

    return 0;

    }


    Parametri del programma 2

    Parametri del programma (2)

    • Lanciato con il comando

      prompt> mytest questo e un test

    • il programma produrra` il seguente output:

      argc e` : 5

      il nome dell’eseguibile e`/user/andrea/myprogram

      Argomento #1 = questo

      Argomento #2 = e

      Argomento #3 = un

      Argomento #4 = test


    Organizzazione dei files

    Organizzazione dei files

    • Normalmente, le dichiarazioni delle interfacce e le specifiche sono separate dall’implementazione

      • header files (.h o .hh)

        • inclusi nei file sorgente utilizzando direttive del precompilatore

        • non contengono codice eseguibile (con l’eccezione delle definizioni delle funzioni inline)

        • non devono essere inclusi piu` di una volta, per evitare problemi con il linker

    #include <iostream.h>

    #ifndef MyHeader_H

    #define MyHeader_H

    // dichiarazioni

    …..

    #endif


    Organizzazione dei files 2

    Organizzazione dei files (2)

    • Files sorgente (.C,.cxx,.cpp,.cc)

      • contengono l’implementazione di funzioni e metodi

      • codice eseguibile

      • includono gli header files utilizzando le direttive del preprocessore

      • vengono compilati

    • Funzioni inline (.icc)

      • La definizione di una funzione inline deve essere visibile là dove viene usata.

      • Normalmente implementate negli header files o in files separati (con estensione .icc) che devono essere inclusi nel files sorgente che ne facciano uso


    C e object orientation1

    C++ e Object Orientation

    • Definizione di nuovi tipi (oltre a int, float, double) come:

      • numeri complessi,

      • vettori,

      • matrici, . . .

  • ma anche:

    • traiettorie,

    • superfici,

    • elementi di apparati sperimentali,...

  • Gli oggetti permettono di modellare una problema che rappresenti la realtà


  • C e object orientation2

    …C++ e Object Orientation

    • Object Orientation implementata in C++ attraverso il concetto di classe:

    • I dati privati (o attributi) di una classe definiscono lo stato dell’oggetto

    • Le funzioni (o metodi) di una classe implementano la risposta ai messaggi


    Una classe c

    Una classe C++

    Messaggio

    Metodo

    Messaggio

    Metodo

    Attributo

    Attributo

    Attributo

    Metodo

    Messaggio


    Classe vector2d

    Classe Vector2D

    Vector2D.h

    class Vector2D

    {

    public:

    Vector2D(double x, double y);

    double x();

    double y();

    double r();

    double phi();

    private:

    double x_;

    double y_

    };

    Vector2D.cc

    #include “Vector2D.h”

    Vector2D::Vector2D(double x, double y): x_(x), y_(y) { }

    double Vector2D::x() {

    return x_;

    }

    double Vector2D::r() {

    return sqrt(x_*x_ + y_*y_);

    }

    ...

    • Un esempio: un vettore bidimensionale

    costruttore

    funzioni o metodi

    dati o attributi

    Punto e virgola!


    Interfaccia e implementazione

    Interfaccia e implementazione

    Vector2D.cc

    #include “Vector.h”

    Vector2D::Vector2D(double x,

    double y) : x_(x), y_(y){}

    double Vector2D::x()

    {

    return x_;

    }

    double Vector2D::r()

    {

    return sqrt(x_*x_ + y_*y_);

    }

    Vector2D.h

    class Vector2D

    {

    public:

    Vector2D(double x, double y); double x();

    double y();

    double r();

    double phi();

    private:

    double x_;

    double y_;

    };

    • Gli attributi privati non sono accessibili al di fuori della classe

    • I metodi pubblici sono gli unici visibili


    Costruttori e distruttori

    Costruttori e distruttori

    • Un costruttore è un metodo il cui nome è quello della classe a cui appartiene

    • Lo scopo di un costruttore è quello di costruire oggetti del tipo della classe. Questo implica l’inizializzazione degli attributi e, frequentemente, allocazione di memoria dallo heap

    • Un costruttore la cui lista di argomenti è vuota o composta di argomenti di default viene normalmente chiamato costruttore di default

    Vector2D::Vector2D() {. . . .} // costruttore di default

    #include “Vector2D.h”

    . . .

    Vector2D v; // oggetto costruito con il

    // costruttore di default


    Costruttori e distruttori 2

    Costruttori e distruttori (2)

    • Un costruttore del tipo che ha come argomento un riferimento ad un oggetto della stessa classe viene chiamato copy constructor

    • Il copy constructor viene normalmente utilizzato:

      • quando un oggetto è inizializzato per assegnazione

      • quando un oggetto è passato come argomento ad una funzione

      • quando un oggetto è ritornato da una funzione

    • Se non viene fornito esplicitamente dall’utente, il compilatore ne genererà uno automaticamente

    Vector2D::Vector2D(const Vector2D& v) {. . . .}

    Vector2D v(v1); // dove v1 e` di tipo Vector2D


    Costruttori e distruttori 3

    Costruttori e distruttori (3)

    • Gli attributi di una classe possono essere inizializzati nel costruttore per mezzo di una lista di inizializzatori, che precede il corpo della funzione

    • Quando uno degli attributi è esso stesso una classe, il costruttore appropriato viene scelto sulla base dei parametri forniti nell’inizializzazione

    • E` obbligatorio inizializzare gli attributi (non statici) che siano o riferimenti o const

    Vector2D::Vector2D(double x, double y) : x_(x), y_(y)

    {

    . . .

    }


    Costruttori e distruttori 4

    Costruttori e distruttori (4)

    • Il distruttore è un metodo il cui nome è quello della classe a cui appartiene preceduto da una tilde (~)

    • Il distruttore viene chiamato automaticamente quando un oggetto sta per essere distrutto (sia perchè delete è stato invocato sia perchè l’oggetto è finito fuori scope

    • Il compito del distruttore è di assicurarsi che l’oggetto per cui è invocato verrà distrutto senza conseguenze. In particolare, se memoria è stata allocata nel costruttore, il distruttore dovrà assicurarsi di restituirla allo heap

    Vector2D::~Vector2D() {}// vuoto, in questo caso


    Costruttori e distruttori 5

    Costruttori e distruttori (5)

    • I costruttori con un solo parametro sono automaticamente trattati come operatori di conversione

    • Per evitare la conversione si puo` usare explicit

    Vector2D::Vector2D(int i) {. . .}

    // costruisce un vettore a partire da un intero, ma puo`

    // essere usato per convertire un intero in vettore

    v=Vector2D(i);

    explicit Vector2D(int); // solo costruttore


    Classe vector2d1

    Classe Vector2D

    main.cc

    #include <iostream.h>

    #include “Vector2D.h”

    int main()

    {

    Vector2D v(1, 1);

    cout << “ v = (“

    << v.x() << “,”

    << v.y() << “)” << endl;

    cout << “ r = “ << v.r();

    cout << “ phi = “ << v.phi() << endl;

    return 0;

    }

    Output:

    v = (1, 1)

    r = 1.4141 phi = 0.7854

    • Come usare Vector2D:

    invoca il constructor


    Classe vector2d2

    Classe Vector2D

    main.cc

    #include <iostream.h>

    #include “Vector2D.h”

    int main()

    {

    Vector2D *v = new Vector2D(1, 1);

    cout << “ v = (“

    << v->x() << “,”

    << v->y() << “)” << endl;

    cout << “ r = “ << v->r();

    cout << “ phi = “ << v->phi() << endl;

    delete v;

    return 0;

    }

    Output:

    v = (1, 1)

    r = 1.4141 phi = 0.7854

    • … oppure attraverso un puntatore...

    Allocazione sullo heap

    Attenzione!


    Interfaccia e implementazione1

    Interfaccia e implementazione

    • La struttura interna dei dati (x_, y_) che rappresentano l’oggetto della classe Vector2D sono nascosti (private) agli utilizzatori della classe.

    • Gli utilizzatori non dipendono dalla struttura interna dei dati (come lo erano gli utilizzatori dei common blocks Fortran)

    • Se la struttura interna cambia (es.: r_, phi_), il codice che usa Vector2D non deve essere modificato.


    Classe vector2d3

    Classe Vector2D

    main.cc

    #include <iostream>

    #include “Vector2D.h”

    int main()

    {

    Vector2D v(1, 1);

    cout << “ V = (“

    << v.x_ << “,” //

    << v.y_ << “,” << endl; // non compila !

    cout << “ r = “ << v.r();

    cout << “ phi = “ << v.phi() << endl;

    }

    • Protezione dell’accesso ai dati:

    • I metodi di una classe hanno libero accesso ai dati privati e protetti di quella classe


    Selettori e modificatori

    Selettori e modificatori

    Vector2D.cc

    Vector2D.h

    #include “Vector2D.h”

    void Vector2D::scale(double s)

    {

    x_ *= s; y_ *= s;

    }

    class Vector2D

    {

    public:

    Vector2D(double x, double y); double x() const;

    double y() const;

    double r() const;

    double phi() const;

    void scale(double s);

    private:

    double x_, y_;

    };

    main.cc

    #include “Vector2D.h”

    int main()

    {

    const Vector2D v(1, 0);

    double r = v.r() // OK

    v.scale( 1.1 ); // errore!

    }

    • Selettore: metodo che non modifica lo stato (attributi) della classe. E’ dichiarato const

    • Modificatore: metodo che può modificare lo stato della classe

    Selettori (const)

    modificatore


    Friend

    friend

    • La keyword friend puo` essere usata perche` una funzione (o una classe) abbia libero accesso ai dati privati di un’altra classe

    class A {

    . . .

    friend int aFunc();

    friend void C::f(int);

    };

    class B {

    friend class C;

    };

    class C {

    . . .

    };


    Friend 2

    friend (2)

    • friend (nonostante il nome) e` nemico dell’incapsulamentoe quindi dell’Object Orientation

    • Un uso eccessivo di friend è quasi sempre sintomo di un cattivo disegno

    • Esistono anche situazioni in cui un friend può essere accettabile

      • Overloading di operatori binari

      • Considerazioni di efficienza

      • Relazione speciale fra due classi

    “A programmer must confer with an architect before making friend declarations”


    Static

    static

    • Attributi dichiarati staticin una classe sono condivisi da tutti gli oggetti di quella classe

    • Metodi dichiarati static non possono accedere ad attributo non statici della classe

    • Attiributi statici possono essere usati e modificati soltanto da metodi statici

    • Nonostante l’utilizzo di static sembri imporre condizioni troppo restrittive, esso risulta utile nell’implementazione di:

      • contatori

      • singleton (vedi oltre)


    Un contatore

    Un contatore

    Un membro statico deve essere

    inizializzato una e una sola volta

    nel codice eseguibile

    Un metodo statico puo` essere

    invocato cosi`...

    … o cosi`...

    Class MyClass {

    private:

    static int counter;

    static void increment_counter() { counter++; }

    static void decrement_counter() { counter--; }

    public:

    MyClass() { increment_counter(); }

    ~MyClass() { decrement_counter(); }

    static int HowMany() { return counter; }

    };

    #include <iostream.h>

    #include “MyClass.h”

    int MyClass::counter=0;

    int main() {

    MyClass a,b,c;

    MyClass *p=new MyClass;

    cout<<“ How many? “<< MyClass::HowMany() <<endl;

    delete p;

    cout<<“ and now? “<< a.HowMany() <<endl;

    return 0;

    }


    Un singleton

    Un singleton

    • Un singleton è una classe di cui, ad ogni momento nel corso del programma, non può esistere più di una copia (istanza)

    class aSingleton {

    private:

    static aSingleton *ptr;

    aSingleton () {}

    public:

    static aSingleton *GetPointer(){

    if (ptr==0)

    ptr=new aSingleton;

    return ptr;

    }

    };

    Pattern utile per

    l’implementazione di

    classi “manager” di cui

    deve esistere una sola

    istanza

    #include “aSingleton.h”

    aSingleton *aSingleton::ptr=0;

    int main() {

    aSingleton *mySing=

    aSingleton::GetPointer();

    . . .

    Return 0;

    }

    Attenzione a non farlo

    diventare l’equivalente

    di un common block!


    Operatori1

    Operatori

    Vector2D.h

    class Vector2D

    {

    public:

    Vector2D(double x, double y); double x() const;

    double y() const;

    double r() const;

    double phi() const;

    private:

    double x_;

    double y_;

    };

    Vector2D operator+(const Vector2D& v1,

    const Vector2D& v2);

    Vector2D operator-(const Vector2D& v1,

    const Vector2D& v2);

    Vector2D.cc

    Vector2D operator+(const Vector2D& v1,

    const Vector2D& v2)

    {

    return Vector2D(v1.x() + v2.x(), v1.y() + v2.y());

    }

    Vector2D operator-(const Vector2D& v1,

    const Vector2D& v2)

    {

    return Vector2D(v1.x() - v2.x(), v1.y() - v2.y());

    }

    • E’ possibile ridefinire +, -, *, [], ++, ==, . . .


    Operatori 2

    Operatori (2)

    main.cc

    #include <iostream>

    #include “Vector2D.h”

    int main()

    {

    Vector2D v1(1, 0), v2(0, 1);

    Vector2D v;

    v = v1 + v2;

    cout << “ v = “ << v << endl;

    cout << “ r = “ << v.r();

    cout << “ phi = “ << v.phi() << endl;

    }

    Sintassi alternativa (!#@!?) :

    v.operator=( operator+(v1, v2) );

    Output:

    v = (1, 1)

    r = 1.4141 theta = 0.7854

    • Esempio:

    ridefinizione di<<


    Operatori 3

    Operatori (3)

    main.cc

    #include <iostream>

    #include <cmath>

    #include “Vector3D.h”

    #include “Matrix.h” // matrice 3x3

    int main()

    {

    Vector3D v1(1, 1, 0);

    double phi = M_PI/3;

    double c = cos(phi), s = sin(phi);

    Matrix m(1, 0, 0,

    0, c, s,

    0, -s, c);

    Vector3D u = m * v;

    }

    • Esempio:

    pgreco


    Introduzione

    this

    Vector2D.h

    Vector2D.cc

    class Vector2D {

    public:

    Vector2D& operator=(const Vector2D& );

    // ...

    private:

    double x_, y_;

    };

    Vector2D& operator=(const Vector2D& v){

    x_=v.x();

    y_=v.y();

    return *this;

    }

    main.cc

    #include “Vector2D.h”

    int main() {

    Vector2D null(0, 0);

    Vector2D a, b;

    a=b=null;

    }

    L’operatore = ritorna una

    referenza a se stesso.

    Permette assegnazioni

    multiple

    • In una classe è automaticamente definito un attributo particolare: this

      • this è un puntatore all’oggetto di cui fa parte

      • E’ particolarmente utile quando si definisce un operatore di assegnazione (=):


    Overloading di operatori

    Overloading di operatori

    Vector2D.cc

    Vector2D.h

    Vector2D operator*(const Vector2D&,

    double s) {

    return Vector2D( v.x() * s, v.y() * s);

    }

    double

    operator*(const Vector2D& v1,

    const Vector2D& v2) {

    return ( v1.x() * v2.x() + v1.y() * v2.y() );

    }

    class Vector2D {

    public:

    // ...

    private:

    double x_, y_;

    };

    Vector2D operator*(const Vector2D &, double);

    double operator*(const Vector2D&,

    const Vector2D&);

    • possono esistere funzioni con lo stesso nome ma con argomenti diversi

    • Non bisogna pero` esagerare! Ogni operatore deve avere un significato ben preciso, per ragioni di chiarezza.


    Overloading di operatori 2

    Overloading di operatori (2)

    • Permette di utilizzare tipi definiti dall’utente come se fossero tipi fondamentali

    • La cardinalita`, l’associativita` e la precedenza di un operatore non possono essere modificati

    • Operatori unari sono implementati come metodi senza argomenti (l’oggetto è l’argomento implicito)

    • Operatori binari possono essere implementati come metodi con un argomento (il primo argomento, implicito, è l’oggetto il cui operatore agisce) o come funzioni friend a due argomenti.


    Programmazione generica

    Programmazione generica

    Main.cc

    int main() { Vector v1,v2; cout << max<int>(10,20) << endl; cout << max<float>(2.6,1.0) << endl; cout << max<Vector>(v1,v2) << endl;}

    Per il tipo T deve essere definito l’operatore <

    • Il C++ fornisce un metodo per creare un polimorfismo parametrico. E’ possibile utilizzare lo stesso codice per tipi differenti: il tipo della variabile diventa un parametro

    template<class T> T max( T p1, T p2 ) { if ( p1 < p2 ) return p2; else return p1;}


    Sintassi

    Sintassi

    typename

    template < class identifier >

    function definition

    template < class identifier >

    class definition

    • Ogni volta che nella definizione della funzione o della classe appare identifier questo viene sostituito dal compilatore con il tipo fornito nella chiamata.

    • La dichiarazione e l’implementazione del template devono essere nello stesso file ove il template viene utilizzato


    Parametri templati

    Parametri templati

    • Parametri interi possono essere inclusi nella dichiarazione del template

    • I parametri di default possono essere tralasciati

    template <typename T=int , int n=10>

    class array_n {

    ...

    private:

    T items[n]; // n istanziato esplicitamente

    };

    array_n<complex, 1000> w;// w array di complessi


    Templates di templates

    Templates di templates

    • L’argomento di un template puo` essere esso stesso un template

    • questo permette la creazione e l’utilizzo di meta-templates (templates istanziati con templates) molto sofisticati

    • la Standard Template Library fa uso di questa possibilita`

    template <class T1, template <class T2> class T3 >


    Funzioni template e parametri

    Funzioni template e parametri

    OK per ogni compilatore

    ANSI/C++, ma la maggior

    parte dei compilatori lo rifiuta

    • Una buona parte dei compilatori accetta una sintassi ristretta per quel che riguarda le funzioni template. ANSI/C++ prevede invece che anche parametri numerici possano essere inclusi nella definizione del template

    template <class T>

    void swap(T& x, T& y){

    T temp;

    temp=x;

    x=y;

    y=temp;

    }

    template <class T, int n=10>

    T aFunc(){

    T temp[n];

    . . .

    }


    Membri statici

    Membri statici

    • Per le classi template, gli attributi statici non sono universali ma specifici di ogni istanza

    • Le variabili statiche MyClass<int>::counter e MyClass<double>::counter sono diverse

    template <class T>

    class MyClass {

    public:

    static int counter;

    ...

    };

    MyClass<int> a,b;

    MyClass<double> c;


    Un esempio lo stack di interi

    Un esempio: lo stack di interi

    Contenuto

    Contenuto

    Contenuto

    Stack

    Stack

    val

    val

    val

    top

    top

    next

    next

    next

    ...

    class Contenuto {

    ...

    private:

    Contenuto* next;

    int val;

    };

    class Stack {

    ...

    private:

    Contenuto* top;

    };

    Lo stack vuoto


    Un esempio lo stack di interi1

    Un esempio: lo stack di interi

    User code

    int main() {

    Stack s;

    s.push ( 10 );

    s.push ( 20 );

    cout << s.pop() << “ - “ << s.pop;

    return 0;

    };

    Output

    >> 10 - 20

    class Contenuto {

    public:

    Contenuto ( int i, Contenuto* ptn ) { val=i; next=ptn; }

    int getVal (){ return val; }

    Contenuto* getNext() {return next;}

    private:

    Contenuto* next;

    int val;

    };

    class Stack {

    public:

    Stack() {top = 0;}

    ~Stack() {}

    void push ( int i ) {

    Contenuto* tmp = new Contenuto(i,top );

    top = tmp; }

    int pop () {

    int ret = top->getVal();

    Contenuto* tmp = top;

    top = top->getNext();

    delete tmp;

    return ret;

    }

    private:

    Contenuto* top;

    };


    Lo stack templato

    Lo stack “templato”

    User code

    int main() {

    Stack<int> s;

    s.push ( 10 );

    s.push ( 20 );

    Stack<double> s1;

    Stack<Shape *> s2;

    cout << s.pop() << “ “ << s.pop;

    return 0;};

    template <class T>

    class Contenuto {

    public:

    Contenuto ( T i, Contenuto* ptn ) { val = i; next = ptn; }

    T getVal (){ return val; }

    Contenuto* getNext() {return next;}

    private:

    Contenuto* next;

    T val;

    };

    template <class T>

    class Stack {

    public:

    Stack() {top = NULL;}

    ~Stack() {;}

    void push ( T i ) {

    Contenuto<T>* tmp = new Contenuto<T> (i,top );

    top = tmp; }

    T pop () {

    T ret = top->getVal();

    Contenuto<T>* tmp = top;

    top = top->getNext();

    delete tmp;

    return ret;

    }

    private:

    Contenuto<T>* top;

    };


    La standard template library

    La Standard Template Library

    • La libreria standard STL e’ una libreria di classi di contenitori, algoritmi ed iteratori.

    • STL e’ una libreria generica: tutti i suoi componenti sono parametrizzati mediante l’utilizzo dei template

    puntatori intelligenti

    Iteratori

    Contenitori

    vettori, liste, mappe, ….

    find, replace, reverse, sort, ….

    Algoritmi


    Iteratori puntatori intelligenti

    Iteratori (puntatori intelligenti)

    • Gli iteratori sono dei puntatori agli elementi di un contenitore e ci permettono di muoverci all’interno di esso:

      • Iteratori monodirezionali: Permettono di accedere all’elemento successivo o al precedente

      • Iteratori bidirezionali : Permettono di accedere sia all’elemento successivo che al precedente

      • Iteratori ad accesso casuale : Permettono di accedere ad un qualunque elemento del contenitore


    Contenitori

    Contenitori

    • Un contenitore è un oggetto capace di immagazzinare altri oggetti e che possiede metodi per accedere ai suoi elementi.

      • Ogni contenitore ha un iteratore associato che permette di muoversi tra gli elementi contenuti

      • Una sequenza è un contenitore di lunghezza variabile i cui elementi sono organizzati linearmente. E’ possibile aggiungere e rimuovere elementi

      • Un contenitore associativo è una sequenza che permette un efficiente accesso ai suoi elementi basato su una chiave.


    Sequenze

    Sequenze

    • vector

      • Tempo costante di inserimento e cancellazione di elementi all’inizio e alla fine del vettore.

      • Tempo lineare con il numero di elementi per inserimento e cancellazione di elementi all’interno del vettore

      • Iteratore ad accesso casuale

    • list

      • Tempo costante di inserimento e cancellazione di elementi in ogni punto della lista

      • Iteratore bidirezionale


    Vector

    vector

    begin()

    end()

    0

    push_back()

    p

    p

    p

    p

    p

    p

    • Le locazioni di memoria sono contigue

      • Accesso casuale, veloce l’accesso agli elementi, lenti inserimento ed estrazione

    1

    2

    ...

    9

    ++


    Introduzione

    list

    nodo

    nodo

    nodo

    val

    val

    val

    next

    next

    next

    prev

    prev

    prev

    • Simile allo stack, ma consente di muoversi in due direzioni

    • Le locazioni di memoria non sono contigue

      • Lenta la ricerca, veloci inserimento ed estrazione

    ...

    list

    top

    bottom


    Contenitori associativi

    Contenitori associativi

    • Sono contenitore di coppie ( key, value ) e possiedono un iteratore bidirezionale

    • map

      • Viene richiesto l’operatore < per la chiave

      • Gli elementi sono ordinati secondo la chiave


    Algoritmi

    Algoritmi

    • Gli algoritmi sono delle funzioni globali capaci di agire su contenitori differenti

    • Sono incluse operazioni di ordinamento (sort, merge, min, max...), di ricerca (find, count, equal...), di trasformazione (transform, replace, fill, rotate, shuffle...), e generiche operazioni numeriche (accumulate, adjacent difference...).

    sort

    find

    fill

    min, max

    copy

    count


    Esempio uso sequenze

    Esempio uso sequenze

    vector

    list

    vector

    list

    vector

    list

    #include < >

    #include <algorithm>

    #include <iostream>

    int main() {

    <int> container;

    int val;

    for (int i=0; i<10; i++) {

    val = (int)((float)rand()/RAND_MAX*10);

    container.push_back(val); }

    <int>::iterator it1;

    for ( it1=container.begin(); it1!=container.end(); it1++)

    cout << "vector : " << *it1 << endl;

    return 0;

    }


    Esempio uso contenitori associativi

    Esempio uso contenitori associativi

    #include <map>

    #include <algorithm>

    #include <iostream>

    #include <string>

    int main() {

    map<string,int> amap;

    amap["Primo”]=1;

    amap[“Secondo”]=2;

    cout << "Size : " << amap.size() << endl;

    amap["Terzo"]=3;

    amap["Quarto"]=4;

    cout << "Size : " << amap.size() << endl;

    map<string,int>::iterator it;

    for ( it=amap.begin(); it!=amap.end(); it++)

    cout << "map : " << it->first << " " << it->second << endl;

    cout << amap.find("Terzo")->second << endl;

    return 0;

    }


    Assegnazione di un metodo ad un messaggio

    Assegnazione di un metodo ad un messaggio

    • I metodi pubblici di una classe costituiscono l’interfaccia della classe (cioè i messaggi che l’oggetto può interpretare)

    • La funzione è assegnata al messaggio in fase di codifica (early binding)

    • Può essere necessario assegnare la funzione al messaggio a run-time (late binding)

      Polimorfismo


    Controllo dei tipi

    Controllo dei tipi

    • Controllare i tipi significa verificare che ad un oggetto vengano inviati solo messaggi che è in grado di comprendere:

      • controllo del nome del metodo

      • controllo della lista degli argomenti

    • In C++ il controllo è fatto dal compilatore (strong typing)

    • In altri linguaggi (ad esempio SmallTalk) è fatto a run-time (weak typing)


    Typing binding

    Typing & Binding

    Typing

    Definizione

    dei messaggi

    e degli

    argomenti

    Strong

    Consistenza dei

    tipi verificata

    dal compilatore

    Weak

    Consistenza dei

    tipi verificata

    a run-time

    Binding

    Assegnazione

    di un metodo

    ad un

    messaggio

    Early

    In fase di

    programmazione

    INFLESSIBILE

    Late

    A run-time

    POLIMORFISMO


    Esempio i soldati

    Esempio: i soldati

    • Tutti i soldati devono capire il messaggio attacca. Il messaggio ha conseguenze diverse a seconda del tipo di soldato:

      • un arcere lancia una freccia

      • un fante usa la spada

      • un cavaliere lancia una lancia

    • Il gestore della schermata vuole tenere una lista di soldati e vuole poter dire ad ogni soldato di attaccare indipendentemente dal tipo ma basandosi solo sulla posizione.


    Introduzione

    list<Soldato> lista;

    riempiLista(lista);

    Posizione unaPosizione=...;

    list<Soldato>::iterator iter;

    for(iter=lista.begin();iter!=lista.end();iter++){

    Soldato unSoldato=(*iter);

    if(unSoldato.posizione()==unaPosizione) unSoldato.attacca();

    }

    class Soldato {

    void attacca() {

    // cosa scrivo qui?!? Per quale tipo di

    // soldato implemento il metodo attacca()?

    }

    };


    Polimorfismo

    Polimorfismo

    • Polimorfismo con tipi controllati dal compilatore (Strong typing & late binding).

      Come?

    • In C++ viene implementato tramite il concetto di ereditarietà (inheritance)

    • Classe astratta: definisce i messaggi

    • Classe concreta: assegna i metodi ai messaggi

      La classe concreta eredita da quella astratta


    Ereditariet

    Ereditarietà

    • Una classe può essere derivata da una classe esistente usando la sintassi:

      • public, protected e private specificano il tipo di accesso ai membri della classe

    • Se la classe base non ha un costruttore di default:

      • La classe derivata deve implementarlo

    • Se la classe base ha un costruttore di default:

      • il costruttore della classe derivata deve esplicitamente invocarlo nella sua lista di inizializzatione

        • Il costruttore della classe base può così essere eseguito prima che il costruttore della classe derivata sia eseguito

    class newclass: (public|protected|private) oldclass {

    dichiarazioni...

    };


    Ereditariet 2

    Ereditarietà (2)

    • Una classe derivata pubblicamente è a tutti gli effetti un sottotipo della classe base.

      • Un oggetto della classe derivata può essere trattato come se fosse un oggetto della classe base

      • Un puntatore alla classe base può puntare ad oggetti della classe derivata

      • Un riferimento alla classe derivata può, se la cosa ha un senso, essere implicitamente convertito ad un riferimento alla classe base

      • E` possibile dichiarare un riferimento alla classe base ed inizializzarlo ad un oggetto della classe derivata


    Ereditariet 3

    Ereditarietà (3)

    • La definizione dell’interfaccia (metodi pubblici) della classe base è estremamente importante perchè determina il comportamento delle classi derivate

    • Un metodo della classe base può essere:

      • dichiarato e definito normalmente

        • la classe derivata eredita questo metodo e NON puòridefinirlo

      • dichiarato virtual e definito normalmente

        • la classe derivata eredita questo metodo e può ridefinirlo

      • dichiarato virtual e non definito (=0)

        • la classe derivata eredita il metodo e DEVE ridefinirlo


    Classi base astratte

    Classi base astratte

    • Una funzione puramente virtuale è un metodo virtuale non definito. E` dichiarato come:

    • Una classe che ha almeno un metodo puramente virtuale è chiamata classe astratta

    • Oggetti di una classe astratta non possono esistere

    • Puntatori ad una classe base astratta possono essere definiti ed usati polimorficamente (per puntare ad oggetti delle classi derivate)

    • Una classe base astratta viene introdotta per specificare l’interfacciadi una categoria di classi

    virtual func_prototype = 0;


    Introduzione

    class Soldato {

    virtual void attacca()=0;

    };

    class Arcere : public Soldato {

    virtual void attacca() {

    // lancia una freccia

    }

    };

    class Fante : public Soldato {

    virtual void attacca() {

    // usa la spada

    }

    };

    ...


    Erediariet multipla

    Erediarietà multipla

    • L’ereditarietà multipla permette di derivare una classe da due o più classi base. La sintassi viene estesa per permettere una lista di classi base

    • L’ ereditarietà multipla viene spesso utilizzata per combinare un’interfaccia ed una implementazione, ma è molte volte sintomo di un cattivo disegno

    class A {

    . . . .

    };

    class B {

    . . . .

    };

    class AplusB: public A, private B {

    . . . .

    };


    Dynamic cast

    dynamic_cast

    • dynamic_cast opera una conversione, se è possibile, fra due tipi. Il puntatore ritornato NON è nullo soltanto se il tipo dell’oggetto su cui si opera è quello che ci si aspetta

    class Base {

    . . . .// base implementation

    };

    class Derived: public Base {

    . . . .

    void new_method() ; // non e’ definito in Base!

    };

    void func(Base *ptr) // ptr e’ un obbetto dell classe Base

    {

    ptr->new_method(); // Errore!!!

    Derived *p = dynamic_cast<Derived *> (ptr)

    if (p !=0) {

    p->new_method();

    }

    }


    Ereditariet 4

    Ereditarietà (4)

    Track.h

    class Track

    {

    public:

    LorentzVector momentum()

    { return p_; }

    protected:

    LorentzVector p_;

    };

    DchTrack.h

    #include “Track.h”

    class DchTrack : public Track

    {

    public:

    int hits()

    { return hits_->size(); }

    DchHit* hit(int n)

    { return hits_[n]; }

    protected:

    list<DchHit> hits_;

    };

    • Una classe derivata estende la classe base e ne eredita tutti i metodi e gli attributi

    DchTrackè una Track che ha degli attributi in più (hits_) e nuovi metodi (DchHit* hit(int n), int hits())


    Esempio shape

    Esempio: shape

    • Tutti gli oggetti nella finestra hanno comportamenti comuni che possono essere considerati in astratto:

      • disegna

      • sposta

      • ingrandisc

      • etc...


    Cerchi e quadrati

    Cerchi e quadrati

    Quadrato

    Cerchio


    Cerchio

    Cerchio

    Nome della classe

    Circle.h

    Costruttore

    Distruttore

    Point2d: classe che rappresenta un punto in 2 dimensioni.

    Interfaccia Pubblica

    Metodi: operazioni sugli oggetti

    “Dati” privati

    (Attributi, membri)

    Punto e virgola!

    class Circle {

    };

    public:

    Circle(Point2d center, double radius);

    ~Circle();

    void moveAt(const Point2d & p);

    void moveBy(const Point2d & p);

    void scale(double s);

    void rotate(double phi);

    void draw() const;

    void cancel() const;

    private:

    Point2d center_;

    double radius_;


    Cerchio 2

    Cerchio (2)

    Circle.cc

    Main.cc

    #include “Circle.h”

    void Circle::draw() const

    {

    const int numberOfPoints = 100;

    float x[numberOfPoints], y[numberOfPoints];

    float phi = 0, deltaPhi = 2*M_PI/100;

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

    {

    x[i] = center_.x() + radius_ * cos( phi );

    y[i] = center_.y() + radius_ * sin( phi );

    phi += dphi;

    }

    polyline_draw(x, y, numberOfPoints, color_, FILL);

    }

    void Circle::moveAt( const Point2d& p )

    { cancel(); center_ = p; draw();

    }

    void Circle::scale( double s )

    { cancel(); radius_ *= s; draw();

    }

    Circle::Circle( Point2d c, double r ) :

    center_( c ), radius_( r )

    { draw(); }

    Circle::~Circle()

    { cancel(); }

    #include “Circle.h”

    int main()

    {

    Circlec( Point2d(10, 10), 5 );

    c.draw();

    c.moveAt(Point2d(20, 30));

    return 0;

    }


    Quadrato

    Quadrato

    Square.h

    Square.cc

    class Square {

    public:

    Square(const Point2d&, const Point2d&,

    Color color = TRASPARENT);

    ~Square();

    void moveAt( const Point2d& p );

    void moveBy( const Point2d& p );

    void changeColor( Color color );

    void scale( double s );

    void rotate( double phi );

    void draw() const;

    void cancel() const;

    private:

    Point2d center_;

    Vector2d centerToUpperCorner_;

    Color color_;

    };

    #include “Square.h”

    void Square::draw() const

    {

    float x[4], y[4];

    Vector2d delta( centerToUpperCorner_ );

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

    {

    Point2d corner = center_ + delta;

    x[i] = corner.x();

    y[i] = corner.y();

    delta.rotate( M_PI_2 );

    }

    polyline_draw(x, y, 4, color_, FILL);

    }

    void Square::rotate( double phi )

    {

    cancel(); centerToUpperCorner_.rotate( phi );

    draw();

    }

    Square::Square(const Point2d& lowerCorner, const Point2d& upperCorner,

    Color color) :

    center_( median(lowerCorner, upperCorner) ), centerToUpperCorner_( upperCorner - center_ ),

    color_( color ) { draw(); }

    void Square::scale( double s )

    { cancel(); centerToUpperCorner_ *= s; draw(); }

    centerToUpperCorner_

    upperCorner

    loweCorner


    Codice applicativo client

    Codice Applicativo (Client)

    Main.cc

    #include “Circle.h”

    #include “Square.h”

    int main()

    {

    Circle c1( Point2d(2.,3.), 4.23 );

    Square r1( Point2d(2.,1.), Point2d(4.,3.) );

    Circle * circles[ 10 ];

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

    {

    circles[ i ] = new Circle( Point2d(i,i), 2. );

    }

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

    circles[ i ]->draw();

    return 0;

    }

    Costruisce un vettore di puntatori a cerchi, crea oggetti in memoria e salva i loro puntatori nel vettore.

    Itera sul vettore e invoca draw()

    per ogni elemento

    Come gestire cerchi

    e quadrati insieme?


    Polimorfismo1

    Polimorfismo

    Tutte le Shapes

    hanno la stessa interfaccia:

    draw, pick, move, fillColor...,

    ma ogni sottotipo diverso

    può avere la usa personale

    implementazione


    Interfaccia astratta

    Interfaccia astratta

    Shape.h

    Square.h

    Main.cc

    class Shape {

    public:

    Shape() { }

    virtual~Shape() { }

    virtual void moveAt(const Point2d& where) = 0;

    virtual void changeColor(Color newColor) = 0;

    virtual void scale(double s) = 0;

    virtual void rotate(double phi) = 0;

    virtual void draw() const = 0;

    virtual void cancel() const = 0;

    };

    #include “Shape.h”

    class Square: publicShape

    {

    // …. Il resto tutto uguale a prima

    };

    #include “Circle.h”

    #include “Square.h”

    int main()

    {

    Shape * shapes[ 20 ];

    int index = 0;

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

    {

    Shape * s;

    s = new Circle( Point2d(i, i), 2.) );

    shapes[ index ++ ] = s;

    s = new Square( Point2d(i, i),

    Point2d(i+1, i+2)) );

    shapes[ index ++ ] = s;

    }

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

    shapes[ i ]->draw();

    return 0;

    }

    Interfaccia di metodi

    puramente virtuali


    Ereditariet e riuso del codice

    Ereditarietà e riuso del codice

    CenteredShape.h

    Square.h

    Class CenteredShape: public Shape

    {

    public:

    CenteredShape(Point2d c,

    Color color = TRASPARENT)

    : center_(c), color_(color)

    { /*draw();*/ }

    ~Circle()

    { /*cancel();*/ }

    void moveAt( const Point2d& );

    void moveBy( const Vector2d& );

    void changeColor( Color );

    virtual void scale( double ) = 0;

    virtual void rotate( double ) = 0;

    virtual void draw() const = 0;

    virtual void cancel() const = 0;

    protected:

    Point2d center_;

    Color color_;

    };

    #include “CenteredShape.hh”

    class Square : public CenteredShape

    {

    public:

    Square( Point2d lowerCorner, Point2d upperCorner,

    Color col = TRASPARENT) :

    CenteredShape( median(lowerCorner, upperCorner), col),

    touc_(upperCorner - center_) { draw(); }

    ~Square() { cancel(); }

    virtual void scale( double s )

    { cancel(); centerToUpperCorner_ *= s; draw(); }

    virtual void rotate( double phi );

    virtual void draw() const;

    virtual void cancel() const;

    private:

    Vector2d touc_;

    };

    Non si possono chiamare metodi virtuali in costruttori e distruttori (troppo presto, troppo tardi)


    Attenzione alle generalizzazioni

    Attenzione alle generalizzazioni...

    Rectangle.h

    Square.h

    class Square : public Rectangle

    {

    public:

    Square(double x0, double y0, double l) :

    Rectangle(x0, y0, l, l) { }

    };

    class Rectangle

    {

    public:

    Rectangle(double x0, double y0,

    double lx, double ly) :

    lx_(lx), ly_(ly), x0_(x0), y0_(y0) { }

    void scaleX(double s);

    void scaleY(double s);

    protected:

    double x0_, y0_;

    double lx_, ly_;

    };

    • Attenzione: scegliere le relazioni di ereditarietà può essere non banale.

    • Un quadrato è un rettangolo?

    Avere lx_ e ly_ è ridondante per Square

    Cosa succede se si invoca scaleX o scaleY ?


    Ereditariet multipla

    Ereditarietà multipla

    DrawableObj.h

    Shape.h

    DrawableShape.h

    class DrawableShape :

    public DrawableObj, public Shape

    {

    public:

    virtual void draw();

    virtual void scale(double s);

    virtual void moveAt( Vector2d& ); };

    class DrawableObj

    {

    public:

    virtual void draw() = 0;

    };

    class Shape

    {

    public:

    virtual void scale(double s) = 0;

    virtual void moveAt( Vector2d& ) = 0;};

    • Una classe può ereditare da più classi


    Strategie di sviluppo di un progetto

    Strategie di sviluppo di un progetto

    • Requisiti: cosa l’utente vuole

    • Analisi: la visione dell’informatico dei requisiti

    • Disegno: l’aspetto del sistema software

    • Produzione: codifica

    • Testing: debugging e verifica dei requisiti

    • Mantenimento: installazione del prodotto e controllo del funzionamento per il resto della sua vita


    Modello a cascata

    Modello a cascata

    Requisiti

    Analisi

    Disegno

    Produzione

    Testing


    Modello evoluzionario

    Modello evoluzionario

    Requisiti

    Analisi

    Testing

    Disegno

    Produzione


    Confronto fra i modelli di sviluppo

    A cascata

    Processo lineare (si torna al passo precedente solo in caso di problemi)

    Confinamento delle attività in ogni fase

    Facile da gestire (gestione delle scadenze)

    Difficile da modificare

    Prodotto utilizzabile solo alla fine del processo

    Evoluzionario

    Processo ciclico (brevi processi completi)

    Attività distribuite su più fasi

    Difficile da gestire

    Facile da modificare e integrare

    Prototipo utilizzabile fin dal primo ciclo

    Confronto fra i modelli di sviluppo


    Requisiti

    Requisiti

    • Definizione delle richieste da parte dell’utente del programma (o di una sua parte) sul sistema

    • Si parla di programmazione per contratto perchè l’utente richiede solamente la definizione del servizio richiesto NON la metodologia seguita per fornirglielo

      • è possibile delegare parte del lavoro richiesto ad altri

      • il sistema è indipendente da chi è il suo utente

        INCAPSULAMENTO!


    Analisi

    Analisi

    • Comprensione e razionalizzazione delle richieste dell’utente

    • Costruzione di un modello

      • astrazione (semplificazione delle relazioni)

      • rilevanza (identificazione degli oggetti chiave)

    • Da non trascurare: analisi delle soluzioni esistenti. Può far risparmiare molto tempo!!!


    Disegno

    Disegno

    Definizione di oggetti

    e classi

    Definizione delle

    interfacce

    Definizione degli stati

    e dell’implementazione

    Definizione delle

    relazioni


    Disegno 2

    Disegno (2)

    • Dopo ogni ciclo bisogna analizzare i rischi, la stabilità del disegno e la complessità delle classi

    • Se una classe è troppo complessa conviene dividerla

    • Ad ogni ciclo il numero di modifiche deve diminuire

    • Architetture troppo complesse devono essere modularizzate


    Codifica

    Codifica

    • C’è poco da dire…

    • Non sopravvalutate questa fase:


    Testing

    Testing

    • Debugging:è ovvio… il codice non deve dare errori.

    • Use cases: specificano il comportamento del sistema in una regione.

    • Scenarios: sono esempi concreti di use cases. Per definizione se tutti gli scenari sono soddisfatti correttamente il test è positivo.


    Metodi di sviluppo del software

    Metodi di sviluppo del software

    Un metodocomprende:

    • Una notazione

      mezzo comune per esprimere strategie e decisioni

    • Un processo

      specifica come deve avvenire lo sviluppo


    Metodi object oriented

    Metodi Object Oriented

    Grady Booch

    Jim Rumbaugh

    Ivar Jacobson

    • Booch Methodby Grady Booch

    • OMT by Jim Rumbaugh

    • Objectory (Use Cases) by Ivar Jacobson

    • CRCby R.Wirfs-Brock

  • Di recente introduzione: UML

    • uno standard OMG (Object Management Group), dal novembre 1997


  • Uml per l analisi e il disegno

    UML per l’analisi e il disegno

    • Class Diagrams: aspetto statico del sistema. Classi con attributi e metodi e relazioni tra di esse.

    • Sequenceecollaboration digrams: comportamento dinamico del sistema. Sequenza dei messaggi scambiati fra gli oggetti.

    • Use case diagrams: illustra gli use cases, le relazioni fra di essi e gli attori che vi partecipano.

    • State diagrams: descrive gli stati in cui ogni oggetto si può trovare e le modalità con cui passa da uno stato all’altro


    Concetti delle classi rivisitati

    Concetti delle classi rivisitati

    • Relazioni tra oggetti

    • Decomposizione funzionale all’interno di una classe

      • responsabilità dei metodi

    • Decomposizione funzionale tra più classi

      • responsabilità delle classi


    Rappresentazione delle classi

    Rappresentazione delle classi

    Nome

    - dato

    - dato

    + metodo(arg)

    # metodo(arg)

    - metodo(arg)

    attibuti

    pubblico

    protetto

    privato

    operatori


    Rappresentazione di una classe c in uml

    Rappresentazione di una classe C++ in UML

    Nome.h

    Nome

    class Nome {

    private:

    Tipo1variabile1;

    Tipo2variabile2;

    Tipo3variabile3;

    public:

    Nome();

    ~Nome();

    Tipo4funzione1( arg );

    protected:

    Tipo5funzione2( arg );

    private:

    Tipo6funzione3( arg );

    };

    - variabile1:Tipo1

    - variabile2:Tipo2

    - variabile3:Tipo3

    + funzione1(arg):Tipo4

    # funzione2(arg):Tipo5

    - funzione3(arg):Tipo6


    Attributi e metodi

    Attributi e metodi

    Protetto (#)

    Publico (+)

    Privato (-)

    Notazione di

    Rational Rose


    Principali relazioni fra classi

    Principali relazioni fra classi

    • associazione

    • aggregazione by reference

      (il composito non vive senza il componente)

    • aggregazione by value

      (aggregazione fisica: esistenza contemporanea)

    • dipendenza

    • generalizzazione (inheritance)


    Aggregazione contenimento

    Aggregazione (contenimento)

    • By reference (condivisa)

    • un autista guida più automobili

    • By value (possesso)

    • una automobile possiede il suo motore


    Cardinalit e direzionalit

    Cardinalità e direzionalità

    Non navigabile

    • Il punto non conosce

    • i poligoni

    • Il poligono è costituito

    • da punti


    Dipendenza

    Dipendenza

    • Non c’è nessuna associazione

    • C’è comunque relazione di uso

    • Il CD non conosce

    • il CDPlayer

    • Il CDPlayer usa il CD: se cambia

    • il formato del CD il CDPlayer deve

    • essere modificato


    Generalizzazione ereditariet

    Generalizzazione (ereditarietà)

    Ereditarietà

    virtuale!


    Class diagram di shape

    Class Diagram di “Shape”


    Class diagram

    Class Diagram


    Class diagram1

    Class Diagram


    Object sequence diagram

    Object Sequence Diagram


    Object collaboration diagram

    Object Collaboration Diagram


    Crc classi responsabilit collaborazioni

    CRCClassi, Responsabilità, Collaborazioni

    B

    A

    z

    s

    x

    y

    C

    p

    F

    D

    E

    q

    f

    w


    Assegnare responsabilit

    Assegnare Responsabilità

    • Identificare i protagonisti

    • Analizzare il ruolo dei vari oggetti

    • Concentrarsi sul comportamentonon la rappresentazione

    • Cercare Oggetti con proprietà comuni:

      • appartiene a classi diverse, o sono solo oggetti diversi?

    • Definire le interfacce (le operazioni che soddisfano le responsabilità)

      Una corretta assegnazione delle responsabilità è la chiave di una buona modularità e riuso


    Collaborazione tra classi

    Collaborazione tra classi

    • Le responsabilità vanno suddivise tra i vari oggetti del sistema

    • non deve esistere un controllo centralizzato

    • Un oggetto deve compiere le proprie responsabilità e delegare ad altri operazioni specifiche

      • Legge di Demeter: non usate oggetti lontani:

        Invece di: traiettoria.listapunti().aggiungi(Punto);

        usare: traiettoria.aggiungiPunto(Punto);


    Identificare relazioni

    Identificare Relazioni

    • Cercare collaborazioni

    • Cercare aggregazioni

    • Cercare generalizazioni

      Come un clientconosce il suo service provider?


    Relazioni

    Logiche

    Generalizazione: Is-a

    Aggregazione: Has

    Dipendenza: Knows

    Implementazione

    Inheritance

    Template instantiation

    Composizioneby value

    Composizioneby reference

    Relazioni

    {


    Avere o essere

    Avere o essere?

    • Uno dei punti critici è distinguere se il rapporto fra due oggetti è del tipo avere o essere:

      • Un LorentzVector è un Vector o ha un Vector?

      • Una Traccia è un vector<Hit> o ha un vector<Hit>?

      • Un Rivelatore è una Superficie o ha una superficie?

    • Per risolvere il problema bisogna guardare a cosa fanno!


    Principio di liskov

    Principio di Liskov

    • Gli oggetti figli possono essere usati ovunque l’oggetto genitore è richiesto

      • usare l’inheritance quando è richiesto il polimorfismo

      • Non cambiare il comportamento della base class


    Composizione by value o by refrence

    Composizione by value o by refrence

    • In C++ la scelta fra aggregazione by value o by refrence può seguire questo schema:

      • Tipi semplici (int, float, …): by value

      • Parte dello stato dell’oggetto: by value

      • Oggetti condivisi: by reference

      • Assegnati a run time: by reference

    • Oggetti condivisi by reference: attenzione a chi ha la responsabilità di crearli e cancellarli! (1 new  1 delete!)


    Approccio outside in

    Approccio Outside-in

    • Il corretto approccio è quello di guardare il sistema dall’esterno.

    • Identificare prima di tutto gli oggetti che interagiscono con l’utente esterno e i messaggi a cui devono saper rispondere (think client!)

    • In seguito identificare gli oggetti che forniscono servizi a questi ultimi e così via

    • Gli algoritmi vengono per ultimi!!!


    Crc workshop

    CRC Workshop

    • Metodo per la definizione si una architettura bilanciata

    • Ogni partecipante svolge il ruolo di una classe.

      • Individuazione delle classi

      • Contrattazione delle responsabilità

      • Definizione delle collaborazioni

      • Difesa dal tentativo di assegnazione di responsabilità contrarie alla natura della classe


    Regole per il crc workshop

    Regole per il CRC workshop

    • Tentate di rifuutare le responsabilità

      • Dovrei? (Non sono io che lo devo fare!)

      • Potrei? (Non ho i mezzi, o lo stato per farlo!)

    • Cercate di fare poco lavoro

      • Se avete dovuto accettare una responsabilità cercate di far fare il lavoro a qualcun’altro

    • Potenziate i collaboratori, non interferite


    Design patterns

    Design Patterns

    • Sono elementi di software OO riutilizzabile

    • Piccoli insiemi di classi che collaborano implementando dei comportamenti tipici

      • Creational patterns

      • Structural patterns

      • Behavioral patterns

    • I principali sono raccolti in un libro:

      E. Gamma et al., Design Patterns


    Factory

    Factory

    I client possono richiedere la creazione di un prodotto senza dipendervi

    LaFactorydipende dai prodotti concreti, mentre i client dipendono solo da quelli astratti


    Proxy

    Proxy

    Una richiesta da un client a un server, può

    essere mediata dalProxy, che può compiere

    anche altre operazioni (I/O, caching, etc.)


    Composite

    Composite

    Il client può trattare componenti e compositi usando la stessa interfaccia. La composizione può essere ricursiva.

    Esempio: programmi di grafica


    Gruppo di shapes

    Gruppo di Shapes

    Client

    Shape

    draw( )

    1..*

    1..*

    _components

    GroupofShapes

    Circle, Square, ...

    draw( )

    draw( )

    Il gruppo di shapes è il Composite

    La shape è il Component

    Le shapes concrete (Circle, Square, ecc...) sono le Leaf


    Codice del modello composite

    Codice del modello composite

    Shape.h

    class Shape {

    public:

    Shape() {}

    virtual void draw() const = 0;

    // altri metodi virtuali ( = 0 )

    };

    Circle.h

    #include “Shape.h”

    class Circle: public Shape {

    public:

    Circle(Point2D c, double r):

    Shape(), center_(c), radius_(r) {}

    void draw() const { ; // draw circle }

    // altri metodi definiti per Circle

    private:

    double radius_;

    Point2D center_;

    };


    Codice del modello composite1

    Codice del modello composite

    GroupofShapes.h

    #include “Shape.h”

    class GroupofShapes : public Shape {

    public:

    typedef vector<Shape *> Container;

    typedef Container::const_iterator Iterator;

    GroupofShapes(){}

    void draw() const {

    Iterator p=components.begin();

    Iterator pe=components.end();

    while (p!=pe)

    { (*p)->draw(); p++; }

    return;

    }

    // gli altri metodi sono definiti operando

    // sui componenti

    protected:

    Container components;

    };


    Strategy

    Strategy

    Il pattern Strategy permette di scegliere l’algoritmo da eseguire a run-time.

    Nuovi algoritmi possono essere introdotti senza modificare il codice utente.


    Observer

    Observer

    Lo stato dell’Observer dipende dallo stato del Subject.

    Il Subject notifica a tutti gli Observer registrati che il suo stato è cambiato.


    Appendice strighe c style

    Appendice: strighe C-style

    • Le variabili carattere sono gestite come array di char (un char contiene un solo carattere)

      • accesso agli elementi tramite la sintassi degli array

      • carattere nullo usato come terminatore (‘\0’)

    • Funzoni di libreria per la gestione dei char* :

      • #include<cstring> per utilizzarle

      • int strlen(const char*); lunghezza della stringa

      • int strcmp(const char*, const char*); confronto di due stringhe

      • char* strcpy(char*, const char*); copia la seconda stringa nella prima


    Appendice la classe string

    Appendice: la classe string

    • Per semplificare la gestione delle stringhe è stata creata la classe string

      • #include<string> per usarla

      • Definiti gli operatori standard:

        • = per l’assegnazione

        • + e += per la concatenazione

        • == e tutti gli altri operatori relazionali per il confronto

        • [] per l’accesso agli elementi

      • Disponibile sintassi simile a quella dei contenitori STL:

        • iteratori: string::iterator e string::const_iterator

        • funzioni begin() , end() , size() , ecc...

      • Interoperabilità con char*:

        char* c=“Pippo”; string s=c;

        char* c1 = s.c_str(); s += c;


    Confronto stringhe c style e string

    #include<iostream>

    #include<cstring>

    int main(){

    int err=0;int big=1000000;

    char* c1=“LLLong string”;

    for(int i=0;i<big;i++){

    int len=strlen(c1);

    char* c2=new char[len+1];

    strcp(c2,c1);

    if(strcmp(c2,c1))err++;

    delete[] c2;

    }

    cout<<err<<“errori”<<endl;

    return 0;

    }

    #include<iostream>

    #include<string>

    int main(){

    int err=0;int big=1000000;

    string s1=“LLLong string”;

    for(int i=0;i<big;i++){

    // int len=s1.size();

    string s2=s1;

    if(s2!=s1)err++;

    }

    cout<<err<<“errori”<<endl;

    return 0;

    } // 2 volte piu’ veloce!!!

    Confronto stringhe C-style e string


    Appendice operazioni di i o

    Appendice:operazioni di I/O

    • Si utilizza la libreria iostream

      • Gli operatori di stream>> e << dirigono il flusso da/per le unità desiderate:

        • cout : standard output. Si sono già visti molti esempi

        • cerr : standard error. Si usa come cout

        • cin : standard input (normalmente la tastiera)

    include<iostream>

    include<string>

    int main(){

    string nome;

    cout << “Come ti chiami?” << endl;

    cin>> nome; // Notare la direzione!!!

    if(nome.empty()) cerr << “Stringa nulla!” << endl;

    else cout << “Ciao “ << nome << “!” << endl;

    return 0;

    }


    Overloading degli operatori di i o

    Overloading degli operatori di I/O

    • Gli operatori << e >> possono essere ridefiniti per consentire operazioni del tipo:

      Vector2D v(1,2);

      cout << “Il vettore v vale “ << v << endl;

    • Si utilizza una funzione friend:

      class Vector2D {

      friend ostream& operator <<(ostream& os, const Vector2D v);

      [...]

      }

      ostream& operator <<(ostream& os, const Vector2D v){ os << “(“ << v.x() << “,” << v.y() << “)”;}

    • Si ottiene:

      Il vettore v vale (1,2)


    Appendice i o con files

    Appendice: I/O con files

    • E’ possibile definire altre unità di I/O

      • Si utilizza la libreria fstream (include iostream)

      • I files di input sono dichiarati ifstream

      • I files di output sono dichiarati ofstream

      • I files di input/output sono dichiarati fstream

      • Costruttore con argomento const char* (nome file)

    #include <fstream>

    #include <string>

    int main(){

    ifstream fin(“file1.dat”); // deve esistere!

    if(!fin){ cerr << “file1.dat non esiste” << endl; return -1; }

    ofstream fout(“file2.dat”); // se esiste viene sovrascritto

    int i=0; string parola;

    while (inf >> parola)

    fout << “La “ << ++i << “-esima parola e\’ “ << parola << endl;

    fin.close(); fout.close(); return 0;

    }


    Appendice i o in memoria

    Appendice: I/O in memoria

    • E’ possibile definire unità di I/O in memoria (non legate a files)

      • Si utilizza la libreria sstream (include iostream)

      • Le unità di input sono dichiarati istringstream

      • Le unità di output sono dichiarati ostringstream

      • Le unità di input/output sono dichiarati stringstream

      • I costruttori non hanno argomento

      • Il metodo str() applicato ad un oggetto di questo tipo ritorna la stringa (string) contenuta nell’unità:

        ostringstream messaggio;

        messaggio << “Ciao!” << endl;

        string s=messaggio.str();


    Appendice manipolatori di i o

    Appendice: Manipolatori di I/O

    • Modificano il comportamento di una stream.

    boolalpha: true e false rappresentati come stringhe

    noboolalpha: true e false rappresentati come 1 e 0 (default)

    showbase: interi stampati col prefisso che indica la base

    noshowbase: interi stampati senza il prefisso (default)

    showpoint: floating point stampati sempre col punto decimale

    noshowpoint: stampa i floating point come interi se non frazionari (default)

    showpos: stampa + per numeri positivi

    noshowpos: non stampa + per i numeri positivi (default)

    skipws: salta gli spazi bianchi in input (default)

    noskipws: non salta gli spazi bianchi in input

    uppercase: stampa 0X in esadecimale, E in scientifica

    lowercase: stampa 0x oppure e (default)

    dec: interi in base 10 (default)

    hex: interi in base 16

    oct: interi in base 8


    Appendice manipolatori di i o 2

    Appendice: Manipolatori di I/O (2)

    • I seguenti manipolatori richiedono:

      #include <iomanip>

    left: aggiunge caratteri di riempimento alla destra del val.

    right: aggiunge caratteri di riempimento alla sinistra

    internal: aggiunge caratteri fra segno e valore

    fixed: floating point in notazione decimale (default)

    scientific: floating point in notazione scientifica

    flush: svuota il buffer

    ends: aggiunge il carattere nullo (\0) e svuota il buffer

    endl: aggiunge un “newline” e svuota il buffer

    ws: “mangia” gli spazi bianchi

    setfill(ch): definisce il carattere di riempimento

    setprecision(n): definisce la precisione per i floating point

    setw(n): scrive o legge in n caratteri

    setbase(b): interi in base b


    Esempio di i o con manipolatori

    Esempio di I/O con manipolatori

    #include <iomanip>

    int main() {

    cout << "inserisci un numero: ";

    double num=0;

    while(cin >> num) {

    int pi = (int)(num);

    cout << setfill('0') << setprecision(5);

    cout << "Il numero inserito e\' " << num << endl;

    cout << "La parte intera e\' " << pi << "(" << hex

    << setw(6) << pi << " esadecimale)" << dec << endl;

    cout << "La parte frazionaria e\' " << num-pi << endl;

    cout << "inserisci un numero: ";

    }

    return 0;

    }

    Non tutti i compilatori supportano tutti i manipolatori!!!

    inserisci un numero: 12345.678

    Il numero inserito e' 12346

    La parte intera e' 12345(003039 esadecimale)

    La parte frazionaria e' 0.678

    inserisci un numero:


    Per saperne di pi

    Per saperne di più

    • sugli elementi di base del linguaggio C++

      **** Lippman, Lajoye, The C++ Primer, 3rd Edition - Addison Wesley

      *** Pohl, Object-Oriented Programming Using C++, 2nd Edition - Addison Wesley

      *** Stroustrup, The C++ Programming Language, 3rd Edition - Addison Wesley

    • su trucchi e tranelli in C++

      **** Myers, Effective C++, Addison Wesley

      **** Myers, More Effective C++, Addison Wesley

      *** Coplien, Advanced C++, Addison Wesley

    • su STL

      **** Glass, Schuchert, The STL <PRIMER>, Prentice Hall

      *** Ammeraal, Wiley, STL for C++ Programmers -

      ** Musser, Saini, STL Tutorial and Reference Guide, Addison Wesley


    Per saperne di pi 2

    Per saperne di più (2)

    • su OO A&D

      *** Booch, Object-Oriented Analysis and Design with Applications, Benjamin/Cummings

      *** Booch, Object Solutions, Addison Wesley

    • su UML

      **** Fowler, Scott, UML Distilled, Addison Wesley

      *** Booch, Rumbaugh, Jacobson, The Unified Modeling Language User Guide, Addison Wesley

    • sui Design Patterns

      *** Gamma, Helm, Johnson, Vlissides, Design Patterns, Addison Wesley


  • Login