back to top

Iniziare a programmare in C++

Come detto nell’introduzione della guida al C, il C++ nasce come estensione del C, in particolare esso aggiunge una serie di funzionalità che lo rendono un ottimo strumento per la programmazione orientata agli oggetti.

Gli oggetti non sono altro che componenti software riutilizzabili modellati sul mondo reale. Programmare seguendo il paradigma OO (Object Oriented) rende più semplice la lettura del codice, la sua correzione e la sua modifica.

Lo scopo principale di questa guida è quello di fornire una visione generale del linguaggio C++ e del paradigma OO, tuttavia, prima di entrare nel merito, ritego opportuno formulare una piccola ma doverosa premessa:

Poco sopra abbiamo detto che il C++ è un’evoluzione del C, capirete quindi che esso eredita gran parte dello stile di programmazione del suo genitore. Si rimanda quindi il lettore alla guida al C per imparare i fondamenti della programmazione di questo linguaggio, tra i quali soprattutto:

  • i tipi di variabili (cap 4)
  • le istruzioni condizionali (cap 5)
  • le istruzioni iterative (cap 7)
  • la strutturazione in funzioni (cap 6)

In generale una buona lettura della guida al C rende più facilmente comprensibile i codici presentati in questa guida. Quindi se non conoscete il C e non avete ancora letto la sua guida… fatelo subito!

Editor e ambiente di sviluppo

Per il C++ utilizzeremo il compilatore freeware Dev-C++.

Per utilizzare a pieno le potenzialità del paradigma OO bisognerà utilizzare il compilatore in un’altra maniera, creando dei progetti. La cosa è abbastanza intuitiva:

Clicchiamo sull’icona nuovo progetto

e selezioniamo l’icona empty project…

A questo punto diamo un nome al nostro progetto ed inseriamo un nuovo file sorgente cliccando su

, salviamo tale file sorgente dandogli il nome main (in quanto lì andrà scritto il nostro programma principale) e scriviamo nella finestra bianca il seguente codice per verificare il corretto funzionamento:

#include <iostream>

using namespace std;

int main()
{
 cout << "Benvenuti nella guida al C++" << endl;
 
     system("PAUSE");
     return 0;   
}

Il vostro compilatore dovrebbe apparire così:

Ricordo che per compilare un progetto bisogna cliccare su una delle seguenti icone

il cui significato è spiegato sempre nel cap. 2 della guida al C presente su questo sito.

Nella colonna a sinistra verranno visualizzati tutti i file inclusi nel progetto, a destra il codice del file selezionato.

Se dopo la compilazione, se tutto è andato a buon fine, dovreste avere in output la seguente schermata:

Lo streaming di input/output

Chi ha letto la guida al C, leggendo il codice proposto per verificare il funzionamento del compilatore, avrà notato che vi è un diverso modo di chiamare una stampa a video. Infatti con il C++ si abbandonano le funzioni printf() e scanf() e tutta la libreria stdio.h a vantaggio dei nuovi operatori di streaming:

  • << (appartenente alla classe cout)
  • >> (appartenente alla classe cin)

Questi operatori sono contenuti all’interno della librerie iostream che dovrà essere inclusa nel nostro codice come al solito.

Grazie allo streaming si risolvono tutte le problematiche dovute all’uso di specificatori all’interno delle funzioni printf() e scanf() in quanto sarà il compilatore stesso a farsi carico dell’assegnazione del giusto tipo ad una data variabile. Facciamo un esempio:

// Controlla se due numeri sono multipli

#include <iostream>

//usa lo spazio dei nomi standard
using namespace std;

//prototipo della funzione
void multiple (int, int ) ;

int main()
{
    //dichiarazione di due interi
    int a, b;      
    cout << "Inserisci due interi:" << endl;
    //acquisizione di due interi
    cin >> a >> b;
    //invocazione della funzione
    multiple (a,b);

    system("PAUSE");
    return 0;
}

//dichiarazione della funzione
void multiple (int x, int y )
{
//dichiarazione di una variabile boolenana
bool risultato;

//due if innestati per stabilire se x è o meno 
//multiplo di y
 if (x >= y )
 {
     risultato = x%y ;
       if ( risultato == 0 )
           cout << x << " e' multiplo di " << y << endl;
       else
           cout << x << " non e' multiplo di " << y << endl;
 }

 if (x < y )  
 {
     risultato = y%x;
       if ( risultato == 0 )
           cout << y << " e' multiplo di " << x << endl;
       else
           cout << y << " non e' multiplo di " << x << endl;
 }                          

}

Analizziamo il codice: tale programma non fa altro che controllare se un numero è multiplo di un altro utilizzando l’operatore % che indica il resto di una divisione, quindi se un numero è multiplo di un altro allora il resto sarà pari a zero.

using namespace std: indica che si intende usare lo spazio dei nomi standard. Questa è una delle nuove caratteristiche del C++ che permette di evitare conflitti con i vari header file che si andranno a creare, in particolare questa istruzione indica che si adopereranno librerie standard del C++ e non librerie proprie.

void multiple (int, int ); è il prototipo della funzione che deve calcolare se i numeri sono multipli; essa non deve restituire nulla quindi è dichiarata void, notiamo che essa accetta in ingresso due parametri di tipo intero.

