appunti di c / c++

158
1 APPUNTI DI C / C++ Prof. Nicola Sansone

Upload: others

Post on 15-Feb-2022

2 views

Category:

Documents


0 download

TRANSCRIPT

1

APPUNTI DI C / C++

Prof. Nicola Sansone

2

STRUTTURA DI UN PROGRAMMA 5

LE ESPRESSIONI 7

GENERALITA’ SUI TIPI DI DATO 8 TIPI SEMPLICI 9

LE VARIABILI 10

DOVE SI DICHIARANO LE VARIABILI ? 10 LE TRE REGOLE DI VISIBILITA’ 12 I MODIFICATORI DI ACCESSO 13 SPECIFICATORI DI CLASSI DI MEMORIZZAZIONE 14 COME SI INIZIALIZZANO LE VARIABILI ? 15

LE COSTANTI 16

COSTANTI INTERE E IN VIRGOLA MOBILE 16 COSTANTI CARATTERI SPECIALI 17

GLI OPERATORI 18

OPERATORE DI ASSEGNAZIONE 18 OPERATORI ARITMETICI 19 OPERATORI RELAZIONALI 21 OPERATORI LOGICI 21 OPERATORI BIT A BIT 22 OPERATORE SIZEOF 23 ORDINE DI VALUTAZIONE DEGLI OPERATORI 24 CONVERSIONI DI TIPO NELLE ESPRESSIONI 25 OPERATORE : : 26 L’OPERATORE CAST ( ) 27

LE ISTRUZIONI IN C / C++ 28

SEQUENZA O BLOCCO 29 LE ISTRUZIONI DI SELEZIONE 30 ISTRUZIONE IF 30 OPERATORE ? : 31 ISTRUZIONE SWITCH 32 ISTRUZIONE IF – ELSE – IF 34 LE ISTRUZIONI DI ITERAZIONE 35 ISTRUZIONE FOR 35 VARIANTI DEL FOR 36 ITERAZIONE WHILE 37 ITEREAZIONE DO-WHILE 38 LE ISTRUZIONI DI SALTO 39 ISTRUZIONE RETURN 39 ISTRUZIONE GOTO 40

3

ISTRUZIONE CONTINUE 41 ISTRUZIONE BREAK 41

TYPEDEF 42

I TIPI AGGREGATI 43

ARRAY 43 ARRAY DI STRINGHE 45 STRUTTURE 46 OPERATORE . 47 I CAMPI DI BIT 48 ARRAY DI STRUTTURE 49 STRUTTURE NIDIFICATE 50 ENUMERAZIONE 51 UNIONE 52 UNION REGS 53

LE FUNZIONI 54

SINTASSI DELLE FUNZIONI 55 CHIAMATA DI UNA FUNZIONE 56 VALORI DI RITORNO DA UNA FUNZIONE 57 I PARAMETRI DELLE FUNZIONI 58 LA FUNZIONE MAIN 63 FUNZIONI INLINE 64 OVERLOADING DELLE FUNZIONI 65 FUNZIONI DI MANIPOLAZIONE DELLE STRINGHE 66

IL PREPROCESSORE C 67

DIRETTIVA #INCLUDE 68 DIRETTIVA #DEFINE 69

PUNTATORI 70

OPERATORE * 71 OPERATORE & 71 ARITMETICA DEI PUNTATORI 72 CONFRONTO FRA PUNTATORI 73 ARRAY E PUNTATORI 74 ARRAY DI PUNTATORI 76 PUNTATORI A PUNTATORI 77 PUNTATORI A STRUTTURE 78 PUNTATORI E FUNZIONI 79 FUNZIONI CHE RITORNANO PUNTATORI 79 PUNTATORI A FUNZIONI 80 ALLOCAZIONE DINAMICA DELLA MEMORIA 82 NEW 83 DELETE 83

4

LE CLASSI 84

DICHIARAZIONE DI UN OGGETTO 86 SPECIFICATORI DI ACCESSO 86 MEMBER FUNCTION 86 COSTRUTTORI DI UNA CLASSE 87 DISTRUTTORE DI UNA CLASSE 89 DATI MEMBRI DEFINITI STATIC 90 METODI DICHIARATI STATIC 91 PASSAGGIO DI OGGETTI A FUNZIONI 92 ARRAY DI OGGETTI 94 PUNTATORI A OGGETTI 95 IL PUNTATORE THIS 96 LA FUNZIONE FRIEND 97 OVERLOADING DEGLI OPERATORI 99 OVERLOADING DEGLI OPERATORI ++ E – 102 OVERLOADING DEGLI OPERATORI CON FUNZIONI FRIEND 105 OVERLOADING DI ++ E -- CON L’ USO DELLE FUNZIONI FRIEND 106 PERCHE’ SOVRAPPORRE CON LA FUNZIONE FRIEND 107 EREDITARIETA’ 108 CLASSI DERIVATE 109 EREDITARITA’ MULTIPLA 114 PASSAGGIO DI PARAMETRI AI COSTRUTTORI DELLA CLASSE BASE 115

CLASSI BASI VIRTUALI 117

FUNZIONI VIRTUALI 118 FUNZIONI VIRTUALI PURE 120

LE FUNZIONI TEMPLATE 123

OVERLOADING ESPLICITO DI UNA FUNZIONE TEMPLATE 125 NOTE SULLE FUNZIONI TEMPLATE 126 CLASSI TEMPLATE 127

IL SISTEMA DI I/O DEL C++ 130

CLASSI BASE RELATIVE AI CANALI 131 OPERAZIONI DI I/O FORMATTATO 132 FLAGS DI FORMATTAZIONE 132 FUNZIONI MEMBRO DI IOS 133 FORMATTAZIONE CON L’USO DEI MANIPOLATORI 135 OPERAZIONI DI I/O SU FILE 137 LETTURA/SCRITTURA DI UN FILE DI TESTO 139 OPERAZIONI DI I/O BINARIE ERRORE. IL SEGNALIBRO NON È DEFINITO. READ ( ) E WRITE ( ) 141 GETLINE 142 ACCESSO DIRETTO AI FILE 143

GESTIONE DELLE ECCEZIONI 145

5

STRUTTURA DI UN PROGRAMMA

Un programma in C è UN INSIEME DI UNA o PIU’ FUNZIONI. E’ obbligatoria la presenza di una funzione denominata MAIN.

Dichiarazioni Globali

FUNZIONE MAIN Dichiarazioni Locali

ISTRUZIONI

FUNZIONE DichiarazioniLocali ISTRUZIONI

FUNZIONE Dichiarazioni Locali ISTRUZIONI

- MAIN è sempre la prima funzione ad essere chiamata - MAIN avrà il compito di chiamare le eventuali altre funzioni.

6

ESEMPIO int a,b; int abs( int ); int minimo ( int, int ); int main ( void ) {

int z = 3; cout << abs( z ); a = 3; b = 7; cout << minimo( a, b ); return 0;

} int abs( int x ) {

if ( x >= 0 ) return x else return-(x);

} int minimo ( int x, int y ) {

if ( x >= y) return y else return x;

}

7

LE ESPRESSIONI Un’ ESPRESSIONE è una insieme di OPERANDI e OPERATORI la cui VALUTAZIONE da origine ad un VALORE di un certo tipo. Gli OPERANDI sono valori (costanti o variabili) che vengono manipolati nell’espressione. Poiché ogni operando ha un valore ne segue che anche gli OPERANDI SONO ESPRESSIONI. Gli OPERATORI sono SIMBOLI che specificano come devono essere modificati gli operandi nell’ espressione.

(a + 10) / 2

A seconda del numero degli operandi che coinvolge, un OPERATORE può essere:

UNARIO se prevede un’ unico operando BINARIO se prevede due operandi TERNARIO se prevede tre operandi Esistono delle REGOLE DI PRECEDENZA che indicano in che

ORDINE gli operatori devono essere applicati agli operandi. Le parentesi ( ) possono modificare l’ ordine prefissato dalle regole

8

GENERALITA’ SUI TIPI DI DATO Un tipo di dato è un’entita definita dai seguenti elementi :

1. Un insieme A di valori detto insieme di sostegno del tipo. 2. Un insieme di costanti che denotano elementi distinti di A. 3. Un insieme di operazioni su A. 4. Un insieme di predicati per classificare e/o confrontare i dati.

Per operazione su un insieme A si intende la funzione :

f : An A

Un predicato è un operazione che fornisce un valore di verità:

VERO FALSO TRUE FALSE

1 0 I tipi possono essere divisi in due categorie : 1. Tipi semplici: i dati non sono strutturati, cioè non costituiti da altri dati. 2. Tipi aggregati: i dati sono costituiti da aggregazione di altri dati.

9

TIPI SEMPLICI I tipi semplici in C sono quattro:

char int float double Il C++ ne aggiunge altri due

bool wchar_t Ogni tipo necessita di un certo numero di byte di memoria che dipende dal compilatore e dal microprocessore in uso. Oltre ai tipi esistono i MODIFICATORI DI TIPO

signed unsigned short long che associati ai tipi nella dichiarazione permettono di variare l’ intervallo dei valori stabilito per i tipi base.

TIPO BYTE OCCUPATI

bool 1 char / signed 1 unsigned char 1 wchar_t 2 int ( signed int ) 4 unsigned int 4 short int ( signed short int ) 2 unsigned short int 2 long int ( signed long int ) 4 unsigned long int 4 float 4 double 8 long double 12

10

LE VARIABILI Le variabili corrispondono a celle di memoria che contengono un valore modificabile dal programma. Esse devono essere DICHIARATE come segue: SINTASSI

tipo var1, var2,........, varn;

ESEMPIO

int cont, i; short int k; unsigned int pippo; char car, b, c; double x, y, z; bool l;

DOVE SI DICHIARANO LE VARIABILI ? Possono essere dichiarate : 1. all’ INTERNO DI UNA FUNZIONE Variabili Locali o Automatiche 2. all’ ESTERNO DI TUTTE LE FUNZIONI Variabili Globali o Esterne 3. nella definizione di PARAMETRI delle funzioni Parametri Formali Ricordiamo due definizioni: La DURATA di un oggetto è l’ intervallo di tempo che intercorre dalla sua allocazione alla sua disallocazione e quindi durante questo tempo la variabile può essere letta e scritta.

Essa può essere: DURATA GLOBALE, se ha riservata memoria e un valore per tutta la

durata dell’esecuzione del programma del programma

11

DURATA LOCALE, se ha riservata memoria e un valore nel solo BLOCCO in cui è dichiarata.

La VISIBILITA’ di un oggetto è la porzione di programma in cui l’ oggetto può essere referenziato per nome ( letta e/o scritta ). Per BLOCCO si intende insieme di istruzioni o/e dichiarazioni racchiuse tra { }. definizione di funzione ( intestazione e corpo )

DURATA VISIBILITA’ Variabili locali Locale blocco

Parametri Formali Locale blocco Variabili globali Globale programma

ESEMPIO int i = 10; // i è una variabile globale int main ( void ) { cout << i; // stampa 10

