Così come il nome di un array rappresenta l’indirizzo del suo primo elemento, il nome di una function rappresenta l’indirizzo di partenza del suo codice.
Un puntatore a function contiene l’indirizzo di memoria della function; esso può essere passato o restituito da un’altra function e può essere assegnato ad un altro puntatore a function.
Il puntatore a function è molto utile quando è necessario, nel corso di un programma, scegliere tra più function.
Supponiamo di avere due function, funz1 e funz2 del tipo
int funz1(char) ……. e ……. int funz2(char)
ed un’altra function funz
void funz(int, int)
A seconda delle varie opportunità, ci si vuole servire di funz1 o di funz2: il primo intero int può essere il risultato della prima (funz1), il secondo di funz2 o viceversa.
Come possiamo scrivere il codice in maniera tale che la function funz possa richiamare l’una o l’altra tra funz1 o funz2?
Utilizzando i puntatori a function possiamo scrivere il prototipo di funz in questo modo:
void funz(int (*)(char), int)
il primo parametro int da assegnare alla function deve essere il risultato di una function che ha un parametro char (potrebbe essere funz1 o funz2 o qualsiasi altra function che ha le stesse caratteristiche: stessa tipologia di parametri formali e stesso tipo di valore restituito).
L’implementazione della function funz deve avere un’intestazione del tipo
void funz(int (*puntafunz)(char), int n)
mentre la function funz può essere richiamata con una delle seguenti modalità
funz(funz1, m) ……. funz(funz2, m)
Un esempio di utilizzo dei puntatori a function concerne l’ordinamento.
Supponiamo di voler ordinare un array A di interi.
Possiamo decidere di ordinarli sia in modo crescente che decrescente.
A questo scopo supponiamo di avere due function, chiamate crescente e decrescente, che restituiscono un valore booleano.
Come organizzare il programma in modo tale da passare la function che serve allo scopo?
Il puntatore a function risolve il problema.
Di seguito è riportato il codice che permette di scegliere fra il realizzare un ordinamento crescente o decrescente dei dati contenuti in un array di interi.
Le function interessate sono:
void ordinaB (int vet[], int N, bool (*confronta)(int,int))
bool crescente(int x, int y)
bool decrescente(int x, int y)
void scambia (int &x1, int &x2)
Con i puntatori a funzione scrivere una function che valuti il valore massimo contenuto in un vettore di interi, il minimo e il valore medio.
Con i puntatori a funzione scrivere una function che verifichi se assegnata una matrice di interi essa è unitaria, oppure simmetrica oppure con elementi tutti non negativi.
Nel caso delle variabili statiche la memoria viene allocata nel momento in cui la variabile è definita; per esempio le variabili
int x, a[10];
Impegnano la memoria nel momento stesso in cui vengono definite, mentre vengono deallocate nel momento in cui il programma termina, se la variabile è globale; se, invece, la variabile è locale (cioé definita in una function) allora viene deallocata quando termina la function.
In molti casi, però, è più comodo avere un controllo completo sulla allocazione di memoria cioè decidere quando allocare o deallocare la variabile in un qualsiasi punto del programma.
Questo è il concetto chiave di gestione dinamica della memoria.
Il C++ mette a disposizione del programmatore due istruzioni:
Volendo definire due variabili dinamiche, ad esempio un intero ed un array di 100 interi, si procede in questo modo:
Se l’operatore new fallisce, ritorna il puntatore nullo NULL: ciò indica che, per qualche ragione, non è stato possibile allocare altra memoria.
Il programma può utilizzare i puntatori P1 e P2 nei suoi calcoli; può, inoltre, deallocarli in un qualsiasi momento con l’istruzione delete:
delete P1; // cancella dalla memoria heap l'intero di cui P1 contiene l'indirizzo
delete [ ] P2; // cancella dalla memoria heap l'array di cui P2 contiene l'indirizzo-base
Vediamo l’utilizzo delle variabili dinamiche per un algoritmo di selection sort.
Dal main si evince che viene creato lo spazio e caricato un array con N interi (Legge Vettore), successivamente vengono ordinati (SortSel) e stampati (StampaVettore) i dati e quindi viene liberato lo spazio di memoria.
Si noti che gli elementi del vettore vengono individuati tramite i puntatori.
Allegato: SortPuntatori2
NB. Il codice allegato prevede l'uso del file header InsertArraypunt.h introdotto nella diap.6 di questa lezione.
Assegnato un array A scrivere una funzione booleana ricorsiva che, operando con i puntatori, determini se i valori disposti nell’array sono simmetrici rispetto al centro.
Assegnato un array A scrivere una procedura ricorsiva che, operando con i puntatori, ordini l’array con l’algoritmo dell’insertion sort.
Allegato: arraySimmetrico
NB. Il codice allegato prevede l'uso del file header InsertArraypunt.h introdotto nella diap.6 di questa lezione.
bool simm (int *array,int *lng, int pos)
{
*lng=*lng-1;
if(*lng <= pos)
return true;
else
{
if(array[*lng]==array[pos])
return simm (array,lng,pos+1);
else
return false;
}
}
Consideriamo il tipo in figura.
La variabile puntP non ha un nome in quanto il suo nome coincide con l’indirizzo.
Le variabili di questo tipo sono dette anonime; il loro contenuto può essere perso se non ne conosciamo più l’indirizzo.
Per memorizzare il contenuto cui punta puntP dobbiamo:
Una volta inseriti i dati questi si trovano nella memoria heap a cui è possibile accedere solo attraverso puntP e l’operatore “.”
Passaggio dei puntatori ad una function
Tenendo presente che il puntatore è una variabile che fornisce l’indirizzo di un’altra variabile, analizziamo alcune situazioni-tipo.
Abbiamo un programma principale contenente due puntatori ad un intero; esso richiama due procedure: nella prima la variabile intera è passata per valore, nella seconda per riferimento.
Il programma principale è lo stesso, mentre faremo variare le due procedure.
#include <iostream>
#include <cstdlib>
using namespace std;
void CallVal(int*);
void CallRif(int* &);
int main () {
int *p1, *p2;
p1=new int;
p2=new int;
*p1=5; *p2=5;
CallVal(p1);
CallRif(p2);
system("pause");
}
Di seguito si mostra il codice e l’output del che opera chiamate dei puntatori sia per valore che per riferimento.
Nell’esempio che segue il programma va in errore perché la procedura CallRif(p2) restituisce un puntatore p2=NULL che quindi non può essere stampato come richiesto.
/*
Scrivere un algoritmo ricorsivo per la funzione booleana che ricerca in una
matrice quadrata di ordine N se nelle sue righe dispari esiste almeno uno 0. Se
ciò accade restituisce TRUE altrimenti FALSE. */
#include
#include
using namespace std;
const int N=5;
bool verifica(int*, int*, int*);
Sia K un intero positivo ed R una matrice mxn di interi. Si scriva una funzione ricorsiva che stabilisca se R contenga al suo interno una serie di almeno K elementi consecutivi allineati in uno qualsiasi dei tre versi orizzontale, verticale oppure diagonale. Utilizzare i puntatori.