All’interno del main vengono poi dichiarati due interi a e b i quali ci serviranno per le operazioni di input.

cout << "Inserisci due interi:" << endl; – cout significa che vogliamo mandare in output a video ciò che segue il simbolo << in questo caso ciò che è riportato tra doppi apici "……" viene stampato a video interpretato come una stringa di caratteri, invece endl sta per vai a capo (cosa che in C facevamo usando n).

cin >> a >> b; – cin indica che vogliamo acquisire un dato da tastiera e memorizzarlo nella variabile indicata dopo il simbolo >> nel nostro caso prima la variabile a poi quella b. Notiamo come non è più necessario specificare che quelli in input sono dei dati interi %d come avviene nel C, sarà il compilatore stesso ad interpretare per noi tale dato.

multiple (a,b); – come al solito è il modo in cui si richiama una funzione, in questo caso gli passiamo i parametri a e b.

return 0; – come nel C indica che il main è terminato correttamente.

void multiple (int x, int y ) – indica che da qui in poi vi è la dichiarazione della funzione multiple che non restituirà nulla e i cui valori di input di tipo intero saranno chiamati x e y.

Segue un if in cui si entra nel caso in cui x è maggiore o uguale di y; a questo punto si effettua l’istruzione risultato = x%y in cui si calcola il resto di x per y. Segue un if…else dove viene stampata la frase "x è multiplo di y" oppure "x non è multiplo di y" a seconda del valore contenuto in risultato.

Nota: anche il cout non ha più bisogno di specificatori di tipo basta porre nello streaming il dato che si vuole quando lo si vuole mandare in stampa cosi facendo l’istruzione cout << x << " e’ multiplo di " << y << endl; produrrà in output video la seguente frase supposto x = 30 e y = 5 30 è multiplo di 5.

L’altro if viene usato quando x è minore di y per invertire l’ordine di divisore e dividendo per ottenere il resto fra i due.

E’ possibile effettuare un casting (cambiare il tipo della variabile) sui dati mandati in output direttamente nello streaming. Per fare ciò basta usare delle parentesi tonde che contengano il tipo in cui si vuole trasformare il nostro dato nel seguente modo:

#include <iostream>

using namespace std;

float x;
char a;

int main()
{
     cout << "Immetti un numero reale (float) ";
   cin >> x ;
   cout << "Immetti un carattere (char)";
   cin >> a;
   cout << "Il numero immesso e' " << x 
          << " convertito in intero e' " << (int) x 
      << endl;
 
    cout << "Il carattere immesso e' " << a
    << " il suo valore ASCII e' " << (int) a 
          << endl;
      
    system("PAUSE");
    }

L’esecuzione sarà la seguente:

Immetti un numero reale (float) 3.999

Immetti un carattere (char) g

Il numero immesso e’ 3.999 convertito in intero e’ 3

Il carattere immesso e’ g il suo valore ASCII e’ 103

Premere un tasto per continuare . . .

Nell’output i valori immessi dall’utente sono stati scritti in grassetto quelli trasformati dopo il cast in corsivo.

La strutura di selezione Switch

Il costrutto switch serve per effettuare decisioni multiple sulla base di una variabile. Esso prevede varie etichette case più una opzionale default.

Facciamo subito un esempio: poniamo di voler contare i voti immessi secondo i parametri di giudizio da A (voto più alto) a F (voto più basso), permettendo di inserire i voti fino a che non viene premuto il carattere EOF (sotto windows si ottiene premendo CTRL+Z sotto UNIX CTRL+D).

//Uso dello switch
#include <iostream>

using namespace std;

int main()
{
    int voto,     //voto generico
    aCount = 0,   //occorrenze di a
    bCount = 0,   //occorrenze di b
    cCount = 0,   //occorrenze di c
    dCount = 0,   //occorrenze di d
    eCount = 0,   //occorrenze di e
    fCount = 0;   //occorrenze di f
    
    cout << "Inserisci la lettera corrispondente al voto."
         << endl
         << "Premi CTRL+Z per smettere di immettere voti."
         << endl;
         
    while (( voto = cin.get() ) != EOF )
    {
      switch (voto)
      {
        case 'A':     //voto A maiuscola
        case 'a':     //voto a minuscola
            ++aCount; // incrementa le a
            break;    //esce dallo switch  

        case 'B':     //voto B maiuscola
        case 'b':     //voto b minuscola
            ++bCount; // incrementa le b
            break;    //esce dallo switch
            
        case 'C':     //voto C maiuscola
        case 'c':     //voto c minuscola
            ++cCount; // incrementa le c
            break;    //esce dallo switch
            
        case 'D':     //voto D maiuscola
        case 'd':     //voto d minuscola
            ++dCount; // incrementa le d
            break;    //esce dallo switch
            
        case 'E':     //voto E maiuscola
        case 'e':     //voto e minuscola
            ++eCount; // incrementa le e
            break;    //esce dallo switch
            
        case 'F':     //voto F maiuscola
        case 'f':     //voto f minuscola
            ++fCount; // incrementa le f
            break;    //esce dallo switch
           
        case 'n':     //ignora nuova linea
        case 't':     //ignora tabulazoni
        case ' ':      //ignora spazi vuoti
            break;     //esce dallo switch             
            
        default:       //tutti gli altri caratteri
           cout << "Voto non consentito."
                << "Immettere un nuovo voto." << endl;
           break;                 
       }
    }            
    
cout << endl << endl << "Occorrenze di tutti i voti:"
     << endl << "A: " << aCount   
     << endl << "B: " << bCount
     << endl << "C: " << cCount
     << endl << "D: " << dCount
     << endl << "E: " << eCount
     << endl << "F: " << fCount << endl;
system("PAUSE");
return 0;    
}