{ int i = 20, j = 30; // i e j sono variabili locali cout << i << j; // stampa 20 e 30 { int i = 40; // i è variabile locale cout << i << j; // stampa 40 30; } cout << i; // stampa ? }

cout << i; // stampa ? return 0; }

12

LE REGOLE DI VISIBILITA’

Dalle definizioni ed esempi precedenti possimo dare tre regole generali per la visibilità di un oggetto:

Considerando due blocchi annidati ( l’uno dentro l’altro ) Y e X { Y ………..

{ X ……….. ………..

} ……….. }

1. Qualsiasi oggetto dichiarato in Y È VISIBILE in X

2. Qualsiasi oggetto dichiarato in X NON È VISIBILE in Y, e “nasconde” un eventuale variabile con lo stesso nome dichiarato in Y

Considerando due blocchi esterni l’uno all’altro { ……….. ……….. } { ……….. ……….. }

3. Qualsisi oggetto dichiarato in un blocco NON È VISIBILE in un

blocco indipendente da esso

13

LO SPAZIO DEI NOMI : NAMESPACE

In progetti complessi che coinvolgono più persone che elaborano numerosi file, si potrebbero involontariamente definire identificatori con lo stesso nome nello spazio globale. Questo fatto crea errori al momento del linkage. Per risolvere questo problema il C++ consente di suddividere lo spazio globale dei nomi in più parti definite namespace o spazio dei nomi in cui possono convivere identificatori con lo stesso nome purché definiti in namespace differenti. SINTASSI

namespace nome { lista di dichiarazioni di dati e funzioni } ESEMPIO namespace mio { const int max = 100; char v [ max ]; int b = 10; } int b = 100 int main() { int b = 1000; cout << mio :: b ; // b del namespace mio cout << ::b ; // b dello spazio globale cout << b ; // b dello spazio locale } // stamperà 10 100 1000 Dall’esempio appare evidente che per riferirsi ad un identificatore appartenente ad un namespace differente bisogna far precedere il nome del namespace e lo scope resolution al nome dell’identificatore : mio :: b Se vogliamo evitare di anteporre la coppia (nome spazio, scope resolution) possiamo con la direttiva using importare tutti gli identificatori di mio nello spazio globale

14

using namespace mio; ma è evidente che ciò vanifica la funzione del namespace rendendo nuovamente possibili i conflitti di nome. Tutti gli identificatori del C++ standard sono inseriti nel namespace std. ESEMPIO #include<iostream> #include<iostream>

using namespace std; int main() oppure int main() { int b = 4; { int b = 4; std::cout << b; cout << b ; } }

15

I MODIFICATORI DI ACCESSO Sono due CONST e VOLATILE e controllano il modo in cui le variabili possono essere lette e modificate. SINTASSI

modificatore tipo var1, var2.... ESEMPIO

const int a = 10; Ad a viene assegnato un valore ma non può essere modificato programma.

volatile unsigned char *port = 300; Comunica al compilatore che il valore di *port potrà essere modificato in modi non specificati esplicitamente dal programma. E’ anche possibile mettere insieme i due modificatori di accesso.

const volatile unsigned char *porta = 300;

La variabile può essere modificata dall’ esterno, ma non dal programma.

16

SPECIFICATORI DI CLASSI DI MEMORIZZAZIONE Sono quattro extern, static, register, auto e chiedono al compilatore di memorizzare in un determinato modo una variabile. SINTASSI

specificatore tipo var; extern dice al compilatore che le variabili che seguono sono DICHIARATE ALTROVE. static il valore di queste variabili PERMANE all’ interno della propria funzione o modulo. A differnza delle variabili globali non sono visibili all’ esterno ma la loro DURATA è globale, cioè mantengono il loro valore fra una chiamata e la successiva. register si chiede al compilatore di memorizzare la variabile in un registro per rendere l’ accesso più veloce. auto usato solo per problemi di compatibilità verso vecchie versioni del C. Specifica che la variabile è di tipo AUTOMATICO (locale).

----------- int a

Extern int a ----------- -----------

17

COME SI INIZIALIZZANO LE VARIABILI ? Possiamo assegnare un valore ad una variabile direttamente nella dichiarazione . SINTASSI

tipo var = costante ESEMPI. char carattere = ‘b’; int primo = 10; float a = 10.32; bool l = true;

18

LE COSTANTI Le costanti fanno riferimento a valori fissi non modificabili nel durante l’esecuzione del programma.

COSTANTI INTERE E IN VIRGOLA MOBILE Normalmente il compilatore C inserisce una costante numerica nel più piccolo tipo in grado di contenerla. L’ unica eccezione sono le costanti in virgola mobile che vengono sempre memorizzate come double. E’ possibile in alcuni casi di FORZARE il tipo attraverso l’ uso delle letter U, L, F. ESEMPI. true costante bool false costante bool 1 2100 - 234 costante int 35000L 2000000000 costante long int 65000 987U costante unsigned int 123.3F 4.3E - 3F costante float 1001.2L costante long double 0x80 costante esadecimale 023 costante intera ottale “z” costante stringa “CLASSE” costante stringa ‘z’ costante carattere

19

COSTANTI CARATTERI SPECIALI Alcuni caratteri del codice ASCII non possono essere immessi in una stringa utilizzando la tastiera. Il C prevede numerosi codici speciali che consentono di trattare questi caratteri come costanti: \ n new line \ a alarm \ “ doppi apici \ ‘ apici \ N costante ottale \xN costante esadecimale \b back space \f from feed \r carriage return \t tabulazione orizzontale \0 carattere nullo \\ back slash \v tabulazione verticale ESEMPIO - - - - cout << ”questa stringa\n”; cout << ”sveglia\a\a”; - - - - cout << 0xFE;

20

GLI OPERATORI Il C è molto ricco di operatori. Prevede quattro tipi di operatori: ARITMETICI RELAZIONALI LOGICI BIT-A-BIT Inoltre abbiamo operatori dedicati a COMPITI SPECIFICI.

OPERATORE DI ASSEGNAZIONE La forma generale è :

var = exp

var1 = var2=…= var n = exp Quando gli operandi sono di tipo differente, avviene in modo automatico una CONVERSIONE DI TIPO secondo una semplice regola : IL VALORE A DESTRA DI = VIENE CONVERTITO NELLO STESSO TIPO DELL’OPERANDO DI SINISTRA ESEMPIO int x = 41; bool l = true; int y, z; char ch; float f = 12,7; ch = x; x = f; // x riceve la parte intera di f ( 12 ) f = ch; // viene convertito il valore intero a 8 bit f = x; // nello stesso valore float x = y = z = 0; // a x, y, z viene assegnato il valore 0

21

OPERATORI ARITMETICI Sono + - * / % +/- Per la divisione viene troncato il resto ESEMPIO int x = 5, y = 2; float a = 5, b = 2; cout << x / y; // avremo 2 cout << x % y; // avremo 1 cout << a / b; // avremo 2.5 E’ possibile usare una notazione abbreviata in alcuni casi: x = x + 4 x + = 4 x = x - 2 x - = 2 x = x * 3 x * = 3 x = x / 2 x / = 2 Il C mette a disposizione due operatori di INCREMENTO (++) e di DECREMENTO (--) che sono unari e aggiungono o sottraggono 1 al proprio operando X = X + 1 ++X X = X - 1 --X Vi sono poi due forme di questi 2 OPERATORI la PREFISSA (avanti) la POSTFISSA(dietro)

++X --X

X++ X --

Vi è differenza tra le due forme solo se usate nelle espressioni

22

ESEMPIO X = 10; X = 10; Y = ++X; Y = X++; Prima incrementa poi assegna quindi: Prima assegna poi incrementa quindi: Y=11 X = 11 Y=10 X = 11 L’ uso di ++ e –– consente al compilatore di produrre un codice più efficiente.

TABELLA DELLE PRECEDENZE ++ -- alta in presenza di operatori - di eguale precedenza * / % viene valutato prima + - bassa quello di sinistra Le parentesi tonde modificano l’ordine di valutazione ESEMPIO y = a + b / 2; // prima / e poi + y = ++a + b * 2; // prima ++ poi * dopo + y = (++ a+b) * 2 ;

23

OPERATORI RELAZIONALI Gli operatori relazionali stabiliscono una relazione tra i valori degli operandi; essi producono:

1 se la relazione è VERA 0 se la relazione è FALSA

Essi sono < > <= >= = = !=

OPERATORI LOGICI Essi sono:

&& prodotto logico o and

|| somma logica o or

! negazione o not ESEMPIO 10 > 5 && !(10<9)|| 3 <= 4 // è vera

TABELLA DELLE PRECEDENZE ! alta < < = > >= = = != && || bassa NOTE Le parentesi tonde modificano l’ordine di precedenza

24

OPERATORI BIT A BIT Questi tipi di operatori si occupano di CONTROLLARE, IMPOSTARE e SPOSTARE i bit che compongono un byte o una word di un tipo char o int e loro varianti. Essi sono: & and | or ^ xor ~ complemento a 1 >> scorrimento a destra << sorrimento a sinistra ESEMPIO unsigned char x = 85; unsigned char y; unsigned char a = 165; unsigned char b; int main ( void ) {

y = ~ x; /* x = 01010101 y = 10101010 */ b = a & 15; /* a = 10100101 15 = 00001111

allora b = 00000101*/

b = a | 15; // b = 10101111 b = a ^15; // b = 10101010

a << 1; // 1 0 1 0 0 1 0 10 return 0; }

bit che esce bit che entra

25

OPERATORE SIZEOF E’ un operatore unario che restituisce la dimensione in byte di una variabile o di un tipo. ESEMPIO unisigned int pippo int main ( void ) {

cout << sizeof ( char ) cout << sizeof ( pippo ); cout << sizeof ( float ); cout << sizeof ( double ); return 0;

} in output avremo 1 4 4 8

26

ORDINE DI VALUTAZIONE DEGLI OPERATORI Segue la tabella delle precedenze dei principali operatori ( ) [ ] . priorità alta

! ~ ++ -- (tipo) * & sizeof * ‘ / % << >>

< <= > >=

= = ! = &

^ ^ & &

||

? : priorità bassa

= += -= *= /=

27

CONVERSIONI DI TIPO NELLE ESPRESSIONI

Quando in un’espressione vi sono variabili e costanti di TIPO DIVERSO esse vengono convertite nello stesso tipo, dell’ OPERANDO PIU’ ESTESO presente nell’ espressione.

ESEMPIO

char ch = ‘b’; int i = 12; floot f = 1.6; double r, d = 11.7;

r = ( ch / i ) + ( f * d ) - ( f + i );

int double float int double double

quindi l’espressione è di tipo double.

NOTA

La prima fase della conversazione detta di PROMOZIONE INTERA consiste nel convertire char, bool e short in int

char bool int short int

La seconda fase vengono convertiti tutti gli operandi nel tipo più esteso fra quelli degli operandi e il valore dell’espressione è di questo tipo.

28

OPERATORE : : La seconda regola di visibilita’ ci dice che una variabile locale “nasconde” una variabile globale con lo stesso nome. L’operatore : : SCOPE RESOLUTION permette di “aggirare” almeno in parte questa regola ESEMPIO # include <iostream> using namespace std; int totale = 1000; // totale è variabile globale int main (void) { { int totale = 2000; // totale è variabile locale cout << totale; // stampa 2000 : : totale = 3000; // si riferisce alla globale } cout << totale; // stampa 3000 return 0; } NOTA In caso di visibilità annidata l’operatore : : permette di accedere alla omonima variabile globale e non a quella del livello di visibilità più interna.

29

L’OPERATORE CAST ( ) La sintassi dell’operatore unario cast è: SINTASSI SEMANTICA

exp viene convertito provvisoriamente

( tipo ) exp nel tipo specificato in parentesi. ESEMPIO int main (void ) {

int i = 5; cout << i / 2 // restituisce 2 cout << ( float ) i / 2 // restituisce 2.5 return 0;

} NOTE Esistono altri operatori sia del C che del C++ che verranno introdotti in seguito.

30

LE ISTRUZIONI IN C / C++ Il C standard ANSI raggruppa le istruzioni in: ISTRUZIONE DI SELEZIONE if-else if if-else switch ISTRUZIONE DI SALTO return continue break goto ETICHETTE identificatori associati al goto ESPRESSIONI BLOCCHI corrisponde al costrutto SEQUENZA BLOCCHI TREE introdotte ISTRUZIONI DI DICHIARAZIONE dal C++

31

SEQUENZA O BLOCCO Si intende per blocco o sequenza la possibilità di richiedere l’esecuzione in successione e in modo ordinato di una o più istruzioni. Nel caso di più istruzioni si parla di ISTRUZIONE COMPOSTA. SINTASSI

{ [dichiarazioni] istruzione 1; istruzione 2; istruzione 3; …………… …………… istruzione n;

} SEMANTICA Esegui in successioni le istruzioni 1,2,3…----------- n - Nel caso di blocco costituito da un’unica istruzione si possono omettere

le parentesi graffe. - Convenzionalmente intenderemo d’ora in poi con istruzione,

un’istruzione composta o semplice. ESEMPIO { int a, b, c; a = 2; b = 3; c = a + b; }

32

LE ISTRUZIONI DI SELEZIONE Sono if else if, if else, switch e l’operatore ? : ISTRUZIONE IF SINTASSI

if ( exp.cond. ) istr1; [ else istr2; ]

SEMANTICA Valuta exp cond, se è VERA esegui istr1, se è FALSA ed esiste la parte else, esegui istr2 ESEMPIO # include <iostream> using namespace std; int main ( void ) {

int x, y; cin >> x; cin >> y; if ( x > y) cout << x << ” è maggiore di ” << y; else cout << y << ” è maggiore di ” << x; return 0;

}

33

OPERATORE ? : Può essere usato in alternativa all’ if else if (exp. condizionale) exp1 else exp2 Attenzione ?: potrà essere usato solo nel caso in cui a la parte then che else sono SINGOLE ESPRESSIONI o CHIAMATE A FUNZIONI, ma non istruzioni. SINTASSI

exp1 ? exp2 : exp3; SEMANTICA Viene valutata exp1: se è VERA viene valutata exp2 che viene assegnata a exp1.Nel caso in cui exp1 è FALSA come prima ma con exp3 ESEMPIO …. …. …. x = 10; x = 10; if ( x > 9 ) y = 100; y = x > 9 ? 100 : 200; else y = 200; …. …. …. …. NOTE ? : è un operatore di tipo TERNARIO perché ha 3 operandi.

34

ISTRUZIONE SWITCH In molti casi il problema di più IF ANNIDATI può essere risolto con l’uso di questa istruzione. SINTASSI switch ( exp )

{ case exp costante1: istruzione; [break;] case exp costante 2: istruzione; [break;]

……………… ……………… case exp costante : istruzione; [break;] default istruzione; } SEMANTICA Viene valutata exp ed il suo valore confrontato con exp costante 1,2,…n. Quando viene trovata una corrispondenza viene eseguita l’istruzione associata al case sino a che o viene incontrata l’istruzione break e lo switch ha fine. Se nessuna exp costante = exp allora viene eseguita istruzione associata a default. NOTE - Lo switch può valutare ogni tipo di exp logiche e relazionali.

35

- In uno stesso switch non vi possono essere exp costanti uguali. - Sono possibili istruzioni CASE senza nessuna istruzione ESEMPIO …….. int mese; cin >> mese; switch ( mese ) { case 1 : cout <<”GENNAIO”; break; case 2: cout <<”FEBBRAIO”; break; …….. case 12: cout <<”DICEMBRE”; break; default : cout <<”NUMERO MESE ERRATO”; } Se in input 5, l’ output sarà : MAGGIO Se si omettono i break l’output sarà: MAGGIOGIUGNOLUGLIOAGOSTOSETTEMBREOTTOBRENOVEMBREDICEMBRE

36

ISTRUZIONE IF – ELSE – IF Si può considerare una generalizzazione del costrutto if – else SINTASSI SEMANTICA if ( exp1 ) Le exp sono valutate nell’ordine. istruzione1; Quando viene trovata una else if ( exp2 ) exp. VERA viene eseguita la istruzione2; istruzione associata e la parte else if ( exp3 ) rimanente ignorata.quando tutte istruzione3; sono FALSE viene eseguita else l’istruzione associata istruzione4; all’else ( se esiste ). ESEMPIO int main ( void ) {

unsigned short int m, g; cin >> m; if (m = = 2) g = 28; else if (m = = 11) g = 30; else if (m = = 4) g = 30; else if (m = = 6) g = 30; else if (m = = 9) g = 30; else g = 31; cout << g; return 0;

} ESERCIZIO - Valutare differenze e analogie con il costrutto SWITCH.

37

LE ISTRUZIONI DI ITERAZIONE Consentono la ripetizione di una o più istruzioni al verificarsi di una determinata condizione. ISTRUZIONE FOR SINTASSI for ( [ inizializzazione ] ; [ condizione ] ; [ incremento ] ) [istruzioni]; SEMANTICA a) Se presente viene valutata la prima espressione che solitamente è

un’assegnazione per inizializzare la variabile di controllo del ciclo

b) Se presente viene valutata la seconda espressione il cui valore determina se deve essere eseguita o no istruzione. Sono possibili 3 casi:

b1) condizione è VERA ( = 0 ) viene eseguita istruzioni viene valutata la terza espressione (che solitamente modifica la variabile di controllo). b2) condizione è FALSA ( = 0) il for ha termine e l’esecuzione passa all’istruzione successiva. b3) condizione MANCA viene considerata sempre vera e tutto procede come nel caso b1)

38

ESEMPIO …….. …….. …….. for ( x = 100; x != 65; x -= 5) int x; { for ( x = 1; x < 100; ++x ) z = x*x; cout << x; cout << z;

} NOTE - Condizione viene valutata ALL’INIZIO DEL CICLO. Questo vuol dire che istruzione potrebbe non essere eseguita mai se la condizione è FALSA dall’inizio. VARIANTI DEL FOR for ( x = 0, y = 0; x + y<10; ++x ) { cin >> y; y = y - ‘0’; /* sottrae a y il codice ASCII di ‘0’ */ …….. } L’operatore “ , “ consente di inizializzare x e y …….. for ( ;; ) cout << ”ciclo infinito \n”; E’ un esempio di ciclo infinito …….. for ( t = 0; t < 1000; t++); …….. E’ un esempio di ciclo senza corpo. Potrebbe ad esempio servire a creare un ritardo nell’esecuzione delle istruzioni che seguono il for.

39

ITERAZIONE WHILE SINTASSI SEMANTICA WHILE ( exp. cond. ) Istruzione viene eseguita finché istruzione; exp cond è VERA ( = 0). Quando diviene falsa, l’esecuzione passa alla prima istruzione che segue il while. ESEMPIO int segreto = 15; int num = 0; while ( num != segreto ) { cout << ”indovina il numero”; cin >> num; } cout << “indovinato”; NOTA for ( i = 0; i < 10; i++ ) e i = 0;

{ while ( i < 10 )

…… {

…… …….. } i++; } sono equivalenti.

40

ITERAZIONE DO-WHILE SINTASSI SEMANTICA do come per il while, ma in questo

istruzione; caso istruzione viene eseguita while ( exp. cond ); almeno 1 volta (nel caso in cui exp. cond è SUBITO FALSA ) ESEMPIO ……….. int num; int segreto = 15; do { cout << ”indovina il numero”; cin >> num; } while ( num != segreto); cout << ”indovinato”;

41

LE ISTRUZIONI DI SALTO Il C/C++ ha quattro istruzioni DI SALTO INCONDIZIONATO:

