Puntatori a funzioni nella programmazione in C con esempi

⚡ Riepilogo intelligente

In C, i puntatori a funzione memorizzano l'indirizzo del codice eseguibile, consentendo ai programmi di chiamare le funzioni indirettamente, passarle come argomenti, costruire tabelle di dispatch da array e implementare callback come la routine di confronto qsort per progetti flessibili e riutilizzabili.

  • 🔗 Dichiarazione: Un puntatore a funzione utilizza la forma tipo_di_ritorno (*nome)(argomenti); le parentesi attorno al nome sono obbligatorie.
  • 🔄 Scambia per indirizzo: Passare indirizzi di variabili a parametri puntatore consente a una funzione di modificare i valori originali del chiamante, come nell'esempio di scambio.
  • 📦 Array: Il nome di un array è già di per sé un puntatore, quindi le funzioni possono ricevere un array o restituire un puntatore a un array statico.
  • Tabelle di dispacciamento: Un array di puntatori a funzione sostituisce lunghe catene di switch o if, indicizzando direttamente l'operazione selezionata.
  • 🧊 puntatori vuoti: Un parametro o tipo di ritorno void * accetta qualsiasi tipo, che la funzione converte nel tipo originale prima dell'utilizzo.
  • 📞 Richiamate: Passare un puntatore a funzione come argomento abilita le callback come la funzione di confronto qsort.
  • 🤖 Assistenza AI: GitHub Copilot e gli assistenti basati sull'intelligenza artificiale generano automaticamente la sintassi dei puntatori a funzione, i typedef e le firme delle callback, quindi segnalano i parametri non corrispondenti.

Puntatori a funzione in C

I puntatori offrono grandi possibilità alle funzioni "C" che siamo limitati a restituire un valore. Con i parametri del puntatore, le nostre funzioni ora possono elaborare i dati effettivi anziché una copia dei dati.

Per modificare i valori effettivi delle variabili, l'istruzione chiamante passa gli indirizzi ai parametri del puntatore in una funzione.

Esempio di puntatori di funzioni

Ad esempio, il programma successivo scambia due valori di due:

void swap (int *a, int *b);
int main() {
  int m = 25;
  int n = 100;
  printf("m is %d, n is %d\n", m, n);
  swap(&m, &n);
  printf("m is %d, n is %d\n", m, n);
  return 0;}
void swap (int *a, int *b) {
  int temp;
  temp = *a;
  *a = *b;
  *b = temp;}
}

Produzione:

m is 25, n is 100
m is 100, n is 25

Esempio di puntatori di funzioni

Il programma scambia i valori effettivi delle variabili perché la funzione vi accede tramite l'indirizzo utilizzando pointer. Qui discuteremo il processo del programma:

  1. Dichiariamo la funzione responsabile dello scambioping i due valori delle variabili, che accetta due puntatori interi come parametri e restituisce un valore qualsiasi quando viene chiamata.
  2. Nella funzione principale, dichiariamo e inizializziamo due variabili intere ('m' e 'n'), quindi stampiamo rispettivamente i loro valori.
  3. Chiamiamo la funzione swap() passando l'indirizzo delle due variabili come argomenti utilizzando il simbolo della e commerciale. Successivamente, stampiamo i nuovi valori scambiati delle variabili.
  4. Qui definiamo il contenuto della funzione swap() che accetta due indirizzi di variabili intere come parametri e dichiariamo una variabile intera temporanea utilizzata come terza casella di archiviazione per salvare una delle variabili valore che verrà inserita nella seconda variabile.
  5. Salva il contenuto della prima variabile puntata da "a" nella variabile temporanea.
  6. Memorizza la seconda variabile puntata da b nella prima variabile puntata da a.
  7. Aggiorna la seconda variabile (indicata da b) con il valore della prima variabile salvata nella variabile temporanea.

Funzioni con parametri di array

In C, non possiamo passare un array per valore a una funzione. Considerando che il nome di un array è un puntatore (indirizzo), quindi passiamo semplicemente un nome di array a una funzione che significa passare un puntatore all'array.

Ad esempio, consideriamo il seguente programma:

int add_array (int *a, int num_elements);
int main() {
  int Tab[5] = {100, 220, 37, 16, 98};
  printf("Total summation is %d\n", add_array(Tab, 5)); 
  return 0;}
int add_array (int *p, int size) {
  int total = 0;
  int k;
  for (k = 0; k < size; k++) {
    total += p[k];  /* it is equivalent to total +=*p ;p++; */}
 return (total);}

Produzione:

 Total summation is 471