Lo switch quindi non fa altro che eseguire l’istruzione ++xCount dove x rappresenta il carattere digitato nel caso in cui esso sia ammissibile altrimenti come descritto nel caso di default viene richiesto di inserire un nuovo voto.

while (( voto = cin.get() ) != EOF ) – questa istruzione sta a significare "ripeti tutto (cioè la struttura switch con i vari case) fino a che non viene immesso EOF".

Per il resto leggendo i commenti contenuti nel codice è facile capire come funziona il costrutto switch.

Nota: la funzione cin.get() non fa altro che leggere un singolo carattere da tastiera e porlo nella variabile voto.

Puntatori e stringhe

A differenza delle variabili viste fino ad ora, le quali contengono un valore specifico, il valore di una variabile di tipo puntatore è una locazione di memoria; o meglio un puntatore contiene l’indirizzo di un’altra variabile che a sua volta contiene un valore specifico. Questo è uno degli argomenti più ostici della programmazione in C++!!!

Come tutte le altre variabili anche i puntatori hanno bisogno di essere dichiarati, tale dichiarazione va effettuata nella seguente maniera:

tipo_puntatore *nomePuntatore;

è quindi il simbolo * ad identificare un puntatore. La dichiarazione può essere interpretata come nomePuntatore è un puntatore ad una variabile di tipo tipoPuntatore.

Dobbiamo inoltre inizializzare un puntatore; se nella dichiarazione ciò non viene fatto esso assume il valore NULL oppure 0 che corrisponde a non punta a nulla. Facciamo un esempio per spiegare l’uso dei puntatori e il loro uso:

//Uso dei puntatori
#include <iostream>

using namespace std;

int main()
{
   
  int a;     //a è un intero
  int *aPtr; //aPtr è un puntatore ad a    

  a = 7; //assegniamo ad a il valore 7
  aPtr = &a; //assegniamo ad aPtr l'indirizzo di a
  
  cout << "L'indirizzo di a e':  " << &a 
       << "nIl valore di aPtr e': " << aPtr;
       
  cout << "nnIl valore di a e': " << a
       << "nIl valore puntato da *aPtr e' " << *aPtr
       << endl<<endl;
       
system("PAUSE");
return 0;               
}

L’operatore & è un operatore unario esso restituisce l’indirizzo del suo operando.

L’operatore * è detto operatore di risoluzione del riferimento esso restituisce una copia dell’oggetto a cui punta il suo operando. Quindi quando si dereferenzia un puntatore esso si può utilizzare come se fosse la variabile a cui punta.

Nota: in realtà il nome di un’array non è altro che il puntatore al suo primo elemento!

Abbiamo visto che in C la trattazione di una stringa non era un argomento molto semplice… il C++, con l’uso dei puntatori ed una libreria apposita, semplifica molto la trattazione delle stringhe grazie a funzioni di copia e acquisizione di stringhe, inoltre la stampa è resa più agevole dall’uso dello streaming.

Facciamo ora degli esempi per spiegare l’utilizzo delle funzioni della libreria string.h.

//utilizzo delle funzioni
//strcpy e strncpy
#include <iostream>
#include <string.h>

using namespace std;

int main()
{
    //dichiarazione della stringa x
    char x[] = "Guida al C++ di MrWebMaster";
    //dichiarazione di y e z con relative dimensioni 
    char y[28], z[13];
    
    cout << "La stringa contenuta nell'array x e':tt" 
         << x
         << "nnCopiamo la stringa x in quella y con strcpy: t"
         //effettuiamo la copia di x in y
         << strcpy(y,x) << endl << endl;
    //copiamo i primi 12 caratteri di x in z
    strncpy(z, x, 13); 
    //aggiungiamo il carattere terminatore
    z[13] = '';      
    cout << "Copia dei primi 12 caratteri di x in z: "
         << z << endl;       
    
    system("PAUSE");
    return 0;
}

strcpy(y,x) – copia ad uno ad uno i caratteri contenuti in x e li pone in y; se y non è abbastanza grande da contenere x allora tronca la stringa.

strncpy(z,x,13) – copia ad uno ad uno i primi 13 caratteri di x e li pone in z. Logicamente non riesce a copiare il carattere terminatore che va copiato a mano con l’istruzione z[13] = ‘’;.

Vediamo ora come unire due stringhe mediante le funzioni strcat e strncat. La prima funzione unisce il secondo argomento al primo, mentre la seconda funzione unisce i solo i primi n caratteri. Vediamo come:

//utilizzo delle funzioni
//strcat e strncat
#include <iostream>
#include <string.h>

