Condizione necessaria (e sufficiente), per continuare a leggere il post, è il possesso di alcune nozioni di C++ e OOP: se non le avete iscrivetevi a un corso accelerato di C++ o leggetevi rapidamente un manuale... comunque, data la semplicità dell'esempio, non tentate diventare super-esperti del C++ per finire di leggere il post. Anche perché, in tal caso (super-esperti!), vi potrebbe servire così tanto tempo che potrebbe non esistere più né questo blog né internet... ("life is too long to be good at C++", Erik Naggum).
Veniamo al dunque: scriviamo una classe elementare in C++ che implementa un contatore. Scriveremo, secondo la prassi classica, su tre file: header, implementazione e uso. Vediamo l'header counter.hpp:
/* counter.hpp - header classe CCounter */ class CCounter { private: // attributi privati int value; public: // metodi pubblici CCounter(); // costruttore void incValue(int n); int getValue(); };Come promesso è una classe semplicissima, con un attributo privato, due metodi di lettura e scrittura e un costruttore esplicito. Passiamo al file di implementazione, counter.cpp:
/* counter.cpp - implementazione classe CCounter */ #include "counter.hpp" // CCounter() - costruttore della classe CCounter CCounter::CCounter() { value = 0; } // incValue() - metodo di incremento attributo value void CCounter::incValue(int n) { value += n; } // getValue() - metodo di lettura attributo value int CCounter::getValue() { return value; }Evidentemente non c'è neanche da spiegare come funzionano i metodi appena descritti (e, se state ancora leggendo, dovreste avere già fatto, con successo, il corso accelerato di C++...). Per ultimo vediamo il file di uso della classe, countermain.cpp:
/* countermain.cpp - esempio d'uso della classe CCounter */ #include <iostream> #include "counter.hpp" using namespace std; main() { CCounter cnt_a; CCounter cnt_b; // leggo valori cout << "cnt_a value = " << cnt_a.getValue() << endl; cout << "cnt_b value = " << cnt_b.getValue() << endl; // incremento valori cnt_a.incValue(5); cnt_b.incValue(7); // leggo valori cout << "cnt_a value = " << cnt_a.getValue() << endl; cout << "cnt_b value = " << cnt_b.getValue() << endl; }Ok, l'uso della classe è un po' stupido, ma per quest'esempio è più che sufficiente. Instanziamo due oggetti della classe CCounter, cnt_a e cnt_b, stampiamo i valori di value per entrambi gli oggetti, incrementiamo e ristampiamo: se compilate ed eseguite il programma l'uscita sarà:
cnt_a value = 0 cnt_b value = 0 cnt_a value = 5 cnt_b value = 7Come vedete, per la magia della OOP e del C++ i due oggetti hanno vita propria, e, alla fine, il campo value ha valore diverso nei due oggetti (merito del mitico puntatore this... ma questo dovreste già saperlo e questo non è un corso di C++, quindi non lo spiegherò).
Tutto questo si può fare anche in C? Si! Beh, non proprio identico, ma quasi. Useremo la stessa struttura a tre file. Cominciamo con l'header ccounter.h:
/* ccounter.h - header classe CCounter in C */ typedef struct _ccounter { // attributi int value; // metodi void (*construct)(struct _ccounter *this); // costruttore void (*incValue)(struct _ccounter *this, int n); int (*getValue)(struct _ccounter *this); } CCounter;Somiglia molto a ccounter.hpp, no? Ovviamente non ci sono le parole magiche public e private, e i metodi sono degli splendidi (?) puntatori a funzione, comunque la somiglianza è evidente. passiamo alla implementazione in ccounter.c:
/* ccounter.c - implementazione classe CCounter in C */ #include "ccounter.h" // incValue() - metodo di incremento attributo value static void incValue(CCounter *this, int n) { this->value += n; } // getValue() - metodo di lettura attributo value static int getValue(CCounter *this) { return this->value; } // construct() - costruttore della classe CCounter static void construct(CCounter *this) { this->incValue = &incValue; this->getValue = &getValue; this->value = 0; } // pubConstruct() - costruttore pubblico della classe CCounter void pubConstruct(CCounter *this) { this->construct = &construct; this->construct(this); }Qui le differenze sono più evidenti, però la somiglianza strutturale è altrettanto chiara. Rispetto alla versione C++ dobbiamo fare in maniera esplicita ciò che il compilatore C++ fa implicitamente: a ogni metodo passiamo un puntatore all'oggetto chiamante (che strano, l'ho chiamato this, anche se non so perché, sarà qualcosa di inconscio), i metodi sono assegnati ai puntatori a funzione, e dobbiamo aggiungere un metodo in più, un costruttore pubblico, da chiamare dopo l'instanziazione dell'oggetto (anche questo il compilatore C++ lo fa implicitamente).
Ci resta solo da vedere il file d'uso, ccountermain.c:
/* ccountermain.c - esempio d'uso della classe CCounter in C */ #include <stdio.h> #include "ccounter.h" main() { CCounter cnt_a; pubConstruct(&cnt_a); CCounter cnt_b; pubConstruct(&cnt_b); // leggo valori printf("cnt_a value = %d\n", cnt_a.getValue(&cnt_a)); printf("cnt_b value = %d\n", cnt_b.getValue(&cnt_b)); // incremento valori cnt_a.incValue(&cnt_a, 5); cnt_b.incValue(&cnt_b, 7); // leggo valori printf("cnt_a value = %d\n", cnt_a.getValue(&cnt_a)); printf("cnt_b value = %d\n", cnt_b.getValue(&cnt_b)); }Beh, questo è praticamente identico alla versione C++, salvo la chiamata esplicita al costruttore pubblico, e all'uso della printf() per la stampa dei risultati. Se compilate e eseguite, il risultato (giuro!) è identico a quello della versione C++.
Nel prossimo post, vi mostrerò come sia possibile implementare, in C, anche la ereditarietà delle classi (wow!). Adesso non ne ho voglia, e mi dilungherei troppo (non voglio annoiare nessuno). Anzi, come previsto all'inizio del post, non scriverò neanche le disquisizioni filosofiche... ma anche quelle sono solo rimandate al prossimo post.
Prima di lasciarci non posso evitare di scrivere una nota di riflessione (ma non perdeteci il sonno ripensandoci!): l'esempio appena mostrato serve a poco (anzi, a niente!). Però è una interessante dimostrazione di flessibilità e potenza del nostro amato C, e aggiungo, una dimostrazione reale delle origini del C++: forse non tutti sanno che, moooolti anni fa (anni '80) alcuni dei compilatori C++ disponibili sul mercato erano, in realtà, dei mega-preprocessori, che leggevano il codice C++, lo trasformavano in C, e lo compilavano con un normale compilatore C. Ecco, l'esempio qui sopra è (più o meno...) quello che facevano i compilatori C++ primordiali, quando il C++ era ancora solo un C a oggetti. Meditate gente, meditate...
Ciao e al prossimo post.
Nessun commento:
Posta un commento