Perché dobbiamo menzionare il tipo di dati della variabile in C


19

Di solito in C, dobbiamo dire al computer il tipo di dati nella dichiarazione variabile. Ad esempio nel seguente programma, voglio stampare la somma di due numeri in virgola mobile X e Y.

#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}

Ho dovuto dire al compilatore il tipo di variabile X.

  • Il compilatore non può determinare il tipo da Xsolo?

Sì, posso farlo se lo faccio:

#define X 5.2

Ora posso scrivere il mio programma senza dire al compilatore il tipo di Xcome:

#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  

Quindi vediamo che il linguaggio C ha un qualche tipo di funzionalità, usando la quale può determinare il tipo di dati da solo. Nel mio caso ha determinato che Xè di tipo float.

  • Perché dobbiamo menzionare il tipo di dati, quando dichiariamo qualcosa in main ()? Perché il compilatore non può determinare da solo il tipo di dati di una variabile main()come fa #define?

14
In realtà quei due programmi non sono equivalenti, in quanto possono dare risultati leggermente diversi! 5.2è un double, quindi il primo programma arrotonda i doppi letterali alla floatprecisione, quindi li aggiunge come float, mentre il secondo arrotonda di nuovo la doppia rappresentazione di 5.1 doublee la aggiunge al doublevalore 5.2 usando add double, quindi arrotonda il risultato di quel calcolo alla floatprecisione . Poiché l'arrotondamento si verifica in luoghi diversi, il risultato potrebbe essere meno intenso. Questo è solo un esempio per i tipi di variabili che influenzano il comportamento di un programma altrimenti identico.

12
Quando lo fai #define X 5.2, Xnon è una variabile, ma una costante, quindi viene letteralmente sostituito essere preprocessore con 5.2qualsiasi punto menzionato X. Non puoi riassegnare X.
scriptin

16
Proprio come una nota: questa è una benedizione e una maledizione. Da un lato, devi digitare alcuni caratteri quando il compilatore potrebbe davvero averlo fatto per te (il C ++ in autorealtà fa quello che vuoi). D'altra parte, se pensi di sapere cosa sta facendo il tuo codice e in realtà hai digitato qualcos'altro, la digitazione statica come questa rileverà un errore prima, prima che diventi un grosso problema. Ogni lingua raggiunge un equilibrio: tipizzazione statica, inferenza di tipo, digitazione dinamica. Per alcune attività, la digitazione extra vale davvero la pena. Per altri, è uno spreco.
Cort Ammon - Ripristina Monica il

Impara Ocaml e / o Haskell .... sarai felice delle loro abilità di inferenza di tipo.
Basile Starynkevitch il

Risposte:


46

Stai confrontando le dichiarazioni delle variabili con #defines, che non è corretto. Con a #define, si crea una mappatura tra un identificatore e uno snippet di codice sorgente. Il preprocessore C sostituirà quindi letteralmente qualsiasi occorrenza di quell'identificatore con lo snippet fornito. scrittura

#define FOO 40 + 2
int foos = FOO + FOO * FOO;

finisce per essere la stessa cosa per il compilatore della scrittura

int foos = 40 + 2 + 40 + 2 * 40 + 2;

Pensalo come copia e incolla automatizzato.

Inoltre, le variabili normali possono essere riassegnate, mentre una macro creata con #definenon può (sebbene sia possibile riprogrammarla #define). L'espressione FOO = 7sarebbe un errore del compilatore, dal momento che non possiamo assegnare a "rvalues": 40 + 2 = 7è illegale.

Quindi, perché abbiamo bisogno dei tipi? Apparentemente alcune lingue si liberano 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. A C piacciono le prestazioni, quindi ha un concetto molto semplice ed efficiente di variabili:

C'è un tratto di memoria chiamato "stack". Ogni variabile locale corrisponde a un'area nello stack. Ora la domanda è: quanti byte deve avere questa area? In C, ogni tipo ha una dimensione ben definita che puoi interrogare tramite sizeof(type). Il compilatore deve conoscere il tipo di ogni variabile in modo da poter riservare la quantità corretta di spazio nello stack.

