490 likes | 654 Views
Elementi di programmazione ad oggetti a. a. 2009/2010. Corso di Laurea Magistrale in Ingegneria Elettronica Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica, Gestionale e dell’Automazione. Programma del corso.
E N D
Elementi di programmazione ad oggettia. a. 2009/2010 Corso di Laurea Magistrale in Ingegneria Elettronica Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica, Gestionale e dell’Automazione
Programma del corso • Introduzione al linguaggio C++ e alla libreria standard; richiami di programmazione procedurale. • Regole di visibilità e ciclo di vita. • Funzioni e sovraccaricamento. • Introduzione alla progettazione e programmazione ad oggetti: classi ed oggetti, notazione UML. • Sovraccaricamento degli operatori. • Ereditarietà. Funzioni virtuali e polimorfismo. • Template. • Gestione delle eccezioni. • Input/output e stream. • La libreria standard: i contenitori; cenni su oggetti funzione, algoritmi, iteratori, allocatori, stringhe, calcolo numerico. • Unit testing e test-driven programming.
Testi di riferimento • Lippman, Lajoie, "C++ Corso di Programmazione", Addison-Welsey • consigliato • Stroustrup, "C++ Linguaggio, libreria standard, principi di programmazione", Addison-Wesley • La bibbia del C++, consigliato a chi sa già programmare (in altri linguaggi) e desidera la fonte più autorevole sul C++ • … o qualsiasi altro libro di C++ • Aguilar, “Fondamenti di programmazione in C++”, McGraw-Hill • Schildt, "Guida Completa al C++", Mc Graw-Hill • Deitel, Deitel, "C++ Fondamenti di Programmazione", Apogeo
Alcune note • Le slides del corso saranno messe al più presto a disposizione sul portale del DIIGA • Le slides non sono dispense, occorre seguire le lezioni e/o leggere uno dei libri di riferimento, ed esercitarsi all’elaboratore • Quale ambiente di sviluppo per C++? • Windows: DevC++, Visual C++ Express • Mac: XCode • Linux: KDevelop
Lezione 1 Introduzione al linguaggio C++ ed alla libreria standard Richiami di programmazione procedurale
Introduzione al C++ • Bjarne Stroustrup ha inventato il C++ • Eredita dal C le caratteristiche di basso livello • Altre fonti di ispirazione: Simula67, Algol68, Ada, Clu • Il primo “C con le classi” è del 1980, i primi usi fuori dagli ambienti di ricerca sono del 1983 • Standard ISO nel 1998
Cos’è il C++? • Il C++ è un linguaggio di programmazione general-purpose orientato alla realizzazione di sistemi • È un C migliorato • Supporta l’astrazione dei dati • Supporta la programmazione orientata agli oggetti • Supporta la programmazione generica
Programmazione procedurale • La programmazione procedurale si basa sull’individuazione delle procedure che occorrono e utilizzo dei migliori algoritmi possibili • Decomposizione successiva del sistema da implementare in funzionalità più semplici • Ci si ferma quando le funzionalità sono sufficientemente semplici da essere implementabili come funzioni
Esempio di programmazione procedurale #include <iostream> using namespace std; void scambia(int& n1, int& n2) { int temp = n1; n1 = n2; n2 = temp; } int main() { int a, b; cout << "a="; cin >> a; cout << "b="; cin >> b; scambia(a, b); cout << "a=" << a << ", b=" << b << endl; system("pause"); return 0; }
Alcune note sul programma #include <iostream> • Direttiva per il preprocessore, indica che il programma ha la necessità di usare le funzioni della libreria predefinita per la gestione dell’I/O using namespace std; • Gli identificatori possono avere un prefisso (“spazio dei nomi”); tramite questa direttiva è possibile usare le funzioni di libreria omettendo il prefisso std cout << "a="; • Output di una stringa sullo standard output cin >> a; • Input di una stringa dallo standard input
Programmazione modulare • Un insieme di procedure correlate e di dati da esse manipolati costituisce un modulo • La programmazione modulare si basa sulla suddivisione del programma in moduli, in modo che i dati siano nascosti all’interno dei moduli
namespace • Dati, funzioni ed altre entità correlate possono essere correlati in spazi di nomi (namespace) separati // interfaccia namespace stack { void push(int); int pop(); } // implementazione namespace stack { const int MaxSize = 100; int data[MaxSize]; int top = 0; void push(int i) { // controlla che la pila non sia piena ed inserisce i in cima } int pop() { // controlla che la pila non sia vuota e restituisce l’elemento in cima } }
Compilazione separata • stack.h namespace stack { void push(int); int pop(); } • stack.cpp #include “stack.h” namespace stack { const int MaxSize = 100; int data[MaxSize]; int top = 0; } void stack::push(int i) { // controlla che la pila non sia piena ed inserisce i in cima } int stack::pop() { // controlla che la pila non sia vuota e restituisce l’elemento in cima }
Progettazione orientata agli oggetti • Si individuano le classi di oggetti che caratterizzano il dominio applicativo • Entità reali o astratte • Si individuano le modalità secondo cui gli oggetti devono interagire per realizzare le funzionalità dell’applicazione • Ogni classe è descritta da un’interfaccia che specifica il comportamento degli oggetti della classe
Classi e oggetti • Un oggetto è una istanza di una classe di oggetti che condividono lo stesso comportamento • Lo stato di un’istanza è indipendente dallo stato delle altre istanze • Un oggetto rappresenta un’entità del mondo reale o astratta • Un oggetto è caratterizzato da un nome, da dati (variabili locale che ne descrivono lo stato) e metodi (funzioni che ne descrivono i comportamento) • Gli oggetti dialogano tra loro scambiandosi messaggi
Programmazione generica • La programmazione generica consente di parametrizzare gli algoritmi che occorrono in modo che funzionino per un’adeguata varietà di tipi e strutture dati • Contenitori • Algoritmi generici
Tipi di dato • Tipi di dato primitivi: • int • char • float, double • bool • enum • Tipi composti a partire dai tipi primitivi: • struct • Puntatori • Vettori
Variabili • Per memorizzare ed utilizzare un dato è necessario dichiararne il tipo ed il nome • Le locazioni di memoria dell’elaboratore contengono un dato • La locazione di memoria è indentificata da un indirizzo • Il contenuto della locazione di memoria è il valore int a = 2; • dichiara il tipo ed inizializza il valore char b; • Dichiara il tipo ma non inizializza il valore
Operatore di assegnamento • L’operatore di assegnamento: = • All’elemento a sinistra dell’operatore (Lvalue) deve poter essere cambiato il valore • Solitamente, variabili • Dell’elemento a destra (Rvalue) è importante solo il contenuto • Variabili, numeri, output di funzioni • Un Lvalue può fungere da Rvalue ma non viceversa • Operatore di uguaglianza: ==
Aritmetici + addizione - sottrazione * moltiplicazione / divisione % resto (modulo) Logici && AND || OR ! NOT Relazionali > maggiore < minore >= maggiore o uguale <= minore o uguale == uguale != diverso Operatori
Identificatori • Gli identificatori sono i nomi usati per rappresentare variabili, costanti, tipi, funzioni • Un identificatore viene dichiarato specificandolo della dichiarazione • Sequenza di caratteri (lettere, numeri, _), il primo deve essere una lettera o _
Parole chiave • Non possono essere usate come identificatori! asm, auto, bool, break, case, catch, char, class, const, const_cast, continue, default, delete, do, double, dynamic_cast, else, enum, explicit, export, extern, false, float, for, friend, goto, if, inline, int, long, mutable, namespace, new, operator, private, protected, public, register, reinterpret_cast, return, short, signed, sizeof, static, static_cast, struct, switch, template, thwows, true, try, typedef, typeid, typename, union, unsigned, using, virtual, void, volative, wchar_t, while
Strutture di controllo: if if (espressione) { // codice che viene eseguito solo se l’espressione è true } else { // codice che viene eseguito solo se l’epsressione è false }
Strutture di controllo: while e do while (espressione) { // istruzioni che vengono eseguite fintanto che l’espressione è true } do { // istruzioni che vengono eseguite almeno una volta e fintanto che l’espressione è true } while (espressione)
Strutture di controllo: for for (espr1; espr2; espr3) { // istruzioni eseguite fintanto che espr2 è true } • Espr1 viene valutata una volta sola, prima di iniziare • Espr2 si valuta prima delle istruzioni • Espr3 si valuta dopo le istruzioni
Puntatore • Il puntatore è un tipo di dato derivato, che contiene l’indirizzo di una locazione di memoria capace di contenere valori di un certo tipo int *a; *a = 3; • * è l’operatore di deferenziazione • int *a si legge “il contenuto della locazione di memoria è un intero” (dunque, a è un puntatore)
Riferimenti • Un riferimento fornisce un nome alternativo ad un elemento int a=5; int &b = a; • Un riferimento deve essere inizializzato contestualmente alla sua dichiarazione • L’operatore & fornisce l’indirizzo di una variabile: int *c; c = &a;
Array int a[5] = { 5, 10, 15, 20, 25 } int *p; • Il nome di un array è un puntatore al primo elemento: p = a; • oppure p = &a[0]
Puntatori const • Il qualificatore const indica che un elemento non può essere cambiato const int a = 5; • È obbligatorio inizializzare una costante nel momento in cui viene definita • Un puntatore const punta sempre alla stessa locazione di memoria int a = 5; const int b =6; const int* c = &a; const int* d = &b; int* d = &b;
Allocazione della memoria • L’operatore new alloca a run-time della memoria • La memoria allocata a run-time risiede in un’area apposita chiamata heap int *a = new int; • a contiene un indirizzo corrispondante ad una locazione dell’heap • a viene solo allocata, non inizializzata • int *a = new int(2); // a viene anche inizializzata int *b = new int[2]; // allocazione di un vettore b[0] = 3; b[1] = 4;
Rilascio della memoria • L’operatore delete libera della memoria dinamica allocata con new int *a = new int; … delete a; • la memoria all’indirizzo a può essere riallocata • la memoria non viene cancellata • a non cambia valore • Dopo il delete, a è un puntatore dangling (punta ad una locazione di memoria non valida) • Errori comuni • Memory leak: non rilasciare con delete memoria allocata con new • Applicare due volte delete alla stessa locazione di memoria • Usare l’oggetto dopo il delete
Allocazione dinamica di array int *p1 = new int(24); // alloca un solo intero, inizializzandolo a 24 int * p2 = new int[24]; // alloca un vettore di 24 interi, non inizializzati int (*p3)[1024] = new int[4][1024]; //alloca una matrice di 4 x 1024 interi • Non si possono inizializzare gli elementi di un array alla loro allocazione, occorre un ciclo for int a = 1024; int *p = new int[a]; for (int i = 0; i < a; i++) p[i] = 1; • Un array si rilascia con delete[] delete[] p;
Allocazione dinamica const • Una variabile const una volta inizializzata non può essere modificata, anche se creata sull’heap const int *a = new const int(23); • Una variabile allocata dinamicamente const • Deve essere sempre inizializzata • L’indirizzo di memoria restituito deve essere assegnato a un puntatore const
Funzioni • Operazione definita dall’utente • Gli operandi sono i parametri • Il risultato è il valore di ritorno • Il tipo del valore di ritorno è il tipo di ritorno della funzione • void se non restituisce nulla • Le azione svolte sono contenute nel blocco della funzione • Tipo di ritorno, nome della funzione e lista dei parametri costituiscono la definizione della funzione
Parametri e variabili locali • Le variabili definite nel corpo di una funzione sono note solo all’interno della funzione • I parametri sono variabili locali istanziate alla chiamate della funzione
Prototipo • Il prototipo di una funzione è costituito dalla dichiarazione di tipo di ritorno, nome e parametri (senza il corpo) int middle(int, int, int); • E’ necessario definire il prototipo solo quando la definizione avviene dopo la chiamata della funzione
Passaggio di parametri • Il C++ è fortemente tipizzato: il tipo degli argomenti è controllato dal compilatore • La maniera predefinita di passare gli argomenti è per valore • vengono copiati nello spazio di memoria dei parametri • è un problema passare argomenti molto grandi • la funzione manipola solo delle copie locali • non possono essere modificati i valori degli argomenti
Uso delle funzioni della libreria standard • Per utilizzare una funzione della libreria standard, bisogna includere l’intestazione in cui sono definite: #include <iostream> using namespace std; […] cout << “hello!”;
Flussi di input/output #include <iostream> Using namespace std; […] cout << “a=”; int a; cin >> a; cout << a; cout << endl; cout << “Ripeto: il valore di a è” << a << “!”;
Stringhe string s1 = “benvenuto”; string s2 = s1 + “!\n”; bool affermativo(const string &risposta) { return risposta == “sì”; } string s3 = s1.substr(5, 4); // nuto string s4 = s1.replace(0, 5, “mal”); // malvenuto La stringa sostituita può non avere la stessa lunghezza del sostituto!
Input di stringhe string s; cin >> s; // inserendo “Mauro”.. cout << “Ciao “ << s; // si ottiene “ciao Mauro” Per leggere una riga intera: getline(cin, s);
Contenitori: vector • Un array ha dimensione fissa int numeri1[100]; • Un vettore ha dimensione variabile vector<int> numeri2(100); cout << numeri2.size(); \\ 100 cout << numeri2[40]; \\ si accede ad un elemento come ad un array numeri2.resize(200); cout << numeri2.size(); \\ 200 vector numeri3[300]; \\ 300 vettori vuoti!!
Contenitori: list • Una lista è adatta quando inserimenti e cancellazioni sono frequenti list<double> valori; • Di solito non si accede alla lista con un indice, ma si scorrono i suoi elementi for (list<int>::const_iterator i = valori.begin(); i != valori.end(); i++) cout << *i; for (list<int>::iterator i = valori.begin(); i != valori.end(); i++) *i = 1.0; • Un iteratore non è un semplice puntatore, però • Con ++ si punta all’elemento successivo • Con * si accede al contenuto • Aggiungere elementi ad una lista: valori.push_front(2.0); // inserimento in testa valori.push_back(3.0); // inserimento in coda valori.insert(i, 4.0); // inserimento prima dell’elemento a cui i si riferisce
Altri contenitori • map: array associativo (dizionario) map<string, int> rubrica […] cout << rubrica[“Mauro”]; • queue: coda • stack: pila • deque: coda bidirezionale • priority_queue: coda ordinata • set: insieme • multiset: insieme con valori ripetuti • multimap: array associativo con valori ripetuti
Algoritmi • La libreria standard fornisce gli algoritmi più comuni che operano su elementi dei contenitori standard • Gli algoritmi operano su sequenze, determinate da coppie di iteratori vector<int> v; list<int> l; […] sort(v.begin(), v.end()); // ordinamento copy(v.begin(), v.end(), l.begin()); // copia copy(v.begin(), v.end(), back_inserter(l)); // copia in coda, estendendo la lista quanto serve int::const_iterator i = find(l.begin(), l.end(), 2); // restituisce un iteratore che punta all’elemento trovato int c = count(v.begin(), v.end(), 3); // conta i 3 nel vettore