return goto break continue ISTRUZIONE RETURN SINTASSI SEMANTICA return [ exp ]; interrompe l’esecuzione di una funzione, ritornando alla funzione CHIAMANTE il valore di exp (se presente). L’esecuzione della funzione CHIAMANTE riprende

dalla istruzione successiva alla chiamata della funzione.

NOTE exp deve essere presente solo se il prototipo della funzione prevede il valore di ritorno. Vi possono essere in una funzione più di un return, ma l’esecuzione della funzione ha termine quando viene incontrato il primo. La funzione termina anche quando si incontra } della funzione. Essa corrisponde ad un return senza exp. Una funzione void (che non ritorna alcun valore) non può contenere istruzioni di return.

42

ISTRUZIONE GOTO SINTASSI SEMANTICA goto etichetta; L’esecuzione passa

incondizionatamente …….. all’istruzione successiva

all’etichetta. …….. etichetta : ……… ESEMPIO … x = 1; loop : x ++ if ( x < 100) goto loop; … … NOTE - Si consiglia di usare il goto solo nei casi in cui l’efficenza del

programma è un fattore determinante, anche perché è dimostrato che si può costruire un qualunque programma senza l’uso di questa istruzione.

- L’etichetta si deve trovare nella stessa funzione in cui è presente goto. - Rende poco leggibili i programmi

43

ISTRUZIONE CONTINUE SINTASSI SEMANTICA continue; consente di ritornare

anticipatamente il controllo all’ inizio del ciclo interrompendo,l’esecuzione dell’iterazione corrente.

NOTE Questa istruzione può essere usata per controllare un iterazione for o while

o do while. ISTRUZIONE BREAK SINTASSI SEMANTICA Break; interrompe l’esecuzione di un ciclo

passando il controllo all’istruzione seguente al ciclo stesso NOTE Oltre che nel for, while e do-while può essere usata nell’ istruzione switch

44

TYPEDEF Typedef consente di creare dei SINONIMI per tipi già esistenti (semplici e aggregati) SINTASSI typedef tipo nome; ESEMPIO typedef unsigned char BYTE; typedef int INTERO;

45

I TIPI AGGREGATI I tipi aggregati in C/C++ sono : Array, Strutture, Enumerazione e Unione

ARRAY Un ARRAY è un insieme ordinato di dati dello stesso tipo a cui si fa riferimento tramite un unico nome. Il singolo elemento è individuato tramite l’INDICE. Una STRINGA è un array monodimensionale di caratteri. SINTASSI

tipo nome [dim1][dim2]…[dimk];

tipo nome [ ]; ESEMPI int a [10]; // vettore di 10 interi int b [2][3] // matrice di 6 interi

float c [2][3][6]; // array a 3 dim di 36 float char s [30]; // vettore di 30 caratteri = STRINGA char s1 [10][30]; …….. …….. a [3] =10; b [2][1] = 23; c [1][1][1] = 1.2; s [1] = ‘c’;

E’ possibile INIZIALIZZARE un’array in fase di dichiarazione

ESEMPI ……..

46

float g [3] = { 30.4, 1.2, 3.0 }; float f [ ] = { 1.2, 33.4 }; char saluto [ ] = “ciao”; che corrispondono: g [0] =30.4; f [0] = 1.2; saluto [0] = ‘c’; g [1] = 1.2; f [1] = 33.4; saluto [1] = ‘i’; g [2] = 3.0; saluto [2] = ‘a’; saluto [3] = ‘o’; NOTA - Dagli esempi precedenti si nota:

INDICE 1° ELEM. ARRAY = 0 INDICE ULTIMO ELEM. ARRAY = DIMENSIONE MAX –1

- Per calcolare l’occupazione in memoria ( in byte ) di un array byte totali = sizeof (tipo)* dim1 * dim2 … * dimk ESEMPIO int k [7][3][2]; byte totali = sizeof (int) * 7* 3*2 = 16 x 7 x 3 x 2.

47

ARRAY DI STRINGHE - La stringa è un array di caratteri conclusa dal carattere \0 per questo

motivo dovremo dimensionare la stringa +1 rispetto alla necessità. - Per dichiarare un array di stringhe:

char pagina [60][80]

avremo 60 righe di 79 caratteri l’una ( l’ ottantesimo è \0 ) Per accedere alla singola riga, ad esempio la 4° cout << pagina [5]; equivalente a

cout << pagina [5][0];

48

STRUTTURE

Fra i tipi aggregati in C/C++ riveste particolare importanza la struttura che come gli array consente di dare uno stesso nome ad un gruppo di dati ma da esso differisce perché i dati possono essere di tipo differente.

SINTASSI:

struct [ nome ] { lista_membri } [ nome1],[nome2]; struct nome { lista_membri }; [struct] nome nome1, nome2;

ESEMPI:

struct motore // viene dichiarato il tipo motore {

char tipo[6]; float potenza;

unsigned numero_giri; char alimentazione;

}; Per dichiarare un’ istanza del tipo motore potremo scrivere :

struct motore pippo; oppure motore pippo;

struct motore // in questo caso viene dichiarata { // sia la struttura che un’istanza di

………..; // motore ………..; } pippo; struct // struttura anonima { ………..; ………..; } pippo;

49

OPERATORE . Inizializzare una struttura vuol dire: assegnare un valore ad ogni singolo membro (detto anche campo) SINTASSI: nome_istanza . nome_membro

attraverso l’operatore binario . (“membro di …..”) possiamo selezionare il campo di una struttura.

pippo . numero_giri = 4000;

membro o campo - Una struttura può essere inizializzata anche in fase di dichiarazione struct motore {

char tipo[6]; float potenza; unsigned numero_giri;

char alimentazione; }; …………..; struct motore pippo = { “D001” , 54.7 , 7500 , ‘B’ }; - E’ possibile assegnare una struttura ad un’altra struttura dello stesso tipo struct motore pippo = { “D001” , 54.7 , 7500 , ‘B’ }; struct motore ciccio; …………….. ; …………….. ; ciccio = pippo;

50

I CAMPI DI BIT Sappiamo che il tipo più piccolo è il char ( 8 bit ). A volte risulta necessario un numero di bit inferiore a 8. Il C consente di raggruppare un insieme distinto di informazioni nello spazio occupato da una variabile di tipo int ( signed o unsigned ). Questa operazione è possibile solo per la struttura e i suoi membri prendono il nome di campi di bit. SINTASSI: tipo [ nome ] : n_bit; ESEMPIO: struct MODO {

unsigned carattere : 8; unsigned primo_piano : 3; unsigned intensità : 1; unsigned lampeggio : 1; unsigned sfondo : 3;

} …………… MODO modalità_carattere; NOTE: - sono consentite strutture miste - se campo bit è costituito da n bit l’insieme di valori memorizzabili

se unsigned da 0 a 2^N se signed da –2^N-1 a 2^(N-1)-1

- i campi di lunghezza = 1 devono essere unsigned int

51

ARRAY DI STRUTTURE struct bilancia {

char nome [ 20 ]; char cognome [ 20 ]; unsigned peso;

} struct bilancia pippo[10]; o bilancia pippo [10]; abbiamo definito un array di 10 elementi ciascuno dei quali è una struttura BILANCIA. pippo[3] . peso = 72; // al quarto elemento assegno…. pippo[3] . nome = “franco”;

52

STRUTTURE NIDIFICATE E’ possibile che i membri di una struttura siano tipi aggregati (vettori , strutture). ESEMPIO: struct ANAGRAFICA {

char nome[20]; char cognome[20]; unsigned telefono;

} struct SCUOLA {

ANAGRAFICA nominativo; int voti[10];

}studenti; studenti . voti[2] = 8; studenti . nominativo . nome = ”franco”; ………………….. SCUOLA alunni[20]; // array di struttura alunni[3] . nominativo . nome = ”giuseppe”;

53

ENUMERAZIONE

L’enumerazione è formata da un insieme di costanti intere dotate di nome, che specificano l’insieme di sostegno di variabile di tale tipo. SINTASSI: enum[nome] { insieme } [nome1,][nome2,]…… enum nome [nome1,][nome2,]…….. ESEMPIO: enum SETTIMANA {

lunedì, martedì, mercoledì, giovedì, venerdì, sabato, domenica

}; enum SETTIMANA a, b, c; o SETTIMANA a, b, c; Il compilatore ad ogni elemento dell’insieme associa un numero intero quindi posso indifferentemente

a = 1 b = giovedì int k; k = venerdì; nota: è possibile cambiare l’ordine: enum SETTIMANA{ lun = -3, mar = 0, mer = 2,gio,ven,sab,dom } a,b; -3 0 2 3 4 5 6

54

UNIONE In C l’ UNIONE è un’indirizzo di memoria condiviso, in momenti diversi, da una o più variabili anche di tipo diverso. SINTASSI: union [nome] { lista_membri} [nome1,][nome2,]…… union nome [nome1,][nome2,]…….. ESEMPIO: union COSA {

int primo; char secondo; double terzo;

};

union COSA numero; o COSA numero; numero può essere usata per memorizzare un valore di tipo int, char o double. Lo spazio occupato è uguale a quello del membro che occupa più spazio ( nell’esempio il double ). Nel caso di memorizzazione di elementi più piccoli, lo spazio eccedente resta inutilizzato. numero.primo = 10; numero.secondo = ‘s’; numero.terzo = 34.1;

55

UNION REGS struct WORDREGS struct BYTEREGS { {

unsigned ax; unsigned char al,ah; unsigned bx; unsigned char bl,bh; unsigned cx; unsigned char cl,ch; unsigned dx; unsigned char dl,dh; unsigned si; }; unsigned di; unsigned cflag;

}; union REGS {

WORDREGS x; BYTEREGS y;

}; #include<dos.h> int int86 ( int inter, union REGS *inregs, union REGS *outregs )

56

LE FUNZIONI Nei problemi visti sino ad ora, è spesso capitato di ripetere interi gruppi di istruzioni che differivano solo per le variabili utilizzate. Tutto cio è : Noioso

Poco produttivo per il programmatore

Rende il programma poco leggibile

L’uso delle funzioni presenta numerosi vantaggi:

1. Riduce o elimina la ripetizione di istruzioni identiche 2. I programmi diventano più chiari e leggibili 3. Un problema viene decomposto in più sottoproblemi 4. Il codice scritto è riutilizzabile per la soluzione di altri

problemi

57

SINTASSI DELLE FUNZIONI La sintassi generale di una funzione è: [tipo] nome funzione ( [tipo parametro1] parametro,……); {

// dichiarazioni variabili locali // corpo della funzione

} NOTE: - tipo specifica il tipo del dato che la funzione ritorna; - una funzione può restituire dati di qualunque tipo, tranne array e

funzioni; - se manca il tipo della funzione viene assunta come int; - le variabili e i parametri formali sono variabili locali( automatiche )

l’unica eccezione sono le static ( durata globale visibile nella funzione );

- le funzioni hanno lo stesso livello di visibilità quindi non si può

definire una funzione all’interno di un’altra funzione. int quad ( int ); //dichiarazione funzione(prototipo) int main( void ) {

int i; // il main è una funzione cin >> i; cout << quad(i); // chiamata della funzione return 0;

} int quad ( int k ) { return k * k } // definizione della funzione

58

CHIAMATA DI UNA FUNZIONE

SINTASSI

identificatore ( [parametro_attuale_1],..…[parametroattuale_n] );

Nome funzione lista parametri attuali

Quando viene eseguita una chiamata a funzione:

1. Vengono valutate le espressioni che costituiscono i parametri attuali. Se esiste un prototipo vengono poi convertiti nel tipo del parametro corrispondente.

2. In un’area di memoria detta stack viene allocato ( riservato ) lo spazio necessario alle variabili locali compresi i parametri.

3. Tutti i parametri formali vengono inizializzati con i valori dei parametri attuali ( nel caso di passaggio per valore ).

4. L’esecuzione passa alla prima istruzione della funzione

5. Un return o l’ultima istruzione della funzione provocano il ritorno alla funzione chiamante ( all’ istruzione successiva alla chiamata ).

6. Viene disallocato lo spazio nello stack.

programma chiamante funzione

………………. ………………. somma ( a + b , c + 2 ) ……………….

int somma ( int x, int y ) {

int s; s = x + y; return s; }

59

VALORI DI RITORNO DA UNA FUNZIONE

Le funzioni possono ritornare valori di qualunque tipo tranne array e funzioni. SINTASSI

return [exp]; SEMANTICA exp viene valutata (se presente) ed il suo valore costituisce il valore di ritorno della funzione. Se exp manca il valore di ritorno è indefinito. Note: - Se una funzione non prevede valori di ritorno è conveniente dichiarare

la funzione come void. - Se il valore di ritorno di una funzione e’ diverso da void può essere

impiegata come una costante in una espressione. - se manca il return il controllo passa alla funzione chiamante dopo

l’esecuzione dell’ultima istruzione della funzione. ESEMPIO: int quad ( int k ) // prototipo della funzione …………. …………. cout << quad ( 3 ); z = 3 + quad( 2 ); quad( 10 ); …………. ………….

60

I PARAMETRI DELLE FUNZIONI

In C++ è possibile inizializzare i parametri formali di una funzione in fase di dichiarazione. ESEMPIO: void ordina ( int primo = 8, double secondo = 8.3 ); la chiamata alla funzione ordina potrà essere: ordina( ); //vengono usati i parametri di default ordina( 3 ); // solo il secondo rimane è di default ordina( 21, 7.3 ); // non vengono usati. Il passaggio dei parametri può avvenire: per valore per riferimento( o indirizzo ) per reference Il passaggio per valore consente alla funzione di ricevere una copia dei parametri attuali - “nascondo” i valori originali [parametri attuali]; - poca efficienza nel caso i parametri siano strutture di dati di grandi

dimensioni. Il passaggio per riferimento consente alla funzione di ricevere l’indirizzo dei parametri attuali - “non nascondo” i valori; - maggiore efficienza nel caso in cui i parametri sono strutture di dati di

grandi dimensioni.

61

ESEMPIO: /* PROTOTIPI DELLE FUNZIONI */ char * maiusc ( char *st ); // passaggio di un array int prod ( int x ); // passaggio per valore void swap ( int *x , int *y ) // passaggio per riferimento int main( void ) {

int a, b, c; a = 4; b = 7; c = 8; cout << prod ( a ); swap ( &b , &c ); maiusc ( “pippo” ); return 0;

} int prod ( int x ) // FUNZIONE PRODOTTO {

return x*x; } void swap ( int *x , int *y ) // FUNZIONE SCAMBIO DEL { // VALORE DI DUE INTERI

int temp; temp = *x; *x = *y; *y = temp;

} char *maiusc ( char *st ) // METTE IN MAIUSCOLO UNA { // STRINGA

register int t; for ( t = 0; st [ r ] != ‘\0’; ++t ) // oppure for ( t = 0; st [ r ]; ++t )

st [ t ] = toupper ( st [ t ] ); return st;

}

62

NOTE:

- Gli array devono esser passati sempre per riferimento.

- guardando la funzione swap è evidente come sia difficoltoso il passaggio dei parametri per indirizzo in quanto è sempre necessario l’utilizzo dell’operatore di dereference (*x cioè il contenuto della cella il cui indirizzo è x).