Perché le costanti create con non #definerichiedono un'annotazione di tipo? Non sono memorizzati nello stack. #defineCrea invece frammenti riutilizzabili di codice sorgente in modo leggermente più gestibile rispetto a copia e incolla. I letterali nel codice sorgente come "foo"o 42.87sono archiviati dal compilatore in linea come istruzioni speciali o in una sezione di dati separata del file binario risultante.

Tuttavia, i letterali hanno tipi. Una stringa letterale è a char *. 42è un intma può essere utilizzato anche per tipi più brevi (restringimento della conversione). 42.8sarebbe un double. Se hai un valore letterale e desideri che abbia un tipo diverso (ad esempio per creare 42.8un float, o 42un unsigned long int), puoi usare i suffissi: una lettera dopo il valore letterale che modifica il modo in cui il compilatore tratta quel valore letterale. Nel nostro caso, potremmo dire 42.8fo 42ul.

Alcune lingue hanno la tipizzazione statica come in C, ma le annotazioni del tipo sono opzionali. Esempi sono ML, Haskell, Scala, C #, C ++ 11 e Go. Come funziona? Magia? No, questo si chiama "inferenza di tipo". In C # and Go, il compilatore esamina il lato destro di un compito e ne deduce il tipo. Questo è abbastanza semplice se il lato destro è letterale come 42ul. Quindi è ovvio quale dovrebbe essere il tipo di variabile. Altre lingue hanno anche algoritmi più complessi che tengono conto del modo in cui viene utilizzata una variabile. Ad esempio, se non lo fai x/2, xnon può essere una stringa ma deve avere un tipo numerico.


Grazie per aver spiegato. La cosa che capisco è che quando dichiariamo il tipo di una variabile (locale o globale), stiamo effettivamente dicendo al compilatore, quanto spazio dovrebbe riservare per quella variabile nello stack. D'altra parte, #defineabbiamo una costante che viene convertita direttamente in codice binario - per quanto lungo possa essere - e viene memorizzata così com'è.
user106313

2
@ user31782 - non del tutto. Quando si dichiara una variabile, il tipo indica al compilatore quali proprietà ha la variabile. Una di queste proprietà è la dimensione; altre proprietà includono il modo in cui rappresenta i valori e quali operazioni possono essere eseguite su tali valori.
Pete Becker,

@PeteBecker Allora come fa il compilatore a conoscere queste altre proprietà #define X 5.2?
user106313

1
Questo perché passando il tipo sbagliato a printfte ha invocato un comportamento indefinito. Sulla mia macchina quel frammento stampa ogni volta un valore diverso, su Ideone si arresta in modo anomalo dopo aver stampato zero.
Matteo Italia,

4
@ user31782 - "Sembra che io possa eseguire qualsiasi operazione su qualsiasi tipo di dati" No. X*Ynon è valido se Xe Ysono puntatori, ma va bene se sono ints; *Xnon è valido se Xè un int, ma va bene se è un puntatore.
Pete Becker,

4

X nel secondo esempio non è mai un float. Si chiama macro, sostituisce il valore di macro definito 'X' nell'origine con il valore. Un articolo leggibile su #define è qui .

Nel caso del codice fornito, prima della compilazione il preprocessore modifica il codice

Z=Y+X;

per

Z=Y+5.2;

e questo è ciò che viene compilato.

Ciò significa che puoi anche sostituire quei "valori" con un codice simile

#define X sqrt(Y)

o anche

#define X Y

3
Si chiama semplicemente una macro, non una macro variadica. Una macro variadica è una macro che accetta un numero variabile di argomenti, ad es #define FOO(...) { __VA_ARGS__ }.
hvd,

2
Mio cattivo, risolverà :)
James Snell il

1

La risposta breve è che C ha bisogno di tipi a causa della storia / che rappresenta l'hardware.

Storia: C è stato sviluppato nei primi anni '70 e inteso come linguaggio per la programmazione dei sistemi. Il codice è idealmente veloce e sfrutta al meglio le capacità dell'hardware.