using namespace std;

int main()
{
    //dichiarazione delle stringhe
    char x[] = "Guida al C++ di MrWebMaster";
    char y[80] = "Benvenuti alla ";
    char z[80];

    //copiamo y in z
    strcpy(z, y);
    cout << "La stringa contenuta nell'array x e':" 
       << x << endl;
    cout << "nAggiungiamo la stringa x a quella y:ntttt"
         //effettuiamo la concatenazione di x in y
         << strcat(y,x) << endl << endl;

    //aggiungiamo i primi 12 caratteri di x a z
    strncat(z,x,12);      
    cout << "Aggiunta dei primi 12 caratteri di x in z:ntttt" 
         << z << endl << endl;       
    
    system("PAUSE");
    return 0;
}

Nota: è importante verificare che le stringhe destinazione, i primi parametri che si passano alle due funzioni, siano abbastanza grandi da contenere le concatenazioni.

Vediamo ora quelle che sono le funzioni di maggior utilità quando abbiamo bisogno di effettuare dei controlli sulle stringhe, ovvero strcmp e strncmp. Come al solito la prima confronta la tutta la stringa mentre la seconda funzione solo gli n caratteri specificati, il risultato di tale funzione è 0 se le due stringhe sono uguali. Segue un esempio:

//utilizzo delle funzioni
//strcat e strncat
#include <iostream>
#include <string.h>

using namespace std;

int main()
{
  //dichiarazione delle stringhe
  char s1[] = "Guida al C++ di MrWebMaster";
  char s2[] = "Guida al C++ di MrWebMaster";
  char s3[] = "Guida";
   
  cout << "s1: " << s1 << endl;
  cout << "s2: " << s2 << endl;
  cout << "s3: " << s3 << endl << endl;
   
  //effettuiamo ora le comparazioni
  if (strcmp(s2,s1) == 0)
      cout << "s1 ed s2 sono uguali!" << endl;
  else
      cout << "s1 e' diversa da s2" << endl;

  if (strcmp(s3,s1) == 0)
      cout << "s1 ed s3 sono uguali!" << endl;
  else
      cout << "s1 e' diversa da s3" << endl;

  if (strncmp(s1,s3,5) == 0)
      cout << "s3 ed i primi 5 caratteri di s1 sono uguali!nn";
  else
      cout << "s3 ed i primi 5 caratteri di s1 sono diversenn";

  system("PAUSE");
  return 0;
}

Oggetti e classi

Eccoci arrivati a quella che è stata la grande introduzione del C++: la Programmazione ad oggetti!

Per programmare ad oggetti bisogna pensare che tutta la realtà sia formata da essi. Vi chiederete cosa è un oggetto???, la risposta è semplice tutto è un oggetto!

Il discorso può sembrare assurdo ma un oggetto non è altro che un elemento dotato di attributi e metodi o in parole povere di variabili e funzioni, la cosa importante da dire è che tali attributi sono nascosti, o meglio incapsulati nei metodi, in modo tale da nascondere agli utenti le proprie caratteristiche le quali sono accessibili solo mediante le funzioni.

Questa caratteristica è molto importante poichè ci permette di riusare oggetti creati da altre persone ed inserirli in un nostro progetto usando solo i metodi lì definiti, senza conoscere come tali metodi agiscano sull’oggetto. Logicamente il discorso vale anche all’inverso, noi possiamo fornire oggetti a terzi.

In C++ un oggetto viene realizzato mediante il costrutto class il quale funziona come le strutture viste nella guida al C. Nell’esempio che segue creeremo una semplice classe per spiegarne le varie funzionalità.

//questo prog acquisisce due numeri
//e stampa il relativo numero complesso

#include <iostream>
#include <iomanip>

using namespace std;

//definizione della classe NumeroComplesso
class NumeroComplesso{
  //parte public accessibile a tutti
  public:
    //costruttore
    NumeroComplesso();
    //stampa numero complesso
    void stampaComplesso();      
    // acquisisce un numero complesso
    void setComplesso ();
  //parte accessibile solo tramite le 
  //funzioni dichiarate public
  private:
    int parteReale;
    int parteComplessa;
};//non dimenticate il ;

    //definizione del costruttore
    NumeroComplesso::NumeroComplesso()
    {
      parteReale=parteComplessa=0;
    }

    //definizione funzione stampaComplesso
    void NumeroComplesso::stampaComplesso()
      {
      cout << parteReale ;
      if (parteComplessa < 0)
         cout << "-j" << parteComplessa*-1 << endl;
      else
         cout << "+j" << parteComplessa << endl;
    }

    //definizione funzione di acquisizione
void NumeroComplesso::setComplesso()
    {
    int reale,immaginaria;
      cout << "Immetti il coefficiente reale:";
      cin >> reale;
      cout << "Immetti il coefficiente immaginario:";
      cin >> immaginaria;
      //associazione dei valori immessi
      //in input alle variabili private
      parteReale=reale;
      parteComplessa=immaginaria;
    } 