Come compromesso fra la semplicità del p.v. e l’efficienza del p.r. il C++ introduce il passaggio per reference che consente alla funzione di: ricevere un sinonimo ( alias ) dei parametri attuali. - semplicità del p.v. infatti non si usa l’operatore di dereference - efficienza del p.r.

SINTASSI:

tipo &nome1 = nome2;

SEMANTICA: nome1 è dichiarato come REFERENCE ( sinonimo o alias ) di nome2. Il reference non è una copia di una variabile,ma la stessa variabile sotto un altro nome. Qualunque operazione viene fatta sul reference si riflette sulla var e viceversa: ESEMPIO: ………. int d = 5; int &z = d; ……………….. cout <<’\n’ << ++d; // 6 cout <<’\n’ << z; // 6 cout <<’\n’ << ++z; // 7 cout <<’\n’ << d; // 7 Un reference può essere visto come un particolare tipo di puntatore che può essere utilizzato senza il deference *

63

void swap( int & a, int & b ); int main( void ) {

int x =5 ; int y = 6; int &a = x; int &b = y; swap(a , b) return 0;

} void swap( int &a, int &b ) {

int temp; temp = a; a = b; b = temp;

} - Si potrebbe obiettare che con il passaggio attraverso il reference i parametri non vengono “ nascosti “ allora: int d = 5; // d può essere modificata intervenendo const int &z = d; // su z e non su d

z = 10 ; no d = 10; si

In conclusione:

- il passaggio per valore è necessario se i parametri attuali non devono essere modificati;

- il passaggio per indirizzo è utile se i parametri attuali devono essere modificati; qualunque sia la loro dimensione;

- il passaggio per reference è utile se i parametri attuali sono strutture di grandi dinensioni che non devono essere modificate;

64

PASSAGGIO DI ARRAY DI PUNTATORI AD UNA FUNZIONE

Per passare un’array di puntatori ad una funzione si opera come per gli altri tipi di array: void pippo ( int * q[ ] ) la funzione pippo, ad esempio riceve l’array q; ESEMPIO: Scriviamo una funzione che visualizza un messaggio di errore, in base ad un codice numerico dato in ingresso: unsigned mostra ( int num ) {

static char * err[ ] = {

“tasto sbagliato \ n”, “guasto al dispositivo \ n”, “errore di lettura \ n”, “errore di scrittura \ n”

}; return err [num]; } -

65

LA FUNZIONE MAIN Anche il main è una funzione , quindi può avere un valore di ritorno e dei parametri formali. La funzione main non viene chiamata da un’altra funzione ma dal sistema operativo e quindi riceve e restituisce dei valori al sistema operativo. Il tipo della funzione può essere un void o int in quest’ultimo caso il valore potrà essere restituito tramite return( num_int ) o exit( num_int ). Parametri formali di main void main ( int argc, char * argv [] ) { ……. ……. } numero dei parametri array di puntatori a char ESEMPIO: // nome programma = mio.cpp int main ( int argc , char * argv[] ) {

cout << ”num. Parametri “ << argc << ’\n’; for ( int i = 0; i < argc ; i++ )

cout << argv[ i ] << ’\n’; return 0; } se a livello di sistema operativo scrivo: mio classe 4 B informatica + tasto invio 5 mio.exe classe 4 B informatica

66

FUNZIONI INLINE Le funzioni inline sono uno strumento efficiente per implementare funzioni con poche istruzioni. SINTASSI inline definizione_funzione ESEMPIO: inline double minimo( double x, double y ) {

return x > y ? y : x ; } NOTE: le funzioni inline godono di tutte le proprietà delle normali funzioni. ogni chiamata ad una funzione inline viene sostituita con tutto il codice

della funzione. Questo meccanismo è identico a quello delle macro. E’ più efficiente di una normale funzione: non viene perso tempo con il

salvataggio e ripristino del contesto. a differenza delle macro, vengono effettuati i controlli sui dati. IMPORTANTE: inline viene considerato dal compilatore solo come un’indicazione: se la funzione ha troppe istruzioni non viene più espansa, ma trattata come una normale funzione.

67

OVERLOADING DELLE FUNZIONI L’overloading delle funzioni è un processo che consente di usare lo stesso nome per 2 o più funzioni. Sarà possibile al momento dell’esecuzione distinguere fra le varie funzioni poiché ognuna deve utilizzare parametri differenti o in tipo o in numero. ESEMPIO: inline int min ( int x, int y ) { return x > y ? y : x ; } inline double min ( double a, double b ) { return a > b ? a : b ; } inline int min ( int x, int y, int z) {

int q; q = x > y ? y : x; return q > z ? z :q;

} int main( ) { int a = 7;

cout << min ( 7, 3 ); cout << min ( 3.4, 120.43 ); cout << min ( a, 22, 100 ); return 0;

}

NOTA:

Non è possibile scrivere funzioni che differiscono per il solo valore di ritorno.

68

FUNZIONI DI MANIPOLAZIONE DELLE STRINGHE Nelle librerie del c esistono una serie di funzioni per la manipolazione delle stringhe char *strcat ( char *s1, const char *s2 ) Concatena ( aggiunge ) s2 a s1 int strcmp ( const char*s1, const char*s2 ) Confronta la stringa s1 con s2 ( carattere a carattere ) Il valore di ritorno è = 0 se s1 = s2

> 0 se s1 > s2 < 0 se s1 < s2

char *strcpy ( char *s1, const char*s2 ) assegna s2 a s1, copiando tutti i caratteri di s2 in s1 sino a quando non viene incontrato il carattere \0 (NULL) size_t strlen ( char *s ) restituisce il numero di caratteri di s char *strchr ( const char *s , const char c ) restituisce un puntatore alla prima occorrenza di c in s char *strstr( const char *s, const char *s1 ) restituisce un puntatore alla prima occorrenza di s1 in s.

69

IL PREPROCESSORE C Il preprocessore è un programma chiamato automaticamente prima della

fase di compilazione che esamina ed eventualmente modifica il file sorgente. Il tipo di manipolazione dipende dalle direttive del processore presenti nel file sorgente. Il processore riconosce una direttiva perché essa è preceduta dal carattere #. Le direttive possono comparire ovunque nel file sorgente e valgono da quel punto in poi. Le principali direttive sono: # include # define # if # else # elif #endif # ifdef # ifndef # error # undef # pragma # line

70

DIRETTIVA #INCLUDE SINTASSI:

#include <path_file > #include “path_file “

SEMANTICA: sostituisce la riga che contiene la direttiva con il contenuto dell’intero file ( path_file ). Se < > la ricerca del file avviene nella directory standard definita dal compilatore. Se “ “ la ricerca del file avviene secondo il path specificato. ESEMPIO: #include “c:\ pippo \ mio.h “ #include < string.h >

71

DIRETTIVA #DEFINE SINTASSI: #define identificatore[ (lista_parametri) ] [testo_da_sostituire] SEMANTICA: Nel programma sostituisce ogni volta che compare identificatore, viene sostituito con testo_da_sostituire. ESEMPI: # define PIGRECO 3.14 # define MESSAGGIO “Premi un tasto” # define INIZIO { # define FINE } # define MIN( x, y ) (( x ) < ( y ) ? ( x ) : ( y ))// macro PRIMA DOPO …………. PREPROCESSORE ……………………….. MIN(5, 7); (( 5 ) < ( 7 ) ? ( 5 ) : ( 7 )) …………. ……………………….. …………. Espansione della macro ……………………….. MIN(3, -3); (( 3 ) < ( -3 ) ? ( 3 ) : ( -3 )) NOTE: #define può sostituire efficacemente piccole funzioni ( non vi è perdita

di tempo per il salvataggio e ripristino del contesto). Non vi è nessun controllo sui parametri passati alla macro. Meglio usare le funzioni INLINE

72

DIRETTIVE #IF, #IFDEF, #IFNDEF, #ELSE, #ELIF #ENDIF

Queste direttive servono per la compilazione condizionale. Simile all’ istruzione if ma con una sostanziale differenza: mentre l’ if opera al tempo di esecuzione, la direttiva #if seleziona a tempo di compilazione quale

gruppo di istruzioni debba essere compilato, mentre le istruzioni scartate vengono completamente rimosse. Una forma particolare di direttiva #if è la #ifdef e la sua corrispondente negata #ifndef, che restituiscono un valore di verità rispettivamente vero e falso se la macro fornita come parametro è stata precedentemente definita. ESEMPIO:

int main(void) {

#define NUM 10 #define EXPR(a) (a)==6 ? 6 : 0 #ifdef NUM // vera: NUM è stato definito #if NUM == 10 // vera printf("NUM è uguale a 10.\n"); #if EXPR(6) // vera: se il del parametro == 6, l'espressione restituisce 6

printf("EXPR ha dato un risultato! %d\n", EXPR(6)); #else printf("EXPR non ha dato alcun risultato!\n"); #endif #elif NUM < 10 // falsa printf("NUM è minore di 10: %d\n", NUM); #else printf("NUM è maggiore di 10: %d\n", NUM); #endif #else printf("NUM non è definito.\n"); #endif return 0;

}

73

LE LIBRERIE IN C/C++

Una libreria software, per definizione, non è altro che un insieme precompilato di funzioni, variabili e tipi di dato pronti all'uso (quindi una libreria è a tutti gli effetti un eseguibile, ma non "stand-alone", ossia non lo si può eseguire direttamente ma solo richiamare. Utilizzare librerie in un progetto comporta numerosi vantaggi rispetto ad un tipo di programmazione "monolitica" in cui si ha un solo eseguibile (di dimensioni solitamente generose).

Modularità: È più facile mantenere più codici sorgenti di dimensioni contenute che un unico sorgente di centinaia di righe di codice. Ogni libreria è un modulo, ossia una parte ben identificabile (e quindi più facilmente manutenibile) di un progetto.

Riusabilità del codice: Una libreria può essere utilizzata in più programmi.

Fiducia nel codice: Un codice già usato più volte è codice già testato e quindi più affidabile.

Centralizzazione delle modifiche: Se più programmi utilizzano una stessa libreria correggere un’eventuale errore in essa, vuol dire correggere una sola volta un errore che si è propagato in più programmi. Aggiornare o correggere programmi talvolta può essere ridotto alla sola modifica delle librerie.

Eleganza e qualità del codice: usare le librerie può richiedere una più attenta fase di progettazione e sviluppo ma può favorire anche una migliore qualità del codice finale.

Le librerie possono essere :

librerie statiche librerie dinamiche e dinamiche condivise

Vi sono anche due modalità di caricamento delle librerie: caricamento statico caricamento dinamico ( a runtime e on demand ).

74

Librerie statiche Una libreria statica è un archivio di file oggetto (file con suffisso .o, ossia "object"), cioé i file prodotti dalla compilazione dei vari file sorgenti costituenti un progetto. Una libreria statica è agganciabile (linkabile) senza la necessità di

ricompilare l’intero progetto. Una libreria statica solitamente ha performance migliori di una

dinamica. Generalmente però occupa più memoria sul disco. La libreria diviene quindi una "scatola nera", sfruttabile attraverso le poche informazioni fornite dall'autore. Generalmente viene fornita la sola interfaccia ( l’insieme degli header delle funzioni in libreria ) ed una documentazione sintetica. Librerie dinamiche Il linker in fase di collegamento quando incontra un riferimento ad una libreria esterna, non richiede l'inclusione fisica di parte della libreria stessa nell'eseguibile finale, bensì salva un semplice riferimento, qualcosa del tipo "se ti serve la funzione X, essa è nella libreria esterna Y". Molto comodo e, sicuramente, meno dispendioso di spazio occupato su disco.La libreria dinamica ha anche un grande vantaggio, può essere utilizzata contemporaneamente da più programmi ( libreria dinamica condivisa ). A differenza della versione statica, una libreria dinamica risiede in un file separato rispetto all'applicazione a cui viene linkata.

75

PUNTATORI

Un PUNTATORE è l’indirizzo di memoria di una variabile o di una funzione , più in generale di un’area di memoria. Una VARIABILE PUNTATORE è una variabile che CONTIENE un PUNTATORE od un oggetto di un certo tipo. Le variabili puntatore devono essere dichiarate in modo particolare. SINTASSI

tipobase *nome dove il tipobase non è RIFERITO AL PUNTATORE MA AL TIPO DELL’OGGETTO PUNTATO. ESEMPIO char *ind ind è un puntatore AD UNA

VARIABILE DI TIPO char

Variabile puntatore variabile 1000

1000

76

In C i puntatori hanno 3 funzioni : 1. Come riferimento rapido agli elementi di un array 2. Per realizzare il PASSAGGIO PER INDIRIZZO dei parametri ad una

funzione. 3. Per realizzare strutture dinamiche NOTA: Ricordiamo che l’indirizzo è un numero intero positivo che corrisponde alla posizione fisica della locazione di memoria principale in cui è memorizzata una variabile. OPERATORE * (“all’indirizzo di …” ) è un operatore unario che restituisce il valore

contenuto nella locazione di memoria il cui indirizzo e’ nel proprio operando.

OPERATORE & & (“indirizzo di ….”) operatore unario che restituisce l’indirizzo di memoria del proprio operando; int dato, *punt; punt = &dato; dato = 10; cout << dato << ‘\n’ << * punt ; // avremo 10 10 * punt = 11 cout << dato << ‘\n’ << * punt ; // avremo 11 11

77

ARITMETICA DEI PUNTATORI Sui puntatori possiamo fare solo due operazioni aritmetiche:

Addizione Sottrazione

ESEMPIO: Supponiamo che il tipo int occupi 4 bytes int dato, * punt; punt = & dato; // Supponiamo che punt = 2000 punt ++; // punt = 2004 cioè il

// nuovo = vecchio + sizeof ( int ) * n punt +=2 // punt = 2012 ogni volta che incrementiamo di 1 un puntatore ad un array, esso punterà alla locazione di memoria dell’elemento successivo: punt - = 2; // punt = 2004 Stessa cosa per la sottrazione: int * punt2 , diff ; punt2 = & dato; diff = punt - punt2; // diff = 4 E’ possibile sottrarre 2 puntatori dello stesso tipo per sapere il numero di oggetti del tipo base che separano i due puntatori. Tutte le altre operazioni aritmetiche sono proibite.

78

CONFRONTO FRA PUNTATORI E’ possibile applicare gli operatori logici (<,>,==,!=,>=,<= ) a variabili puntatore. ESEMPIO: Vogliamo implementare un programma che azzera tutti gli elementi di dati [10]. Senza l’uso dei puntatori …………… int dati [ 10], i ; for ( i = 0 ; i < 10 ; i ++ ) dati [ i ] = 0; …………… Usando i puntatori …………… int dati [ 10]; int *p; for ( p = dati ; p < & dati [ 10 ] ; *p++ = 0 ) ; …………… NOTA Un puntatore può essere confrontato con zero per verificare se è un puntatore nullo ( che non punta a nessuna locazione di memoria) ESEMPIO: if ( p = = 0 )

cout << “p è un puntatore nullo “ ;

79

ARRAY E PUNTATORI In C array e puntatori sono strettamente correlati.

int vett[10]; vett è il &vett[0]

il nome puntatore al 1^ del vettore elemento del