Qui spiegheremo il codice del programma con i suoi dettagli

Funzioni con parametri di array

  1. Dichiariamo e definiamo la funzione add_array() che accetta un indirizzo di array (puntatore) con il suo numero di elementi come parametri e restituisce la somma totale accumulata di questi elementi. Il puntatore viene utilizzato per iterare gli elementi dell'array (usando la notazione p[k]) e accumuliamo la somma in una variabile locale che verrà restituita dopo aver iterato l'intero elemento dell'array.
  2. Dichiariamo e inizializziamo un array di numeri interi con cinque elementi interi. Stampiamo la somma totale passando il nome dell'array (che funge da indirizzo) e la dimensione dell'array al file aggiungi_array()chiamata funzione come argomenti.

Funzioni che restituiscono un array

In C, possiamo restituire un puntatore a un array, come nel seguente programma:

#include <stdio.h>
int * build_array();
int main() {
  int *a;
  a = build_array(); /* get first 5 even numbers */
  for (k = 0; k < 5; k++)
    printf("%d\n", a[k]);
  return 0;}
int * build_array() {
  static int Tab[5]={1,2,3,4,5};
   return (Tab);}

Produzione:

1
2
3
4
5

E qui discuteremo i dettagli del programma

Funzioni che restituiscono un array

  1. Definiamo e dichiariamo una funzione che restituisce un indirizzo di array contenente un valore intero e non accetta argomenti.
  2. Dichiariamo un puntatore intero che riceve l'array completo costruito dopo la chiamata della funzione e stampiamo il suo contenuto eseguendo l'iterazione dell'intero array di cinque elementi.

Si noti che per memorizzare l'indirizzo dell'array restituito dalla funzione viene definito un puntatore, non un array. Nota anche che quando una variabile locale viene restituita da una funzione, dobbiamo dichiararla come statica nella funzione.

Puntatori di funzioni

Poiché sappiamo per definizione che i puntatori puntano a un indirizzo in qualsiasi posizione di memoria, possono anche puntare all'inizio del codice eseguibile come funzioni in memoria.
Un puntatore a una funzione viene dichiarato con *, l'istruzione generale della sua dichiarazione è:

return_type (*function_name)(arguments)

Devi ricordare che le parentesi attorno a (*nome_funzione) sono importanti perché senza di esse, il compilatore penserà che nome_funzione restituisca un puntatore di tipo_ritorno.
Dopo aver definito il puntatore di funzione, dobbiamo assegnarlo a una funzione. Ad esempio, il programma seguente dichiara una funzione ordinaria, definisce un puntatore di funzione, assegna il puntatore di funzione alla funzione ordinaria e dopodiché chiama la funzione tramite il puntatore:

#include <stdio.h>
void Hi_function (int times); /* function */
int main() {
  void (*function_ptr)(int);  /* function pointer Declaration */
  function_ptr = Hi_function;  /* pointer assignment */
  function_ptr (3);  /* function call */
 return 0;}
void Hi_function (int times) {
  int k;
  for (k = 0; k < times; k++) printf("Hi\n");}

Produzione:

Hi
Hi
Hi

Puntatori a funzioni in C

  1. Definiamo e dichiariamo una funzione standard che stampa un testo Hi k volte indicato dal parametro times quando viene chiamata la funzione
  2. Definiamo una funzione puntatore (con la sua dichiarazione speciale) che accetta un parametro intero e non restituisce nulla.
  3. Inizializziamo la nostra funzione puntatore con la funzione Hi_, il che significa che il puntatore punta alla funzione Hi_().
  4. Invece della chiamata di funzione standard da parte di taping il nome della funzione con argomenti, chiamiamo solo la funzione puntatore passando il numero 3 come argomento, e questo è tutto!

Tieni presente che il nome della funzione punta all'indirizzo iniziale del codice eseguibile come un nome di array che punta al suo primo elemento. Pertanto, istruzioni come function_ptr = &Hi_function e (*funptr)(3) sono corrette.
NOTA: Non è importante inserire l'operatore di indirizzo & e l'operatore indiretto * durante l'assegnazione della funzione e la chiamata della funzione.

Matrice di puntatori a funzioni

Un array di puntatori a funzione può svolgere un ruolo di interruttore o di istruzione if per prendere una decisione, come nel prossimo programma:

#include <stdio.h>
int sum(int num1, int num2);
int sub(int num1, int num2);
int mult(int num1, int num2);
int div(int num1, int num2);

