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.