vettore vett ESEMPIO: int s [ 10 ], * p; // le due espressioni sono equivalenti e p = s; // assegnano a p l’indirizzo del PRIMO p = & s[ 0 ]; // elemento dellarray s …………… s [ 5 ] = 100; // le due espressioni sono equivalenti e *( p+5 ) = 100; // assegnano al sesto elemento di s …………….. // il valore 100 int k [ 3 ] [ 3 ], *punt; punt = k; …………………. k [ 2 ] [ 1 ] = 200; * ( punt + 7 ) = 200; In generale :

punt [ i ] [ j ] = * ( punt + ( i * colmax ) + j ) dove COLMAX è il numero massimo di colonne. 0 1 2

80

200 200 0 1 2 3 4 5 6 7 8 9

punt + 7 o &punt[ 0 ] + 7 ESERCIZIO: Acquisire un vettore utilizzando l’aritmetica dei puntatori ESEMPIO: int vett [ 4 ]; for ( int i = 0; i < 4; i + + )

cin >> vett [ i ]; ………………………. ………………………. int vett [ 4 ], * p; p = vett ; // oppure p = & vett [ 0 ] for( int i = 0; i < 4; i + +)

cin >> * ( p+i ) ………………………. ………………………. int * vett; for ( int i = 0; i < 4; i + + )

cin > * ( vett + i); // sconsigliato ………………………. ………………………. char s[ 5 ], *punt; punt = s;

81

int lun = 0; cin >> s; se voglio sapere la lunghezza della stringa s : for ( ; *punt ; punt++, lun++ ) ; COUT << “LA LUNGHEZZA DI S È ” << LUN;

82

ARRAY DI PUNTATORI Anche i puntatori come un qualsiasi altro tipo di dato possono essere disposti in un array. ESEMPIO: int * x [10], var ; // array di 10 puntatori a int var = 10; x[ 2 ] = & var; cout <<* x[ 2 ] << ‘\n’ << var; // 10 10.

83

PUNTATORI A PUNTATORI Puntatore Variabile Puntatore Puntatore Variabile E’ un puntatore che punta ad un altro puntatore, quest’ultimo infine punta ad una variabile. SINTASSI

tipo ** nome; ESEMPIO: #include<iostream> using namespace std; int main (void) { int x , *p , **q; // q punta a p che punta a un int x = 10; p = &x; q = &p; cout << **q; // stampa valore di q = 10 return 0; } NOTA: Sono possibili più livelli di puntatori a puntatori, ma raramente si supera il secondo livello.

indirizzo valore

indirizzo indirizzo valore

84

PUNTATORI A STRUTTURE

Il puntatore ad una struttura può essere utile quando vi sono molti membri. ESEMPIO: struct BILANCIA {

char nome[30]; float peso;

} BILANCIA persone, *punt; // punt è un puntatore alla punt = & persone; // struttura BILANCIA si può accedere ai membri di una struttura tramite il puntatore

punt peso; o persone.peso;

85

PUNTATORI E FUNZIONI FUNZIONI CHE RITORNANO PUNTATORI Una funzione può restituire come dato un puntatore. Anche quando il puntatore è il valore di ritorno di una funzione dobbiamo dichiarare il tipo base di tale puntatore, cioè quale è il tipo del dato “puntato”. ESEMPIO: // prototipo della funzione cerca char * cerca( char c, char * s ); // cerca ritorna un puntatore a char int main ( void ) {

char s[10], ch, *p; // funzione main cin >> s; cin >> ch; p = cerca ( ch , s ); if ( p ) cout << *p; else cout << “ non trovato”; return 0;

} char * cerca ( char c, char *s ) // definizione della funzione cerca {

char * punt; punt = s; while ( c != *punt && *punt != ‘\0’ )

punt++; return ( punt );

}

86

PUNTATORI A FUNZIONI Normalmente quando viene chiamata una funzione viene in realtà eseguito ( fra le altre cose ) un salto ( jump ) alla prima istruzione della funzione. L’indirizzo della prima istruzione, in C/C++ può essere inserito in un puntatore.

La sintassi per dichiarare un puntatore a funzione è:

SINTASSI:

tipo (*punt-fun ) ( [ tipo par1 ],[ tipo par2 ]……) Nella dichiarazione non viene messo il nome ma ( *punt-alla-funzione ). Se non mettiamo le parentesi avremo una funzione che ritorna un puntatore a tipo. ESEMPIO: double quadrato ( double n ) { return n*n } // definizione della funz. quadrato double ( *punt_quad ) (double );//punt_quad è il puntatore alla

// funzione quadrato punt_quad = quadrato; // assegno un valore al puntatore La funzione può esser chaimata in due modi:

r = quadrato( 3 ); r = ( *punt_quad ) ( 3 );

L’uso dei puntatori a funzioni è utile quando il nome della funzione è noto al tempo dell’esecuzione del programma ( RUNTIME ).

NOTA IMPORTANTE: Il nome della funzione è l’indirizzo alla sua prima istuzione.

87

ESEMPIO: #include <float.h> #include <math.h> #include <iostream> using namespace std; int main ( void ) {

double ( *funzione [ ] ) (double ) = { sin, cos, tan };

(array di puntatore di funzioni)

unsigned scelta; double x, r; cout << ”1 seno “ << ’ \n ’; // visualizza menù cout << ” 2 coseno “ << ’ \n ‘; cout << ” 3 tangente “<< ’ \n ‘; do { // input scelta

cout << ” funzione scelta “ ; cin >> scelta; }

while ( scelta < 1 || scelta > 3 ); cin >> x; r = ( *funzione[ scelta-1 ] ) ( x ); cout >> r; return 0;

}

88

ALLOCAZIONE DINAMICA DELLA MEMORIA Se dichiariamo nel nostro programma

char s [ 1000 ]; verranno riservati 1000 byte per questo array di char ( stringa ) per tutta la sua DURATA ( LOCALE o GLOBALE ). In questo caso si parla di ALLOCAZIONE STATICA DELLA

MEMORIA.

Questo modo di gestire la memoria in molti casi può risultare inefficiente. Il C++ mette a disposizione del programmatore due operatori: NEW DELETE che consentono rispettivamente di allocare e disallocare lo spazio di memoria DURANTE L' ESECUZIONE DEL PROGRAMMA. In questo caso si parla di ALLOCAZIONE DINAMICA DELLA

MEMORIA.

89

NEW

SINTASSI SEMANTICA

var_ punt = new tipo [ ( dato ) ]; Restituisce in var_ punt un puntatore ad un'area di memoria sufficiente a contenere un dato di tipo tipo ed eventualmente inizializzarla con dato.

ESEMPIO

unsigned char *p; p = new char ( 41 ); // inizializzo a 41

char *s = new char[10]; // oppure insieme dichiarare e int *p1 = new int ( 23 ); // assegnare ………….. *s = “casa”; *p = ‘s’; *p1 = 46; DELETE

SINTASSI SEMANTICA

delete var_ punt; Disalloca (libera) la memoria precedentemente allocata con l'operatore new

(var_punt deve essere la stessa dell' op. new).

ESEMPI

delete [] s; delete p; delete p1;

90

LE CLASSI La CLASSE è un TIPO, simile alla struct, ma diversamente da questa contiene DATI e FUNZIONI che permettono di controllarne l’accesso. I dati che dichiareremo di tipo classe prendono il nome di OGGETTI. Le funzioni di una classe prendono il nome di METODI o MEMBER FUNCTION. In generale si parla di MEMBRI DI UNA CLASSE sia riferendosi ai dati che alle funzioni di una classe. SINTASSI class nome {

dati e funzioni private specificatore di accesso: dati e funzioni specificatore di accesso: dati e funzioni ……………… ………………

} lista_oggetti; oppure separatamente nome lista_oggetti

91

ESEMPIO # include<iostream> using namespace std; class punto { // definizione di una classe

float x,y; //dichiaroMEMBRI PRIVATI public:

void set (); // dichiaro METODI void print ();

}; void punto::set() { // definisco 1° metodo cin >> x; cin >> y; } void punto::print() { // definisco 2° metodo cout << x; cout << y; } int main ( void ) {

punto a; // dichiaro a di tipo punto a.set(); a.print(); a.x = 10.2; // NO è PRIVATO a.y = 0.5; // NO è PRIVATO return 0;

}

92

DICHIARAZIONE DI UN OGGETTO SINTASSI nome_classe lista_oggetti E’ importante non confondere i concetti di Classe e Oggetto. LA CLASSE è UN TIPO. L’OGGETTO di una determinata classe è un dato dichiarato di tipo classe ( è un’ISTANZA DI QUELLA CLASSE ).

SPECIFICATORI DI ACCESSO private i membri dichiarati PRIVATE possono essere usati solo

dai membri della classe. public i membri dichiarati PUBLIC possono essere usati da altre

“parti di programma”, cioè sono accessibili in tutto lo spazio di visibilità dell’oggetto di quella classe.

protected come PRIVATE ( per ora )

MEMBER FUNCTION

La sintassi della definizione è tipo nome_classe::nome_funz. ([tipo param1].......) { ....... ....... class scope } Come per gli altri membri della classe la chiamata al METODO può avvenire usando gli operatori :

pippo.print(); // applica il metodo print() all’ oggetto

// pippo, istanza della classe punto

93

COSTRUTTORI DI UNA CLASSE Un errore molto comune è quello dovuto all’ uso di variabili non inizializzate. Nella OOP per evitare questo tipo di errore è possibile INIZIALIZZARE UN OGGETTO AUTOMATICAMENTE AL MOMENTO DELLA SUA DICHIARAZIONE DEFINENDO UNO o PIU’ METODI DETTI COSTRUTTORI DELLA CLASSE CHE VERRANNO RICHIAMATI OGNI VOLTA CHE DICHIAREREMO UN NUOVO OGGETTO DI QUELLA CLASSE. ESEMPIO class punto {

float x, y; public:

punto ( ); // dichiaro due costruttori punto ( float x0, float y0 ); void set ( ); void print ( );

}; punto :: punto ( ) // definisco il primo { // costruttore

x = y = 0; } punto :: punto ( float x0, float y0 ) // definisco il secondo { // costruttore

x = x0; y = y0;

} .......... punto a , b (2.3 , 7.4); b.print ; // avremo 2.3 7.4

94

OSSERVAZIONI - Un costruttore ha lo STESSO NOME DELLA CLASSE. - Vi possono essere più COSTRUTTORI. Attraverso l’

OVERLOADING delle FUNZIONI potremo inizializzare oggetti in modo diverso.

- Un costruttore NON HA VALORE DI RITORNO. - Un costruttore come tutti gli altri metodi può avere PARAMETRI DI DEFAULT . punto ( float x = 0, float y = 0 ); - Un costruttore come tutti gli altri metodi può essere una FUNZIONE

INLINE ma in questo caso non è necessario specificare INLINE

print ( ) { cout << x << ’\n’ << y; }

- Un costruttore può essere CHIAMATO ESPLICITAMENTE per creare, in modo temporaneo, un oggetto

............... punto p = punto ( 7.3, 4.1 ); ............... return punto ( 1.0, 2.1 );

- Il costruttore non viene eseguito se si dichiara una variabile puntatore ad oggetto istanza di una classe:

punto *ptr; - Il costruttore viene eseguito al momento dell’allocazione dinamica dello spazio in memoria:

ptr = new punto

95

DISTRUTTORE DI UNA CLASSE

Il distruttore è un METODO che viene automaticamente chiamato quando UN OGGETTO DI UNA CLASSE ESCE DAL SUO SPAZIO DI VISIBILITA’. HA LO SCOPO DI ASSICURARE CHE DETERMINATE OPERAZIONI VENGANO ESEGUITE PRIMA CHE L’OGGETTO VENGA DISTRUTTO. ESEMPIO class punto {

float x, y; public:

punto ( ) { x = y = 0; }; ~punto ( ) { cout << ”oggetto distrutto\n”; }; void set ( ); void print ( );

}; ............. ............. NOTE - Un distruttore NON PUO’ AVERE VALORE DI RITORNO,

PARAMETRI FORMALI E NON SE NE PUO’ FARE L’ OVERLOADING.

- Non sempre è necessario un distruttore. Può essere utile quando si deve

rilasciare memoria allocata dinamicamente.

- Il distruttore viene richiamato automaticamente quando si applica l'operatore delete ad un puntatore alla classe oppure quando un programma esce dal campo di visibilità di un oggetto della classe.

- Infine, anche i distruttori, quando non vengono definiti esplicitamente, vengono creati automaticamente dal compilatore (distruttore di default).

96

DATI MEMBRI DEFINITI STATIC Ricordiamo che lo specificatore di classe STATIC consente di dare durata globale ad una variabile lasciando invariata la sua visibilità. In una classe dichiarare static un dato corrisponde a CREARE UNA SOLA COPIA DI TALE VARIABILE E DI UTILIZZARE LA COPIA PER TUTTI GLI OGGETTI DELLA CLASSE. Questo significa che tutti gli oggetti di quella classe condivideranno la stessa variabile. ESEMPIO class pippo {

int a; static int b; // dichiaro b ( descrivo b ) ………… …………

}; int pippo :: b // definisco b ( creo b ) ........... ........... NOTA - E’ necessario definire il dato STATIC al di fuori della classe utilizzando

l’ operatore di RISOLUZIONE DI CAMPO per identificare la classe. - Il dato static deve essere INIZIALIZZATO A LIVELLO GLOBALE

UNA SOLA VOLTA E NON VERRA’ REPLICATO PER OGNI OGGETTO DELLA CLASSE, MA ALLOCATO UNA SOLA VOLTA.

97

METODI DICHIARATI STATIC Anche un MEMBER FUNCTION può essere dichiarato STATIC con le seguenti limitazioni: - Può accedere solo a membri static - Non possono avere un puntatore THIS - Non possono esistere versioni static e non static della stessa funzione. ESEMPIO # include<iostream> using namespace std; class statico {

static int i; // DICHIARO i ( DESCRIVO i ) public: static void init ( int x ) { i = x; } // DEFINISCO init void show ( ) { cout << i; }

}; int statico :: i; // DEFINISCO i (CREO i) int main ( void ) {

statico p; // inizializzo i dati static p.init( 100 ); p.show( ); return 0;

}

98

PASSAGGIO DI OGGETTI A FUNZIONI

Il passaggio di oggetti a funzioni può avvenire PER VALORE ( viene creata una copia ). - Al momento della creazione della copia NON VIENE CHIAMATO IL COSTRUTTORE. - All’ uscita dalla funzione VIENE CHIAMATO IL DISTRUTTORE. ESEMPIO class mia { // definizione classe mia

int i; public:

mia ( int n ) { i = n;

cout << ”costrisco ” << i << endl; }

~mia ( ) { cout << ”distruzione di ” << i << endl; } void set ( int n ) { i = n; } int get ( ) { return i; }

}; void f ( mia ob ); // dichiarazione di f int main ( void ) {

mia o ( 1 ); f (o); cout << ”questa è la i di main” << o.get ( ) << endl; return 0;

} void f ( mia ob ) {

ob.set ( 2 ); cout << ”questa è la i locale” << ob.get () << endl;

} L’ output sarà: costruisco 1

questa è la i locale 2 distruzione di 2 questa è la i di main 1 distruzione di 1

99

NOTA - Nonostante il passaggio per valore, vi potrebbero essere dei problemi

