Scarica - Nur-Ab-Sal

Transcript

Scarica - Nur-Ab-Sal
Vocabolario C++
Sommario
000. Commenti
001. Un semplice programma
002. Tipi di dati
002.1 Qualificatori
002.2 Operatore sizeof()
003. Definzione di una varibile
004. Assegnazione
005. Input/Output
006. Costanti
007. Tipi enumerati (enum)
008. Casting (Arrotondamento int/double)
009. Operatori aritmetici
010. Funzioni matematiche ( #include <cmath> )
011. Operatori di confronto o logici
012. Funzioni relative alle stringhe
013. If-Else
014. Operatore ternario o di selezione
015. While - Do While - For
016. Funzioni
017. Procedure
018. Parametri passati per valore e per riferimento
019. Array
020. Array multidimendionali
021. Strutture (costrutto struct)
022. Classi
La sintassi
Le istanze
Incapsulamento dei dati
Definizione di una funzione membro
Definizione di un costruttore
Classi e funzioni esterne
Overloading degli operatori
Ereditarietà e Polimorfismo
023. Vettori (costrutto vector)
024. Puntatori
Memoria statica e memoria dinamica
Sintassi
Deriferimento e accesso
Array e puntatori
//============================================================//
000. Commenti
/* commento */
//commento
//============================================================//
001. Un semplice programma:
#include <iostream>
// inclusione del file header che contiene funzioni e operatori per la
gestione degli strem di input/output
using namespace std;
// i nomi utilizzati nel programma appartengono al namespace
standard
int main()
// funzione principale del programma (int perchè il programma
restituirà un intero alla fine dell'esecuzione)
{
// le istruzioni del corpo della funzione main sono racchiuse in due
parentesi graffe
cout << "Hello, World!\n";
// ogni istruzione termina con un punto e virgola; cout invia
alla periferica di output la stringa "Hello, World!\n" ; \n manda a capo
return 0;
// alla fine dell'esecuzione il programma restituisce un valore intero 0
}
//============================================================//
002. Tipi di dati
Int
Numero intero
2 byte (16 bit)
Short Numero intero "corto"
2 byte (16 bit)
Long Numero intero "lungo"
4 byte (32 bit)
Float Numero reale
4 byte (32 bit)
Double
Numero reale "lungo"
8 byte (64 bit)
String Sequenza di caratteri
//============================================================//
002.1 Qualificatori
signed ......... solo per char e int
unsigned ....... solo per char e int
short .......... solo agli int
long ........... solo a int e double
//============================================================//
002.1 Operatore sizeof()
sizeof( <tipo_dati> )
// restituisce lo spazio in memoria occupato da una varibile del tipo indicato
sizeof(int) // restituisce 2 cioè 2 byte ossia 16 bit ossia 2^16 numeri
//============================================================//
003. Definizione di una variabile
nome_tipo nome_variabile;
int numero;
double pi_greco;
string nome;
// definizione di una varibile di tipo int
// definzione della varibile pi greco di tipo double
// def. della stringa di caratteri nome
//============================================================//
004. Assegnazione
variabile = espressione;
nomero = 1204;
// assegnazione di un valore ad una costante int
nome = "NurAbSal"; // assegnazione di un'espressione ad una stringa. l'espressione è contenuta
all'interno di due doppi apici " "
double = 3.14;
// assegnazione di un numero a virgola mobile
una variabile puo' essere assegnata anche nel momento della definizione:
int numero = 12340;
string nome = "Francesco";
//============================================================//
005. Istruzioni di Input/Output
005.1 output
cout << espressione;
cout << espressione1 << espressione2 << espressione3;
cout << "Il tuo nome e' " << nome;
// le stringhe sono sempre contenute all'interno
dei doppi apici
cout << "Il numero scelto e' " << numero << "\n"; // \n serve per andare a capo
//============================================================//
005.2 input
cin >> variabile;
cin >> variabile1 >> varibile2 >> variabile3;
cin >> numero;
cin >> nome >> cognome;
// legge il valore di numero in input
// legge il valore di piu' varibili in input
//============================================================//
006. Costanti
const nome_tipo nome_costante = valore;
const int giorni_dell_anno = 365; // definizione di una costante. una costante mantiene il suo
valore per tutta l'esecuzione del programma
//============================================================//
007. Tipi enumerati (enum)
enum settimana { lunedi, martedi, mercoledi, giovedi, venerdi, sabato, domenica }
in questo modo 'settimana' diventa un 'tipo' come int e come string e si possono dichiarare variabili
di quesl tipo
settimana giorno_riposo = domenica;
l'espressione domenica
// definisco una varibile di tipo settimana e le assegno
//============================================================//
008. Casting (Arrotondamento int/double)
double virgola = 23.68;
int intero = virgola;
// intero vale 23 perde tutti i dati dopo la virgola
int approssimato = static_cast<int>(virgola + 0.5); // approssimato vale 24, in questo modo viene
approssimato il numero a virgola mobile
casting:
(< Nuovo_Tipo >)<valore>
< Nuovo_Tipo >(<valore>)
es. (float)a
es. float(a)
//============================================================//
009. Operatori aritmetici
Operazioni con gli int
Addizione
Sottrazione
Moltiplicazione
Divisione intera
Simbolo
+
*
/
Divisione con modulo
%
Esempio
4 + 27 = 31
76 - 23 = 53
4 * 7 = 28
10 / 3 = 3
(3 è il n di volte divisibili senza resto)
11 / 6 = 5
(5 è il resto della divisione)
x++; equivale a x = x + 1;
y--; equivale a y = y - 1;
Operazioni con i double
Simbolo
Esempio
Addizione
Sottrazione
Moltiplicazione
Divisione
+
*
/
2.5 + 14.3 = 16.8
43.8 - 12.7 = 31.1
7.5 * 3.0 = 22.5
5.0 / 2.0 = 2.5
//============================================================//
010. Funzioni matematiche ( #include <cmath> )
richiedono varibili di tipo double o float
sin(x)
di x
cos(x)
tan(x)
asin(x)
acos(x)
atan(x)
exp(x)
esponenziale con base e
log(x) logaritmo naturale
log10(x) log. in base dieci
fabs(x) valore assoluto
floor(x) intero più grande minore o uguale di x
ceil(x) intero più piccolo maggiore o uguale di x
sqrt(x)
pow(x, y)
radice quadrata
x^y
//============================================================//
011. Operatori di confronto e logici
Simbolo
Significato
Utilizzo
==
!=
<
>
<=
>=
uguale a
diverso da
minore
maggiore
minore o uguale
maggiore o uguale
a == b
a != b
Simbolo
Significato
Utilizzo
&&
||
AND logico
OR logico
a && b
a || b
a<b
a>b
a <= b
a >= b
//============================================================//
012. Funzioni relative alle stringhe
string name = "nurabsal";
int lunghezza_nome = name.length();
della stringa name
/ assegno alla variabile di tipo int la lunghezza
string pezzo;
pezzo = name.substr(0, 3);
/ assegno alla stringa pezzo la sottostringa ottenuta da
name a partire dal primo carattere (indice 0) di lunghezza 3 cioè 'nur'
getline(cin, name);
/legge la stringa name dall'input cin (questa istruzione permette di
inserire spazi nella lettura dell'input)
//============================================================//
013. If-Else
if (condizione)
istruzione
// se la condizione è verificata esegue l'istruzione
if (condizione)
// se la condizione è verificata esegue le istruzioni contenute nel blocco
compreso tra le parentesi graffe
{ istruzione1;
istruzione2;
}
if (condizione)
istruzione1
else
istruzione2
// se la condizione è verificata esegue l'istruzione1
// altrimenti esegue l'istruzione2
if (condizione1)
// else if concatenato che permette di verificare più condizioni
istruzione1
else if (condizione2)
istruzione2
else
istruzione3
//============================================================//
014. Operatore ternario o di selezione
// Operatore ternario ?
test ? espressione2 : espressione3
// Corrispondente blocco if-else
if (test)
{
espressione2
} else {
espressione3
}
if (alfa > beta)
max = alfa;
else
max = beta;
// che corrisponde a...
max = (alfa>beta) ? alfa : beta
//============================================================//
015. While - Do While - For
while (condizione)
istruzione/i // esegue l'istruzione finchè la condizione rimane vera
ESEMPIO:
int sentinella,
x,
temp = 1;
cin << x;
while ( sentinella <= x )
// algoritmo del fattoriale di x
{
fattoriale = fattoriale * sentinella;
sentinella++;
}
//============================================================//
do
istruzione/i
while (condizione)
condizione
// come il while solo che esegue almeno una volta l'istruzione
// esegue l'istruzione almeno una volta e poi la ripete finchè rimane vera la
//============================================================//
for (inizializzazione ; condizione ; incremento)
istruzione/i
ESEMPIO:
int sentinella,
a,
temp = 1;
cin << a;
for (sentinella = 1; sentinella <= a; sentinella++)
// algoritmo del fattoriale
{
temp = temp * sentinella;
}
che equivale alla rappresentazione con un while con la seguente struttura:
inizializzazione
while (condizione)
istruzione/i
incremento
//============================================================//
016. Funzioni
Dichiarazione:
tipo_restituito nome_funzione (parametro1, parametro2, ...., parametroN);
es. int modulo(x);
// una funzione va dichiarata all'inizio prima del main
//============================================================//
Definizione:
// una funzione va definita all'inizio del programma prima del main o alla fine
// se la si definisce all'inizio non servirà fare il passaggio precedente della dichiarazione poichè la
definizione contiene già la dichiarazione
tipo_restituito nome_funzione (parametro1, parametro2, ...., parametroN)
{
istruzioni
}
es. double modulo(double x);
{
if (x >= 0)
return x;
else return -x;
}
es.
int fattoriale( int a ) {
int sentinella,
temp = 1;
for (sentinella = 1; sentinella <= a; sentinella++)
{
temp = temp * sentinella;
}
return temp;
}
//============================================================//
Invocazione funzione
main() {
...
int variabile = fattoriale(4);
valore 4!
cout << variabile;
...
// 4 viene detto parametro attuale, a variabile viene assegnato il
// viene stampato a schermo: 24
}
//============================================================//
017. Procedure
Dichiarazione
void <nome_procedura>(<parametri>);
Definizione
void <nome_procedura>(<parametri>)
{
...
}
Una procedura è una funzione che non restituisce nessun valore. Puo' essere invocata come un
istruzione senza
dover utilizzare una variabile, proprio perchè non assume nessun valore. (void = nulla)
Esempio:
void paro_o_caffo(int n)
{
if ( (n%2) == 0 ) cout << n << " e' paro";
else cout << n << " e' caffo";
}
int main()
{
int numero = 5;
paro_o_caffo(numero);
// verrà stampato da cout a schermo: '5 e' caffo'
}
//============================================================//
018. Parametri passati per valore e per riferimento
void paro_o_caffo(int n)
{ ... }
int modulo(int x)
{
return( x >= 0 ? x : -x );
}
n e x qui sopra sono parametri formali, ossia hanno una funzione formale all'interno della
definizione della funzione o della procedura
main() {
...
int numero = 2, numero2 = 3;
y = modulo(numero);
paro_o_caffo(numero2);
...
}
numero e numero2 sono detti parametri attuali poichè sono i parametri con cui viene attualizzata la
funzione o procedura
una volta invocata la funzione il ruolo del parametro formale viene attualizzato dal parametro
attuale con il quale è stata invocata
nei casi precedenti si parla di parametri passati per valore, ossia la funzione prende il lettura i
parametri attuali ma non compie modifiche su di loro. una funzione restituirà un valore
presumibilmente a seconda del valore del parametro attuale ma quest'ultimo non subirà modifiche.
anche per le procedura vale questa cosa con i parametri passati per valore. affichè non sia inutile
una procedura con parametri per valore questa eseguirà delle istruzioni a seconda del valore per
parametro attuale senza modificarlo
esempio:
void abs(int n)
{
if (n < 0) n = -n;
}
questa procedura e' del tutto inutile poichè il parametro formale n è un poarametro passato per
valore che non viene modificato dalla procedura nella sua invocazione. per modificare un parametro
con il quale viene invocata una procedura o una funzione si debbono usare i parametri per
riferimento.
esempio
void abs(int& n)
{
if (n < 0) n = -n;
}
questa procedura ha un parametro n passato per riferimento. questo significa che ogni modifica che
subirà il parametro formale nella ddefinizione della procedura si ripercuoterà sulla variabile con la
quale è stata attualizzata la procedura
int numero=-2;
abs(numero);
//ora numero vale 2
i parametri attuali passati per riferimento possono essere solo variabili e non espressioni (numeri,
espressioni matematiche)
i parametri passati per valore (in lettura) consistono in una copia del parametro attuale, una volta
conclusa l'attivazione della funzione viene cancellata questa copia
i parametri passati per riferimento consistono nell'inidirizzo della cella di memoria contenente il
parametro attuale con il quale è stata attivata la funzione o procedura. quindi le modifiche vengono
a ripecuotersi direttamente ed immediatamente durante l'attivazione sul parametro attuale
//============================================================//
019. Array
Gli array sono dei tipi di dati 'vettori' che contengono una serie composta da un certo numero fissato
di variabili di tipo omogeneo, tutti int o tutti double ecc...
Un array di interi si definisce così:
int vettore[100]; // questo vettore contiene 100 elementi int
o meglio:
const int dim = 100;
int vettore[dim]; //la dimensione deve essere sempre una costante intera
L'ordinamento di un array di N elementi è organizzato in modo che il primo elemento dell'array
abbia indice 0 (e NON 1) e che l'ultimo elemento abbia quindi indice N-1.
per puntare all'elemento i-esimo dell'array:
array[i] = 2;
per scandire un vettore:
for(int i=0;i<dim;i++)
array[i]=0;
//imposta a zero i valori di un array
Gli array vengono passati alle funzioni o procedure sempre per riferimento per questioni di velocità
di calcolo. Non è quindi necessario indicare nel prototipo (dichiarazione) e nella definizione della
funzione tramite il simbolo '&' che la variabile array che viene passata come parametro alla
funzione è per rifermento. Inoltre un array non puo' mai essere restituito da una funzione. Questo
problema si aggira infatti tramite il passaggio per riferimento (obbligato) di un array ad una
funzione.
void imposta(int array[], int valore, int dim) { //senza indicare la dimensione del vettore nella
dichiarazione si estende
//l'utilizzo della funzione per ogni generico vettore
che verrà scandito
//fino all'elemento dim-1 (passando come param. in
lett. un 'int dim'
for(int i=0;i<dim;i++)
array[i]=valore;
}
Se si vuole esser certi che un vettore non venga modificato da una funzione si utilizza la parola
'const'
void vedi(const int array[], int dim) {
for(int i=0;i<dim;i++)
cout << array[i] << endl;
}
Chiamate corrette delle procedure di cui sopra:
vedi(array, dim);
imposta(array, 120, dim);
//============================================================//
020. Array multidimendionali
const int R = 20 ;
const int C = 30 ;
double matrice[R][C];
Possono essere definiti array multidimensionali, per rappresentare ad esempio una matrice.
Vengono utilizzati due indici di cui il primo indica sempre il numero di righe ed il secndo sempre il
numero di colonne. Nel passaggio ad una funzione di un array multidimensionale il secondo indice,
o indice delle colonne va sempre specificato e non puo' essere omesso come nell'array
unidimensionali.
void stampa(double matrice[R][C], int r, int c);
oppure
void stampa(double matrice[][C], int r, int c);
ma non: // void stampa(double matrice[][], int r, int c); SCORRETTO!!!
Chiamata corretta della funzione:
stampa(matrice, 10, 5);
Funzione che stampa solo i valori significativi di un array bidimensionale.
void stampa(double matrice[R][C], int r, int c) {
for (int i=0; i<r; i++) {
cout << endl;
for (int j=0; j<c; j++)
cout << matrice[i][j] << " ";
}
//============================================================//
021. Strutture (costrutto struct)
Strutture
Le strutture permettono un'aggregazione di variabili, molto simile a quella degli array, ma a
differenza di questi non ordinata e non omogenea (una struttura può contenere variabili di tipo
diverso). Per denotare una struttura si usa la parola chiave struct seguita dal nome identificativo
della struttura, che è opzionale. Nell'esempio sottostante si definisce una struttura "libro" e si crea
una variabile di essa chiamata "biblio":
// dichiarazione della struct
struct libro
{
char titolo[100];
char autore[50];
int anno_pubblicazione;
float prezzo;
};
//dichiarazione dell'istanza biblio
struct libro biblio;
//o anche
libro biblio;
La variabile "biblio" può essere dichiarata anche mettendo il nome stesso dopo la parentesi graffa:
// dichiarazione della struct e della variabile biblio
struct libro
{
char titolo[100];
char autore[50];
int anno_pubblicazione;
float prezzo;
} biblio;
mentre è possibile pre-inizializzare i valori, alla dichiarazione, mettendo i valori (giusti nel tipo)
compresi tra parentesi graffe:
struct libro biblio = {"Guida al C", "Fabrizio Ciacchi", 2003, 45.2};
// o anche
libro biblio = {"Guida al C", "Fabrizio Ciacchi", 2003, 45.2};
Per accedere alle variabili interne della struttura si usa l'operatore "."; una volta che si può accedere
ad una variabile interna questa può essere trattata e/o manipolata come qualsiasi altra variabile:
// assegna un valore al prezzo del libro
biblio.prezzo = 67.32;
// assegna ad una variabile int l'anno di pubblicazione del libro
int anno = biblio.anno_pubblicazione;
// stampa il titolo del libro
cout << biblio.titolo;
022. Classi
La programmazione ad oggetti si basa sulle classi. Le classi sono oggetti che comprendono campi
dati e funzioni membro proprie dell’oggetto.
La sintassi:
class nome_classe {
public:
funzioni membro o metodi
private:
dati
};
Le funzioni, che si chiamano anche metodi, si dividono in costruttori, funzioni di accesso, e
funzioni di mutazione.
I campi dati possono essere occupati da tipi di dati fondamentali, array, o altre classi anche.
Esempio:
class complesso {
public:
complesso();
complesso(double x);
complesso(double x, double y); //costruttori
double re() const ;
double im() const;
sum(double x, double y) ;
private:
double real;
double imag;
};
//funzioni di accesso
//funzioni di mutazione
I dati vengono preferibilimente inseriti tra i dati privati. Questi quindi sono accedibili solo dalle
funzioni membro proprie della classe.
//============================================================//
Le istanze
Le istanze di una classe si dichiarano in questo modo:
complesso x1;
complesso x2(4.5, 3);
Vengono utilizzati i due costruttori, nel primo caso quello di default che viene sempre usato quando
un oggetto viene dichiarato senza parametri, e nel secondo caso il costruttore con l’esatto numero di
paramentri specificati nella dichiarazione. Questo si chiama overloading dei costruttori.
Un oggetto puo’ essere reinizializzato utilizzando uno dei costruttori:
x1 = complesso(1, 2);
x2 = complesso();
Ma non si puo’ ripetere una dichiarazione come:
complesso x1;
complesso x1(2, 4); //ERRORE
//============================================================//
Incapsulamento dei dati
Non sono visibili dall’esterno i campi dati impostati come ‘private’ e’ quindi scorretto:
double parte_reale = x1.real; //ERRORE
ma va usata una funzione membro per accedere ai campi dato
double parte_reale = x1.re();
//============================================================//
Definizione di una funzione membro:
tipo_restituito nome_classe::nome_funzione( … parametri … ) [const] //se funz.di accesso
{
istruzioni
}
Esempio:
double complesso::im() const
{
return imag;
}
void complesso::sum(double x, double y)
{
imag = imag + y;
real = real + x ;
}
//============================================================//
Definizione di un costruttore.
Nome_classe::nome_classe( … parametri … )
{
istruzioni
}
Esempi
complesso::complesso() { } //costruttore di default, non vengono inizializzati i campi dato
oppure
complesso::complesso() { real=0; imag=0; }
ancora
complesso::complesso(double x, double y) { real=x ; imag=y ; }
Costruttore con elenco di inizializzazione dei campi
nome_classe::nome_classe(parametri)
: campo1 (espressione), .... , campoN (espressione)
{
istruzioni
}
Esempio
complesso::complesso(double x, double y)
: real(x), imag(y)
{
}
//============================================================//
Classi e funzioni esterne
Le classi possono essere restituite da una funzione. Il passaggio delle classi come parametri alle
funzioni di default è per valore. Il passaggio per riferimento va esplicitato con ‘&’. Per non
appesantire la memoria si puo’ utilizzare un passaggio per riferimento con ‘&’ ed inoltre ‘const’ che
assicura che i campi dato della classe non vengano modificati.
Esempi:
class complesso {
public:
…
complesso somma(const complesso& n) const;
private:
…
};
//funzione membro che restituisce una classe complesso.
complesso a, b, c;
a = b.somma(c);
//a=b ‘+’ c
Il primo (l’istanza b) si chiama parametro implicito, il secondo esplicito. Il parametro esplicito in
questo caso viene passato per riferimento ma utilizzando la parola riservata ‘const’ non viene
modificato dalla funzione. Il parametro implicito è sempre passato per riferimento. La funzione è di
accesso e non modifica i campi dato della funzione, quindi alla fine c’è const.
Definizione di complesso::somma
complesso complesso::somma(const complesso& n) const {
complesso result;
double x, y;
x = real + n.re() ;
y = imag + n.im();
result = complesso(x, y) ;
return result ;
}
//============================================================//
Overloading degli operatori.
Possiamo ridefinire il significato degli operatori di base per una classe.
Esempi:
class complesso {
public:
…
bool operator==(complesso a);
complesso operator+(complesso a);
private:
…
};
Esempi di utilizzo:
complesso a, b;
…
if (a==b) { … }
a = a + b;
Definizioni
bool complesso::operator==(complesso a)
{
return ( real==a.re() && imag==a.im() );
}
complesso complesso::operator+(complesso a)
{
complesso result;
double x, y;
x = real + a.re() ;
y = imag + a.im();
result = complesso(x, y) ;
return result ;
}
Ereditarietà e Polimorfismo
E’ possibile ampliare le calssi già esistenti grazie alla facoltà dell’ereditarietà delle classi. Vengono
cosi ad esistere contestualmente una classe base, e una classe derivata, che eredita i metodi e i
campi dati della classe base e puo’ eventualemente ed opportunamente estenderli, sostituirli, o
ereditarli semplicemente.
Tutte la funzioni membro e i membri dati della classe base vengono ereditati dalla classe derivata.
Sintassi:
class Nome_classe_derivata : public Nome_classe_base
{
public:
nuove f.ni membro
private:
nuovi membri dati
};
Esempio:
class Employee
{
public:
Employee();
Employee(string _name, double _salary);
void set_salary(double new_salary);
string get_name() const;
double get_salary() const;
private:
string name;
double salary;
};
class Manager : public Employee
{
public:
Manager(string name, double salary, string dept);
string get_department() const;
private:
string department;
};
Se i campi dati della classe base sono ‘protected’, la classe derivata puo’ accedere direttamente ad
essi tramite i propri metodi. Se invece sono dichiarati ‘private’ non sarà cio’ possibile e si dovrà
ricorrere alle funzioni membro della classe base. Tuttavia si preferisce non utilizzare i dati protected
ed accedere tramite la funzioni membro della classe base ai campi dati della classe base.
class nome_classe_base {
protected: //invece che private:
dati...
public:
metodi...
};
I campi dati della classe base non sono visibili ai metodi delle classi derivate se dichiarati, come è
buona norma, privati. Vanno utilizzati i metodi della classe base per modificarli.
class Manager : public Employee
{
public:
...
void aumenta(double x)
private :
...
};
void Manager::aumenta(double x) {
//SCORRETTO salary=salary+x
set_salary( get_salary() + x );
//viene intepretato param_implicito.set_salary( param_implicito.get_salary() + x );
}
Il che è equivalente a:
void Manager::aumenta(double x) {
Employee::set_salary(Employee::get_salary()+x);
}
In effetti quando viene chiamata la set_salary() senza esplicitare Employee::set_salary() viene
chiamata la funzione mebro ereditata dalla classe base. In questo caso dato che il metodo viene
semplicemente ereditato si puo' omettere il richiamo esplicito alla classe base. Non si puo' omettere
invece questo richiamo se viene ampliata la funzione membro base da quella derivata.
class Employee
{
public:
...
void print() const;
private:
...
};
class Manager : public Employee
{
public:
...
void print() const;
private :
...
};
void Employee::print() const
{
cout<<"L'impiegato si chiama "<<name
<<" e guadagna "<<salary;
}
Manager::print() const
{
Employee::print();
cout<<" lavora nel dipartimento "<<department;
}
Se nell'implementazione del metodo print() di Manager che amplia la print() di Employee si fosse
usato semplicemente usata l'istruzione print(); sarebbe stata richiamata ricorsivamente la stessa
funzione Manager::print() fino allo stack overflow.
Un procedimento analogo va effettuato nell’implementazione dei costruttori della classe derivata.
Sintassi del costruttore della classe derivata con inizializzazione della classe base.
Nome_classe_derivata::Nome_classe_derivata(espressioni)
: Nome_classe_base(espressioni)
{
istruzioni
}
Esempio:
Manager::Manager(string name, double salary, string dept)
: Employee(name, salary)
{
department=dept;
}
In questa maniera viene invocato prima di eseguire le istruzioni del costruttore della classe derivata,
il costruttore della classe base con i parametri ‘name’ e ‘salary’ in questo caso. Nel caso in cui
venga omesso questa chiamata del costruttore della classe base, verrà chiamato il costruttore di
default della classe base.
Il polimorfismo consiste nella possibilità di creare un assortimento, tramite i vettori (vector) ad
esempio, di oggetti di piu’ tipi. Una raccolta di questo tipo è definita polimorfica. Sarà possibile
cioe’ disporre di un vettore di puntatori (*) ad oggetti di una classe base, che potranno puntare
anche ad istanze di classi derivate di quella base.
Esempio:
vector<Employee*> staff(3);
staff[0]=new Employee("francesco",2000);
staff[1]=new Employee("simone",3000);
staff[2]=new Manager("nello",4000,"colleverde");
In questa maniera la raccolta è polimorfica poichè vengono allocati sia oggetti della classe base che
di quella derivata, il vettore che punta ai diversi oggetti è unico e contiene dei puntatori alla classe
base. E' importante che siano dei puntatori alla classe base perchè sarebbe un errore utilizzare dei
puntatori a classi derivate per cercare di accedere alle relative classi base.
Tuttavia essendo gli elementi del vettore dei puntatori alla classe base, quando verrà de-referenziato
un puntatore ad un oggetto per utilizzare una funzione membro dell'oggetto, se le funzioni membro
delle classi sono dichiarate in modo standard come fino ad ora visto, verrà chiamata la funzione
membro della classe base anche se il puntatore punta ad un’istanza di una classe derivata. Non sarà
così possibile accedere alle funzioni membro caratteristiche degli oggetti di classi derivate nè a
quelle ampliate nè a quelle sostituite o modificate. L'utilizzo che se ne puo' fare degli oggetti
derivati sarà il medesimo di quello degli oggetti di base, se la funzioni membro sono dichiarate nel
modo classico.
Esiste un modo per gestire una raccolta di puntatori ad oggetti polimorfica in modo dinamico
permettendo in runtime di scegliere la funzione membro relativa all'oggetto puntato e non
solamente di quello base. Questa capacità è detta polimorfismo e la tecnica si chiama Binding
Dinamico, la scelta cioe' in tempo reale, in modo dinamico delle funzioni membro dell'oggetto
interessato. Si parla di Binding Statico nei casi precedenti, quando si sa già quale sarà la funzione
da applicare all'oggetto che non viene scelta infatti in runtime. Per effettuare il binding dinamico di
una particolare funzione membro va utilizzata la parola chiave 'virtual' nella dichiarazione della
funzione membro. Il binding dinamico è una tecnica più costosa in termini di prestazioni e quindi di
tempistica durante l’esecuzione in quanto vanno effettuati maggiori controlli prima dell'esecuzione
della corretta funzione, rispetto al caso del binding statico. E' cosi consigliato utilizzarlo solo in
caso di reale necessità e non per ogni funzione membro.
La parola chiave virtaul e quindi il binding dinamico non si puo' associare ai costruttori, ed è
inefficente, nel constesto di una raccolta di puntatori di una classe base, applicarlo ad una funzione
membro propria solo della classe derivata. Il binding dinamico consente infatti solo la scelta
dinamica tra metodi che sono definiti nella classe base.
L'utilizzo della parola virtual è d'obbligo nella dichiarazione all'interno del corpo della classe base,
mentre è facoltativa nel corpo della classe derivata, anche se viene consigliato farlo anche nella
classe derivata per maggiore chiarezza. Nella definizione esterna del metodo non va invece piu'
specificato l'attributo virtual.
Definizione di una funzione virtuale
class Nome_classe
{
virtual Tipo_restituito Nome_funzione ( ...parametri... );
...
};
class Employee
{
public:
virtual void print() const;
...
};
Scopo: Definire una fuinzione collegata in binding dinamico che puo essere ridefinita nelle classi
derivate. Quando la funzione viene chiamata il tipo del parametro implicto stabilisce quale versione
della funzione viene eseguita.
staff[0]=new Employee("francesco",2000);
staff[1]=new Employee("simone",3000);
staff[2]=new Manager("nello",4000,"colleverde");
staff[0] -> print(); cout<<endl;
staff[1] -> print(); cout<<endl;
staff[2] -> print(); cout<<endl;
In questo modo verrà chiamato per gli oggetti puntati da staff[0] e staff[1] la print() della classe
Employee di base e per l’oggetto puntato da staff[2] la fuzione di Manager
023. Vettori (vector)
Il tipo di dato vector è una classe che basandosi sul costrutto, di livello più basso, array, semplifica
e potenzia l'uso di un tipo di dato aggregato, anche se con la limitazione di dover rinunciare a dei
vettori multidimensionali (nel qual caso si puo' pensare di utilizzare vettori paralleli). Va inclusa la
libreria vector se si vuole far uso dei vettori ( #include <vector> )
Sintassi:
vector<tipo_dato> nome_variabile;
vector<tipo_dato> nome_variabile(dimensione_iniziale);
Esempi:
vector<double> salari(10);
vector<complesso> compl;
La dichiarazione di una dimensione iniziale di un vettore è facoltativa.
Per accedere a un elemento del vettore si utilizza la sintassi comune anche agli array:
espressione_vettore[espressione_intera]
Es:
salari[0] //punta al primo elemento
Gli indici di un vettore di dimensione n, vanno da 0 a n-1, come analogamente per gli array.
vector<complesso> compl(5);
compl[1]
// punta al secondo elemento
compl[0]
// al primo
compl[5]
// espressione scorretta
compl[4]
// punta all'ultimo elem.
Essendo una vera e propria classe, il costrutto vector è caratterizzato da alcune funzioni membro:
.size()
.push_back(item)
.pop_back()
restituisce la dimensione del vettore
inserisce un elemento ‘item’ in fondo al vettore
elimina l'ultimo elemento del vettore
Gli oggetti vector possono essere restituiti dalle funzioni. Il passaggio di default ad una funzione è
per lettura, mentre quello per riferimento va specificato esplicitamente con l'operatore &.
La classe vector, in quanto tale, ha un costruttore di default, che restituisce un vettore vuoto di
elementi del tipo specifico
salari = vector<double>(); // sovrascrivo all'oggetto vettoriale quello che viene restituito
dal costruttore di default della classe vector, che è in particolare un vettore vuoto.
024. Puntatori
Memoria statica e memoria dinamica
Tutti gli oggetti e le variabili discusse finora venivano memorizzate nella memoria cosidetta
STACK. La memoria Stack è una memoria gestita in modo statico. Contiene le variabili e gli
oggetti locali al blocco funzionale a cui ci si riferisce e ogni variabile o oggetto dichiarato viene
allocato all'inizio dell'attivazione del blocco funzionale, e viene deallocato automaticamente alla
fine dell'esecuzione del blocco. Per questo la memoria stack si dice anche automatica e le variabili
che ci risiedono vengono dette automatiche. Esiste una memoria detta HEAP che è gestita invece in
maniera dinamica, e cioè su richiesta del programmatore. Il programmatore può scegliere di
allocare e deallocare oggetti e variabili nella memoria heap durante il programma.
Per allocare un oggetto o una var. nell'heap:
new nome_tipo;
new nome_tipo(espressione);
Es.
new complesso(1,3);
new int;
Il comando new alloca nell'heap una cella di memoria per una variabile, o un oggetto e restituisce
un puntatore a quella variabile, o a quell'oggetto. Si chiarirà poi come utilizzare il comando e cosa è
un puntatore.
Se le variabili automatiche avevano un ‘nome’ con le quali venivano memorizzate, le variabili
allocate nell’heap sono ‘mute’. Non hanno cioè un nome proprio e possono essere raggiunte
solamente con un puntatore.
//============================================================//
Sintassi
I puntatori sono tipi di dato che permettono di memorizzare l'indirizzo di memoria di un oggetto, o
di una variabile. Ogni oggetto o variabile ha un indirizzo che può essere contenuto in una variabile
puntatore. Per ogni tipo di variabile e per ogni tipo di variabile o oggetto esiste un particolare
puntatore.
Sintassi:
nome_tipo* nome_variabile;
nome_tipo* nome_variabile = espressione;
Esempio:
complesso* z1;
int* i;
//puntatore ad un oggetto complesso
//puntatore ad un intero
complesso* e int* sono tipi di dato, in particolarte sono un puntatore ad un oggetto complesso e un
puntatore ad una variabile intera. z1 e i sono variabili e servono a contenere l'indirizzo di memoria
di un oggetto o variabile.
Ad un puntatore si può assegnare sia l'indirizzo di memoria di un oggetto o variabile dello stack che
dell'heap. Si puo' anche associare un valore nullo. Questo si effettua con la parola chiave NULL e
non semplicemente senza specificare nessuna espressione. In quel caso infatti il contenuto
momentaneo della variabile puntatore viene convertito in esadecimale (il modo in cui sono scritti gli
indirizzi) e il puntatore avrà sì un contenuto, ma punterà ad una cella di memoria ‘a caso’.
L'espressione che segue la dichiarazione di un puntatore puo' cosi essere completata:
int n;
int* p = &n; //Ora p punta a n. Dentro a p c'è l'indirizzo di memoria di n. n si trova nello
stack così come p.
o ancora
complesso* z1 = new complesso;
complesso* z2 = new complesso(3,6);
//viene chiamato il costruttore di default
//costruttore con parametri
Il comando new infatti restituisce un puntatore ad un complesso che viene allocato nella memoria
heap. L'unico modo per accedere a quell'oggetto sarà attraverso il puntatore.
complesso* z3 = NULL;
z3 = new complesso(8);
//puntatore che per ora nn punta a nulla
//============================================================//
Deriferimento e accesso
Per accedere al contenuto dell'oggetto o della variabile puntata da un puntatore si utilizza l'operatore
di de-riferimento '*'.
int n;
int* p = &n;
*p = 12;
//assegno 12 al contenuto della variabile puntata da p
//è come scrivere n=12;
L'operatore di deriferimento ha precedenza rispetto all'operatore punto, quindi per utilizzare una
funziome membro di un oggetto attraverso un puntatore va utilizzata questa sintassi:
(*nome_puntatore).funzione_mebro(parametri);
es:
complesso* z2 = new complesso(3,6);
double y = (*z2).reale();
oppure indifferentemente esiste l'operatore freccia che semplifica quest'operazione:
nome_puntatore -> funzione_membro(parametri);
es:
complesso* z2 = new complesso(3,6);
double y = z2 -> reale();
E' possibile deallocare e quindi liberare la memoria nell'heap ad un certo punto del programma. E'
buona norma farlo sempre alla chiusura del programma, altrimenti quell’area di memoria rimarrà
occupata (fino allo spegnimento del computer) e gli altri programmi non potranno piu accerderci.
Quest'operazione si puo' effettuare se si è utilizzato il comando new e quindi il puntatore in
questione punta ad un oggetto o ad una variabile nell'heap.
Sintassi:
delete nome_puntatore;
int* p = new int(9);
*p = *p + 1;
cout << *p;
delete p;
valeva 10
p = NULL;
//p punta ad una variabile intera collocata nell'heap inizializzata a 9
//il contenuto dell'intero puntato da p viene incrementato
//stampa 10
//viene deallocata la memoria nell'heap che conteneva l'intero che
complesso* c2 = new complesso(2,4);
cout << c2 -> reale();
delete c2;
c2 = NULL;
Esiste la possibilità all'interno di una funzione membro di utilizzare il puntatore 'this' che è
associato al parametro implicito.
double complesso::re ()
{
return this -> real;
}
//============================================================//
Array e puntatori
Quando viene dichiarato un array il nome dell’array è un puntatore al primo elemento della
variabile array.
int a[10];
int* p = a;
*a = 10
oppure
a[0] = 10;
*(p+3)
oppure
*(a+3)
oppure
a[3];
I puntatori contengono un indirizzo di memoria espresso in valore esadecimale. Gli operatori +,-,+
+,-- sono in grado di manipolare correttamente gli operatori per eseguire somme ecc...
Da un elemento all’altro dell’array le celle di memoria di distanza possono essere anche piu di una
ma il compilatore pensa da solo ad aggiornare nel modo corretto il puntatore dopo un’operazione
algebrica in modo che punti alla cella di memoria dell’elemento desiderato.
Il nome di un array nel blocco in cui viene dichiarato non puo’ essere aggiornato con il contenuto di
qualsiasi altro puntatore. Quando viene pero’ passato un array ad una funzione, questo passaggio
viene fatto per riferimento e viene passato al blocco della funzione un puntatore che contiene
l’indirizzo di memoria del primo elemento dell’array. Questo puntatore puo’ essere modificato
invece all’interno del blocco della funzione.
void funzione(int p[]) {
int* q;
const int* r;
int s[10];
....
p=q ; //corretto
p=r;
p=s; //corretto
//NON CORRETTO
q=p;
q=r;
q=s;
//corretto
//NON CORRETTO
r=q;
r=s;
r=p;
//corretto
//corretto
//corretto
s=q;
s=r;
s=p;
//corretto
//NON CORRETTO
//NON CORRETTO
//NON CORRETTO
I puntatori possono essere dichiarati const. Sintassi: const tipo_dato* nome_puntatore. I puntatori
const possono essere sovrascritti con il constenuto di un altro puntatore const, con quello di un
puntatore normale, e con quello di un identificatroe di array. Al contrario un puntatore normale non
puo’ essere aggiornato con il valore di un puntatore const ma solo con quello di un puntatore
normale, o con quello di un array.
Se si alloca dinamicamente un array di oggetti o di variabili:
const int n =10;
complesso* serie = new complesso[n];
serie[i]
delete[] serie;
<= punta all’i-esimo complesso
<= dealloca tutto l’array, e non solo il primo elelemento (delete senza [])