int main() 
{  int x, y, choice, result;
  int (*ope[4])(int, int);
  ope[0] = sum;
  ope[1] = sub;
  ope[2] = mult;
  ope[3] = div;
  printf("Enter two integer numbers: ");
  scanf("%d%d", &x, &y);
  printf("Enter 0 to sum, 1 to subtract, 2 to multiply, or 3 to divide: ");
  scanf("%d", &choice);
  result = ope[choice](x, y);
  printf("%d", result);
return 0;}

int sum(int x, int y) {return(x + y);}
int sub(int x, int y) {return(x - y);}
int mult(int x, int y) {return(x * y);}
int div(int x, int y) {if (y != 0) return (x / y); else  return 0;}
Enter two integer numbers: 13 48
Enter 0 to sum, 1 to subtract, 2 to multiply, or 3 to divide: 2
624

Qui discutiamo i dettagli del programma:

Matrice di puntatori a funzioni

  1. Ne dichiariamo e ne definiamo quattro funzioni che prendono due argomenti interi e restituiscono un valore intero. Queste funzioni sommano, sottraggonotract, moltiplica e dividi i due argomenti relativi alla funzione che viene chiamata dall'utente.
  2. Dichiariamo 4 numeri interi per gestire rispettivamente gli operandi, il tipo di operazione e il risultato. Inoltre, dichiariamo un array di quattro puntatori a funzione. Ogni puntatore a funzione dell'elemento dell'array accetta due parametri interi e restituisce un valore intero.
  3. Assegniamo e inizializziamo ogni elemento dell'array con la funzione già dichiarata. Ad esempio, il terzo elemento che è il terzo puntatore a funzione punterà alla funzione dell'operazione di moltiplicazione.
  4. Cerchiamo operandi e tipo di operazione digitati dall'utente con la tastiera.
  5. Abbiamo chiamato l'elemento appropriato dell'array (puntatore a funzione) con argomenti e memorizziamo il risultato generato dalla funzione appropriata.

L'istruzione int (*ope[4])(int, int); definisce l'array di puntatori a funzione. Ogni elemento dell'array deve avere gli stessi parametri e tipo restituito.
L'istruzione result = ope[choice](x, y); esegue la funzione appropriata in base alla scelta fatta dall'utente. I due interi immessi sono gli argomenti passati alla funzione.

Funzioni che utilizzano puntatori void

I puntatori void vengono utilizzati durante le dichiarazioni di funzioni. Usiamo un void * return type permette di restituire qualsiasi tipo. Se assumiamo che i nostri parametri non cambino quando passiamo a una funzione, lo dichiariamo come const.
Per esempio:

 void * cube (const void *);

Considera il seguente programma:

#include <stdio.h>
void* cube (const void* num);
int main() {
  int x, cube_int;
  x = 4;
  cube_int = cube (&x);
  printf("%d cubed is %d\n", x, cube_int);
  return 0;}

void* cube (const void *num) {
  int result;
  result = (*(int *)num) * (*(int *)num) * (*(int *)num);
  return result;}

Risultato:

 4 cubed is 64

Qui discuteremo i dettagli del programma:

Funzioni che utilizzano puntatori void

  1. Definiamo e dichiariamo una funzione che restituisce un valore intero e accetta un indirizzo di variabile immutabile senza un tipo di dati specifico. Calcoliamo il valore del cubo della variabile content (x) puntata dal puntatore num, e poiché è un puntatore void, dobbiamo digitarlo cast su un tipo di dati intero usando un puntatore di notazione specifica (* datatype), e restituiamo il valore del cubo.
  2. Dichiariamo l'operando e la variabile risultato. Inoltre, inizializziamo il nostro operando con il valore “4”.
  3. Chiamiamo la funzione cubo passando l'indirizzo dell'operando e gestiamo il valore restituito nella variabile risultato

Puntatori a funzioni come argomenti

Un altro modo per sfruttare un puntatore a funzione passandolo come argomento a un'altra funzione talvolta chiamata "funzione di callback" perché la funzione ricevente "lo richiama".
Nel file di intestazione stdlib.h, la funzione Quicksort “qsort()” utilizza questa tecnica che è un algoritmo dedicato all'ordinamento di un array.