quando la funzione alloca spazio dinamicamente o fa uso di puntatori. Per evitare ciò, si definisce un metodo detto COSTRUTTORE COPIA che si fa carico di DUPLICARE L’ OGGETTO A CUI SI RIFERISCE IL SUO ARGOMENTO e che verrà richiamato automaticamente ogni volta che deve essere inizializzato un oggetto con i dati di un altro.

- Per evitare queste complicazioni quando dobbiamo PASSARE o

RITORNARE UN OGGETTO ad/da una funzione è conveniente USARE IL PASSAGGIO PER REFERENCE ( SINONIMO ).

100

ARRAY DI OGGETTI La sintassi e l’uso di questi array è identica agli array di altro tipo. ESEMPIO class cl {

int i; public :

cl ( ) { i = 0; } //costruttore per array non iniziliz cl ( int j ) { i = j; } //costruttore per array inizializzat int get ( ) { return i; } void set ( int k ) { i = k; }

}; int main ( void ) { int i;

cl a[ 3 ] = { 3, 5, 6 }; // array inizializzato cl b[ 4 ]; // array non inizializzato for ( i = 0; i < 4; i++ )

b[ i ].set ( i+1 ); for ( i = 0; i < 4; i++ )

cout << b[ i ].get ( ) << ‘\n’; for ( i = 0; i < 3; i++ )

cout << a[i].get () << endl; return 0; }

101

PUNTATORI A OGGETTI E’ possibile definire puntatori a oggetti ricordando che per accedere ad un membro della classe non dovremo usare l’ operatore . ma

# include<iostream> using namespace std; class cl {

int i; public :

int x; cl ( int j ) { i = j; } int get ( ) { return i; }

}; int main ( void ) { cl o( 88 ), *p; // p è un puntatore ad un oggetto

p = &o; // indirizzo di o in p cout << p get ( );

……….. cl k[ 3 ] = { 1, 7,4 }; cl *p1; p1 = k; // punta al 1° elem.dell’ array di o.

p1++; // punta all’ elemento successivo ……….. cl b (1); int *p2; b.x = 100; //x è un membro PUBLIC

// è possibile assegnare ad un puntatore un membro public

p2 = &b.x; // è possibile accedere ad un membro public tramite un puntatore

cout << *p2; return 0;

}

102

IL PUNTATORE THIS

Quando viene richiamata una funzione membro, automaticamente le viene passato un parametro che e’ il puntatore all’ oggetto che ha

generato la chiamata.

ESEMPIO class st {

char *s public :

void stampa () { cout << s; };

oppure { cout << this s; }; oppure { cout << ( * this ) . s; }; }; NOTE - E’ utile quando si fa l’overloading di un operatore - E’ utile per verificare se l’oggetto che richiama il metodo è uguale al

parametro passato.

103

LA FUNZIONE FRIEND Una funzione FRIEND di una classe PUR NON ESSENDO UN MEMBRO DELLA CLASSE PUO’ ACCEDERE A TUTTI I MEMBRI PRIVATE e PROTECTED. SINTASSI friend prototipo della funzione ESEMPIO class mia {

int a, b; public :

friend int sum ( mia x ); void set ( int i, int j );

}; void mia :: set ( int i, int j ) {

a = i; b = j;

} int sum ( mia x ) {

return x.a + x.b; } int main ( void ) {

mia n; n.set ( 3, 4 ); cout << sum ( n ); return 0;

}

104

NOTE - Una f. friend per una classe C1 può essere un metodo per una classe C2. - Una classe derivata non eredita una funzione friend. - Una funzione friend non può essere STATIC o EXTERN. OSSERVAZIONI Le funzioni friend possono essere utili: 1. Nell’overloading degli operatori. 2. Semplificano la creazione di alcuni tipi di funzioni di i/o. 3. In caso vi siano due o più classi che contengono membri correlati con

altre parti di programma. class C2; //dichiarazione anticipata class C1 {

…………… friend int f ( C1 a, C2 b ); // dichiaro f in C1 ……………

}; class C2 {

…………… friend int f ( C1 a, C2 b ); // dichiaro f in C2 …………… int f ( C1a, C2b ) { // definisco f ……………

};

105

OVERLOADING DEGLI OPERATORI

In C++ è possibile ESTENDERE IL SIGNIFICATO DI UN OPERATORE, cioè fare in modo che esso possa operare su OPERANDI DI TIPO DIFFERENTE in modo simile a quello dei tipi fondamentali. ■ In C++ è possibile eseguire l'overloading della maggior parte degli operatori, per consentire loro di svolgere operazioni specifiche rispetto a determinate classi. ■ l'overlading di un operatore, estende l'insieme dei tipi al quale esso può essere applicato, lasciando invariato il suo uso originale. ■ L'overloading degli operatori consente di integrare meglio le nuove classi create dall'utente nell'ambiente di programmazione. ■ L'overloading degli operatori è alla base delle operazioni di I/O del C++. ■ L'overlaoding degli operatori viene realizzato per mezzo delle funzioni operator. ■ Un funzione operator definisce le specifiche operazioni che dovranno essere svolte dall'operatore sovraccaricato (overloaded) rispetto alla classe specificata. ■ Ci sono due modi per sovraccaricare un operatore. – Tramite i metodi della classe; – Tramite funzioni esterne che però devono essere friend per la classe. ESEMPIO ………….. ………….. int x = 10, y = 20; char s[] = ”Rossi”; char s1[] = “Mario”; cout << x + y; // ho 30 cout << s + s1; // VORREI Rossi Mario --- o --- #include <iostream> #include <string.h> using namespace std; class stringa {

106

unsigned lung; char *s;

public : stringa() { lung = 0 ; s = 0; }; // primo costruttore stringa( char *costante ) ; // secondo costruttore ~stringa(); // distruttore void stampa() { cout << s << endl; }; unsigned len() { return lung; }; stringa operator+ ( const stringa &b ); void operator= ( const stringa &b ); int operator==( const stringa &b ); int operator!=( const stringa &b ); };

stringa::stringa ( char *costante ) { // definizione del secondo lung = strlen ( costante ); // costruttore s = new char ( lung + 1); s = costante; }; stringa::~stringa() { lung = 0; delete s; } stringa stringa::operator+( const stringa &b ) { char *temp = new char[ lung + strlen(b.s) + 1 ]; stringa t; strcpy(temp, s); strcat(temp, b.s); t.s = temp; t.lung = strlen(temp); return t;

107

} void stringa::operator=(const stringa &b ) { lung = b.lung; delete s; s = new char [ lung+1]; strcpy ( s,b.s ); } int stringa::operator==( const stringa &b ) { return strcmp ( s, b.s ); }

int stringa::operator!= ( const stringa &b ) { return !strcmp ( s, b.s ); }

int main ( void ) // PROGRAMMA PRINCIPALE {

stringa s2("rossi"); stringa s1("mario"); stringa s3; cout << (s1 == s2) << '\n'; cout << (s1 != s2) << '\n'; s1.stampa(); s2.stampa(); s3 = s1 + s2; s3.stampa(); cout << s3.len(); system ("pause"); return 0; }

108

SINTASSI tipo nome_classe :: operator # ( elenco-param ) { corpo } tipo può essere un qualunque tipo. # simbolo che rappresenta l’operatore da sovrapporre. NON SI POSSONO SOVRAPPORRE: . operatore punto .* operatore puntatore a membro :: operatore scope revolution ?: operatore condizionale ternario elenco-param Se sovrapponiamo un operatore

BINARIO avremo un solo parametro UNARIO non avremo parametri

Infatti nel caso ad esempio di un operatore binario L’OPERANDO DI SINISTRA VIENE PASSATO ALLA FUNZIONE IN MODO IMPLICITO UTILIZZANDO IL PUNTATORE THIS. In altre parole E’ L’OPERANDO DI SINISTRA CHE RICHIAMA LA FUNZIONE MEMBRO CHE SOVRAPPONE L’OPERANDO BINARIO. NOTE - NON SI PUO’ MODIFICARE IL N° DI OPERANDI. - NON SI PUO’ CAMBIARE LA PRECEDENZA DEGLI OPERANDI

109

OVERLOADING DEGLI OPERATORI ++ E –

Ricordiamo che esistono due forme di questi operatori, la PREFISSA e la POSTFISSA ++a a++ ESEMPIO class loc { int lat, lon; public: loc ( ) { };

loc ( int lg, int lt ) { lat = lt, lon = lg }; void stampa ( ) { cout << lat << ‘\n’ << lon; }; loc operator+ ( loc o2 ); loc operator- ( loc o2 ); loc operator= ( loc o2 ); loc operator++ ( ); // operatore prefisso loc operator++ ( int o2 ); // operatore postfisso loc operator+= ( loc o2 );

}; NOTE Quando facciamo l’overloading di un operatore dobbiamo tener presente: Se l’operatore e BINARIO o UNARIO : da questo dipende il numero di

parametri della funzione. E’ sempre l’operando di sinistra ( in caso di operatore binario ) a

richiamare la funzione. Se l’operatore modifica o no gli operandi.

110

loc loc :: operator+ ( loc o2 ) { loc temp; temp.lat = lat + o2.lat;

temp.lon = lon + o2.lon; return temp;

} loc loc :: operator- ( loc o2 ) { loc temp; temp.lat = lat - o2.lat;

temp.lon = lon - o2.lon; return temp;

} loc loc :: operator= ( loc o2 ) { lat = o2.lat;

lon = o2.lon; return *this;

} loc loc :: operator++ ( ) { lat ++ ;

lon ++; return *this;

}

111

loc loc :: operator++ ( int o2 ) { o1 = 0; lat ++ ;

lon ++; return *this;

}

loc loc :: operator+= ( loc o2 ) { loc temp; lat = o2.lat + lat;

lon = o2.lon + lon; return *this;

}

void main ( void ) { loc o3( 3, 3 ), o4( 4, 4 ), o5; o5 = o3 + o4; o5.stampa ( ); // 7 7

o5 = o3 +++ o4; o5.stampa ( ); // 8 8

o5 = o3 + o4 ++; o5.stampa ( ); // 8 8 o4.stampa ( ); // 6 6 o3 += o4; o3.stampa ( ); // 9 9

}

112

OVERLOADING DEGLI OPERATORI CON FUNZIONI FRIEND La funzione friend non è membro della classe non può quindi impiegare il puntatore THIS. La conseguenza è che gli operatori devono essere passati esplicitamente. ESEMPIO class loc { int lat, lon; ………..

friend loc operator+ ( loc o1, loc o2 ); ……….. };

loc loc :: operator+ ( loc o1, loc o2 ) { loc temp; temp.lat = o1.lat + o2.lat;

temp.lon = o1.lon + o2.lon; return temp;

} NOTE Con la funzione friend non è possibile sovrapporre gli operatori =, ( ), [

], Se si voglioni sovrapporre ++ e – si dovrà utilizzare il passaggio per reference

113

OVERLOADING DI ++ E -- CON L’ USO DELLE FUNZIONI FRIEND Gli operatori ++ e -- modificano gli operandi per cui passando i parametri per valore non otterremmo il risultato voluto. ESEMPIO class loc { int lat, lon; ………….

friend loc operator++ ( loc & o1); friend loc operator++ ( loc & o1, int x ); …………. ………….

}; loc loc :: operator++ ( loc & o1 ) { o1.lat ++ ; // operatore prefisso

o1.lon ++; return o1;

} loc loc :: operator++ ( loc & o1, int x ) { x = 0;

o1.lat ++ ; // operatore postfisso o1.lon ++; return o1;

}

114

