5 La variabile
Le variabili sono uno dei concetti fondamentali nella programmazione, essenziali per la manipolazione e la gestione dei dati. Una variabile è un nome simbolico associato a una locazione di memoria che può contenere uno o più valori di un certo tipo di dato. Questo concetto permette agli sviluppatori di astrarre dalla memoria fisica e concentrarsi sulla logica del programma.
La gestione delle variabili varia tra i diversi linguaggi di programmazione, quindi esploreremo le variabili con particolare attenzione a Python, Java, C e C++.
5.0.1 La dichiarazione e la inizializzazione
La dichiarazione di una variabile è il processo mediante il quale si introduce una variabile nel programma, specificandone il nome e, in molti casi, il tipo di dato che essa può contenere. Questo processo informa il compilatore o l’interprete che una certa variabile esiste e può essere utilizzata nel codice. Abbiamo visto che esiste una istruzione specifica in alcuni linguaggi, mentre in altri è implicita nella assegnazione.
L’inizializzazione di una variabile è il processo di assegnazione di un valore iniziale alla variabile. Questo può avvenire contestualmente alla dichiarazione o in un’istruzione separata successiva, quella di assegnamento.
Esempi:
In Python, le variabili sono dichiarate automaticamente al momento dell’assegnazione del valore. Non è necessario specificare il tipo di dato, poiché Python è dinamicamente tipizzato, cioè determina durante l’esecuzione il tipo di dato del valore associato alla variabile.
- 1
- Dichiarazione e inizializzazione.
- 2
-
Cambia il tipo di
x
dinamicamente a stringa.
In Java, le variabili devono essere dichiarate con un tipo di dato esplicito. La dichiarazione può avvenire contestualmente all’inizializzazione o separatamente:
- Dichiarazione.
- Inizializzazione.
- Dichiarazione e inizializzazione.
In C, la dichiarazione delle variabili richiede la specifica del tipo di dato. La dichiarazione e l’inizializzazione possono essere separate o combinate:
- Dichiarazione.
- Inizializzazione.
- Dichiarazione e inizializzazione.
Il C++ è simile al C, ma con funzionalità aggiuntive come l’inizializzazione a lista:
- 1
- Dichiarazione.
- 2
- Inizializzazione.
- 3
- Dichiarazione e inizializzazione.
- 4
-
Dichiarazione e inizializzazione a lista di
z
con l’intero30
. - 5
- Dichiarazione e inizializzazione a lista di un array con 5 valori predefiniti.
5.0.2 L’ambito
L’ambito di una variabile rappresenta la porzione del codice in cui l’identificatore della variabile è definito e, quindi, può essere utilizzato.
Gli approcci dei diversi linguaggi sono diversi, infatti Java, C e C++ hanno una gestione dell’ambito delle variabili per cui quelle dichiarate all’interno di un blocco sono limitate a quel blocco e non sono visibili al di fuori di esso. In Python, invece, le variabili definite all’interno di un blocco di un’istruzione composta (come un ciclo for
o una condizione if
) rimangono accessibili anche al di fuori del blocco, purché siano ancora nel medesimo ambito di funzione o modulo e, soprattutto, quel blocco sia stato eseguito.
Le variabili con ambito globale sono dichiarate al di fuori di qualsiasi blocco e sono accessibili ovunque nel programma:
- 1
- Variabile globale.
- 2
- Inizio del blocco.
- 3
- Accesso alla variabile globale.
Le variabili con ambito locale sono dichiarate all’interno di un blocco, come una funzione o un loop, e sono accessibili solo all’interno di quel blocco:
public class Main { public static void main(String[] args) { if (true) { 1 int x = 10; } 2 System.out.println(x); for (int i = 0; i < 10; i++) { 3 int y = i; } 4 System.out.println(y); } }
- 1
-
Variabile locale al blocco
if
. - 2
-
Errore:
x
non è visibile qui! - 3
-
Variabile locale al blocco
for
. - 4
-
Errore:
y
non è visibile qui.
In Python, una variabile definita all’interno di un blocco di un’istruzione composta, come all’interno di un ciclo
for
o di una condizioneif
, rimane accessibile anche dopo l’esecuzione del blocco:- Variabile locale al ciclo.
loopVar
è ancora accessibile qui.
5.0.3 La visibilità
La visibilità si riferisce alla possibilità che in una regione di codice una certa variabile possa essere vista e utilizzata. Anche se correlata all’ambito, la visibilità può essere influenzata da altri fattori come la modularità e gli spazi di nomi (namespace).
Consideriamo un esempio in C++ per illustrare la differenza tra ambito e visibilità:
#include <iostream> 1int globalVar = 10; void function() { 2 int localVar = 5; 3 std::cout << globalVar << std::endl; 4 std::cout << localVar << std::endl; } int main() { function(); 5 std::cout << globalVar << std::endl; 6 std::cout << localVar << std::endl; return 0; }
- 1
- Variabile globale (ambito globale).
- 2
- Variabile locale (ambito locale alla funzione).
- 3
-
Visibilità
globaleVar
all’interno della funzione. - 4
-
Visibilità
localVar
all’interno della funzione. - 5
-
Visibilità
globalVar
all’interno dimain
. - 6
-
Errore:
localVar
non è visibile qui (ambito locale alla funzionefunction
).
In Python, le variabili definite all’interno di una funzione sono locali a quella funzione, ma le variabili definite all’interno di un blocco (come un ciclo
for
o unif
) sono visibili all’interno della funzione o del modulo in cui si trovano:1globalVar = 10 def function(): 2 localVar = 5 if True: 3 blockVar = 20 4 print(localVar) 5 print(blockVar) function() 6print(globalVar) 7print(localVar) 8print(blockVar)
- 1
- Variabile globale.
- 2
- Variabile locale.
- 3
- Visibile all’interno della funzione.
- 4
- Visibile.
- 5
- Visibile.
- 6
- Visibile.
- 7
- Errore: non visibile al di fuori della funzione.
- 8
- Errore: non visibile al di fuori della funzione.
5.0.4 La durata di vita degli oggetti
La durata di vita descrive per quanto tempo un oggetto rimane in memoria durante l’esecuzione del programma. Questo è distinto dalla variabile (o puntatore) che fa riferimento all’oggetto.
In alcuni linguaggi di programmazione è presente il garbage collector, cioè un processo avviato dal compilatore o interprete che si occupa di rendere nuovamente disponibili le aree di memoria precedentemente occupate da oggetti non più referenziati da variabili. Questo accade quando l’esecuzione del programma raggiunge regioni di codice dove quelle variabili non sono più visibili. In questo modo, la visibilità è legata alla durata di vita degli oggetti, rendendo la gestione della memoria non più una preoccupazione del programmatore.
Distinguiamo tra variabile automatica, variabile statica e variabile dinamica:
Variabile automatica: L’oggetto esiste solo durante l’esecuzione del blocco di codice in cui è stata dichiarata la variabile a cui è associato. Esempio in C:
- 1
-
Dichiarazione di
autoVar
e creazione in memoria di un oggetto corrispondente all’intero 10. - 2
- Prima di questa istruzione l’oggetto 10 non è più presente in memoria.
Variabile statica: La variabile esiste per tutta la durata del programma, ma è accessibile solo all’interno del blocco in cui è dichiarata. Esempio in C:
void function() { 1 static int staticVar = 10; }
- 1
- Variabile statica ottenuta con una parola chiave ad hoc in fase di dichiarazione.
Variabile dinamica: Le variabili dinamiche sono utilizzate per riservare memoria che persiste oltre la durata del blocco di codice in cui sono state create. L’oggetto è creato in memoria e deve essere cancellato esplicitamente dall’utente, utilizzando funzioni di gestione della memoria come
delete
. La variabile che punta all’oggetto è separata dall’oggetto stesso, quindi se la variabile non è più visibile, l’oggetto continuerà a rimanere in memoria e non sarà più eliminabile, causando una perdita di memoria (memory leak). Esempio in C++:#include <iostream> void function() { 1 int* dynamicVar = new int(10); 2 std::cout << "dynamicVar: " << *dynamicVar << std::endl; } int main() { 3 function(); 4 int* safeDynamicVar = new int(20); 5 std::cout << "safeDynamicVar: " << *safeDynamicVar << std::endl; 6 delete safeDynamicVar; return 0;
- 1
-
Allocazione dinamica di un intero all’interno della funzione
function()
. - 2
-
Stampa del valore puntato da
dynamicVar
. Prima della chiusura del blocco non viene deallocatadynamicVar
per dimostrare il problema di perdita di memoria - 3
-
Chiamata alla funzione
function()
. Dopo l’uscita dalla funzione,dynamicVar
non è più accessibile, causando una perdita di memoria poiché non è stata deallocata. - 4
- Allocazione dinamica di un intero.
- 5
-
Stampa del valore puntato da
safeDynamicVar
. - 6
- Deallocazione dinamica dell’intero e ciò mostra il corretto uso di allocazione e deallocazione dinamica.
In Python, la gestione della memoria è automatica grazie al garbage collector. Quando non ci sono più riferimenti di variabili a un oggetto, il garbage collector lo rimuove dalla memoria.
6 Lo spazio di nomi, il modulo e il file
In molti linguaggi di programmazione, la gestione dell’ambito e della visibilità delle variabili e delle funzioni può essere ulteriormente organizzata utilizzando spazio di nomi (namespace), moduli, header e file separati. Questa organizzazione aiuta a evitare conflitti di nome e a mantenere il codice più modulare e manutenibile.
6.0.1 Python
In Python, i moduli sono file che contengono definizioni di variabili, funzioni e classi. I moduli possono essere importati in altri moduli o script per riutilizzare il codice. Quando un modulo viene importato, gli identificatori definiti in quel modulo (come variabili, funzioni e classi) diventano accessibili attraverso il nome del modulo. Sebbene i moduli siano spesso implementati come file separati, è possibile definirli anche all’interno di un file di codice sorgente principale.
Esempio di modulo (mymodule.py
):
- 1
- Variabile globale nel modulo.
- 2
- Funzione nel modulo.
Importazione di un modulo in un altro file sorgente (main.py
):
- 1
-
Importazione del modulo
mymodule
. - 2
-
Accesso alla variabile
my_var
dal modulomymodule
. - 3
-
Chiamata della funzione
my_function
dal modulomymodule
.
6.0.2 Java
In Java, i pacchetti (package) sono utilizzati per organizzare le classi in namespace separati. Ogni classe deve dichiarare il pacchetto di appartenenza.
Esempio di classe in un pacchetto (mypackage/MyClass.java
):
// mypackage/MyClass.java
1package mypackage;
public class MyClass {
2 public static int myVar = 10;
public static void myMethod() {
3 System.out.println("Metodo del pacchetto");
}
}
- 1
-
Dichiarazione del pacchetto
mypackage
. - 2
- Variabile globale di classe.
- 3
- Metodo della classe.
Importazione di una classe da un pacchetto in un’altra classe (Main.java
):
// Main.java
1import mypackage.MyClass;
public class Main {
public static void main(String[] args) {
2 System.out.println(MyClass.myVar);
3 MyClass.myMethod();
}
}
- 1
-
Importazione della classe
MyClass
dal pacchettomypackage
. - 2
-
Accesso alla variabile
myVar
dalla classeMyClass
. - 3
-
Chiamata del metodo
myMethod
dalla classeMyClass
.
6.0.3 C
In C, i file di intestazione (header file) sono utilizzati per dichiarare funzioni e variabili che possono essere utilizzate in più file sorgente, a mo’ di libreria.
Esempio di file di intestazione (mymodule.h
):
- 1
-
Dichiarazione della variabile globale
myVar
. - 2
-
Dichiarazione della funzione
myFunction
.
Esempio di file sorgente (mymodule.c
):
- 1
-
Definizione della variabile globale
myVar
. - 2
-
Definizione della funzione
myFunction
.
Utilizzo del file di intestazione in un altro file sorgente (main.c
):
#include <stdio.h>
1#include "mymodule.h"
int main() {
2 printf("%d\n", myVar);
3 myFunction();
return 0;
}
- 1
-
Inclusione del file di intestazione
mymodule.h
. - 2
-
Accesso alla variabile
myVar
dichiarata inmymodule.h
. - 3
-
Chiamata della funzione
myFunction
dichiarata inmymodule.h
.
6.0.4 C++
In C++, la parola chiave namespace
è utilizzata per organizzare le classi, le funzioni e le variabili in spazi di nomi separati, simili ai pacchetti in Java.
Esempio di dichiarazione di uno spazio di nomi (mymodule.h
):
#ifndef MYMODULE_H
#define MYMODULE_H
1namespace mynamespace {
2 extern int myVar;
3 void myFunction();
}
#endif
- 1
-
Dichiarazione dello spazio di nomi
mynamespace
. - 2
-
Dichiarazione della variabile globale
myVar
all’interno dello spazio di nomi. - 3
-
Dichiarazione della funzione
myFunction
all’interno dello spazio di nomi.
Esempio di definizione dello spazio di nomi (mymodule.cpp
):
#include "mymodule.h"
#include <iostream>
namespace mynamespace {
1 int myVar = 10;
void myFunction() {
2 std::cout << "Funzione del namespace" << std::endl;
}
}
- 1
-
Definizione della variabile globale
myVar
all’interno dello spazio di nomi. - 2
-
Definizione della funzione
myFunction
all’interno dello spazio di nomi.
Utilizzo dello spazio di nomi in un altro file sorgente (main.cpp
):
#include "mymodule.h"
#include <iostream>
int main() {
1 std::cout << mynamespace::myVar << std::endl;
2 mynamespace::myFunction();
return 0;
}
- 1
-
Accesso alla variabile
myVar
all’interno dello spazio di nomimynamespace
. - 2
-
Chiamata della funzione
myFunction
all’interno dello spazio di nomimynamespace
.
6.0.5 Impatti
L’uso di spazio di nomi, moduli e file di intestazione influisce sull’ambito e sulla visibilità delle variabili e delle funzioni. In generale, questi meccanismi consentono una maggiore modularità e organizzazione del codice, facilitando la gestione di grandi progetti.
Ambito: L’ambito delle variabili e delle funzioni può essere limitato a uno spazio di nomi o a un modulo, riducendo il rischio di conflitti di nome.
Visibilità: Le variabili e le funzioni dichiarate in namespace o moduli possono essere visibili solo all’interno di quel namespace o modulo, a meno che non vengano esplicitamente esportate.
Durata di vita degli oggetti: La durata di vita degli oggetti non è direttamente influenzata dallo spazio di nomi o moduli, ma l’organizzazione del codice può rendere più chiaro quando e dove gli oggetti vengono creati e distrutti.
6.0.6 La variabile e il tipo di dato
Una variabile in un linguaggio di programmazione è un nome simbolico associato a una locazione di memoria. Le variabili sono utilizzate per referenziare e manipolare dati durante l’esecuzione di un programma. Il valore associato alla variabile è memorizzato nella locazione di memoria a cui la variabile fa riferimento, e il tipo di dato della variabile determina come quel valore viene interpretato e quali operazioni possono essere eseguite su di esso.
Due operazioni importanti con le variabili sono:
Dichiarazione: Introduce una variabile nel programma, specificando il suo nome e, in molti linguaggi, il suo tipo di dato. La dichiarazione informa il compilatore o l’interprete che una variabile esiste.
Definizione: Oltre a dichiarare la variabile, le assegna anche uno spazio di memoria.
La gestione di queste operazioni varia significativamente tra linguaggi con tipizzazione statica e tipizzazione dinamica:
Linguaggi con tipizzazione statica (come Java, C++, TypeScript):
Richiedono generalmente una dichiarazione esplicita del tipo di variabile.
La verifica del tipo avviene prima dell’esecuzione del programma.
La dichiarazione e la definizione possono avvenire separatamente.
Esempio in Java:
- 1
-
Dichiarazione di
x
. - 2
- Definizione.
In Java, la verifica del tipo avviene durante la compilazione in bytecode. Anche se Java è eseguito su una macchina virtuale (JVM), il controllo dei tipi è effettuato prima dell’esecuzione.
Esempio in C++:
int y; /* Dichiarazione e definizione (alloca spazio in memoria) */ y = 10; /* Assegnazione di un valore */
In C++, la verifica del tipo avviene completamente durante la compilazione.
Linguaggi con tipizzazione dinamica (come Python, JavaScript, Ruby):
- Non richiedono una dichiarazione esplicita del tipo.
- Il tipo viene inferito e verificato durante l’esecuzione del programma.
- La dichiarazione e la definizione avvengono spesso contemporaneamente.
Esempio in Python:
Esempio in JavaScript:
È importante notare che alcuni linguaggi, come TypeScript, offrono un approccio ibrido:
let a: number; /* Dichiarazione con tipo esplicito */
a = 25; /* Definizione */
let b = 30; /* Dichiarazione e definizione con inferenza di tipo */
TypeScript effettua il controllo dei tipi durante la compilazione, ma viene poi compilato in JavaScript, che è dinamicamente tipizzato.
Alcuni linguaggi moderni, come Go o Kotlin, utilizzano l’inferenza di tipo con tipizzazione statica:
6.0.7 Altri aspetti delle variabili
Il ciclo di vita di una variabile si riferisce al periodo durante il quale la variabile esiste in memoria. Questo ciclo è influenzato dall’allocazione dinamica o statica della memoria e dall’ambito della variabile. Il ciclo di vita del dato si riferisce a quanto a lungo un dato rimane accessibile e utilizzabile nel programma.
L’ambito (scope) definisce la porzione di codice in cui la variabile può essere utilizzata, mentre la visibilità indica da dove la variabile può essere accessibile. Questi aspetti sono influenzati dal modo in cui le variabili vengono dichiarate e definite.
6.0.8 Il tipo di dato e l’inferenza di tipo
Il tipo di dato definisce l’insieme di valori che una variabile può assumere e le operazioni che possono essere eseguite su di essa. Nei linguaggi con tipizzazione statica, il tipo di dato deve essere specificato esplicitamente.
L’inferenza di tipo è una caratteristica in cui il compilatore deduce automaticamente il tipo di una variabile dal contesto, come avviene in linguaggi come TypeScript e Python.
Esempio in TypeScript:
6.0.9 Il modello dati
Il modello dati di un linguaggio di programmazione descrive come vengono rappresentati e manipolati i dati. Questo modello include:
- Tipi di dati primitivi: Numeri interi, stringhe, booleani, ecc.
- Tipi di dati complessi: Liste, array, oggetti, ecc.
- Tipi generici: Permettono di scrivere codice che può lavorare con diversi tipi di dati mantenendo la sicurezza dei tipi.
Esempio di tipi generici in Java:
6.0.10 Esempi di tipi semplici, contenitori e classi
Esempio in Python:
x = 5 /* Variabile intera */
y = [1, 2, 3] /* Lista di interi */
class Persona: /* Definizione di una classe */
def __init__(self, nome):
self.nome = nome
p = Persona("Alice") /* Istanza della classe Persona */
Esempio in Java:
int x = 5; /* Variabile intera */
int[] y = {1, 2, 3}; /* Array di interi */
class Persona { /* Definizione di una classe */
String nome;
Persona(String nome) {
this.nome = nome;
}
}
Persona p = new Persona("Alice"); /* Istanza della classe Persona */
Questi concetti forniscono una panoramica più completa degli elementi semantici fondamentali nei linguaggi di programmazione moderni, coprendo sia aspetti di base che avanzati.
6.0.11 Il modello dati
Il modello dati di un linguaggio di programmazione descrive come vengono rappresentati e manipolati i dati. Questo modello include:
- Tipi di dati primitivi: Numeri interi, stringhe, booleani, ecc.
- Tipi di dati complessi: Liste, array, oggetti, ecc.
- Tipi generici: Permettono di scrivere codice che può lavorare con diversi tipi di dati mantenendo la sicurezza dei tipi.
Esempio di tipi generici in Java:
List<String> stringList = new ArrayList<>(); // Una lista che può contenere solo stringhe
List<Integer> intList = new ArrayList<>(); // Una lista che può contenere solo interi
stringList
è una lista di stringhe.intList
è una lista di interi.
6.0.12 Esempi di tipi semplici, contenitori e classi
Esempio in Python:
x = 5 # Variabile intera
y = [1, 2, 3] # Lista di interi
class Persona: # Definizione di una classe
def __init__(self, nome):
self.nome = nome
p = Persona("Alice") # Istanza della classe Persona
x
è una variabile intera.y
è una lista di interi.Persona
è una classe con un attributonome
.p
è un’istanza della classePersona
.
Esempio in Java:
int x = 5; // Variabile intera
int[] y = {1, 2, 3}; // Array di interi
class Persona { // Definizione di una classe
String nome;
Persona(String nome) {
this.nome = nome;
}
}
Persona p = new Persona("Alice"); // Istanza della classe Persona
x
è una variabile intera.y
è un array di interi.Persona
è una classe con un attributonome
.p
è un’istanza della classePersona
.
Questi concetti forniscono una panoramica più completa degli elementi semantici fondamentali nei linguaggi di programmazione moderni, coprendo sia aspetti di base che avanzati.
6.0.13 Espressioni e valutazione
La semantica delle espressioni definisce come queste vengono valutate per produrre un risultato. Ciò include:
- Ordine di valutazione degli operandi: La sequenza con cui vengono valutati i componenti di un’espressione.
- Comportamento degli operatori: Come gli operatori agiscono su diversi tipi di dati.
- Gestione degli errori: Come vengono trattate le situazioni eccezionali durante la valutazione delle espressioni.
Esempio in JavaScript:
- 1
- Il risultato sarà 2.5, a differenza di linguaggi come Java o C dove sarebbe 2 a causa della divisione intera.
- 2
- La seconda condizione viene valutata solo se la prima è vera, grazie alla valutazione “short-circuit”.
6.0.14 Controllo del flusso
La semantica del controllo del flusso definisce come l’esecuzione del programma procede attraverso le istruzioni. Ciò include:
- Istruzioni condizionali: Come
if
,else
,switch
. - Cicli: Come
for
,while
. - Chiamate a funzione: E il passaggio dei parametri.
Esempio in Python:
- 1
-
Ciclo
for
che itera da0
a4
. - 2
-
Controllo condizionale per verificare se
i
è pari. - 3
-
continue
salta le iterazioni pari. - 4
-
Stampa solo i valori dispari di
i
.
6.0.15 Gestione della memoria
La semantica della gestione della memoria definisce come il programma alloca, utilizza e libera la memoria. Ciò include:
- Stack e heap: Distinzione tra memoria automatica (stack) e dinamica (heap).
- Allocazione e deallocazione: Regole per riservare e liberare memoria.
- Garbage collector: Comportamento nei linguaggi che lo utilizzano.
Esempio in C++:
- 1
-
new
alloca memoria dinamicamente sull’heap. - 2
-
Il puntatore
p
viene utilizzato per accedere alla memoria allocata. - 3
-
delete
libera la memoria allocata, prevenendo potenziali perdite di memoria.
6.0.16 Funzioni e procedure
Le funzioni sono blocchi di codice riutilizzabili che eseguono specifiche operazioni. La semantica delle funzioni include:
- Definizione e chiamata: Come vengono definite e chiamate le funzioni.
- Passaggio dei parametri: Modalità di passaggio dei parametri (per valore, per riferimento, ecc.).
- Restituzione dei valori: Come vengono restituiti i risultati.
- Ambito delle variabili: Lo scope delle variabili all’interno delle funzioni.
Esempio in JavaScript:
- 1
-
Definizione della funzione
add
con due parametri. - 2
- La funzione restituisce la somma dei parametri.
- 3
- Chiamata della funzione con argomenti 3 e 4.
Nei linguaggi moderni, troviamo anche concetti avanzati come:
- Funzioni di ordine superiore: Funzioni che possono accettare altre funzioni come argomenti o restituirle come risultato.
Esempio in Python:
def apply(func, x, y):
return func(x, y)
def multiply(a, b):
return a * b
result = apply(multiply, 3, 4) # Restituisce 12
- Chiusure: Funzioni che “catturano” variabili dal loro ambiente circostante.
Esempio in JavaScript:
6.0.17 Programmazione asincrona
La programmazione asincrona permette l’esecuzione di operazioni non bloccanti, migliorando l’efficienza e la reattività dei programmi. Concetti chiave includono:
- Promise/Future: Rappresentano il risultato di un’operazione asincrona.
- async/await: Sintassi che semplifica la scrittura di codice asincrono.
- Callback: Funzioni che vengono chiamate al completamento di un’operazione asincrona.
Esempio in JavaScript:
async function fetchData() {
try {
1 const response = await fetch('https://api.example.com/data');
2 const data = await response.json();
console.log(data);
} catch (error) {
console.error('Errore:', error);
}
}
3fetchData();
- 1
-
await
sospende l’esecuzione della funzione finché la Promise restituita dafetch
non si risolve. - 2
- Attende che il corpo della risposta venga convertito in JSON.
- 3
- Chiama la funzione asincrona.
6.0.18 Sistemi di tipi
Il sistema di tipi di un linguaggio definisce come i tipi di dati vengono gestiti. Ciò include:
- Conversione tra tipi: Regole per la conversione implicita o esplicita tra tipi.
- Controllo dei tipi: Comportamento del controllo dei tipi (statico o dinamico).
- Polimorfismo e ereditarietà: Implementazione nei linguaggi orientati agli oggetti.
Esempio in TypeScript:
interface Shape {
area(): number;
}
class Circle implements Shape {
constructor(private radius: number) {}
area(): number {
return Math.PI * this.radius ** 2;
}
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
area(): number {
return this.width * this.height;
}
}
1function printArea(shape: Shape) {
```typescript
console.log('Area:', shape.area());
}
let circle = new Circle(5);
let rectangle = new Rectangle(4, 6);
printArea(circle); // Stampa: Area: 78.53981633974483
printArea(rectangle); // Stampa: Area: 24
- 1
-
La funzione
printArea
accetta qualsiasi oggetto che implementa l’interfacciaShape
.
Questi concetti forniscono una panoramica più completa degli elementi semantici fondamentali nei linguaggi di programmazione moderni, coprendo sia aspetti di base che avanzati.