|
emi7oP <a href="http://etyxbsmwlbop.com/">etyxbsmwlbop</a>, [url=http://ejkbnyaqouhe.com/]ejkbnyaqouhe[/url], [link=http://iytqgxxmxvee.com/]iytqgxxmxvee[/link], http://ddjproogyauv.com/ |
ObjectOrientedCppObject Oriented in C++Autore: Estefan Civera Sommario
Scopo di questo articolo è di fare una introduzione alla programmazione ad oggetti in C++.
Storia
Abstract data type
Diffusione del linguaggio
Abstraction
Using constructor
Inizializzazione del costruttore
Distruttore
Operatori new e delete
Function declaration
Passaggio di parametri
Metodi e attributi statici
Funzioni inline
Argomenti di default
Overloading delle funzioni
StoriaNon dovrebbe sorprendere più di tanto il fatto che il C++ abbia un'origine simile al C. Lo sviluppo del linguaggio C++ all'inizio degli anni Ottanta è dovuto a Bjarne Stroustrup dei laboratori Bell (Stroustrup ammette che il nome di questo nuovo linguaggio è dovuto a Rick Mascitti). Originariamente il C++ era stato sviluppato per risolvere alcune simulazioni molto rigorose e guidate da eventi; per questo tipo di applicazione la scelta della massima efficienza precludeva l'impiego di altri linguaggi. Uno degli scopi principali del C++ era quello di mantenere piena compatibilità con il C. L'idea era quella di conservare l'integrità di molte librerie C e l'uso degli strumenti sviluppati per il C. Grazie all'alto livello di successo nel raggiungimento di questo obiettivo, molti programmatori trovano la transizione al linguaggio C++ molto più semplice rispetto alla transizione da altri linguaggi. Il C++ consente lo sviluppo di software su larga scala. Grazie a un maggiore rigore sul controllo dei tipi, molti degli effetti collaterali tipici del C, divengono impossibili in C++. Il miglioramento più significativo del linguaggio C++ è il supporto della programmazione orientata agli oggetti (Object Oriented Programming: OOP), Per sfruttare tutti i benefici introdotti dal C++ occorre cambiare approccio nella soluzione dei problemi. Ad esempio, occorre identificare gli oggetti e le operazioni ad essi associate e costruire tutte le classi e le sottoclassi necessarie. Abstract data typeGli ADTs possono essere definiti come un dato indipendente dalla sua rappresentazione interna e dalla effettiva implementazione delle operazioni su tale dato. Viene definito specificando: rappresentazione interna e dalla effettiva
A differenza del C, il C++ permette la gestione degli ADTs (concetto realizzato dal costrutto Diffusione del linguaggioGrazie ad un notevole sforzo da parte dei progettisti il C++ è risultato uno tra i più (se non il primo) linguaggi robusti ed efficienti. La notevole diffusione è stata avvantaggiata anche dal fatto che
Altre caratteristiche fondamentali che sono state inserite in C++ sono la gestione delle eccezioni, l'utilizzo del costruttore di copia, il passaggio per riferimento, e la gestione dell'overloading/overriding. Comunque il C++ presenta anche degli aspetti negativi (come ogni linguaggio), in parte ereditate dal C:
AbstractionIl concetto di abstraction sta ad indicare che i dettagli implementativi vengono nascosti all'utente che interagisce con essi tramite metodi e funzioni. Type definition Struct vs ClassQuesto esempio realizzato tramite il costrutto struct vuole mettere in evidenza le problematiche relative all'utilizzo delle strutture in un linguaggio imperativo come il C. // Create a structure, set its members, and print it
#include <iostream.h>
struct Time { // structure definition
int hour; // 0-23
int minute; // 0-59
int second; // 0-59
};
void printMilitary(const Time &); // prototype
void printStandard(const Time &); // prototype
//===========================================
main()
{
Time dinnerTime; // variable of new type Time
// set members to valid values
dinnerTime.hour = 18;
dinnerTime.minute = 30;
dinnerTime.second = 0;
cout << "Dinner will be held at";
printMilitary(dinnerTime); // 18:30:00
// set members to invalid values
dinnerTime.hour = 29;
dinnerTime.minute = 73;
dinnerTime.second = 103;
cout << "\nTime with invalid values: ";
printMilitary(dinnerTime); // 29:73:103 bad values!
}
L'esempio qui descritto mostra come l'istruzione Ecco la stessa soluzione ottenuta tramite l'implementazione della classe Time // Time abstract data type (ADT) definition
class Time {
public:
Time(); // default constructor
void setTime(int, int, int);
void printMilitary();
void printStandard();
private:
int hour; // 0 - 23
int minute; // 0 - 59
int second; // 0 - 59
}; // Time constructor initializes each data member to zero.
// No return value
// Ensures all Time objects start in a consistent state.
Time::Time() { hour = minute = second = 0; }
// Set a new Time value using military time.
// Perform validity checks on the data values.
// Set invalid values to zero (consistent state)
void Time::setTime(int h, int m, int s)
{
hour = (h >= 0 && h < 24) ? h : 0;
minute = (m >= 0 && m < 60) ? m : 0;
second = (s >= 0 && s < 60) ? s : 0;
}
Using constructorNella programmazione orientata agli oggetti, i costruttori sono metodi associati alle classi che hanno lo scopo di inizializzare le nuove istanze della classe durante il processo di creazione. In molti linguaggi (per esempio in Java e C++) hanno lo stesso nome della classe a cui appartengono. Come tutti gli altri metodi, i costruttori possono essere definiti in molteplici versioni attraverso overloading. Facendo sempre riferimento all'esempio precedente ecco come è possibile fare l'overloading del costruttore e come ogni volta cambia la sua implementazione //Interface
Time(); // default constructor
Time(int hr);
Time(int hr, int min, int sec);
//Implementation
Time::Time(){ hour = minute = second = 0; }
Time::Time(int hr) { setTime(hr, 0, 0); }
Time::Time(int hr, int min, int sec)
{ setTime(hr, min, sec); }
Time t1;
Dichiaro un oggetto t1. In questo caso viene richiato il costruttore di default. Time t2(08); Time t2 = Time(08); Time t2 = 08; Time t2 = (Time) 08; Le quattro precedenti istruzioni sono equivalenti (pongono l'ora a 8), ma la medesima istruzione è eseguita sintatticamente in maniera diversa. Nel primo caso (classico) si invoca il costruttore sovraccaricato. Nel secondo caso si assegna a t2 l'istanza
ottenuta dall'invocazione di Time t3(08,15,04); Time t3 = Time(08,15,04); Per quanto riguarda l'allocazione dinamica... Time *t; t = new Time; // Time() is invoked t = new Time(08); // Time(int) is invoked t = new Time(08,15,04); // Time(int, int, int) is invoked Come vettore... e come vettore dinamico.... Inizializzazione del costruttorePer richiamare il costruttore padre si usa l'operatore ":". class Base;
class Derived public Base {
public:
Derived(int x, y)
:
Base(x){
y = 0;
}
}
DistruttoreUn distruttore è una funzione membro di una classe normalmente utilizzata per restituire al sistema la memoria allocata da un oggetto. Il distruttore, come il costruttore, ha sempre lo stesso nome della classe nella quale è definito ma è preceduto dal carattere tilde (~). In pratica, i distruttori hanno una funzione opposta a quella dei costruttori.
Il distruttore viene richiamato automaticamente quando si applica l'operatore Operatori new e deleteIn C++, l'operatore new costruisce uno o più oggetti nell'area heap e ne restituisce l'indirizzo. In caso di errore (memoria non disponibile) restituisce NULL. Per deallocare la memoria dell'area heap in C++ mette a disposizione l'operatore Contrariamente a quanto sembra l'operatore Se l'operando punta a un'area in cui sono stati allocati più oggetti (array), int i = new int; delete i; float* punt = new float [100] ; // alloca 100 oggetti float delete [] punt; // libera tutta la memoria allocata L'operatore delete costituisce l'unico mezzo per deallocare memoria heap, che, altrimenti, sopravvive fino alla fine del programma, anche quando non é più raggiungibile. Ad ogni operazione di allocazione di memoria (new) deve corrispondere una e una sola operazione di rimozione (delete). In caso contrario si possono verificare resource leak con conseguente instabilità del processo. Per evitare problemi con l'operatore Function declaration
L'operatore int Car::fun_weight(const double weight)
{
// weight++; ERROR non è ammesso perché è presente il mod. const
new_weigth += weight;
return (int) new_weight;
}
Passaggio di parametriIl passaggio di parametri in C/C++ avviene per valore e per riferimento. Il passaggio per valore comporta la copia sullo stack dell'intero oggetto (molto inefficienti per oggetti di notevole dimensione). Inoltre ogni modifica apportata non impatta sull'oggetto ma solo sulla copia. Il passaggio per riferimento come indica il termine è caratterizzato dal fatto che viene passato l'indirizzo di memoria dell'oggetto in questione. Questa soluzione è molto performante in quanto non viene copiato sullo stack l'oggetto (con notevole risparmio di tempo) ma anche perché le modifiche apportate all'oggetto in questione e non ad una sua copia. C++ permette di realizzare il passaggio per riferimento in due modi. # void foo(Type* t); // Basato sull'utilizzo dei puntatori, vantaggi e svantaggi //dell'utilizzo dei puntatori # void foo(Type& t); // Non necessita del not null checking. Metodi e attributi staticiPer usare un metodo generalmente serve sempre una istanza. Questo può essere una limitazione perché alcune procedure sono funzioni: prendono un input e producono un output, senza che debba essere memorizzato uno stato. È fastidioso dover creare ogni volta una istanza. Per fortuna è possibile dichiarare un metodo in modo che non richieda una istanza per essere utilizzata: si tratta dei metodi statici. Come i campi statici, possono essere invocati solamente utilizzando il nome della classe, senza che occorra avere alcuna istanza. I metodi statici hanno qualcosa in meno rispetto ai metodi non statici:
Funzioni inlineLe funzioni consentono di scomporre in più parti un grosso programma facilitandone sia la realizzazione che la successiva manutenzione. Tuttavia spesso si è indotti a rinunciare a tale beneficio perché l'overhead imposto dalla chiamata di una funzione è tale da sconsigliare la realizzazione di piccole funzioni.
Per non rinunciare ai vantaggi forniti dalle (piccole) funzioni e a quelli forniti da un controllo statico dei tipi, sono state introdotte nel C++ le funzioni
La keyword Argomenti di defaultA volte siamo interessati a funzioni il cui comportamento è pienamente definito anche quando in una chiamata non tutti i parametri sono specificati, vogliamo cioè essere in grado di avere degli argomenti che assumano un valore di default se per essi non viene specificato alcun valore all'atto della chiamata. Ecco come fare:
Quella che abbiamo appena visto è la definizione della funzione Overloading delle funzioniL'overloading consiste nel sovraccaricare un operatore o una funzione per far si che essa funzioni con variabili aventi tipi diversi. void Foo(int a, float f); int Foo(int a, float f); // Errore! int Foo(float f, int a); // Ok! char Foo(); // Ok! char Foo(...); // OK La seconda dichiarazione è errata perché per scegliere tra la prima e la seconda versione della funzione, il compilatore si basa unicamente sui tipi dei parametri che nel nostro caso coincidono; la soluzione è mostrata con la terza dichiarazione, ora il compilatore è in grado di distinguere perchè il primo parametro anziché essere un int è un float. Infine le ultime due dichiarazioni non sono in conflitto per via delle regole che il compilatore segue per scegliere quale funzione applicare; in linea di massima e secondo la loro priorità:
Se nessuna di queste regole può essere applicata, si genera un errore (funzione non definita!). Seconda parte, esclusi Template e STL Altre risorse utiliThinking in C++, di Bruce Eckel |