int main()
{
    /*istanziazione di un oggetto
    di tipo numero complesso*/
    NumeroComplesso c;
      
    cout << "Il valore iniziale del numero complesso e':";
    //chiamata alla funzione stampa complesso
    //mediante l'operatore .
    c.stampaComplesso();
  
    cout << "Immetti i seguenti valori:" << endl;
    //setta il numero complesso
    c.setComplesso();
    cout << "Il numero complesso immesso e':";
    c.stampaComplesso();
    cout << endl << endl;

      system("PAUSE");
      return 0;
}

Analizziamo a fondo tale codice:

class NumeroComplesso{…}; – class indica che ciò che segue è una classe, NumeroComplesso è il nome con cui bisognerà istanziare una classe e con cui bisognerà riferirsi alla classe all’interno della sua definizione. All’interno delle parentesi andranno inseriti gli attributi e i metodi della classe. Nota: non dimenticate mai il ; dopo la parentesi graffa potreste impazzire alla ricerca di tale errore.

public – a seguito di tale identificativo vi è la dichiarazione di tutto ciò a cui può accedere il normale utente utilizzando l’operatore ‘.’, praticamente ciò che viene scritto in public corrisponde a quello che era scritto all’interno di una struct.

NumeroComplesso(); – è il costruttore della classe. E’ obbligatorio inserirlo nella parte public in quanto esso serve quando istanziamo una classe scrivendo NumeroComplesso c; quello qui presente è solo il prototipo, la sua dichiarazione sarà effettuata più avanti.

void stampaComplesso(); e void setComplesso (); – sono i prototipi di due funzioni il cui scopo è abbastanza chiaro. Anch’esse sono nella parte public poichè devono operare su quelli che sono i dati privati della nostra classe.

private – con questo identificativo indichiamo gli attributi o i metodi che intendiamo proteggere dall’esterno e quindi sarà possibile accedere ad essi esclusivamente mediante l’uso di funzioni dichiarate public.

int parteReale; e int parteComplessa; – sono i nostri membri privati quelli a cui non si può accedere dal main scrivendo cose del tipo:

Numero complesso c;
c.parteReale = 2;
c.parteComplessa = -3;

ad essi, lo ripetiamo ancora una volta, si può accedere solo mediante le funzioni public. Quindi per far acquisire un nuovo numero complesso useremo la funzione setComplesso.

Nota: spesso vengo utilizzati come nomi di metodi public le parole set e get le quali indicano, rispettivamente, il settaggio e il prelievo di info contenute in variabili private.

Segue ora la definizione dei metodi; notiamo subito che tutti i metodi devono essere dichiarati nella seguente forma:

tipoRitorno NomeClasse::nomeFunzione(eventuali argomenti)

Nota: l’operatore :: è il risolutore di scope e sta ad indicare la classe a cui appartiene il metodo, lo deve precedere il nome della classe e seguire il metodo.

Esaminiamo solo il codice della funzione setComplesso in quanto nelle definizioni delle funzioni non vi sono novità… tutto funziona come al solito:

void NumeroComplesso::setComplesso() – significa che segue la definizione del metodo setComplesso che non restituisce nulla, privo di parametri di scambio appartenente alla classe NumeroComplesso. Il codice segue effettuando una richiesta di immissione della parte reale e di quella immaginaria, memorizzate in variabili d’appoggio temporanee i quali valori poi verranno assegnati alle variabili private (parteReale e parteComplessa) a cui ora possiamo accedere in quanto siamo noi i proprietari della classe.

Il codice continua con il main in cui viene istanziato un oggetto c di tipo NumeroComplesso e viene visualizzatto il suo valore iniziale con la chiamata alla funzione stampaComplesso effettuata secondo la forma c.stampaComplesso(); a questo punto viene richiesto di inserire un nuovo numero complesso con la chiamata c.getComplesso() a cui segue una nuova stampa.

Vi chiederete qual è stata l’utilità di creare una classe NumeroComplesso, la risposta è semplice: in C non esiste un tipo predefinito in grado di utilizzare gli oggetti quindi in questa guida creeremo man mano tutto ciò di cui abbiamo bisogno per gestire il tipo complesso compresi gli operatori di somma differenza etc etc.

La classe numero complesso

In questo capitolo amplieremo le nostre conoscenze riguardo all’uso delle classi e parleremo dell’overloading. L’overloading consiste nel sovraccaricare un operatore o una funzione per far si che essa funzioni con variabili aventi tipi diversi. Ad esempio nel seguito del capitolo vedremo come sovraccaricare l’operatore + per far si che esso sommi due numeri complessi.

Il modo in cui abbiamo scritto il codice precedente non permette un facile ri-uso… scriviamo quindi la nostra classe complessa in modo tale che essa sia riusabile in qualsiasi momento. Per fare ciò basta dividere il codice nelle seguenti parti:

  1. Un file di tipo header dove scrivere la dichiarazione di classe (complex.h)
  2. Un file di tipo cpp dove deve essere inserito il precedente file header e in cui vanno definite le funzioni il cui prototipo si trova nella definizione di classe.
  3. Un terzo file, che includa il file header di classe, dove scriveremo il nostro main.

Iniziamo col creare un nuovo progetto come spiegato nel capitolo 2 di questa guida e diamogli il nome Numero Complesso con Operazioni. A questo punto creiamo all’interno del nostro progetto tre nuovi file sorgente premendo CTRL+N o cliccando su

e rispondendo di "sì" alla richiesta di se vogliamo aggiungere tale file al progetto corrente.

A questo punto salviamo con nome i tre file che compaiono cliccando su

e diamo a tali file i nomi:

  • complex.h
  • complex.cpp
  • main.cpp

mi raccomado di utilizzare le estensioni giuste!!

Dovreste aver ottenuto la seguente schermata:

Iniziamo con lo scrivere il file complex.h:

#include <iostream>

using namespace std;

class complex{

//overloading di estrazione dello streaming
  friend ostream &operator<<( ostream & , const complex  &);
//overloading di immissione dello streaming
  friend istream &operator>>( istream &, complex & );

public:
         //costruttore
         complex(double = 0.0 , double = 0.0 );
         //overloading vari
         complex operator+(const complex & )const;
         complex operator-(const complex & )const;
         complex &operator=(const complex & );
         complex operator*(const complex &)const;
         bool operator==(const complex &)const;
         bool operator!=(const complex &)const;
         
private:
         //come nell'esempio precedente
         double parteReale;
         double parteImmaginaria;

};

In questo codice vediamo la presenza di una nuova keyword (friend) la quale sta a significare che a quei dati possono accedere solo le funzioni della classe; in particolare noi accederemo agli operatori << e >> rispettivamente delle classi ostream (che sta per output stream) e istream (che sta per input stream).

I parametri specificati tra parentesi rappresentano un oggetto ostream/istream passato per riferimento (&) e un oggetto complex sempre passato per riferimento, con la clausola const nel caso dell’ostream in quanto quell’oggetto non deve essere modificato.

Segue la distinzione in parte pubblica e privata come visto in precedenza.

complex(double = 0.0 , double = 0.0 ) – sta a significare che il costruttore per default inizializza a zero i due parametri anche se non lo specifichiamo.

Dopo c’è l’overloading dei vari operatori che intendiamo usare.

Nota: qualsiasi classe avete intenzione di sviluppare sappiate che il prototipo dei vari operatori deve essere sempre questo, quindi tenete tali prototipi a modello e cambiate solo ciò che è necessario (ovvero dove compare complex dovrete scrivere il nome della vostra classe).

La parte privata è la stessa dell’esempio precedente.

Vediamo ora il codice da scrivere in complex.cpp:

#include "complex.h"
#include <iostream>

using namespace std;

//costruttore
complex::complex( double r, double i)
{
parteReale = r;
parteImmaginaria = i;
}

//overloading vari
ostream &operator<<(ostream &output , const complex &p)
{
 if (p.parteImmaginaria >= 0)
    output << '(' << p.parteReale << "+j" 
         << p.parteImmaginaria << ')' << endl;
 else
    output << '(' << p.parteReale << "-j" 
         << p.parteImmaginaria*-1 << ')' << endl;
    
 return output;   
} 

istream &operator>>( istream &input, complex &a )
{
      input >> a.parteReale >> a.parteImmaginaria;


   return input;  
}

//overloading somma
complex complex::operator+(const complex &addendo2)const
{
  return complex ( parteReale + addendo2.parteReale ,
                  parteImmaginaria + addendo2.parteImmaginaria);
}

//overloading differenza
complex complex::operator-(const complex &addendo2)const
{
  return complex ( parteReale - addendo2.parteReale ,
                   parteImmaginaria - addendo2.parteImmaginaria);
}

//operatore di assegnamento
complex& complex::operator=(const complex &rValue )
{

  parteReale = rValue.parteReale;
  parteImmaginaria = rValue.parteImmaginaria;
   return *this;
}
//operatore di moltiplicazione
complex complex::operator*(const complex &fattore2)const
{

   return complex(parteReale * fattore2.parteReale -
                parteImmaginaria * fattore2.parteImmaginaria,
           parteImmaginaria * fattore2.parteReale +
            parteReale * fattore2.parteImmaginaria);

}

//operatore di uguaglianza
bool complex::operator==(const complex &pippo)const
{
   if (parteReale==pippo.parteReale &&
             parteImmaginaria==pippo.parteImmaginaria)
      return true;
   else
      return false;
}

//operatore diverso da
bool complex::operator!=(const complex &pluto)const
{

   if (parteReale!=pluto.parteReale && 
           parteImmaginaria!=pluto.parteImmaginaria)
     return true;
   else
     return false;
}

In tale codice è contenuta la dichiarazione delle funzioni membro e la ridefinizione degli operatori.

Esamineremo solo alcuni di questi operatori in quanto la comprensione di tale codice dovrebbe ormai essere semplice:

Notiamo che vi è l’inclusione del file dove è definita la dichiarazione della classe complex ovvero l’istruzione #include "complex.h".

ostream &operator<<(ostream &output , const complex &p) – significa che stiamo per ridefinire l’operatore << il quale restituisce un oggetto di tipo ostream e prende in ingresso una variabile di tipo ostream chiamata output e un const complex, constante poichè non deve essere modificato. All’interno delle parentesi graffe vi è scritta la stessa funzione che avevamo chiamato nell’esempio precedente stampaComplesso, il vantaggio è che ora possiamo permetterci di incanalare nello streaming d’uscita un numero complesso utilizzando l’operatore <<.