void qsort(void *base, size_t num, size_t width, int (*compare)(const void *, const void *))
  • void *base: puntatore void all'array.
  • size_t num: il numero dell'elemento dell'array.
  • size_t larghezza La dimensione dell'elemento.
  • int (*compare (const void *, const void *) : puntatore a funzione composto da due argomenti e restituisce 0 quando gli argomenti hanno lo stesso valore, <0 quando arg1 viene prima di arg2 e >0 quando arg1 viene dopo arg2.

Il seguente programma ordina un array di numeri interi dal più piccolo al più grande utilizzando la funzione qsort():

#include <stdio.h>
#include <stdlib.h>
int compare (const void *, const void *); 
int main() {
  int arr[5] = {52, 14, 50, 48, 13};
  int num, width, i;
  num = sizeof(arr)/sizeof(arr[0]);
  width = sizeof(arr[0]);
  qsort((void *)arr, num, width, compare);
  for (i = 0; i < 5; i++)
    printf("%d ", arr[ i ]);
  return 0;}
int compare (const void *elem1, const void *elem2) {
  if ((*(int *)elem1) == (*(int *)elem2))  return 0;
  else if ((*(int *)elem1) < (*(int *)elem2)) return -1;
  else return 1;}

Risultato:

 13 14 48 50 52

Qui discuteremo i dettagli del programma:

Puntatori a funzioni come argomenti

  1. Definiamo la funzione di confronto composta da due argomenti e restituisce 0 quando gli argomenti hanno lo stesso valore, <0 quando arg1 viene prima di arg2 e >0 quando arg1 viene dopo arg2. I parametri sono un tipo di puntatori void espressi nel tipo di dati dell'array appropriato (numero intero)
  2. Definiamo e inizializziamo un array di numeri interi. La dimensione dell'array è memorizzata nel file num variabile e la dimensione di ciascun elemento dell'array viene memorizzata nella variabile larghezza utilizzando sizeof() predefinito Operatore C.
  3. Richiamiamo la funzione qsort e passiamo il nome dell'array, la dimensione, la larghezza e la funzione di confronto definita in precedenza dall'utente per ordinare l'array in ordine crescente. Il confronto verrà eseguito prendendo ad ogni iterazione due elementi dell'array fino a quando l'intero array non sarà ordinato.
  4. Stampiamo gli elementi dell'array per essere sicuri che il nostro array sia ben ordinato eseguendo l'iterazione dell'intero array utilizzando per loop.

DOMANDE FREQUENTI

Sì. Un puntatore a funzione deve corrispondere esattamente al tipo di ritorno e all'elenco dei parametri della funzione di destinazione. Assegnare a una funzione una firma diversa è un comportamento indefinito, anche se il codice viene compilato correttamente, perché la chiamata leggerebbe e passerebbe gli argomenti in modo errato in fase di esecuzione.

typedef assegna un nome breve a un tipo di puntatore a funzione complessa. Scrivendo typedef int (*Operation)(int, int); ti permette di dichiarare Operaaggiunta di zione; invece di ripetere l'intera sintassi del puntatore, rendendo array, parametri e membri di struct molto più facili da leggere.

I puntatori a funzione guidano callback, tabelle di salto o di dispatch, sistemi basati su plugin ed eventi, macchine a stati e routine di libreria come qsort e bsearch. OperaI sistemi di controllo e il firmware integrato si affidano a questi meccanismi per selezionare il comportamento in fase di esecuzione senza lunghe catene di condizioni.

Un puntatore dati contiene l'indirizzo di una variabile in memoria, mentre un puntatore a funzione contiene l'indirizzo di ingresso del codice eseguibile. Per eseguire una funzione, la si chiama tramite un puntatore a funzione, anziché dereferenziarlo per leggere o scrivere dati.

Sì. Un puntatore a funzione può essere impostato su NULL per indicare che non è assegnata alcuna funzione. Chiamare un puntatore a funzione NULL causa un comportamento indefinito, quindi è necessario proteggere la chiamata con if (ptr != NULL) prima di invocarla, soprattutto per le callback opzionali.

Sì. Inserire puntatori a funzione come membri di una struttura consente di raggruppare i dati con le relative operazioni, emulando oggetti e tabelle di metodi virtuali. Questo schema è alla base di molte librerie C e driver di periferica, dove ogni istanza seleziona le proprie implementazioni di funzione.

Sì. Un assistente di programmazione basato sull'IA può generare dichiarazioni di puntatori a funzione, typedef e firme di callback a partire da un prompt in linguaggio naturale, spiegare la sintassi complessa e individuare parametri o tipi di ritorno non corrispondenti. Compila e testa sempre il codice suggerito prima di fidarti.

Copilota GitHub Suggerisce la sintassi dei puntatori a funzione, completa i corpi delle callback a partire da un commento o da una firma e propone funzioni di confronto qsort. Apri i file di intestazione pertinenti in modo che i completamenti corrispondano ai prototipi dichiarati, quindi rivedi la logica.

Riassumi questo post con: