sabato 28 febbraio 2015

Modern C++ - Le funzioni

Salve a tutti e ben ritornati su queste pagine del corso di Modern C++, dopo una pausa dovuta ad impegni personali.

Le ultime puntate abbiamo parlato di puntatori e allocazione statica e dinamica: abbiamo visto anche come funzionano gli operatori * e & che permettono di accedere al valore contenuto nelle variabili mediante il loro indirizzo di memoria.

Oggi parliamo delle funzioni e del passaggio dei parametri per valore o per indirizzo.
Innanzitutto, una funzione si dichiara nel seguente modo:

[tipo di dato] nomeFunzione([lista di parametri])
{
    //corpo della funzione
}

dove il tipo di dato puo' essere built-in (char, int, float, double, bool), un vettore o stringa di caratteri (quindi std::string, std::vector<>, ecc.) oppure di tipo void, ovvero non ritorna alcun valore.
Il nome della funzione puo' esser definita in qualsiasi modo, secondo linee di stile personale o convenzionali, come ad esempio:
  • nome_funzione
  • NomeFunzione
  • nomeFunzione
  • _nomeFunzione
In genere, in C++ si preferisce utilizzare la prima lettera maiuscola solo per Strutture e Classi, mentre per le funzioni, si preferisce utilizzare il carattere minore e, preferibilimente, un nome esplicativo di cosa viene eseguito all'interno di una funzione, del tipo aggiungi_numero, salva_file, ecc., in modo da non dover scrivere commenti aggiuntivi in cui si descrive cosa viene eseguito all'interno della funzione.

Una funzione, al fine di operare, necessita (ma non sempre) di dati di input sui quali operare: una funzione che esegue la somma di due numeri interi e che restituisce il risultato, ha bisogno di esser definita nel seguente modo:

int somma(int a, int b)
{
    return a+b;
}

Come si vede dall'esempio, nella lista dei parametri, ne ho specificati 2, di tipo int e che si chiamano a e b. In uscita da questa funzione, si ottiene la somma dei due parametri, espressa dalla parola chiave return. Solo le funzioni di tipo void non necessitano della parola chiave return in modo esplicito, ma solo se si vuole uscire dalla funzione stessa in modo forzato (si pensi ad un ciclo infinito in cui si verifica una condizione per cui si debba interromperlo).

Ma come viene chiamata questa funzione? Si pensi al seguente esempio:

#include <iostream>
using namespace std;

int somma(int a, int b)
{   
   a = a+1;
   return a+b;
}

int main()
{
   int risultato = somma(2,3);  //a = 2, b = 3
   cout << "Il risultato di 2+3 e': " << risultato << endl; //6!
   return 0;
}
In questo modo, il valore 2 viene copiato nella variabile a e il valore 3 viene copiato nella variabile b. Tale tipo di chiamata viene detto passaggio per valore, ovvero i parametri specificati, al termine dell'esecuzione della funzione, tornano al loro valore originale, precedente alla chiamata della funzione stessa. Nell'esempio, a = 2 quando la funzione somma() termina.

Se volessimo mantenere il valore nuovo di a anche dopo l'esecuzione della funzione somma()? Dovremmo ricorrere al passaggio per riferimento, ovvero passare il riferimento della variabile, in modo da modificarla senza eseguirne una copia temporanea:

#include <iostream>
using namespace std;

int somma(int &a, int b)
{   
   a = a+1;
   return a+b;
}

int main()
{
   int x = 2;
   int y = 3; 
   int risultato = somma(x, y);  //a = x, b = y
   //adesso x = 3
   cout << "Il risultato di x+y e': " << risultato << endl; //6!
   return 0;
}
 
Occorre quindi modificare la lista dei parametri, facendo precedere l'operatore & al nome della variabile.

Una alternativa al passaggio di parametri per riferimento e' il passaggio di parametri per indirizzo, ovvero mediante l'operatore *:

#include <iostream>
using namespace std;

int somma(int *a, int *b)
{   
   a = a+1;
   return a+b;
}

int main()
{
   int x = 2;
   int y = 3;
   int risultato = somma(&x,&x);  //a = x, b = y
   //adesso x = 3
   cout << "Il risultato di x+y e': " << risultato << endl; //6!
   return 0;
}
 
Sia per il passaggio per riferimento o per indirizzo, occorre dichiarare delle variabili, in modo da poter poi salvarne il risultato dopo l'esecuzione della funzione.
Per quanto riguarda tipi di dato semplici (int, double, float), le prestazioni dei tre metodi si equivalgono, mentre per quanto riguarda strutture di dati grandi o complesse, e' preferibile utilizzare il passaggio per riferimento o indirizzo, mai quello per valore. Nel caso in cui non si voglia modificare il valore delle variabili all'interno del corpo di una funzione, occorre far precedere la parola chiave const:

int somma(const int &a, const int &b)
{
   return a+b;
}

in tal modo, si garantisce che a e b non possono esser modificate.

Alla prossima puntata :-)

Nessun commento:

Posta un commento