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
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
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
Stroustrup, "C++ Linguaggio, libreria standard,
principi di programmazione", Addison-Wesley
consigliato
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
Un oggetto rappresenta un’entità del
mondo reale o astratta
Lo stato di un’istanza è indipendente dallo stato
delle altre istanze
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
Richiami di programmazione
procedurale
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,
Dell’elemento a destra (Rvalue) è
importante solo il contenuto
Variabili,
variabili
numeri, output di funzioni
Un Lvalue può fungere da Rvalue ma
non viceversa
Operatore di uguaglianza: ==
Operatori
Aritmetici
+ addizione
- sottrazione
* moltiplicazione
/ divisione
% resto (modulo)
Logici
&& AND
|| OR
! NOT
Relazionali
> maggiore
< minore
>= maggiore o
uguale
<= minore o
uguale
== uguale
!= diverso
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
Introduzione alla libreria standard
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
Algoritmi della libreria standard
for_each()
Invoca una funzione per ogni
elemento
find_if()
Trova il primo elemento che
soddisfa la condizione
count_if()
Conta gli elementi che
soddisfano la condizione
replace_if()
Sostituisce un elemento che
soddisfa una condizione
unique_copy() Copia elementi non duplicati
merge()
Combina sequenze ordinate