PERCHE’ SOVRAPPORRE CON LA FUNZIONE FRIEND void main ( void ) { loc o1( 10, 20 ), o2( 5, 30 ), o3( 7, 14 ); o1 = o2 + 10; o5.stampa ( );

o3 = 10 + o2; // scorretta }

Ricordiamo che è l’operando di sinistra che chiama la funzione di overloading ed in questo caso è una costante non un oggetto della classe loc. class loc { int lat, lon; ………… …………

friend loc operator+ ( loc o2, int o1 ); friend loc operator+ ( int o1, loc o2 );

}; In questo caso la funzione friend è più FLESSIBILE, in quanto gli OPERANDI SONO PASSATI IN MODO ESPLICITO.

115

EREDITARIETA’ In C++ è possibile definite una o più classi ( CLASSI DERIVATE), a partire da una classe detta CLASSE BASE. Queste classi EREDITANO TUTTE LE PROPRIETA’ DELLA CLASSE BASE AGGIUNGENDO FUNZIONALITA’ SPECIFICHE.

In questo modo costruire una classe diventa più semplice :

Scelgo una classe simile a quella da realizzare Aggiungo le nuove proprietà.

Questo meccanismo prende il nome di inferenza, cioè reagire a situazioni nuove, compiere azioni mai fatte prima, capire il funzionamento di oggetti sconosciuti, semplicemente perché simili ad oggetti noti.

CLASSE ABITAZIONI CLASSE UFFICI

CLASSE CASA

CLASSE SCUOLE CLASSE ENTI

116

CLASSI DERIVATE SINTASSI class nome: specificatore_accesso nome_classe_base { corpo della classe }; Quando una classe deriva da un’ altra. I MEMBRI DELLA CLASSE BASE DIVENGONO MEMBRI DELLA CLASSE DERIVATA. Il tipo di accesso che i membri della classe derivata possono avere su quelli della classe base dipende dallo specificatore di accesso. Se lo specificatore è PUBLIC CLASSE BASE CLASSE DERIVATA membro public membro public membro protected membro protected membro private non accessibile Se lo specificatore è PRIVATE CLASSE BASE CLASSE DERIVATA membro public membro private membro protected membro private membro private non accessibile Se lo specificatore è PROTECTED CLASSE BASE CLASSE DERIVATA membro public membro protected membro protected membro protected membro private non accessibile

117

NOTE Qualunque sia lo specificatore di accesso i membri private della classe

base non sono accessibili. Con lo specificatore private, i membri public e protected divengono

private per la classe derivata; questo comporta : classe base classe derivata1 classe derivata2 Con lo specificatore protected, i membri public e protected divengono

protected per la classe derivata; questo comporta : classe base classe derivata1 classe derivata2

public protected

private private

non acces. non acces.

public protected

protected protected

protected protected

118

ESEMPIO class base { public:

int a; protected: int b; private: int c;

}; class derivata : public base {

int a; …….. void funzione ( );

}; void derivata :: funzione ( ) {

a = 1; // ammesso : a è public b = 2; // ammesso : b è protected c = 3; // ERRORE: c è membro private

}; void main ( void ) {

base uno; derivata due; uno.a = 1; // ammesso : a è public uno.b = 2; // ERRORE: b è membro protected due.a = 3; // ammesso : a è public due.b = 4; // ERRORE: b è membro protected due.c = 5; // ERRORE: c è membro private

}

119

ESEMPIO class base { public:

int a; protected: int b; private: int c;

}; class derivata : private base {

int a; …….. void funzione ( );

}; void derivata :: funzione ( ) {

a = 1; // ammesso : a è public b = 2; // ammesso : b è protected c = 3; // ERRORE: c è membro private

}; void main ( void ) {

base uno; derivata due; uno.a = 1; // ammesso : a è public uno.b = 2; // ERRORE: b è membro protected due.a = 3; // ERRORE: a è membro private due.b = 4; // ERRORE: b è membro private due.c = 5; // ERRORE: c è membro private

}

120

ESEMPIO class base { public:

int a; protected: int b; private: int c;

}; class derivata : protected base {

int a; …….. void funzione ( );

}; void derivata :: funzione ( ) {

a = 1; // ammesso : a è public b = 2; // ammesso : b è protected c = 3; // ERRORE: c è membro private

}; void main ( void ) {

base uno; derivata due; uno.a = 1; // ammesso : a è public uno.b = 2; // ERRORE: b è membro protected due.a = 3; // ERRORE: a è membro public due.b = 4; // ERRORE: b è membro protected due.c = 5; // ERRORE: b è membro private

}

121

EREDITARITA’ MULTIPLA Una classe drivata può ricevere “in eredità” membri dati e funzioni di due o più classi. ESEMPIO class base_1 { protected:

int x; public: void show_x ( ) { cout << x; };

}; class base_2 { protected:

int y; public: void show_y ( ) { cout << y; };

}; class derivata : public base_1, public base_2 {

public: void set ( int i, int j ) { x = i; y = j; };

}; void main ( void ) {

derivata o; o.set ( 10, 20 ); o.show_x( ); o.show_y( );

}

122

PASSAGGIO DI PARAMETRI AI COSTRUTTORI DELLA CLASSE BASE E’ possibile passare parametri ai costruttori di una classe base SINTASSI costruttore_derivata ( elenco_param ) :base1 ( elenco_param )

base2 ( elenco_param ) …………………… baseN ( elenco_param )

{ // corpo del costruttore derivato }

123

ESEMPIO #include<iostream> using namespace std; class base { protected:

int i; public:

base ( int x ) { i = x; cout << “costruttore di base\n”; }; ~base ( ) { cout << “distruttore di base\n”; };

}; class derivata : public base {

int j; public:

derivata ( int x, int y ) : base ( y ) { j = x; cout << “costruttore di derivata\n”; }; ~derivata ( ) { cout << “distruttore di derivata\n”; }; void show ( ) { cout << i << j; };

}; void main ( void ) {

derivata o( 3, 4 ); o.show( ); // mostra 4 3

}

124

CLASSI BASI VIRTUALI

Se dovessimo trovarci in una situazione del tipo: class base {

public : int i;

……………. int main( void ) // l’espressione a chi si { // riferisce? Ad i di

derivata3 o; // derivata1 o ad i di ……………. // derivata2 eredeitati da o.i = 20; // derivata3. …………….

o.derivata2 : : i = 20; // prima soluzione class derivata1 virtual public base { ……………. class derivata2 virtual public base { // seconda soluzione ……………. base è ereditata virtual da derivata 1 e 2 pertanto ogni derivazione successiva comporterà l’uso di una sola copia di base.

CLASSE DERIVATA1 CLASSE DERIVATA2

CLASSE BASE

CLASSE DERIVATA3

125

FUNZIONI VIRTUALI Bisogna precisare che un oggetto di una classe derivata è anche oggetto della classe base, e di conseguenza può essere puntato da un puntatore alla classe base. Una funzione virtuale viene : - dichiarata in una classe base ( virtual ) - definita nelle varie classi derivate ( stesso nome ) - chiamata con un puntatore di tipo classe base la versione che verrà effettivamente eseguita al momento dell’esecuzione dipenderà dall’oggetto a cui punta il puntatore. ESEMPIO: class base {

public: virtual void f ( ) { cout << “ questa è f ( ) di base \n”; }

}; class d1 : public base {

public : void f ( ) { cout << “ questa è f ( ) di d1 \n”; }

}; class d2 : public base {

public : void f ( ) { cout << “ questa è f ( ) di d2 \n”; }

};

126

int main ( void ) {

base b, *p; d1 o1; d2 o2; p = &b; // punta a base

p f ( ); // chiama f ( ) di b p = &o1; // punta a d1

p f ( ); // chiama f ( ) di d1 p = &o2; // punta a d2

p f ( ); // chiama f ( ) di d2 return 0; } l’uscita sarà:

questa è f ( ) di base questa è f ( ) di d1 questa è f ( ) di d2

note: - le funzioni virtuali non possono essere STATIC, FRIEND O

COSTRUTTORI - i prototipi delle funzioni virtuali devono essere identici altrimenti

avremmo l’overloading. - una classe con una funzione virtuale detta classe POLIMORFICA - se una classe d eredita da b ( classe base ) una f.v. anche d1 derivata da

d eredita la f.v. - se nella classe derivata non viene ridefinita la f.v., quando un oggetto di

questa classe tenterà di accedere alla f.v. verrà richiamata quella della classe base.

127

FUNZIONI VIRTUALI PURE Sappiamo che se una funzione virtuale non viene ridefinita viene impiegata la versione definita nella classe base. Ma se vogliamo obbligare le classi derivate a definire la funzione virtuale è sufficente definirla PURA nella classe base. SINTASSI:

virtual tipo nome ( elenco_param ) = 0; La funzione virtuale pura è necessaria quando nella classe base non si può creare una funzione virtuale perché l’oggetto non è ben definito, ma è necessario assicurarsi che tutte le classi derivate ridefiniscano la funzione virtuale. Quando una funzione virtuale viene resa pura ogni classe derivata deve fornire la sua versione altrimenti avremo un errore in compilazione. Una classe che contiene una funzione virtuale pura è detta CLASSE ASTRATTA. Poiché questa classe contiene funzioni non definite NON È POSSIBILE DEFINIRE OGGETTI DI QUESTA CLASSE ( puntatori si ).

128

ESEMPIO:

#include < iostream > using namespace std; class converti { protected:

double val1 , val2; public : converti ( double 1 ) { val1 = i; }; double scrivi1 ( ) { return val1; }; double scrivi2 ( ) { return val2; }; virtual void calcola ( ) = 0;

} class lg : public converti { public :

lg ( double i ) : converti ( i ) { }; void calcola ( ) { val2 = val1 / 3,7854 };

} class fc : public converti { public :

fc ( double i ) : convetri ( i ) { }; void calcola ( ) { val2 = ( val1-32 ) / 1.8; };

} int main( void ) {

converti *p; // di una classe astratta non posso lg a ( 4 ); // definire oggetti ma puntatori si p = &a;

cout << p scrivi1 ( ) << “ litri = “; p calcola ( ); cout << p scrivi2 () << “ galloni \n “; return 0; }

129

LE FUNZIONI TEMPLATE

In molti casi incontriamo algoritmi identici che operano su diversi tipi di dati. Ad esempio un algoritmo di ordinamento di un vettore di interi è uguale a quello di ordinamento di un vettore di caratteri, la differenza è solo nel tipo di dati da ordinare. Una funzione TEMPLATE o GENERICA è adeguata per gestire situazioni di questo tipo: SINTASSI:

template <class TIPO> TIPO nome_funz ( parametri formali ) { ………… // corpo della funzione ………… }

TIPO è un simbolo qualsiasi che il compilatore sostituirà con lo stesso tipo dei parametri attuali presenti nella chiamata della funzione. Nel caso le chiamate siano più di una e con tipi diversi, verranno create automaticamente dal compilatore tante versioni della funzione, quante sono le chiamate con tipi differenti.

130

ESEMPI:

#include <iostream> using namespace std; template < class T > T massimo ( T x , T y ) // T è SOLO un simbolo {

return x > y ? x : y; }

int main ( void ) {

cout << massimo ( 2, 3 ); // due interi cout << massimo ( 1.2, 0.5 ); // due float return 0;

} In questo caso il compilatore genererà due versioni di max:

T = int T = float

template < class X > void scambia ( X &a , X &b ) {

X temp; temp = a; a = b; b = temp;

} NOTE:

in una funzione template è possibile definire più di un tipo generico. template < class T1 , class T2 > void funz ( T1 x , T2 y ) {

……….. }

131

OVERLOADING ESPLICITO DI UNA FUNZIONE TEMPLATE Se si esegue un overloading di una funzione tradizionale questa verrà preferita rispetto alla funzione template in fase di chiamata. ESEMPIO: template < class T > T massimo ( T x , T y ) // funzione template { return x > y ? x : y; } int massimo ( int x , int y ) // funzione “ normale “ { return x > y ? x : y; } int main(void) {

cout << massimo ( 1.2 , 3.3 ); cout << massimo ( 7 , 21 ); // chiama funzione return 0; // int massimo ( int , int )

} NOTE: Come è evidente dall’esempio , l’overloading consente di adattare con precisione una versione di una funzione generica a particolari situazioni. In generale comunque se avremo FUNZIONI DIVERSE PER TIPI DIVERSI DI DATI, sarà meglio utilizzare funzioni modificate tramite l’overloading piuttosto che funzioni template.

132

NOTE SULLE FUNZIONI TEMPLATE Le funzioni template sono simili alle funzioni modificate tramite overloading ma in questo caso è necessario creare una funzione per ogni tipo di dato, d’altrocanto nelle funzioni template il corpo è UNICO PER TIPI DIVERSI e ciò in alcuni casi potrebbe risultare una restrizione.

Funzione template Compilazione In sintesi le funzioni generiche o template sono utili quando ci troviamo a definire algoritmi GENERALIZZABILI quando ciò non è possibile possimo utilizzare l’overloading delle funzioni.

MAX( T x, T y )

MAX ( INT , INT )

MAX ( FLOAT , FLOAT )

MAX ( CHAR , CHAR )

133

CLASSI TEMPLATE E’ possibile definire anche una classe generica. In questo caso si crea una classe che definisce tutti gli algoritmi della classe ma nella quale il TIPO DI DATI DA MANIPOLARE VIENE SPECIFICATO COME PARAMETRO NEL MOMENTO IN CUI VENGONO GENERATI GLI OGGETTI DELLA CLASSE. SINTASSI:

template < class tipo > class nome {

………. ……….

} tipo: è un simbolo che dovrà essere sostituito col nome del tipo dell’oggetto dichiarato dalla classe.

nome < tipo > oggetto; tipo sarà il tipo su cui la classe troverà ad operare. LE FUNZIONI DI UNA CLASSE TEMPLATE SONO AUTOMATICAMENTE ANCH’ESSE TEMPLATE.

134

ESEMPIO: // creazione di una classe generica per una pila #include < iostream > using namespace std; cost int SIZE = 100; // dimensione max della pila template < class S > class pila {

S stack [ SIZE ]; unsigned sp; public : pila ( ); // costruttore

void push ( S i ); // inserisce elemento nella pila S pop ( ); // estrae elemento dalla pila void stampa ( ); // stampa la pila

}; template class < S > // definizione del costruttore pila < S > : : pila ( ) { sp = 0; cout << “pila inizializzata\n”; };

template class < S > // definizione di push void pila < S > : : push ( S i ) {

if ( sp = = SIZE ) {

cout << “ pila esurita \n”; return;

} stack[ sp ] = i; sp++;

};

135

template < class S > // definizione di pop S pila < S > : : pop ( ) {

if ( sp = = 0 ) {

cout << “ pila vuota \n”; return 0;

} return stack [ --sp ];

} template class < S > // definizione di stampa void pila < S > : : stampa ( ) {

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

cout << stack [ i ] << endl; } int main ( void ) {

pila < int > a; // a è una pila di interi pila < char > b; // b è una pila di char a.push ( 10 ); b.push ( ‘a‘ ); a.push ( 5 ); a.push ( 4 ); b.push ( ‘b’ ); a.pop ( ); a.stampa ( ); b.stampa ( ); return 0;

} L’output sarà: 10 5 a b.

136

LA CLASSE STRING DEL C++

Per poter utilizzare degli oggetti di classe string in un programma C++ bisogna includere l'header di libreria che in questo caso si chiama string2 nel modo seguente #include <string> Le dichiarazioni che seguono sono usate negli esempi: string s = "abc def abc"; string s2 = "abcde uvwxyz"; char c; char ch[] = "aba daba do"; char *cp = ch; unsigned int i; stream input

cin >> s;

In s il valore letto in ingresso. Il valore si ferma allo spazio bianco.

stream output

cout << s;

Scrive s nello stream di output.

line input

getline ( cin, s );

Legge tutto fino al prossimo carattere newline e pone il risultato nella variabile stringa s.

assegnazione

s = s2; s = "abc"; s = ch; s = cp;

Una stringa letterale o una variabile stringa o un array di caratteri può essere assegnato ad una variabile stringa. Gli ultimi due assegnazioni hanno lo stesso effetto.

posizione

s2.begin()

Restituisce la posizione in memoria del primo carattere della stringa s2.

subscript

s[ 1 ] = 'c'; c = s[ 1 ];

Modifiche alle pari s "acc def abc" Imposta c a 'b'. L'operatore indice restituisce un valore char, non un valore di stringa.

lunghezza

i = s.length(); i = s.size();

In entrambi i casi ad i viene assegnata la lunghezza di s

vuota?

if ( s.empty( ) ) i++; if ( s == "" ) i++;

In entrambi i casi i viene incrementata di 1 se s è vuota

operatori relazionali

if ( s < s2 ) i++;

Determina quale è la stringa più piccola ( secondo il codice ASCII). In questo caso la condizione è vera perchè lo

137

spazio viene prima della lettera d

concatenazione

s2 = s2 + "x"; s2 += "x";

In entrambi i casi aggiunge il carattere ‘x’ a s2

inserimento s2.insert( 3, ”xxx” )

Permette l’inserimento di una stringa in una posizione qualsiasi di un altra stringa. In s2 viene inserita la stringa “xxx” a partire dalla posizione 3.

sottostringhe s = s2.substr( 1, 4 )

In s la sottostringa ricavata da s2 a partire dalla posizione 1 per 4 caratteti, quindi "bcde". La prima posizione nella stringa è 0.

sostituzione di sottostringhe

s.replace( 4,3, "x" )

Sostituisce in s 3 caratteri partendo dalla posizione 4 con il carattere “x”. s sarà uguale a "abc x abc".

rimozione di sottostringhe

s.erase( 4, 5 ); s.erase( 4 );

Rimuove i cinque caratteri, a partire in posizione 4 di s. Il nuovo valore di s è "abc bc". Cancella dalla posizione 4 alla fine della stringa. Il nuovo valore di s è "abc".

da array di caratteri a stringa

s = ch;

Converte da array di caratteri a stringa

da stringa a array di char

cp = s.c_str( );

Il puntatore cp punta ad un array di caratteri con gli stessi caratteri di s.

riconoscimento di un pattern

i = s.find( "ab", 4 )

Il primo esempio restituisce la posizione della sottostringa "ab" la ricerca inizia dalla posizione 4.

138

IL SISTEMA DI I/O DEL C++

Il sistema di I/O opera tramite i CANALI, che non sono altro che dispositivi logici che producono o consumano informazioni. - ogni canale è collegato a un dispositivo fisico - tutti i canali si comportano allo stesso modo anche se il dispositivo

fisico cambia. Ad esempio potremo usare stessa funzione che scrive su un file per scrivere sulla stampante o sul video. Quando si esegue un programma C++ vengono automaticamente aperti 4 canali di I/O; cin input standard tastiera cout output standard schermo cerr output di errori standard schermo clog versione bufferizzata di cerr schermo Tutti i canali possono essere REDIRETTI AD ALTRI DISPOSITIVI come ad esempio file.