complex complex::operator+(const complex &addendo2)const – tale ridefinizione agisce sull’operatore di somma il quale deve restituire un ogetto di tipo complex prendendo in ingresso il secondo addendo che deve essere sempre un complex, a questo punto la funzione non fa altro che restituire la somma della parte reale con la parte reale che gli passiamo in input e lo stesso per la parte immaginaria. Tali somme vengono date come input al costruttore della classe complex.

complex& complex::operator=(const complex &rValue ) – indica che stiamo ridefinendo l’operatore di assegnazione il quale restituirà un complex, prendendo in input un altro complex dichiarato const e chiamato rValue (valore di destra). Il corpo della funzione non fa altro che copiare i valori di rValue in quelli dell’oggetto associato in quel momento all’operatore. Inoltre vi è la riga return *this; la quale sta a significare che si restituisce il valore dell’oggetto puntato. Facciamo un esempio:

      complex x(10,-3); //corrisponde a 10-j3

 

      complex y; //crea un nuovo oggetto complex

 

      y=x; //ad y viene associato il valore 10-j3

 

Il resto delle ridefinizioni è simile; ripeto ancora che i prototipi degli operatori sono sempre gli stessi bisogna quindi ricordarli o andare a consultarli ogni volta che si vuole effettuare tale operazione.

In ultimo vi è il codice da inserire nel main.cpp:

#include <iostream>
#include "complex.h"

using namespace std;

int main()
{
 //costruisce a con valori di default
 complex a;
 cout << "Valore di deafult del numero complesso a: "<<a;
  
 cout<< "nImmetti i nuovi valori di a" << endl;
 //modifica a
 cin >> a;
 cout << a;

 //cotruisce b con i valori di a
 complex b(a);
 cout << "Istanziamo b mediante il costruttore con i valori di a"
       << endl;
 cout << "b vale: " << b;
    
 //inizializza  c con i valori 3 e -5
 complex c(3,-5);  
 cout << "nViene istanziato c con i seg. valori" << c;

 cout << "nLa somma di c e b e': " << c+b;
    
 cout << "nVerifica dell'operatore di assegnamento con c=b" ;
 c=b;
 cout << "nOra c dovrebbe vale quanto b "<< c;
    
 cout << "na: " << a;
 cout << "nc: "<<c;
 cout << "nc*a: "<< c*a;
    
 if (a==b)
   cout << "a e' uguale a b" << endl;
 else
   cout << "a e' diverso da b" << endl;

 //instanzia un oggetto con valori 89 e 87
 complex d(89,87) ;    
 if (c!=d)
 cout << "c e d sono diversi" << endl;

 system ("PAUSE");
 return 0;
}

Unica nota importante da fare al main è l’inclusione del file #include "complex.h" grazie al quale possiamo utilizzare la classe complex con tutte le sue funzioni. Il resto sono semplici operazioni di input/output eseguite grazie all’overloading degli operatori di streaming.

Arrivati a questo punto siamo in grado di gestire una classe.

Output su file

Grazie alla potenza espressiva degli oggetti è possibile redirigere la stampa invece che a video su un file in maniera molto semplice modifichiamo il codice del main creato per la classe complex:

#include <fstream>
#include <iostream>
#include "complex.h"

using namespace std;

int main()
{
 ofstream pippo;
 pippo.open("test.txt",  ios::out);
 char s[]="test file";


    //costruisce a con valori di default
    complex a;
    pippo << "Valore di deafult del numero complesso a: "<<a;
    
    pippo<< "nImmetti i nuovi valori di a" << endl;
    //modifica a
    cin << a;
    pippo << a;

    //cotruisce b con i valori di a
    complex b(a);
    pippo << "Istanziamo b con i valori di a"
         << endl;
    pippo << "b vale: " << b;
    
    //inizializza  c con i valori 3 e -5
    complex c(3,-5);  
    pippo << "nViene istanziato c con i seg. valori" << c;

    pippo << "nLa somma di c e b e': " << c+b;
    
    pippo << "nVerifica dell'operatore di assegnamento con c=b" ;
    c=b;
    pippo << "nOra c dovrebbe vale quanto b "<< c;
    
    pippo << "na: " << a;
    pippo << "nc: "<<c;
    pippo << "nc*a: "<< c*a;
    
    if (a==b)
      pippo << "a e' uguale a b" << endl;
    else
      pippo << "a e' diverso da b" << endl;

    //instanzia un oggetto con valori 89 e 87
    complex d(89,87) ;    
    if (c!=d)
    pippo << "c e d sono diversi" << endl;

   system ("PAUSE");
   return 0;
}

Come prima cosa per creare un file di testo per redirigervi l’output, bisogna includere la libreria fstream.h.

Fatto ciò il codice procede come visto in precedenza a parte l’istruzione ofstream pippo; con la quale dichiariamo pippo di tipo ofstream ovvero streaming di output. Presto vi sarà svelato il perchè di questa istruzione.

pippo.open("test.txt",  ios::out);

questa istruzione ci permette di creare un file avente come nome quello indicato fra doppi apici, nel nostro caso test.txt; il comando ios::out sta ad indicare che quel file deve essere aperto esclusivamente in scrittura.

