C++

Da Wikiversità, l'apprendimento libero.
Jump to navigation Jump to search
Crystal Clear app help index.png Per comprendere appieno gli argomenti trattati in questa lezione è consigliata la lettura della lezione Linguaggio C Crystal Project Kblackbox.png
lezione
C++
Tipo di risorsa Tipo: lezione
Materia di appartenenza Materia: Linguaggi di programmazione




C e C++[modifica]

Il C++ si può considerare un'estensione del linguaggio C. La grande differenza è dovuta all'implementazione della programmazione ad oggetti, per il resto la sintassi è rimasta pressoché uguale. Infatti un obiettivo primario era quello di mantenere la piena compatibilità con il C, conservando molto delle sue librerie. Il C++ è stato creato da Bjarne Stroustrup nel 1983 e continuò la sua evoluzione fino al 1987 nei laboratori della BELL. Come il C, con il C++ si possono creare i più svariati programmi, proprio perché è rimasto un linguaggio considerato di basso livello.

Miglioramenti principali[modifica]

  • Il miglioramento più importante, come già detto, è l'introduzione della programmazione ad oggetti.
  • Altro miglioramento importantissimo sono i namespace, grazie ai quali è possibile separare variabili con nomi uguali
  • È possibile dichiarare variabili contatore direttamente nei cicli for (Es. for(int i=0;i<.. )).
  • È possibile utilizzare i commenti di fine riga (//) oltre che i commenti multilinea (/* */).
  • Introdotto il campo di visibilità con const
  • Overloading delle funzioni (possibilità di utilizzare lo stesso nome per più funzioni)
  • Pieno controllo della memoria da parte del programmatore con new e delete
  • Introdotti i template, le funzioni generiche.

Le variabili[modifica]

In c++ le variabili si dichiarano nello stesso modo del C:

tipovariabile identificatore;
tipovariabile identificatore = valore;
tipovariabile identificatore1,identificatore2,...;

La prima dichiarazione è quella standard. La seconda assegna un valore iniziale alla variabile, mentre la terza permette di dichiarare più variabili dello stesso tipo contemporaneamente.

Tipi di variabile[modifica]

In ordine di dimensione:

  • void (valore nullo)
  • bool
  • char
  • int
  • float
  • double

Tipi composti[modifica]

Per formare altri tipi standard si antepongono al tipo di variabile gli identificatori unsigned, signed, short, long. I tipi possibili sono (in ordine di grandezza):

  • short int
  • long int
  • signed short int
  • signed int
  • signed long int
  • signed char
  • unsigned short int
  • unsigned int
  • unsigned long int
  • unsigned char
  • long double

Quando non è specificato il tipo (esempio short) si sottointende int. Esiste un altro tipo poco utilizzato che è wchar_t.

Costanti[modifica]

Le costanti sono contenitori che non possono modificare il proprio valore nel tempo.

Costante di tipo[modifica]

La dichiarazione delle costanti è identica a quella delle variabili eccetto che deve sempre essere specificato un valore e la dichiarazione inizia con const. Esempio:

const int pippo=7736;

Costante di definizione[modifica]

Si utilizza le direttive di definizione con il comando #define. Non bisogna specificare il tipo. Esempio:

#define pippo "ciao come va?"

Nelle direttive di definizione NON va specificato il punto e virgola alla fine della dichiarazione ed esse vanno poste all'inizio del file, prima di qualsiasi funzione o variabile.

Include[modifica]

Inclusioni di sistema[modifica]

Le inclusioni di file di sistema vanno effettuate anch'esse con le direttive di definizione attraverso il comando #include <nomefile>. Tutti i file che verranno inclusi in questo modo devono essere presenti nel sistema o è necessario passare al compilatore le directory di inclusione. Esempio:

#include <iostream>

Inclusioni personali[modifica]

Le inclusioni di file di sistema vanno effettuate anch'esse con le direttive di definizione attraverso il comando #include "nomefile". Tutti i file che verranno inclusi in questo modo devono essere presenti nella cartella del file che le richiama. Esempio:

#include "myheader.h"

Hello World[modifica]

#include <iostream> //Libreria di sistema per l'I/O

int main()
{  //inizio programma
  cout << "Hello World"; //Stampo un messaggio
  return 0; //Esco dalla funzione
} //fine programma

La funzione che siamo andati a creare è la main(). Questa funzione è l'unica e la prima che il compilatore andrà a cercare e compilare per prima (sarà anche la prima che partirà quindi). In ambiente win32 vedremo che non si chiamerà più main(). il void davanti alla funzione sta ad indicare il tipo della funzione, quindi il tipo che return andrà a ritornare appunto. Per la funzione main non è essenziale il tipo, ma si consiglia sempre di utilizzare void o int. In quest'ultimo caso l'uscita dal programma verrà effettuata da return(0);. Si consiglia di utilizzare sempre anche quest'ultimo costrutto per velocizzare l'esecuzione del compilatore ed evitare spiacevoli errori di warning

Using e Namespace[modifica]

L'esempio precedente non funziona in quanto manca un componente fondamentale: il namespace! La direttiva

#include <iostream>

,infatti, carica dei nomi senza specificare il namespace a cui si riferiscono (infatti se fate cout << "ciao"; non funzionerà). È possibile scegliere un namespace attraverso il costrutto:

using std::funzione

Nella quasi totalità dei compilatori il namespace di default è std. Se volessimo utilizzare invece tutte le funzioni (equivalente a #include <iostream.h>) dovremmo scrivere:

using namespace std

che utilizza tutte le funzioni del namespace. Esempio completo:

#include <iostream> //Libreria di sistema che contiene "cout"
using std::cout; //Attivo la funzione cout
int main()
{  //inizio programma
  cout << "Hello World"; //Stampo un messaggio
  return (0); //Esco dalla funzione
} //fine programma

Programmazione ad oggetti[modifica]

Funzioni[modifica]

Le funzioni si possono considerare dei sottoprogrammi che vengono chiamati dallo script principale e svolgono una determinata azione. Una volta conclusa la loro operazione, l'esecuzione ritorna allo script principale. Una funzione si dichiara in questo modo:

''tipo_restituito identificatore ([tipo argomento1,] [tipo argomento2,] [...]) { funzione }''

Ricorda:(Le [parentesi quadre] indicano che quello che ci sta dentro è opzionale) Il tipo resitutito è il tipo di variabile che la funzione dovrà restituire. L'identificatore è il nome della funzione e gli argomenti sono le variabile che lo script principale passa alla funzione. Nelle {} viene contenuto il codice.

Ecco un esempio completo.

#include <iostream> //Libreria di sistema che contiene "cout"
using namespace std; //Attivo il namespace

int prodotto(int,int); //Prototipo della funzione (vedremo dopo a cosa serve)

int main()
{  //inizio programma
  int a=5;
  int b=20;
  int res=prodotto(a,b); //Eseguo la funzione
  cout << "5x20=" << res << endl; //endl serve per andare a capo
  res=prodotto(a,10); //Eseguo la funzione
  cout << "5x10=" << res << endl;
  return (0); //Esco dalla funzione
} //fine programma

int prodotto(int x, int y) {
   int result=x*y;
   return(result);
}

La funzione principale, come avrete notato, è il main(). È una funzione particolare che di norma possiede come tipo resituito int. Talvolta è accettato anche bool e void. L'utilizzo di questa funzione verrà ripreso più avanti.

Nell'esempio sopra abbiamo creato un prototipo della funzione prodotto. Un prototipo serve a dire al compilatore quali funzioni esistono nel programma. Non è sempre necessario, a meno che, la funzione venga richiamata prima di essere creata. Analizziamo semplificatamente cosa fa il compilatore quanto richiede il codice sopra:

  • Cerco se esistono prototipi: trovato un prototipo della funzione int prodotto
  • Cerco una funzione denominata main()
  • Trovata! Inizio ad eseguirla
  • ALT! Alla riga 10 viene invocata una funzione sconosciuta.
  • Cerco il prototipo: Trovato! Significa che la funzione esiste nel programma
  • Cerco una funzione denominata int prodotto()
  • Trovata! Inserisco il suo codice in memoria
  • Continuo l'esecuzione della main()

Questo è quello che "pensa" il compilatore. Se non ci fosse stato il prototipo, il compilatore non avrebbe saputo dove andare a cercare la funzione, generando un errore. Ora che abbiamo spiegato a che cosa serve, spieghiamo come si usa. La definizione del puntatore può avvenire come con la funzione, cioè:

int prodotto(int x, int y); //RICORDATE IL ; !!!

oppure se non vogliamo "imporre" il nome delle variabili

int prodotto(int,int); //RICORDATE IL ; !!!

Overloading[modifica]

I prototipi diventano utili quando si pratica l'overloading delle funzioni. L'overloading non è possibile in C, quindi è una novità di C++. L'overloading delle funzioni permette di dichiarare due funzioni con lo stesso nome ma con tipi differenti. Vediamo in pratica come funziona.

#include <iostream> //Libreria di sistema che contiene "cout"
using namespace std; //Attivo il namespace

int prodotto(int,int);        //Prototipo 1
float prodotto(float,float);  //Prototipo 2 (Overloading)

int main()
{  //inizio programma
   cout << prodotto(5,5) << endl;
   cout << prodotto(5.2,9.9) << endl;
   return(0);
}

int prodotto(int a, int b) {
   int p = a*b;
   return(p);
}

float prodotto(float a, float b) {
   float p = a*b;
   return(p);
}

In questo modo il programma chiamerà in modo automatico la funzione appropriata in base al tipo di argomenti fornito. Ricordate che non è possibile mantenere gli stessi tipi argomenti, che anch'essi devono cambiare.

La funzione main()[modifica]

La funzione main() (lavorando NON in win32) riceve due argomenti. Se non sono necessari possiamo ometterli come abbiamo fatto fino ad adesso. Il prototipo della funzione main "completa" si presenta come:

int main(int argc, char* argv[]);
  • int argc: numero di argomenti
  • char* argv[]: array degli argomenti

In questo caso gli argomenti vengono passati tramite: programma.exe argomento1 argomento2 ...
Vediamo un esempio dell'utilizzo di questi:

#include <iostream>

using namespace std;

int main(int argc, char* argv[]) {  
   if(argc < 2) { //L'argomento 1 è il nome del programma
      cout << "Devi inserire almeno un argomento!" << endl;
      return(1); //Esco (il valore 1 si usa di solito quando c'è un errore, ma non è obbligatorio)
   }

   for(int i=1;i<argc;i++) {
      cout << "Argomento " << i << ": " << argv[i] << endl; 
   }
   return(0); //Mentre 0 di solito indica corretta esecuzione
}

L'utilizzo dello 0 e dell'1 nel return non causa alcun effetto, se non per alcuni debugger. L'esecuzione del programma non viene modificata.

Classi[modifica]

Una classe di c++ è il punto fondamentale della programmazione ad oggetti. Si può considerare un contenitore dove vengono inserite informazioni con determinati attributi. Un esempio classico: un carrello della spesa è la nostra classe. I prodotti che ci sono all'interno sono ordinati in modo preciso (e il metodo di ordinamento costituisce le proprietà e gli attributi). Le azioni di inserimento ed estrazione dal carrello dei prodotti sono chiamati metodi o funzioni membro.
Occorre prestare attenzione alla differenza tra classe e oggetto. La classe è il modello dal quale l'oggetto è generato: nell'informatica si dice che l'oggetto è istanza di una classe.
La struttura di una classe si presenta:

class nomeclasse
{
public:
     tipovariabile nomevariabile;
     tipofunzione nomefunzione;
protected:
     tipovariabile nomevariabile;
     tipofunzione nomefunzione;
private:
     tipovariabile nomevariabile;
     tipofunzione nomefunzione;
};

RICORDATEVI DI NON DIMENTICARE IL ; DOPO LA CHIUSURA DELLA CLASSE STESSA!!
Vediamo subito un esempio:

class carrello
{
public:
     int tot_prodotti;
     void inserisci_prodotto(int codice);
     int estrai_prodotto(int numero);
private:
     int* codici_prodotti;
};

Visibilità[modifica]

Nella classe carrello abbiamo utilizzato due etichette (public e private). Esistono 3 campi di visibilità nelle classi c++:

  • public: Le variabili, funzioni o oggetti sono visibili in qualsiasi punto del programma e sono richiamabili da qualsiasi funzione.
  • protected: Le variabili, funzioni o oggetti sono visibili solo da funzioni o oggetti dalla classe stessa e dalle relative sottoclassi o classi figlie.
  • private: Le variabili, funzioni o oggetti sono visibili solo da funzioni o oggetti dalla classe stessa.

A differenza di altri linguaggi, non si utilizzano le {} perché sono etichette.

Inizializzazione[modifica]

Per inizializzare una classe si utilizza:

nomeclasse nomeoggetto;

Esempio:

carrello CarrelloDellaSpesa;

In questo modo tutti i metodi e le variabili vengono richiamate con CarrelloDellaSpesa.nomemetodo();.

Implementazione[modifica]

Una classe senza l'implementazione dei metodi è pressocchè inutile (si potrebbe usare una struct in questo caso). I metodi (o funzioni) vengono implementati attraverso l'operatore di scope:

void carrello::inserisci_prodotto(int codice)
{
     codici_prodotti[tot_prodotti] = codice;
     tot_prodotti++;
}
int carrello::estrai_prodotto(int numero)
{
     return(codici_prodotti[numero]);
}

In questo modo è possibile richiamarli dalla main in questo programma (ipotizzando che l'implementazione sia in carrello.cpp e la definizione in carrello.h):

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

int main()
{
    carrello CarrelloDellaSpesa;
    CarrelloDellaSpesa.inserisci_prodotto(322);
    CarrelloDellaSpesa.inserisci_prodotto(342);
    CarrelloDellaSpesa.inserisci_prodotto(882);
    CarrelloDellaSpesa.inserisci_prodotto(912);
    CarrelloDellaSpesa.inserisci_prodotto(623);

    cout << "Sono presenti " << ++CarrelloDellaSpesa.tot_prodotti << " prodotti" << endl;
    cout << "Lista prodotti:" << endl;
    for(int i=0; i<CarrelloDellaSpesa.tot_prodotti;i++)
         cout << CarrelloDellaSpesa.estrai_prodotto(i) << endl;
    return(0);
}

Template[modifica]

I template di funzione e i template di classe permettono di specificare, in un solo segmento di codice, il modello per un vasto insieme di funzioni o di classi. Più semplicemente, i template permettono di generare automaticamente le medesime funzioni che agiscono su dati diversi, restituendo il tipo di dato richiesto. Lo stesso ragionamento vale per l'overloading, solo che i template hanno più flessibilità e velocità, infatti non vengono scritte n funzioni ma una sola, limitando di molto il lavoro del compilatore. È svantaggioso quando bisogna lavorare solo su due tipi, a questo punto conviene utilizzare l'overloading.

Template di funzione[modifica]

Vediamo subito come si dichiarano.

template<typename T>

T è un qualunque identificatore valido. Questa istruzione va messa preferibilmente all'inizio del programma (o nell'header) e comunque prima della funzione che utilizzerà il template. Vediamo immediatamente un esempio concreto del suo utilizzo:

#include <iostream>
using namespace std;

//Definiamo il template
template<typename VAR>

void stampaArray(VAR *array, int count)
{
   for(int i=0; i<count;i++) 
   { //Stampiamo il contenuto dell'array
       cout << i << ": " << array[i] << endl;
   }
}

int main() {
   int arrayI[] = {1,2,564,23,11,34};
   char arrayC[] = "cmk";
   float arrayF[] = {1.5,8.2,12.1,9.0};

   cout << endl << "ArrayI:" <<endl;
   stampaArray(arrayI,6);
   cout << endl << "ArrayC:" <<endl;
   stampaArray(arrayC,3);
   cout << endl << "ArrayF:" <<endl;
   stampaArray(arrayF,4);
}

Analizziamo il codice. Dopo aver dichiarato il template, la funzione stampaArray possiede un argomento che contiene il template. L'argomento è VAR *array. Questa scrittura sostituisce la classica scrittura int *VAR e dato che è un array, si aggiunge la parola array dopo il segno di puntatore. Questo codice ha fatto risparmiare tempo al programmatore che avrebbe dovuto scriversi i tre prototipi (e le relative funzioni) del tipo int, char e float.

La gestione delle eccezioni[modifica]

Metodi di ricerca e ordinamento[modifica]

Altri Progetti[modifica]