Sarebbe stato possibile interferire con i tipi in fase di compilazione, ma i tempi di compilazione già lenti sarebbero aumentati (fare riferimento al fumetto "compilazione" di XKCD. Questo si applicava a "ciao mondo" per almeno 10 anni dopo la pubblicazione di C ). L'inferenza dei tipi in fase di esecuzione non avrebbe adattato gli obiettivi della programmazione dei sistemi. L'inferenza di runtime richiede un'ulteriore libreria di runtime. C è arrivato molto prima del primo PC. Che aveva 256 RAM. Non gigabyte o megabyte ma Kilobyte.

Nel tuo esempio, se ometti i tipi

   X=5.2;
   Y=5.1;

   Z=Y+X;

Quindi il compilatore avrebbe potuto felicemente capire che X & Y sono float e rendere Z lo stesso. In effetti, un compilatore moderno potrebbe anche capire che X & Y non sono necessari e impostare Z su 10.3.

Supponiamo che il calcolo sia incorporato in una funzione. Lo scrittore di funzioni potrebbe voler utilizzare la propria conoscenza dell'hardware o il problema da risolvere.

Un doppio sarebbe più appropriato di un galleggiante? Richiede più memoria ed è più lento ma la precisione del risultato sarebbe maggiore.

Forse il valore di ritorno della funzione potrebbe essere int (o lungo) perché i decimali non erano importanti, sebbene la conversione da float a int non sia priva di costi.

Il valore di ritorno potrebbe anche essere reso doppio garantendo che float + float non trabocchi.

Tutte queste domande sembrano inutili per la stragrande maggioranza del codice scritto oggi, ma erano vitali quando veniva prodotto C.


1
questo non spiega, ad esempio, perché le dichiarazioni di tipo non sono state rese opzionali, consentendo al programmatore di scegliere se dichiararle esplicitamente o fare affidamento sul compilatore per inferire
moscerino del

1
In effetti non @gnat. Ho modificato il testo ma al momento non aveva senso farlo. Il dominio C è stato progettato per essere effettivamente voluto decidere di memorizzare 17 in 1 byte, o 2 byte o 4 byte o come una stringa o come 5 bit all'interno di una parola.
ITJ

0

C non ha un'inferenza di tipo (questo è ciò che viene chiamato quando un compilatore indovina il tipo di una variabile per te) perché è vecchio. È stato sviluppato nei primi anni '70

Molte lingue più recenti hanno sistemi che ti consentono di utilizzare le variabili senza specificarne il tipo (ruby, javascript, python, ecc.)


12
Nessuna delle lingue che hai citato (Ruby, JS, Python) ha l'inferenza del tipo come caratteristica del linguaggio, sebbene le implementazioni possano usarlo per aumentare l'efficienza. Invece, usano la tipizzazione dinamica dove i valori hanno tipi, ma le variabili o altre espressioni no.
amon,

2
JS non ti consente di omettere il tipo, ma non ti consente di dichiararlo in alcun modo. Utilizza la tipizzazione dinamica, dove i valori hanno tipi (ad es. trueÈ boolean), non variabili (ad es. var xPossono contenere valori di qualsiasi tipo). Inoltre, l'inferenza di tipo per casi così semplici come quelli in questione è stata probabilmente conosciuta un decennio prima che C fosse rilasciato.
scriptin

2
Ciò non rende falsa l'affermazione (per forzare qualcosa devi anche permetterlo). L'inferenza di tipo esistente non cambia il fatto che il sistema di tipi di C sia il risultato del suo contesto storico (al contrario di un ragionamento filosofico o di una limitazione tecnica specificatamente dichiarati)
Tristan Burnside,

2
Considerando che ML - che è praticamente vecchio quanto C - ha un'inferenza di tipo, "è vecchio" non è una buona spiegazione. Il contesto in cui è stato utilizzato e sviluppato C (piccole macchine che richiedevano un ingombro molto ridotto per il compilatore) sembra più probabile. Non ho idea del motivo per cui menzioneresti linguaggi di battitura dinamici anziché solo alcuni esempi di linguaggi con inferenza di tipo - Haskell, ML, diamine C # ce l'ha - quasi mai una caratteristica oscura.
Voo,

2
@BradS. Fortran non è un buon esempio perché la prima lettera del nome della variabile è una dichiarazione di tipo, a meno che tu non usi implicit nonenel qual caso devi dichiarare un tipo.
Dmckee,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.