Warum müssen wir den Datentyp der Variablen in C erwähnen?

19

Normalerweise müssen wir in C dem Computer den Datentyp in der Variablendeklaration mitteilen. Im folgenden Programm möchte ich zB die Summe zweier Gleitkommazahlen X und Y ausgeben.

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

}

Ich musste dem Compiler den Typ der Variablen X mitteilen.

  • Kann der Compiler den Typ nicht selbst bestimmen X?

Ja, das kann es, wenn ich das tue:

#define X 5.2

Ich kann jetzt mein Programm schreiben, ohne dem Compiler mitzuteilen, welche Art von X:

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

}  

Wir sehen also, dass die C-Sprache eine Art von Funktion hat, mit der sie den Datentyp selbst bestimmen kann. In meinem Fall wurde festgestellt, dass dies Xvom Typ float ist.

  • Warum müssen wir den Datentyp angeben, wenn wir in main () etwas deklarieren? Warum kann der Compiler den Datentyp einer Variablen auf eigene in bestimmen , main()wie es funktioniert in #define.
user106313
quelle
14
Tatsächlich sind diese beiden Programme nicht äquivalent, da sie leicht unterschiedliche Ausgaben liefern können! 5.2Ist a double, so rundet das erste Programm die doppelten Literale auf floatGenauigkeit und fügt sie dann als Gleitkommazahlen hinzu, während das zweite Programm die doppelte Darstellung von 5.1 zurückrundet doubleund doublemit der doubleAddition zum Wert 5.2 hinzufügt und dann das Ergebnis dieser Berechnung auf floatGenauigkeit rundet . Da die Rundung an verschiedenen Stellen auftritt, kann das Ergebnis abweichen. Dies ist nur ein Beispiel für die Arten von Variablen, die sich auf das Verhalten eines ansonsten identischen Programms auswirken.
12
Wenn Sie dies tun #define X 5.2, Xhandelt es sich nicht um eine Variable, sondern um eine Konstante. Sie wird also buchstäblich durch eine Präprozessor-Variable ersetzt, die Sie an einer 5.2beliebigen Stelle erwähnt haben X. Sie können nicht neu zuweisen X.
Skript
16
Nur als Hinweis: Dies ist ein Segen und ein Fluch. Einerseits müssen Sie ein paar Zeichen eingeben, wenn der Compiler dies wirklich für Sie hätte tun können (C ++ macht autotatsächlich das, was Sie wollen). Wenn Sie jedoch glauben, dass Sie wissen, was Ihr Code tut, und tatsächlich etwas anderes eingegeben haben, wird bei einer statischen Eingabe wie dieser ein Fehler früher erkannt, bevor er zu einem großen Problem wird. Jede Sprache findet ein Gleichgewicht: statische Typisierung, Typisierungsfehler, dynamische Typisierung. Für einige Aufgaben lohnt sich die zusätzliche Eingabe. Für andere ist es eine Verschwendung.
Cort Ammon - Reinstate Monica
Lernen Sie Ocaml und / oder Haskell.
Basile Starynkevitch

Antworten:

46

Sie vergleichen Variablendeklarationen mit #defines, was falsch ist. Mit a #defineerstellen Sie eine Zuordnung zwischen einem Bezeichner und einem Quellcode-Snippet. Der C-Präprozessor ersetzt dann alle Vorkommen dieses Bezeichners durch das bereitgestellte Snippet. Schreiben

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

Am Ende ist es für den Compiler dasselbe wie für das Schreiben

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

Stellen Sie sich das als automatisiertes Kopieren und Einfügen vor.

Außerdem können normale Variablen neu zugewiesen werden, während ein mit erstelltes Makro #definedies nicht kann (obwohl Sie es erneut können #define). Der Ausdruck FOO = 7wäre ein Compilerfehler, da wir keine "rvalues" zuweisen können: 40 + 2 = 7ist illegal.

Warum brauchen wir überhaupt Typen? Einige Sprachen werden offenbar von Typen befreit. Dies ist besonders häufig in Skriptsprachen der Fall. Normalerweise haben sie jedoch eine so genannte dynamische Typisierung, bei der Variablen keine festen Typen haben, sondern Werte. Dies ist zwar viel flexibler, aber auch weniger performant. C mag Leistung, daher hat es ein sehr einfaches und effizientes Konzept von Variablen:

Es gibt einen Speicherbereich, der als „Stapel“ bezeichnet wird. Jede lokale Variable entspricht einem Bereich auf dem Stapel. Nun stellt sich die Frage, wie viele Bytes dieser Bereich haben muss. In C hat jeder Typ eine genau definierte Größe, über die Sie abfragen können sizeof(type). Der Compiler muss den Typ jeder Variablen kennen, damit er den richtigen Speicherplatz auf dem Stapel reservieren kann.

