# Programmazione orientata agli oggetti: utilizzare OOP in C

A differenza dei linguaggi OOP C++ e Objective-C, **C non è dotato di base di alcuna funzionalità orientata agli oggetti**. A causa della diffusione del linguaggio e della popolarità della programmazione orientata agli oggetti, esistono approcci all’uso di OOP in C.

## OOP in C: è davvero possibile?

Il [linguaggio di programmazione C](https://www.ionos.it/digitalguide/siti-web/programmazione-del-sito-web/linguaggio-di-programmazione-c/ "Il linguaggio di programmazione C") non è di per sé destinato alla programmazione orientata agli oggetti. Questo linguaggio è un ottimo esempio dello stile di programmazione strutturato all’interno della [programmazione imperativa](https://www.ionos.it/digitalguide/siti-web/programmazione-del-sito-web/programmazione-imperativa/ "Programmazione imperativa"). Ciononostante, è **possibile emulare approcci orientati agli oggetti in C**, poiché dispone di tutti i componenti necessari a questo scopo. Ad esempio, è servito come base per realizzare la programmazione orientata agli oggetti in Python.

Con OOP, è possibile definire i propri **“tipi di dati astratti” (ADT)**. Un ADT può essere considerato come un insieme di valori possibili e di funzioni che operano su di essi. È importante che l’interfaccia visibile all’esterno e l’implementazione interna siano disaccoppiate tra loro. In questo modo, l’utente può fare affidamento sul fatto che gli oggetti del tipo si comportino secondo la descrizione.

I linguaggi orientati agli oggetti come Python, Java e C++ utilizzano il concetto di “classe” per modellare i tipi di dati astratti. Le classi servono come modello per la creazione di oggetti simili; questa operazione viene anche chiamata istanziazione. **C non conosce intrinsecamente le classi**, che non possono essere modellate all’interno del linguaggio. Esistono invece diversi approcci per implementare le funzionalità OOP in C.

## Come funziona OOP in C?

Per capire come funziona OOP in C, dobbiamo innanzitutto porci la domanda: cos’è esattamente la [programmazione orientata agli oggetti (OOP)](https://www.ionos.it/digitalguide/siti-web/programmazione-del-sito-web/programmazione-orientata-agli-oggetti-oop/)? Si tratta di uno **stile di programmazione comune** che è una manifestazione del [paradigma di programmazione](https://www.ionos.it/digitalguide/siti-web/programmazione-del-sito-web/paradigmi-di-programmazione/ "Paradigmi di programmazione") imperativo. OOP si contrappone quindi alla [programmazione dichiarativa](https://www.ionos.it/digitalguide/siti-web/programmazione-del-sito-web/programmazione-dichiarativa/ "Programmazione dichiarativa") e alla sua specializzazione, la [programmazione funzionale](https://www.ionos.it/digitalguide/siti-web/programmazione-del-sito-web/programmazione-funzionale/ "Programmazione funzionale").

L’idea di base della programmazione orientata agli oggetti consiste nel **modellare gli oggetti e farli interagire tra loro**. Il flusso del programma risulta dalle interazioni degli oggetti e non viene quindi fissato fino all’esecuzione. In sostanza, OOP comprende solo tre proprietà:

1. Gli oggetti **incapsulano** il loro stato interno.
2. Gli oggetti ricevono **messaggi** attraverso i loro metodi.
3. I metodi sono assegnati **dinamicamente** in fase di esecuzione.

Un oggetto in un linguaggio OOP puro come Java è un’unità autonoma. Comprende una struttura di **dati di qualsiasi complessità e metodi (funzioni) che operano su di essa**. Lo stato interno dell’oggetto, rappresentato dai dati che contiene, può essere letto e modificato solo attraverso i metodi. Per la gestione della memoria degli oggetti, viene solitamente utilizzata una funzione del linguaggio chiamata “garbage collector”.

In C, non è facile collegare le strutture di dati e le funzioni agli oggetti. Invece, si crea un **sistema** gestibile **di strutture di dati, definizioni di tipi, puntatori e funzioni**. Quindi, come di consueto in questo linguaggio, il programmatore o la programmatrice è responsabile della corretta allocazione e abilitazione della memoria.

Il codice C a oggetti che ne risulta non assomiglia molto a quello a cui siamo abituati nei linguaggi OOP, ma funziona. Di seguito vi forniamo un riepilogo dei concetti centrali della programmazione orientata agli oggetti e del loro equivalente in C:

<table>
  <thead>
    <tr>
      <th><strong>Concetto in OOP</strong></th>
      <th><strong>Equivalente in C</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Classe</td>
      <td>Tipo struct</td>
    </tr>
    <tr>
      <td>Instanza di classi</td>
      <td>Instanza struct</td>
    </tr>
    <tr>
      <td>Metodo di istanza</td>
      <td>Funzione che accetta i puntatori alla variabile struct</td>
    </tr>
    <tr>
      <td>Variabile this/self</td>
      <td>Puntatori alla variabile struct</td>
    </tr>
    <tr>
      <td>Istanziazione</td>
      <td>Allocazione e referenza tramite puntatori</td>
    </tr>
    <tr>
      <td>Parola chiave new</td>
      <td>Chiamata di malloc</td>
    </tr>
  </tbody>
</table>

### Modellare gli oggetti come strutture dati

Vediamo innanzitutto come la **struttura dei dati di un oggetto** può essere modellata in C nello stile dei linguaggi OOP. C è un linguaggio compatto che se la cava con pochi costrutti linguistici. Per creare strutture di dati arbitrariamente complesse, si utilizzano i cosiddetti “struct”, il cui nome deriva dal termine “Data Structure” (struttura dei dati).

Struct in C definisce una struttura di dati che include **campi chiamati “members” (membri)**. In altri linguaggi, un costrutto di questo tipo è chiamato anche “record”, e quindi si può immaginare uno struct come la riga di una tabella di un database: una combinazione di più campi, eventualmente di tipo diverso.

La **sintassi della dichiarazione di uno struct in C** è molto semplice:

```C
struct struct_name;
```

Opzionalmente, definiamo anche struct specificando i membri con nome e tipo. A titolo di esempio, consideriamo un punto nello spazio bidimensionale con coordinate x e y. Vi mostriamo la definizione di struct:

```C
struct point {
    /*X-coordinate*/
    int x;
    /*Y-coordinate*/
    int y;
};
```

Nel codice C convenzionale, questa operazione è seguita dall’istanziazione di una variabile struct. Creiamo la variabile e inizializziamo entrambi i campi con 0:

```C
struct point origin = {0, 0};
```

In seguito, i valori dei campi possono essere letti e reimpostati. L’accesso ai membri avviene tramite la sintassi origin.x e origin.y, già nota in altri linguaggi:

```C
/*Read struct member*/
origin.x == 0
/*Assign struct member*/
origin.y = 42
```

Tuttavia, questo viola il requisito dell’incapsulamento: si può accedere allo stato interno di un oggetto solo tramite metodi definiti a tale scopo. Quindi al nostro approccio manca ancora qualcosa.

### Definire i tipi per la creazione di oggetti

Come già menzionato, C non conosce il concetto di classe. Invece, i **tipi si possono definire con l’istruzione typedef**. Con typedef, diamo a un tipo di dati un nuovo nome:

```C
typedef <old-type-name> <new-type-name>
```

Questo ci permette di definire un corrispondente tipo Point per il nostro struct point:

```C
typedef struct point Point;
```

La combinazione di typedef con una definizione di struct corrisponde all’incirca alla definizione di una classe in Java:

```C
typedef struct point {
    /*X-coordinate*/
    int x;
    /*Y-coordinate*/
    int y;
} Point;
```

N.B. Nell’esempio, “point” è il nome di struct, mentre “Point” è il nome del tipo definito.

Di seguito la definizione della classe corrispondente in Java:

```Java
class Point {
    private int x;
    private int y;
};
```

L’uso di typedef ci permette di creare una variabile Point senza usare la parola chiave struct:

```C
Point origin = {0, 0}
/*Instead of*/
struct point origin = {0, 0}
```

Ciò che ancora manca è l’incapsulamento dello stato interno.

### Incapsulamento dello stato interno

Gli oggetti mappano il loro stato interno nella loro struttura di dati. Nei linguaggi OOP come Java, le parole chiave “private”, “protected”, ecc. vengono utilizzate per **limitare l’accesso ai dati degli oggetti**. Questo impedisce l’accesso diretto dall’esterno e garantisce la separazione tra interfaccia e implementazione.

Per realizzare OOP in C, si usa un meccanismo diverso. Utilizziamo una cosiddetta dichiarazione forward nel file di intestazione come interfaccia, creando così un “Incomplete type”:

```C
/*In C header file*/
struct point;
/*Incomplete type*/
typedef struct point Point;
```

L’implementazione dello struct point segue in un file del codice sorgente in C separato che incorpora l’intestazione tramite la macro include. Questo approccio impedisce la creazione di variabili statiche del tipo Point. È comunque possibile utilizzare puntatori del tipo. Poiché gli oggetti sono strutture di dati create dinamicamente, vengono comunque referenziati con puntatori. I puntatori alle istanze struct corrispondono all’incirca ai riferimenti agli oggetti utilizzati in Java.

### Sostituire i metodi con le funzioni

Nei linguaggi OOP come Java e Python, gli oggetti includono funzioni che operano su di essi oltre ai loro dati. Queste funzioni sono chiamate metodi o metodi di istanza. Quando scriviamo codice OOP in C, invece dei metodi usiamo funzioni che accettano un puntatore su un’istanza struct:

```C
/*Pointer to `Point` struct*/
Point * point;
```

Poiché C non conosce le classi, non è possibile raggruppare le funzioni appartenenti a un tipo sotto un nome comune. Al contrario, forniamo i **nomi delle funzioni con un prefisso** contenente il nome del tipo. Le firme delle funzioni corrispondenti vengono prima dichiarate nel file di intestazione in C:

```C
/*In C header file*/
/*Function to move update a point’s coordinates*/
void Point_move(Point * point, int new_x, int new_y);
```

Successivamente, **implementiamo la funzione** nel file del codice sorgente in C:

```C
/*In C source file*/
void Point_move(Point * point, int new_x, int new_y) {
    point->x = new_x;
    point->y = new_y;
};
```

L’approccio ricorda i metodi di Python, che sono normali funzioni che accettano self come primo parametro. Inoltre, il puntatore su un’istanza struct corrisponde più o meno alla variabile this in Java o JavaScript. La differenza è che quando la funzione C viene chiamata, il puntatore viene trasmesso esplicitamente:

```C
/*Call function with pointer argument*/
Point_move(point, 42, 51);
```

Con la chiamata di funzione equivalente in Java, l’oggetto point è disponibile all’interno del metodo come variabile this:

```Java
// Call instance method from outside of class
point.move(42, 51)
// Call instance method from within class
this.move(42, 51)
```

Python consente di chiamare i metodi come funzioni con un argomento self esplicito:

```Python
# Call instance method from outside or from within class
self.move(42, 51)
# Function call from within class
move(self, 42, 51)
```

### Istanziare gli oggetti

Una caratteristica di C è la gestione manuale della memoria: i programmatori e le programmatrici sono responsabili dell’allocazione della memoria per le strutture di dati. I linguaggi dinamici orientati agli oggetti, come Java e Python, li sollevano da questo compito. In Java, per istanziare un oggetto si usa la parola chiave new. Sotto la scocca, la memoria viene allocata automaticamente:

```Java
// Create new Point instance
Point point = new Point();
```

Quando scriviamo codice OOP in C, definiamo una **funzione costruttore speciale** per l’istanziazione. Questa funzione alloca la memoria per la nostra istanza struct, la inizializza e restituisce un puntatore a essa:

```C
Point * Point_new(int x, int y) {
    /*Allocate memory and cast to pointer type*/
    Point *point = (Point*) malloc(sizeof(Point));
    /*Initialize members*/
    Point_init(point, x, y);
    // return pointer
    return point;
};
```

Nel nostro esempio, disaccoppiamo l’inizializzazione dei membri struct dall’istanziazione. Anche in questo caso, viene utilizzata una funzione con il prefisso point:

```C
void Point_init(Point * point, int x, int y) {
    point->x = x;
    point->y = y;
};
```

## Come si può riscrivere un progetto in C in modo orientato agli oggetti?

Riscrivere un progetto esistente in C utilizzando le tecniche OOP descritte è consigliabile solo in casi eccezionali. I seguenti approcci sono più ragionevoli:

1. Riscrivere il progetto in un linguaggio simile a C con caratteristiche OOP e utilizzare la base di codice C esistente come specifica
2. Riscrivere parti del progetto in un linguaggio OOP e mantenere componenti C specifici

Se la base di codice C è scritta in modo pulito, il secondo approccio dovrebbe dare buoni risultati. È prassi comune **implementare parti di programma critiche per le prestazioni in C** e accedervi da altri linguaggi. Probabilmente nessun altro linguaggio è più adatto di C a questo scopo. Ma quali sono i linguaggi adatti per ricostruire un progetto C esistente utilizzando i principi OOP?

### Linguaggi C-like orientati agli oggetti

Esiste una **ricca selezione di linguaggi C-like con orientamento agli oggetti incorporato**. Probabilmente il più noto è C++; tuttavia, data la sua complessità, c’è stato un allontanamento da questo linguaggio negli ultimi anni. Poiché i suoi costrutti di base sono in gran parte gli stessi, il codice C è relativamente facile da incorporare in C++.

Molto più leggero di C++ è **Objective-C**. Il dialetto C, basato sul linguaggio OOP originale Smalltalk, è stato utilizzato principalmente per la programmazione di applicazioni su Mac e sui primi sistemi operativi iOS. In seguito, è stato seguito dallo sviluppo del linguaggio Swift di Apple. Le funzioni scritte in C possono essere richiamate da entrambi i linguaggi.

### Linguaggi orientati agli oggetti basati su C

Anche altri linguaggi di programmazione OOP che non sono legati a C in termini di sintassi sono adatti alla riscrittura di un progetto in C. Per Python, Rust e Java esistono approcci standard per includere il codice C.

In Python, i cosiddetti **binding** permettono di includere codice C. È possibile che i tipi di dati Python debbano essere tradotti nei corrispondenti ctypes. Esiste anche la C Foreign Function Interface (CFFI), che automatizza in una certa misura la traduzione dei tipi.

**Rust** supporta anche la chiamata di funzioni C con poco sforzo. La parola chiave extern può essere usata per definire una Foreign Function Interface (FFI). Le funzioni di Rust che accedono a funzioni esterne devono essere dichiarate unsafe (non sicure):

```Rust
extern "C" {
    fn abs(input: i32) -> i32;
}
fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}
```


This is a markdown version of: [https://www.ionos.it/digitalguide/siti-web/programmazione-del-sito-web/oop-in-c/](https://www.ionos.it/digitalguide/siti-web/programmazione-del-sito-web/oop-in-c/) for AI/LLM consumption.