A questo punto il nostro file viene creato e sarà possibile scrivere in esso nella stessa maniera con cui stampiamo messaggi a video, se per stampare a video scriviamo:

cout << "hello, world";

per scrivere all’interno del nostro file useremo la sintassi:

pippo << "hello, world";

Eccovi spiegato perchè bisognava dichiarare un oggetto pippo della classe ofstream.

Il codice risulta quindi essere lo stesso del capitolo precedente sono solo state effettuate le sostituzioni da cout a pippo.

La gestione delle date

Come ultimo esempio svilupperemo la classe data introducendo dei controlli sull’immissione dei dati. Tali controlli si devono effettuare poichè un utente potrebbe inserire sbadatamente la data 32/13/23000 la quale non può esistere per svariati motivi, innanzitutto è impossibile che vi sia un giorno con data 32, poi non esiste il tredicesimo mese inoltre l’anno 23000 è un pò troppo distante per essere preso in considerazione. Vediamo come realizzare tali controlli.

File di dichiarazione di classe data.h:

class data
{
    public:
       //costruttore
       data(int =0,int = 0,int = 0);
       void setData();
       void getData();
       void stampaMese();
        
    private:
      int giorno;
      int mese;
      int anno;    
};

File di definizione di classe data.cpp:

#include "data.h"
#include <iostream>

using namespace std;

data::data(int gg,int mm, int aa)
{
giorno = gg;
mese = mm; 
anno = aa;   
}

void data::setData()
{
int g,m,a;

do
 {
  cout << "Inserire il giorno: ";
  cin >> g;    
 }
 while ( !(g >0 && g < 32))   ;        
 giorno = g;

do
 {
  do
  {
  cout << "Inserire il mese: ";
  cin >> m;    
  }  
  while( (m == 2 && giorno > 28) ||
    (m==4||m==6||m==9 ||m==11 && giorno == 31));
 }
 while ( !(m >0 && m < 13))   ;        
 mese = m;


do
 {
  cout << "Inserire l'anno: ";
  cin >> a;    
 }
 while ( !(a >1000 && a < 9999))   ;        
 anno = a;}

void data::getData()
{
cout << "la data selezionata corrisponde a " << giorno 
      << '/' << mese << '/' << anno << endl;    
}

void data::stampaMese()
{
cout << "la data selezionata corrisponde a " << giorno ;
switch(mese)
{
   case 1:
      cout << " Gennaio ";    
      break;
   case 2:
      cout << " Febbraio ";    
      break;
   case 3:
      cout << " Marzo ";    
      break;
   case 4:
      cout << " Aprile ";    
      break;
   case 5:
      cout << " Maggio ";    
      break;
   case 6:
      cout << " Giugno ";    
      break;
   case 7:
      cout << " Luglio ";    
      break;
   case 8:
      cout << " Agosto ";    
      break;
   case 9:
      cout << " Settembre ";    
      break;
   case 10:
      cout << " Ottobre ";    
      break;
   case 11:
      cout << " Novembre ";    
      break;
   case 12:
      cout << " Dicembre ";    
      break;                                    
}
cout << anno << endl;
    
}

Esempio di uso della classe main.cpp:

#include <iostream>
#include "data.h"

using namespace std;

int main()
{
  //inizializza una data con valori di default
  data d;
  //setta la data
  d.setData();
  //mostra la data in formato xx/xx/xxxx
  d.getData();
  //mostra la data in formato xx mese xxxx
  d.stampaMese();    
    
    
system("PAUSE");
return 0;

Vediamo insieme esclusivamente i controlli effettuati sull’immissione dei dati:

do{…} while ( !(g >0 && g < 32)); – indica che ciò che c’è scritto nel do viene ripetuto sino a che non si immette un valore comprese fra 1 e 30 da assegnare alla variabile privata giorno.

Il controllo successivo può sembrare molto complesso ma basta analizzarlo per parti per capire come funziona. Il do…while esterno controlla che venga immesso un valore per il mese accettabile ovvero compreso fra 1 e 12, quello interno approfondisce tale controllo ed impedisce, ad esempio, di inserire il mese di giugno se era stato immesso come giorno il valore 31. Quei controlli sono uniti tramite degli OR (||), ricordo che una espressione con degli OR risulta vera anche se uno solo dei predicati lo è.

Altri contenuti interessanti

Pubblicità

Leggi anche...

Radice quadrata in C: vediamo come calcolarla in diversi modi

La radice quadrata è un'operazione matematica piuttosto comune (in...

Sperimentare la sequenza di Collatz in C++

Vediamo come verificare la congettura di Collatz con C++....

Calcolare la radice quadrata con Python

In Python, calcolare la radice quadrata è un'operazione piuttosto...

12 film che ogni programmatore dovrebbe guardare (per trovare ispirazione e creatività)

Molti ragazzi hanno deciso di intraprendere una carriera da...

Cartonizzare una foto con Python e la libreria OpenCV

La cartoonization è una procedura grafica che consente di...

Creare flowchart (diagrammi di flusso) online: 5 strumenti gratuiti

Hai bisogno di realizzare una flow chart ma non...
Pubblicità