Warum brauchen Konstanten, die mit erstellt wurden, #definekeine Typanmerkung? Sie werden nicht auf dem Stapel gespeichert. #defineErstellt stattdessen wiederverwendbare Quellcode-Schnipsel auf eine etwas wartbarere Weise als das Kopieren und Einfügen. Literale im Quellcode wie "foo"oder 42.87werden vom Compiler entweder inline als spezielle Anweisungen oder in einem separaten Datenabschnitt der resultierenden Binärdatei gespeichert.

Literale haben jedoch Typen. Ein String-Literal ist a char *. 42ist ein int, kann aber auch für kürzere Typen verwendet werden (Verengungskonvertierung). 42.8wäre ein double. Wenn Sie ein Literal haben und möchten, dass es einen anderen Typ hat (z. B. um 42.8ein floatoder 42ein zu machen unsigned long int), können Sie Suffixe verwenden - einen Buchstaben nach dem Literal, der ändert, wie der Compiler dieses Literal behandelt. In unserem Fall können wir sagen , 42.8foder 42ul.

Einige Sprachen haben eine statische Typisierung wie in C, die Typanmerkungen sind jedoch optional. Beispiele sind ML, Haskell, Scala, C #, C ++ 11 und Go. Wie funktioniert das? Zauber? Nein, dies wird als "Typinferenz" bezeichnet. In C # und Go betrachtet der Compiler die rechte Seite einer Zuweisung und leitet den Typ davon ab. Dies ist ziemlich einfach, wenn die rechte Seite ein Literal wie ist 42ul. Dann ist es offensichtlich, welcher Typ die Variable sein soll. Andere Sprachen haben auch komplexere Algorithmen, die berücksichtigen, wie eine Variable verwendet wird. Wenn Sie dies tun x/2, xkann es sich nicht um eine Zeichenfolge handeln, sondern muss einen numerischen Typ haben.

amon
quelle
Danke für die Erklärung. Das, was ich verstehe, ist, dass wenn wir den Typ einer Variablen (lokal oder global) deklarieren, wir dem Compiler tatsächlich mitteilen, wie viel Platz er für diese Variable im Stapel reservieren soll. Auf der anderen Seite haben #definewir eine Konstante, die direkt in Binärcode konvertiert wird - wie lang es auch sein mag - und die unverändert im Speicher abgelegt wird.
user106313
2
@ user31782 - nicht ganz. Wenn Sie eine Variable deklarieren, teilt der Typ dem Compiler mit, welche Eigenschaften die Variable hat. Eine dieser Eigenschaften ist die Größe; Zu den weiteren Eigenschaften gehört, wie Werte dargestellt werden und welche Vorgänge mit diesen Werten ausgeführt werden können.
Pete Becker
@PeteBecker Woher kennt der Compiler dann diese anderen Eigenschaften in #define X 5.2?
user106313
1
Dies liegt daran, dass Sie durch Übergabe des falschen Typs printfundefiniertes Verhalten ausgelöst haben. Auf meinem Computer gibt dieses Snippet jedes Mal einen anderen Wert aus, auf Ideone stürzt es nach dem Drucken von Null ab.
Matteo Italia
4
@ user31782 - „Es scheint , als ob ich eine Operation auf einem beliebigen Datentyp durchführen kann“ Nein , X*Ywenn nicht gültig Xund Ysind Zeiger, aber es ist in Ordnung , wenn sie sind ints; *Xist nicht gültig, wenn Xist ein int, aber es ist in Ordnung, wenn es ein Zeiger ist.
Pete Becker
4

X im zweiten Beispiel ist niemals ein Float. Es wird als Makro bezeichnet und ersetzt den in der Quelle definierten Makrowert 'X' durch den Wert. Ein lesbarer Artikel zu #define ist hier .

Bei dem bereitgestellten Code ändert der Präprozessor den Code vor dem Kompilieren

Z=Y+X;

zu

Z=Y+5.2;

und genau das wird kompiliert.

Das bedeutet, dass Sie diese "Werte" auch durch Code wie ersetzen können

#define X sqrt(Y)

oder auch

#define X Y
James Snell
quelle
3
Es wird nur ein Makro genannt, kein variadisches Makro. Ein variadisches Makro ist ein Makro, das eine variable Anzahl von Argumenten akzeptiert, z #define FOO(...) { __VA_ARGS__ }.
HDV
2
Mein schlechtes, wird reparieren :)
James Snell
1

Die kurze Antwort lautet: C benötigt Typen aufgrund der Historie / Darstellung der Hardware.

Geschichte: C wurde in den frühen 1970er Jahren entwickelt und ist als Programmiersprache für Systeme gedacht. Code ist idealerweise schnell und nutzt die Fähigkeiten der Hardware optimal aus.

