Wofür braucht man ein Suffix bei Variablen(C++)?

3 Antworten

Vom Fragesteller als hilfreich ausgezeichnet

Vorweg: Du meinst keine Variablen-, sondern Literal-Suffixe. Zu Variablensuffixen gibt es auch viel zu erzählen, aber das ist ein anderes Thema ...

Literalsuffixe sind - gerade bei "float" - extrem wichtig!

Wenn du einen double-Literal nutzt, um eine float-Variable zu initialisieren oder einen neuen Wert zuzuweisen (oder umgekehrt), dann kommt es intern zu einem impliziten Casting, der in sehr vielen Fällen einige Bits Datenverlust mit sich führt.

Demonstration:
#include <ios> // boolalpha
#include <iostream> // cout, endl

int main(void) {
	using namespace ::std;

	cout << boolalpha;

	float a = 0.1234f;
	cout << "a: " << (a == 0.1234f) << endl;

	float b = 0.1234;
	cout << "b: " << (b == 0.1234) << endl;
}

Die Ausgabe lautet:

a: true
b: false

Und das nur, wegen dem ollen Suffix! Gemein, oder? Guck dir bitte ganz genau an, welche Literale über ein angehängtes "f" verfügen, und welche nicht!

Der obige Effekt ist übrigens einer der Gründe dafür, warum man Gleitpunktzahlen niemals direkt mit "==" auf Gleichheit prüfen sollte, aber das nur am Rande ...

Wie du siehst, ist dieses "sinnlose" Suffix doch manchmal gar nicht sooooooooo "sinnlos". :)

Fazit:

Wenn du innerhalb von Berechnungen / Algorithmen schwer auffindbare Fehler vermeiden willst, nutze immer die Suffixe (falls vorhanden), die zu deinen Variablentypen passen.

Das gilt insbesondere bei C++ noch viel stärker, als bei C, allein schon wegen der neuen universellen Initialisierung, der Überladung aus dem Beispiel von Isendrak oder noch extremer bei der Programmierung mit Templates. (Nichts dieser exklusiven C++ Features existiert bei C.)

Meine und die Antworten der anderen decken übrigens nicht alle Gründe FÜR die Literalsuffixe ab, es gibt dazu noch mehr zu wissen, aber ich will das hier jetzt nicht zu sehr in die Länge ziehen. (Habe unter der Antwort von PeterLustig1999 schon einen Roman verfasst ... sorry!) ><

Also dann ... schönen Tag noch! :)

Letztlich bindest du ein Literal eines bestimmten Datentyps an eine Variable eines unter Umständen anderen Datentyps.

Wenn du schreibst

unsigned long a = 12345678;

weiß der Compiler nicht, ob die Zahl rechts vom Gleichheitszeichen ein Integer-Literal, oder ein Unsigned-Long-Literal sein soll. Unsereins als Mensch schaut sich die Seite links vom Gleichheitszeichen an und erkennt direkt "Die linke Seite muss ganz klar eine Konstante vom Typ unsigned long sein!" Für den Compiler ist dies nicht so einfach, denn er weiß nicht, ob das wirklich das ist, was du damit erreichen willst, oder ob du vielleicht doch eine Integer-Konstante meinst.

Das macht bei dieser Konstante erst mal keinen Unterschied. Aber wenn du irgendwann mal eine Konstante an die Variable binden willst, die höher ist, als es ein int zulassen würde, meckert der Compiler, sagt dir, dass die Zahl diesen Raum verlässt. Um dies zu umgehen, schreibt man einfach

unsigned long a = 12345678ul;

um dem Compiler zu zeigen "Pass auf, ich will eine Konstante vom Typ unsigned long an eine Variable des gleichen Typs binden."

EDIT: Hier eine Seite, die dir die Unterschiede aufzählt:

https://www.tutorialspoint.com/cplusplus/cpp_constants_literals.htm

Woher ich das weiß:Berufserfahrung – Hauptberuflich IT-Consultant, Informatikstudium
TeeTier  20.05.2018, 02:42
... weiß der Compiler nicht, ob die Zahl rechts vom Gleichheitszeichen ein Integer-Literal, oder ein Unsigned-Long-Literal sein soll.

Weder bei C, noch bei C++ kennt der Compiler vorzeichenbehaftete Ganzzahl-Literale. Die sind ausschließlich und immer "unsigned". :)

Wenn du ...

int x = -123;

