Stai confrontando le dichiarazioni variabili con #define
s, che non è corretto. Con #define
, si crea una mappatura tra un identificatore e uno snippet di codice sorgente. Il preprocessore C sostituirà quindi tutte le occorrenze di quell'identificatore con lo snippet fornito. Scrivendo
#define FOO 40 + 2
int foos = FOO + FOO * FOO;
finisce per essere la stessa cosa del compilatore come scrivere
int foos = 40 + 2 + 40 + 2 * 40 + 2;
Consideralo come copia e incolla automatizzata.
Inoltre, le variabili normali possono essere riassegnate, mentre una macro creata con #define
non può (anche se è possibile re #define
it). L'espressione FOO = 7
sarebbe un errore del compilatore, poiché non possiamo assegnare a "rvalues": 40 + 2 = 7
è illegale.
Quindi, perché abbiamo bisogno di tipi? Alcuni linguaggi apparentemente si sbarazzano di tipi, questo è particolarmente comune nei linguaggi di scripting. Tuttavia, di solito hanno qualcosa chiamato "tipizzazione dinamica" in cui le variabili non hanno tipi fissi, ma i valori hanno. Mentre questo è molto più flessibile, è anche meno performante. C ama le prestazioni, quindi ha un concetto di variabili molto semplice ed efficiente:
C'è un intervallo di memoria chiamato "stack". Ogni variabile locale corrisponde a un'area nello stack. Ora la domanda è: quanti byte richiede questa zona? In C, ogni tipo ha una dimensione ben definita che puoi interrogare tramite sizeof(type)
. Il compilatore deve conoscere il tipo di ciascuna variabile in modo che possa riservare la giusta quantità di spazio nello stack.
Perché le costanti create con #define
non hanno bisogno di un'annotazione di tipo? Non sono memorizzati nello stack. Invece, #define
crea snippet riutilizzabili del codice sorgente in un modo leggermente più gestibile rispetto alla copia e incolla. I letterali nel codice sorgente come "foo"
o 42.87
sono memorizzati dal compilatore sia inline come istruzioni speciali, sia in una sezione dati separata del binario risultante.
Tuttavia, i letterali hanno tipi. Un valore letterale stringa è un char *
. 42
è un int
ma può essere utilizzato anche per i tipi più brevi (conversione di restringimento). 42.8
sarebbe un double
. Se hai un letterale e vuoi che abbia un tipo diverso (ad es. Per fare 42.8
a float
, o 42
an unsigned long int
), puoi usare i suffissi - una lettera dopo il letterale che cambia il modo in cui il il compilatore considera questo letterale. Nel nostro caso, potremmo dire 42.8f
o 42ul
.
Alcune lingue hanno una digitazione statica come in C, ma le annotazioni sul tipo sono facoltative. Esempi sono ML, Haskell, Scala, C #, C ++ 11 e Vai. Come funziona? Magia? No, questo è chiamato "tipo di inferenza". In C # e Go, il compilatore guarda il lato destro di un compito e ne deduce il tipo. Questo è abbastanza semplice se il lato destro è un valore letterale come 42ul
. Quindi è ovvio quale dovrebbe essere il tipo di variabile. Altre lingue hanno anche algoritmi più complessi che tengono conto di come viene usata una variabile. Per esempio. se fai x/2
, x
non può essere una stringa ma deve avere un tipo numerico.