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
Scarica

Lezione 1