finalize).
Se una classe contiene un oggetto con puntatore(pointer data member)
allora deve obbligatoriamente includere:
private,public,protected. Invece il livello
di accesso di default di Java (accesso da parte delle classi dello stesso pacchetto) si realizza in C++ con l'attributo friend
. Cioe' ogni classe deve dichiarare esplicitamente tutte le funzioni e le
classi che possono accedere ai propri dati protetti come friend.
file Counter.h
class Counter {
public:
Counter(); // Costruttore
int getValue();
void increment(int n);
private:
int value;
}; // fine dichiarazione:nota il ;
file Counter.cc
#include "Counter.h"
Counter::Counter() {
value = 0;
}
void Counter::increment(int n) {
value += n;
}
int Counter::getValue() {
return value;
}
file Calcola.cc
#include "Counter.h"
#include <iostream>
main() {
Counter c;
cout << c.getValue() << endl;
c.increment(5);
cout << c.getValue() << endl;
}
L'istruzione per la compilazione diventa:
g++ -o Calcola Counter.cc Calcola.cc
NomeClasse::nomeMetodo(listaparametri){definizione del metodo}
Ma,attenzione! Un metodo inline puo' essere definito
direttamente nella dichiarazione della classe.
file Counter.h
class Counter {
public:
Counter():value(0){}
Counter(int n):value(n){}
int getValue() {
return value; }
void increment(int n){value += n;}
private:
int value;
};
file Calcola.cc
#include "Counter.h"
#include <iostream>
main() {
Counter c1(10);
Counter *c2 = new Counter(20);
cout << c1.getValue() << endl;
c1.increment(5);
cout << c1.getValue() << endl;
cout << c2->getValue() << endl;
c2->increment(5);
cout << c2->getValue() << endl;
delete c2;
}
inline
inline e' sempre solo un suggerimento al compilatore
che poi decide per conto suo.
new
delete quando non serve piu'.
file Complex.cc
#include <iostream>
class Complex {
public:
Complex ( double a, double b ): real(a),imag(b) { }
Complex () :real(0),imag(0){ }
Complex (const Complex& z):real(z.real),imag(z.imag) { }
Complex add ( Complex y, Complex z ) {
return Complex (y.real+z.real, y.imag+z.imag);
}
Complex subtract ( Complex y, Complex z ) {
return Complex (y.real - z.real, y.imag - z.imag);
}
Complex multiply ( Complex y, Complex z ) {
return Complex (
y.real * z.real - y.imag * z.imag,
y.imag * z.real + y.real * z.imag );
}
Complex divide ( Complex y, Complex z ) {
return Complex (
(y.real * z.real + y.imag * z.imag) /
(z.real * z.real + z.imag * z.imag),
(y.imag * z.real - y.real * z.imag) /
(z.real * z.real + z.imag * z.imag) );
}
bool compare ( Complex y, Complex z ) {
return (y.real == z.real && y.imag == z.imag);
}
void print() {cout << "(" << real << "," << imag << ")" << endl;}
double imagpart () { return imag; }
double realpart () { return real; }
private:
double real, imag;
};
int main()
{
Complex a(1,4), b(3,8), c;
a.print();b.print();
c = a.add(a,b);
c.print();
Complex d(c);
d = d.multiply(c,d);
d.print();
}
Complex d(c) ma avremmo potuto
scrivere anche Complex d = c
const Nomeclasse &. In C++ e' buona norma di programmazione usare dovunque e' possibile questo tipo di passaggio di parametri.
const riferito a un argomento, indica che non vogliamo cambiare il valore dell'argomento
file Complex.cc
#include <iostream>
class Complex {
public:
Complex ( double a, double b ): real(a),imag(b) { }
Complex () :real(0),imag(0){ }
Complex (const Complex& z):real(z.real),imag(z.imag) { }
~Complex () {cout << "L\'oggetto viene distrutto" << endl;}
Complex& operator=(const Complex& z){real=z.real;imag=z.imag;return *this;}
Complex add ( Complex y, Complex z ) {
return Complex (y.real+z.real, y.imag+z.imag);
}
Complex subtract ( Complex y, Complex z ) {
return Complex (y.real - z.real, y.imag - z.imag);
}
Complex multiply ( Complex y, Complex z ) {
return Complex (
y.real * z.real - y.imag * z.imag,
y.imag * z.real + y.real * z.imag );
}
Complex divide ( Complex y, Complex z ) {
return Complex (
(y.real * z.real + y.imag * z.imag) /
(z.real * z.real + z.imag * z.imag),
(y.imag * z.real - y.real * z.imag) /
(z.real * z.real + z.imag * z.imag) );
}
bool compare ( Complex y, Complex z ) {
return (y.real == z.real && y.imag == z.imag);
}
void print() {cout << "(" << real << "," << imag << ")" << endl;}
double imagpart () { return imag; }
double realpart () { return real; }
private:
double real, imag;
};
int main()
{
Complex a(1,4), b(3,8), c;
a.print();b.print();
c = a.add(a,b);
c.print();
Complex d(c);
d = d.multiply(c,d);
d.print();
Complex e = d;
cout <<"il risultato del confronto tra e,d ="<< e.compare(e,d) << endl;
}
~Nomeclasse. Se non viene definito
e' fornito dal sistema in maniera automatica.
Complex e = d
operator seguito dal simbolo dell'operatore.
Complex &
cioe' riferimento(puntatore) all'oggetto.Questo e' obbligatorio per
il sovraccarico di questo operatore e si realizza con return *this
file Complex.cc
#include <iostream>
class Complex {
public:
Complex ( double a, double b ): real(a),imag(b) { }
Complex () :real(0),imag(0){ }
Complex (const Complex& z):real(z.real),imag(z.imag) { }
~Complex () {}
Complex& operator=(const Complex& z){real=z.real;imag=z.imag;return *this;}
friend Complex operator+ ( const Complex& y, const Complex& z ) {
return Complex (y.real+z.real, y.imag+z.imag);
}
friend Complex operator- ( const Complex& y, const Complex& z ) {
return Complex (y.real - z.real, y.imag - z.imag);
}
friend Complex operator* ( const Complex& y, const Complex& z ) {
return Complex (
y.real * z.real - y.imag * z.imag,
y.imag * z.real + y.real * z.imag );
}
friend Complex operator/ ( const Complex& y, const Complex& z ) {
return Complex (
(y.real * z.real + y.imag * z.imag) /
(z.real * z.real + z.imag * z.imag),
(y.imag * z.real - y.real * z.imag) /
(z.real * z.real + z.imag * z.imag) );
}
bool compare ( Complex y, Complex z ) {
return (y.real == z.real && y.imag == z.imag);
}
void print() {cout << "(" << real << "," << imag << ")" << endl;}
double imagpart () { return imag; }
double realpart () { return real; }
private:
double real, imag;
};
int main()
{
Complex a(1,4), b(3,8), c;
a.print();b.print();
c = a+b;
c.print();
Complex d = c;
d = (d*a+b)/c;
d.print();
}
d = (d*a+b)/c
operator che sovraccaricano gli operatori aritmetici non sono membri della classe e percio' vanno definiti come friend
per poter accedere alle proprieta' private della classe.
Complex operator+(const Complex&, const Complex &){implementazione}
omettendo l'operatore di visibilita' Complex::
file Lista.cc
#include <iostream>
class Nodo{
public:
Nodo(int n, Nodo* p=0):dato(n),prossimo(p){}
int dato;
Nodo* prossimo;
};
int main()
{
Nodo* p=new Nodo(0);
for(int i=1;i<10;i++){
p = new Nodo(i,p);
}
for(;p->prossimo;p=p->prossimo){
cout<< p->dato << endl;
}
}
Nodo(0) che
con Nodo(i,p).
new per allocare l'oggetto.Dato che il C++ non prevede una
distruzione automatica di questi oggetti, allora
per il loro corretto trattamento occorrerebbe definire un distruttore,
un costruttore di copia e il sovraccarico dell'operatore di assegnazione. Quelli forniti in maniera automatica non provvedono infatti a fare il delete dell'oggetto creato e condurrebbero a programmi con problemi di memory leak.
Vedremo come si fa questo nel prossimo programma.
file Lista.cc
#include <iostream>
class Lista{
public:
Lista(): dati(new int[SIZE]), ndati(0), lunvec(SIZE){}
~Lista(){delete []dati;}
Lista(const Lista &L):dati(new int[L.lunvec]),
ndati(L.ndati), lunvec(L.lunvec) {
for (int k=0; k < ndati; k++) {
dati[k] = L.dati[k]; } }
Lista & operator=(const Lista &L){
if (this == &L) return *this;
else {
delete [] dati;
dati = new int[L.lunvec];
lunvec = L.lunvec;
for (ndati=0; ndati < L.ndati; ndati++) {
dati[ndati] = L.dati[ndati]; }
return *this; }}
void aggiungi(int n){ndati=ndati+1;if(ndati<=lunvec){dati[ndati-1]=n;}
else { cout << "superata la dimensione del vettore" << endl;}}
void print (int i){cout << dati[i] << endl;}
private:
static const int SIZE=10;
int *dati;
int ndati;
int lunvec;
};
int main()
{
Lista l,l1;
for(int i=0;i<11;i++){
l.aggiungi(i);
}
l1 = l;
for(int i=0;i<10;i++){
l1.print(i);}
}
*dati. Se avessimo omesso queste funzioni la compilazione non avrebbe segnalato errore ma l'esecuzione sarebbe stata problematica.
file Lista.cc
#include <iostream>
template < class T >
class Lista{
public:
Lista(): dati(new T[SIZE]), ndati(0), lunvec(SIZE){}
~Lista(){delete []dati;}
Lista(const Lista < T >&L):dati(new T[L.lunvec]),
ndati(L.ndati), lunvec(L.lunvec) {
for (int k=0; k<ndati; k++) {
dati[k] = L.dati[k]; } }
Lista< T > & operator=(const Lista< T > &L){
if (this == &L) return *this;
else {
delete [] dati;
dati = new T[L.lunvec];
lunvec = L.lunvec;
for (ndati=0; ndati < L.ndati; ndati++) {
dati[ndati] = L.dati[ndati]; }
return *this; }}
void aggiungi(T n){ndati=ndati+1;if(ndati<=lunvec){dati[ndati-1]=n;}
else { cout << "superata la dimensione del vettore" << endl;}}
void print (int i){cout << dati[i] << endl;}
private:
static const int SIZE=10;
T *dati;
int ndati;
int lunvec;
};
int main()
{
Lista < double >l,l1;
for(int i=0;i<11;i++){
l.aggiungi(i);
}
l1 = l;
for(int i=0;i<10;i++){
l1.print(i);}
}
int T. T rappresenta il tipo generico.
template < class T > prima della definizione indica che questo
e' un modello di classe e che T e' il parametro che indica il tipo di dati generico nella definizione del modello.
Questo e' un esempio di contenitore realizzato con template (modelli) .Un contenitore e' un oggetto formato da un insieme di altri oggetti come un array o un vector o una lista.
Il costrutto template permette di realizzare contenitori non solo di oggetti ma anche di dati primitivi(in Java, se avete bisogno di introdurre un dato
primitivo in un contenitore come Vector usate la corrispondente classe wrapper).
Quella appena vista e' un esempio di classe generica .
C++ fornisce nella Standard Template Library(STL) vari tipi
di contenitori realizzati con template. Per navigare nei contenitori
vengono usati degli speciali oggetti detti iteratori che permettono di scrivere algoritmi indipendenti dal particolare contenitore usato.La STL stessa
contiene parecchi di questi algoritmi generici che funzionano con
vari tipi di dati.
A livello di implementazione la STL e' stata divisa
in diversi header files. Essi si trovano assieme agli altri header files
del g++ in /usr/local/include/g++.
Generic Programming(Programmazione Generica) di cui abbiamo visto un esempio nel programma precedente, si riferisce alla possibilita' in un linguaggio di scrivere programmi che possono lavorare con diversi tipi di dati.In C++ si usa per questo l'approccio dei Template. Un template e' in effetti una specie di fabbrica per fare pezzi di codice:a seconda della dichiarazione, il programma generato dal template tratta interi, decimali, stringhe e in effetti qualsiasi tipo di oggetti.La programmazione generica esiste anche in Java, ma qui l'approccio e' diverso:di solito si sfrutta il fatto che ogni oggetto in Java discende da Object per cui un contenitore di Object puo' contenere qualsiasi oggetto. (Ci sarebbe l'eccezione dei tipi primitivi, ma per questi si puo' usare la corrispondente classe wrapper.) Questo approccio,per quanto molto facile e intuitivo, e' invece inefficiente a livello di esecuzione se paragonato all'approccio C++ dei template.
file Vector.cc
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
using namespace std;
int main()
{
vector< int> v(10,0);
ostream_iterator< int> out(cout, " ");
copy(v.begin(), v.end(), out);
cout << endl;
vector<int>::iterator i=v.begin() ;
for(int k=0;k<10;k++){*i++ = rand()%1000;};
copy(v.begin(), v.end(), out);
cout << endl;
sort(v.begin(), v.end());
copy(v.begin(), v.end(), out);
cout << endl;
return 0;
}
algorithm , vector e iterator fanno parte della STL.
file Mapex.cc
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
typedef map<int,string *> Listastudenti;
Listastudenti listas;
// Riempi la mappa
string s1 = "Pinco Pallino";
listas[1021130] = &s1;
string s2= "Rossi Mario";
listas[1001050] = &s2;
string s3= "Verdi Giuseppe";
listas[821102] = &s3;
// Stampa gli elementi della mappa
Listastudenti::iterator pos;
for (pos = listas.begin(); pos != listas.end(); ++pos) {
cout << "chiave: \"" << pos->first << "\" "
<< "valore: " << (*pos->second) << endl;
}
// Vedi se c'e' lo studente con un certo numero di matricola e stampane il nome
int matricola=1001050;
pos = listas.find(matricola);
if(pos!=listas.end()){
cout << pos->first << " " << (*pos->second)<< " trovato" << endl;}
}
map e' un contenitore STL.
map ha un iteratore che ci permette di esaminare tutti
i dati caricati.
typedef serve solo a definire un alias della dichiarazione della mappa in modo da semplificare il codice che segue, ma avremmo potuto scrivere direttamente:map<int,string *> listas; map<int,string *> ::iterator pos;
map.
file SingleCounter.cc
#include <iostream>
using namespace std;
class SingleCounter{
static SingleCounter c;
int value;
SingleCounter(int n):value(n){}
void operator=(SingleCounter&);
SingleCounter(const SingleCounter&);
public:
static SingleCounter& instance(){return c;}
int getValue(){return value;}
void increment(){value++;};
};
SingleCounter SingleCounter::c(0);
int main()
{
SingleCounter& c1 = SingleCounter::instance();
cout << c1.getValue() << endl;
c1.increment();
SingleCounter& c2 = SingleCounter::instance();
c2.increment();
cout << c1.getValue() <<" "<< c2.getValue()<< endl;
}
SingleCounter::c(0); che puo' essere data solo all'interno della
stessa classe SingleCounter essendo il costruttore private.
instance per ottenere l'indirizzo
dell'unica istanza in una qualsiasi parte del programma.
SingleCounter& c1 prima incontrata solo per
forzare il passaggio di indirizzi a funzioni, qui viene usata per dichiarare c1
come un tipo particolare di puntatore detto referenza.
Una referenza e' un puntatore che:
null(essere dereferenziato)
Per concludere ecco un esempio di grosso progetto software C++. Tutta la documentazione e il codice e' accessibile via Web. La gestione del codice stesso e' fatta via CVS che permette a piu' programmatori di collaborare allo stesso progetto software.