Rückschlüsse auf Typen zur Kompilierungszeit wären möglich gewesen, die bereits langsamen Kompilierungszeiten hätten sich jedoch erhöht (siehe XKCDs "Kompilierungs" -Cartoon. Dies galt mindestens 10 Jahre nach Veröffentlichung von C für "Hallo Welt" ). Das Ableiten von Typen zur Laufzeit hätte nicht den Zielen der Systemprogrammierung entsprochen. Für die Laufzeitunterbrechung ist eine zusätzliche Laufzeitbibliothek erforderlich. C kam lange vor dem ersten PC. Welches hatte 256 RAM. Nicht Gigabyte oder Megabyte, sondern Kilobyte.

In Ihrem Beispiel, wenn Sie die Typen weglassen

   X=5.2;
   Y=5.1;

   Z=Y+X;

Dann hätte der Compiler glücklich herausfinden können, dass X & Y Floats sind, und Z gleich gemacht. Tatsächlich würde ein moderner Compiler auch herausfinden, dass X & Y nicht benötigt werden, und einfach Z auf 10.3 setzen.

Angenommen, die Berechnung ist in eine Funktion eingebettet. Der Funktionsschreiber möchte möglicherweise sein Wissen über die Hardware oder das zu lösende Problem nutzen.

Wäre ein Double passender als ein Float? Verbraucht mehr Speicher und ist langsamer, aber die Genauigkeit des Ergebnisses wäre höher.

Möglicherweise könnte der Rückgabewert der Funktion int (oder long) sein, da die Dezimalstellen nicht wichtig waren, obwohl die Konvertierung von float nach int nicht ohne Kosten ist.

Der Rückgabewert könnte auch doppelt angegeben werden, um sicherzustellen, dass float + float nicht überläuft.

All diese Fragen scheinen für die große Mehrheit des heute geschriebenen Codes sinnlos zu sein, waren jedoch bei der Erstellung von C von entscheidender Bedeutung.

itj
quelle
1
dies erklärt nicht , warum zB Typdeklarationen wurden nicht optional gemacht, Programmierer ermöglicht entweder zu holen , sie ausdrücklich zu erklären oder sich auf Compiler herzuleiten
gnat
1
In der Tat tut es nicht @gnat. Ich habe den Text überarbeitet, aber es gab zu der Zeit keinen Grund, dies zu tun. Die Domäne C wurde entwickelt, um tatsächlich zu entscheiden, 17 in 1 Byte oder 2 Bytes oder 4 Bytes oder als Zeichenfolge oder als 5 Bits in einem Wort zu speichern.
itj
0

C hat keine Typinferenz (das ist es, was es heißt, wenn ein Compiler den Typ einer Variablen für Sie errät), weil es alt ist. Es wurde in den frühen 1970er Jahren entwickelt

Viele neuere Sprachen verfügen über Systeme, mit denen Sie Variablen verwenden können, ohne deren Typ anzugeben (Ruby, Javascript, Python usw.).

Tristan Burnside
quelle
12
Keine der von Ihnen genannten Sprachen (Ruby, JS, Python) verfügt über eine Typinferenz als Sprachfunktion, obwohl Implementierungen sie möglicherweise zur Steigerung der Effizienz verwenden. Stattdessen verwenden sie eine dynamische Typisierung, bei der Werte Typen haben, Variablen oder andere Ausdrücke jedoch nicht.
amon
2
JS erlaubt Ihnen nicht, den Typ wegzulassen - es erlaubt Ihnen nur nicht, ihn zu deklarieren. Es wird die dynamische Typisierung verwendet, bei der Werte Typen haben (z. B. trueist boolean) und keine Variablen (z. B. var xWerte eines beliebigen Typs enthalten können). Auch die Typinferenz für solche einfachen Fälle wie die fraglichen war wahrscheinlich ein Jahrzehnt vor der Veröffentlichung von C bekannt.
Skript
2
Das macht die Aussage nicht falsch (um etwas zu erzwingen, muss man es auch zulassen). Die existierende Typinferenz ändert nichts an der Tatsache, dass das Typsystem von C ein Ergebnis seines historischen Kontexts ist (im Gegensatz zu einer spezifisch angegebenen philosophischen Begründung oder technischen Einschränkung)
Tristan Burnside,
2
In Anbetracht dessen, dass ML - das so alt wie C ist - Typinferenz hat, ist "es ist alt" keine gute Erklärung. Der Kontext, in dem C verwendet und entwickelt wurde (kleine Maschinen, die einen sehr kleinen Platzbedarf für den Compiler erforderten), scheint wahrscheinlicher. Keine Ahnung, warum Sie dynamische Eingabesprachen erwähnen würden, anstatt nur einige Beispiele für Sprachen mit Typinferenz zu nennen - Haskell, ML, heck, C # hat es - kaum noch eine obskure Funktion.
Voo
2
@BradS. Fortran ist kein gutes Beispiel , weil der erste Buchstabe des Variablennamens ist eine Typdeklaration, es sei denn , Sie verwenden implicit nonein diesem Fall Sie müssen eine Art erklären.
dmckee