... schreibst, dann ist nur (!) "123" der Literal, und das Minus davor ein unärer Operator, der zur Kompilierzeit angewendet wird, und aus dem "unsigned int" einen "signed int" macht.

Dieses kleine feine Detail spielt in der Praxis allerdings wirklich (fast) nie eine Rolle. ;)

0
TeeTier  20.05.2018, 03:13

Noch ein Zusatz: Der Compiler weiß bei dem Literal aus deinem Beispiel ...

unsigned long a = 12345678;

... übrigens ganz genau um welchen Typen es sich handelt, und beachtet während dem Parsen gar nicht den Typen, der sich vor dem Variablennamen befindet.

Wenn der Literal locker in einen "int" passt, dann wird es ein "int". Da hier "12345678" auf den gängigsten 32- und 64-Bit-Systemen also locker in einen 32-Bit breiten "int" passt, würde der Compiler erst mal von einem "int" ausgehen.

Beachte, dass der Typ des Literals vorzeichenbehaftet ist, der Wert des Literals aber nicht! (siehe meinen vorherigen Kommentar!)

Wäre deine Zahl zu groß für einen "int", würde der Compiler als nächstes probieren, ob die Zahl in einen "long" passt. Wenn nicht, dann (bei neueren C-Versionen bzw. Compilern) einen "long long", und wenn der Literal auch dafür zu groß ist, bekommst du einen Fehler um die Ohren gehauen. :)

Zurück zum Thema: Du hast den Literal "12345678", der automatisch (da kein Suffix vorhanden) ganz eindeutig vom Compiler als "int" identifiziert wurde und danach einer "unsigned long" Variablen zugewiesen wird. Dafür ist ein impliziter Cast nötig, der allerdings von allen großen und modernen Compilern wegoptimeirt wird. Bei kleinen und älteren Compilern (besonders bei herstellerspezifischen Compilern, Mikrocontrollern und eingebetteten Systemen) findet man aber im Kompilat tatsächlich oft noch Reste eines Castings. (Genau das selbe Thema habe ich hier letzte Woche schon mal unter einer anderen Frage angesprochen, aber ist ja auch egal ...)

Würdest du jetzt allerdings ...

unsigned long x = 12345678ul;

... also mit abschließendem "u" und "l" Suffix schreiben, wüsste der Compiler auf anhieb, dass es sich um einen "unsigned long" Literal handelt. Wie gesagt, er beachtet in dieser Phase gar nicht, was auf der anderen Seite des Gleichheitszeichens steht.

Erst im nächsten Schritt sieht er, dass dieser "unsigned long" Literal auch genutzt wird, um eine "unsigned long" Variable zu initialisieren und freut sich ein Loch in den Bauch, weil er jetzt nicht extra casten muss. :)

Noch zwei Anmerkungen:

  1. Bei älteren C-Versionen war es so, dass ein zu großer Literal, der nicht in einen "long" passte, evtl. Platz in einem "unsigned long" fand.
  2. Hat mit dem Thema jetzt eigentlich nix zu tun, aber die Null "0" ist ein oktaler und kein dezimaler Literal.
int a = 0; // oktal
int b = 1; // dezimal

Naja, das war jetzt zwar noch nicht alles zu Integerliteralen, aber ich denke das reicht. ;)

Gute Nacht! :)
1
PeterLustig1999  21.05.2018, 13:12
@TeeTier

Tja, man lernt nie aus. Was die Fehlermelduingen angeht, das war mein Fehler, da hab ich den Java-Compiler und den C(++)-Compiler durcheinander geworfen, mein Fehler ^^

1

Einiges hat PeterLustig1999 ja schon beschrieben, hier aber noch eine Kleinigkeit:

void foo(float bar){
    std::cout << "float" << std::endl;
}

void foo(double bar){
    std::cout << "double" << std::endl;
}

foo(3.1415926);
//Ausgabe: double
foo(3.1415926f);
//Ausgabe: float

Im von dir genannten Beispiel macht das ganze (meistens) tatsächlich keinen (relevanten) Unterschied (ausser evtl. 4 Byte für nen Float und 8 Byte für nen Double).

Aber es gibt Situationen, in denen die Unterscheidung nicht ganz so einfach ist und daher explizit sein sollte. (Okay, man könnte in meinem Beispiel auch einfach nen Cast verwenden, braucht dann aber immer noch ganze 4 Byte mehr ;)

P.S.: Ohne Suffix wird eine Zahl mit Nachkommastellen in C++ standardmäßig als Double angenommen und in deinem Fall implizit zu nem Float "gecastet".