139

CLASSI BASE RELATIVE AI CANALI Nel file di intestazione IOSTREAM sono presenti DUE GERARCHIE DI CLASSI che si occupano delle operazioni di I/O - la classe base ios contiene molte funzioni e variabili membro che

controllano le operazioni fondamentali di un canale. - Istream , ostream e iostream vengono impiegate per creare canali per

operazioni di input , output e input/output. - Dalla classe base IOS vengono derivate altre classi per consentire l’uso

di file e della memoria RAM.

STREAMBUF IOS

ISTREAM

OSTREAM

IOSTREAM

140

OPERAZIONI DI I/O FORMATTATO Con il termine formattare si intende la possibilità , in fase di I/O , di modificare ad esempio la base numerica , o di determinare il numero di cifre decimali da visualizzare ecc… ecc…

FLAGS DI FORMATTAZIONE Nella classe base ios sono definiti dei FLAGS DI FORMATTAZIONE e dei metodi che consentono di manipolare i flags enum {

skipws = 0x0001 // salta blank in input left = 0x0002 // allineamento a sinistra in output

right = 0x0004 // allineamento a destra in output internal = 0x0008 // blank dopo il segno o base dec = 0x0010 // converte in decimale oct = 0x0020 // converte in ottale hex = 0x0040 // converte in esadecimale showbase = 0x0080// visualizza base showpoint = 0x0100// visualizza punto e valori decimali uppercase = 0x0200// x ed e in maiuscolo showpos = 0x0400 // visualizza + valori positivi scientific = 0x0800 // notazione scientifica fixed = 0x0100 // notazione normale unitbuf = 0x0200 // svuota l’I/= dopo ogni output

} - ognuno dei flag può trovarsi nello stato: ATTIVO 1 NON ATTIVO 0 - i flags sono codificati in un tipo long

141

FUNZIONI MEMBRO DI IOS

long setf ( long flag ) attiva ( pone a 1 ) uno o più flag. cout.setf ( ios : : hex ); cout.setf ( ios : : showbase ); cout << 100; // visualizza 0x64 in modo più efficiente cout.setf ( ios : : showbase | ios : : hex ); cout << 100; NOTA: l’esempio mostra che per accedere ai flag ( variabile membro di ios ) dovremo usare : : operatore CLASS SCOPE:

long unsetf ( long flag ) Disattiva ( pone a 0 ) uno o più flag. cout.unsetf ( ios : : showbase | ios : : hex );

long flags ( ) Restituisce l’impostazione corrente dei flags in un intero long. long f; f = flags( ); se f = 3 skipws e left sono attivi tutti gli altri disattivi

142

long flag ( lonf f )

cambia i falg in relazione al valore della variabile f. long f; f = 11; cout.flags ( f ); 11 in binario corrisponde a 0000000000001011 quindi vengono attivati il bit 0, 1 e 3 ( skipws , left , internal ) disattivati i restanti.

int width ( int w ) Modifica l’ampiezza del campo a w e restituisce il valore precedente.

int precision ( int p ) Modifica a p il numero delle cifre decimali e e restituisce il valore precedente.

char fill ( char ch ) Modifica a ch il carattere di riempimento e restituisce il valore precedente. cout.precision ( 4 ); cout.width ( 10 ); cout.fill ( ‘*’ ); cout << 10.12345; In output avremo : ***10.1234 cout << “ ABCDE “; In output avremo *****ABCDE

143

FORMATTAZIONE CON L’USO DEI MANIPOLATORI Un secondo modo per formattare l’I/O è attraverso l’uso di funzioni dette MANIPOLATORI: MANIPOLATORE SCOPO I/O dec I/O decimale I/O endl a capo, svuota canale O ends output carattere nullo O flush svuota canale O hex I/O esadecimale I/O oct I/O ottale I/O resetioflgs( long f ) azzera i flag specificati in f I/O setbase( int base ) imposta base numerica O setfill( int ch ) imposta car. Riempimento a ch O setiosflags( long f ) ATTIVA i flag specificati in f I/O setprecision( int p ) imposta a p la precisione O setw( int w ) imposta a w ampiezza del campo O ws salta spazi bianchi iniziali I NOTE: - i manipolatori sono estremamente comodi in quanto possono essere

direttamente inseriti nelle espressioni di I/O

144

ESEMPIO: #include <iostream > #include <iomanip.h > using namespace std; void main( void ) { cout << hex << 100 << endl; // 64 cout << setfill ( ‘*’ ) << setw ( 10 ) << 2343.0 // ******2343 } void main( void ) { cout << setioflags ( ios : : showpos ); cout << setios flags ( ios : : showbase ); cout << 123 << ‘\t’ << hex << 123; } NOTE: Il manipolatore non richiede parametri , in quanto all’ operatore << viene passato l’indirizzo della funzione.

145

OPERAZIONI DI I/O SU FILE Per eseguire operazioni di I/O su file, si deve includere nel programma il file di intestazione FSTREAM. Questo file contiene le classi ifstream ofstream e ostream che derivano da ios. Ifstream, ofstream e fstream hanno accesso alle funzioni di ios. Apertura e chiusura di un file In C++ si apre un file collegandolo ad un canale che può a sua volta essere di input output e input/output. ifstream in; // in è un canale di input ofstream out; // out è un canale di output fstream io; // io è un canale di I/O Creato un canale dobbiamo associarlo ad un file tramite la funzione OPEN ( ) membro delle 3 classi: void open ( const char *n, int m, int a = filebuf : : openprot ) n = nome del file m = modalità di apertura ios : : app apertura del file in append ios : : ate puntatore alla fine del file ios : : binary modalità binaria ( default testo ) ios : : in apertura del file in input ios : : out apertura del file in output ios : : nocreate apre il file solo se esiste ios : : noreplace apre il file solo se non esiste ios : : trunc apre distruggendo il contenuto

146

ESEMPIO: ofstream a; ifstream b; fstream c; a.open ( “mio” ); // default ios : : out b.open ( “c:\ns\tuo” ); // default ios : : in c.open ( “nostro” , ios : : in | ios : : out ); a.close ( ); b.close ( ); c.close ( ); se l’apertura di “mio” non ha successo a = 0 if ( a == 0 ) cout << “ impossibile aprire il file “; oppure if ( !a ) cout << “ impossibile aprire il file “;

147

LETTURA/SCRITTURA DI UN FILE DI TESTO Un file può essere aperto secondo due modalità: - FILE TESTO in questo tipo di file possono esservi manipolazioni e/o

traduzioni di caratteri - FILE BINARIO non avviene nessuna traduzione.

Canale << dato // scrive dato su canale Canale >> dato // legge su canale dato

ESEMPIO: #include < iostream > #include < fstream > using namespace std; int main ( void ) { ofstream a ( "mio" ); // a canale di input a<<"radio"<< " " << 1000 << endl; // i campi devono esser a << "tele" << " " << 2000 << endl; // separati dal blank a.close ( ); // i record da CR+LF char s [5]; int p; ifstream b; // b canale di output b.open ( "mio" ); // associo canale b a "mio" if( !b ) { // oppure ! b.is_open() cerr << "errore durante l'apertura del file\n"; exit(-1); } b >> s >> p ; // leggo cout << s << '\t' << p << '\n'; b >> s >> p; // leggo cout << s <<'\t' << p << '\n'; return 0; }

148

LETTURA / SCRITTURA DI UN FILE BINARIO

istream &get ( char &car ) ostream &put ( char car )

get : legge un carattere dal canale associato. put : scrive un carattere sul canale associato. ESEMPIO: // questo programma copia un file #include <iostream> #include <fstream> using namespace std; int main ( int argc , char *argv[ ] ) {

char car; ifstream a ( argv [1] , ios : : binary ); ofstream b ( argv [2] , ios : : binary ); while ( !a.eof() ) {

a.get ( car ); b.put ( car );

} a.close ( ); b.close ( ); return 0;

}

149

READ ( ) E WRITE ( ) Queste istruzioni vengono usate quando è necessario leggere blocchi di dati binari

istream & read ( unsigned char * buf , int num ); ostream &write (const unsigned char * buf , int num );

READ: legge num byte dal canale associato e li inserisce nel buffer puntato da buf; WRITE : scrive num byte sul canale associato prelevandoli dal buffer puntato da buf. ESEMPIO: #include <iostream> #include<fstream> using namespace std; struct agenda {

char nome [ 10 ]; int tel;

}; int main ( void ) {

struct agenda ag = { “ nicola “ , 7756 }; ofstream a ( “ prova “ , ios : : out | ios : : binary ); a.write ( ( char * ) & ag , sizeof ( struct agenda ) ); a.close ( ); ifstream b ( “ prova “ , ios : : in | ios : : binary ); b.read ( ( char * ) & ag , sizeof ( struct agenda ) ); cout << ag.nome; cout << ag.tel; b.close ( ); return 0;

}

150

GETLINE Il prototipo della funzione membro è: istream &getline ( char *buf , int num, char delim = ‘\n’ ); Legge caratteri inserendoli nella stringa puntata da buf. La lettura si ferma: - dopo num caratteri - se trova il delimitatore delim ESEMPIO: #include <iostream> #include <fstream> using namespace std; int main( int argc , char * argv[ ] ) {

char s[ 80 ]; ifstream a ( argv[1] ); while ( a ) {

a.getline ( s, 80 ); cout << s << endl;

} a.close ( );

return 0; } NOTE: int eof ( ) è una funzione membro che restituisce un valore = 0 quando viene raggiunta la fine del file.

151

ACCESSO DIRETTO AI FILE Il sistema di I/O del C++ gestisce due puntatori associati a un file: PUNTATORE DI LETTURA: specifica la posizione nel file in cui avverrà la PROSSIMA OPERAZIONE DI INPUT PUNTATORE DI SCRITTURA: specifica la posizione nel file in cui avverrà la PROSSIMA OPERAZIONE DI OUTPUT

istream &seekg ( streamoff num , seek_dir origine ); La funzione seekg sposta il puntatore di lettura di num byte rispetto a origine. Origine può assumere i seguenti valori: ios : : beg inizio file ios : : cur posizione corrente ios : : end fine file

ostream &seekp ( streamoff num , seek_dir origine ); La funzione seekg sposta il puntatore di scrittura di num byte rispetto a origine.

152

ESEMPIO: // mostra il contenuto di un file a partire // da una determinata posizione #include <iostream> #include <fstream> using namespace std; int main( int argc , char * argv [ ] ) {

char c; ifstream a; a.open ( argv[1] , ios : : in | ios : : binary ); a.seekg ( atoi ( argv[2] ) , ios : : beg ); while ( !a.eof ( ) ) {

a.get ( c ); cout << c;

} a.close ( ); return 0;

}

153

GESTIONE DELLE ECCEZIONI Per gestione delle eccezioni si intende la gestione degli errori che si verificano al momento della esecuzione del programma, per evitare che questo si blocchi in modo indesiderato. SINTASSI: try { il blocco tree contiene la parte

………… di programma dove si intende throw eccezione verificare la presenza di errori …………

} catch ( tipo1 param ) { quando viene lanciata ………… un’eccezione, questa viene ………… raccolta dal corrispondente ………… blocco catch che } contiene le istruzioni che gestiscono l’errore catch ( tipon param ) { ………… ………… } L’istruzione throw è quella che lancia l’eccezione “intercettata” poi dal corrispondente blocco catch. Essa deve esser presente nel blocco try o in una funzione chiamata dal blocco try.

154

Il tipo del parametro della CATCH è importante in quanto l’eccezione intercettata sarà quella dello stesso tipo del parametro. ESEMPIO: #include < iostream > using namespace std; int main ( void ) { cout << “inizio“ << endl; try { cout << “ SIAMO NEL BLOCCO TRY\n”; trow 100 cout << “NON VIENE ESEGUITA\n” ; } // fine blocco try catch ( int i ) { cout << “RACCOLTA ECCEZIONE int” << i << endl; } catch ( double k ) { cout <<“RACCOLTA ECCEZIONE double”<<k<< endl; } return 0; } L’output sarà : INIZIO SIAMO NEL BLOCCO TRY RACCOLTA ECCEZIONE int 100

155

NOTE: - se si lancia un’eccezione per cui non vi è il corrispondente blocco

CATCH ( corrispondenza del tipo del parametro ) si ha una fine anomala del programma.

- normalmente le istruzioni presenti nella catch tentano di risolvere il

problema. Se l’errore può essere corretto l’ESECUZIONE CONTINUA CON L’ISTRUZIONE SEGUENTE LA CATCH, in caso contrario il programma termina con un’istruzione di ABORT ( ) o EXIT ( ).

- un blocco try può essere inserito in una funzione.:

void errore ( int test ) {

try {

if ( test ) throw test } catch ( int i ) {

cout << “RACCOLTA ECCEZIONE” << i << endl; }

} int main ( void ) {

errore ( 2 ); errore ( 1 ); return 0;

} L’output sarà: RACCOLTA ECCEZIONE 2 RACCOLTA ECCEZIONE 1

156

NOTE:

E’ possibile raccogliere le eccezioni con un’unica CATCH ( anche se le eccezioni sono di tipo differente ).

ESEMPIO:

void errore ( int test ) {

try { if ( test = = 0 ) throw test; // LANCIA UN INT if ( test = = 1 ) throw ‘a’; // LANCIA UN CHAR if ( test = = 2 ) throw 2.3; // LANCIA UN DOUBLE } catch ( … ) {

cout << “ RACCOLTA ECCEZIONE ” << endl; }

} int main ( void ) {

errore ( 0 ); errore ( 1 ); errore ( 2 ); return 0;

}

L’output sarà: RACCOLTA ECCEZIONE RACCOLTA ECCEZIONE RACCOLTA ECCEZIONE

157

E’ possibile impedire ad una funzione di lanciare una o più eccezioni, aggiungendo alla definizione della funzione la clausola :

thow ( elenco_tipi )

Elenco_tipi saranno i tipi di eccezioni che la funzione potrà lanciare.

ESEMPIO:

void errore ( int test ) throw ( int , char , double ) {

if ( test = = 0 ) throw test; if ( test = = 1 ) throw ‘a’; if ( test = = 2 ) throw 2.3;

}

Questa funzione può lanciare eccezioni di tipo int, char, double. Se la funzione tenterà di lanciare eccezioni di altro tipo avremo un errore. ESEMPIO: void errore ( int test ) throw ( ) {

………… …………

}

Questa funzione NON PUO’ LANCIARE ECCEZIONI.

158

La gestione delle eccezioni fornisce al programma un modo ordinato di gestire gli errori a RUN_TIME. Questo significa che il gestore dell’errore DEVE ESEGUIRE DELLE OPERAZIONI CHE PONGANO RIMEDIO ALL’ERRORE.

ESEMPIO:

#include <iostream> using namespace std; void dividi ( double a , double b ); int main ( void ) {

double i, j; cout << “IMMETTERE NUMERATORE ”; cin >> i; cout << “IMMETTERE DENOMINATORE”; cin >> j; dividi ( i, j ); return 0;

} void dividi ( double a , double b ) {

try {

if ( !b ) throw b cout << “RISULTATO:” << a / b << endl;

} catch ( double b ) {

cout << “NON SI PUO’ DIVIDERE PER 0” << endl; }

}