1. Innerer Typ der Ausdrucksberechnung
Wenn Sie gerade erst anfangen, denkt man leicht: “Na ja, Zahlen sind Zahlen. Was macht es für einen Unterschied, int oder double — ist doch eh 7”. In der Praxis ist der Unterschied riesig: der Typ beeinflusst, wie der Ausdruck berechnet wird, welche Werte möglich sind und was beim Speichern des Ergebnisses verloren geht.
Implizite Konvertierungen (implicit conversions) sind die Regeln, nach denen der Compiler automatisch einen Typ in einen anderen überführt, damit eine Operation sinnvoll ist. Das ist wie ein automatischer Übersetzer im Messenger: praktisch, solange er nicht Ihre Aussage in etwas Merkwürdiges „korrigiert“. Unser Ziel heute ist daher, vorherzusagen, wann der Compiler Typen „übersetzt“, in welche Richtung und wo dadurch Bugs entstehen.
Wenn Sie schreiben:
double x = 5 / 2;
hat ein Anfänger oft das Bild im Kopf: „links ist ein double, also wird die Division auch nach Kommazahlen aussehen“. Der Compiler denkt aber anders: er berechnet das, was rechts steht, nach den Typregeln auf der rechten Seite, erhält ein Ergebnis und versucht erst danach, es in die Variable links zu schreiben (und das Schreiben ist eine separate Konvertierung).
Um sich nicht zu verwirren, ist es nützlich, folgenden Ablauf im Kopf zu behalten:
flowchart TD
A[Operanden des Ausdrucks] --> B[Promotions: kleine Typen werden "angehoben"]
B --> C[Allgemeine arithm. Konvertierung: ein gemeinsamer Typ wird gewählt]
C --> D[Berechnung der Operation]
D --> E[Zuweisung: das Ergebnis wird an den Typ der linken Variable angepasst]
Und hier ein wichtiger psychologischer Punkt: der Typ der linken Variable "befiehlt" nicht die Berechnung rechts. Er beeinflusst nur die finale Speicherung des Ergebnisses.
3. Promotions: kleine Typen anheben
Promotion ist das automatische „Hochstufen“ eines Typs zu einem praktischeren/standardmäßigeren Typ für Berechnungen. Der häufigste Fall, den Sie vielleicht schon gesehen haben, ohne es bewusst zu bemerken: char verhält sich in der Arithmetik wie eine Zahl. Das ist keine Magie und kein Verrat, das sind eben Promotions.
Im C++-Standard existiert die Idee der integral promotions (ganzzahlige Promotions) als eigener, wichtiger Regelblock; selbst in Editorfassungen und Drafts sieht man, dass die Formulierungen und Terminologie rund um promotions sehr sorgfältig diskutiert werden.
Beispiel: char wird zur Zahl
Ein kleines Experiment:
#include <iostream>
int main() {
char c = 'A';
int next = c + 1;
std::cout << next << '\n'; // 66 (wenn 'A' = 65 in ASCII)
}
Sie hätten vielleicht „B“ erwartet, bekamen aber eine Zahl. Warum? Weil der Ausdruck c + 1 Arithmetik ist, und für Arithmetik wird char normalerweise zu int promoted, sodass das Ergebnis ein ganzzahliger Wert wird.
Wenn Sie wirklich ein Zeichen wollen — müssen Sie explizit darüber nachdenken, was Sie tun; explizite Typumwandlungen lassen wir bewusst für spätere Kurse.
Beispiel: bool in Arithmetik — auch eine Zahl
Klingt ein wenig wie ein Witz, aber in Berechnungen verhält sich true oft wie 1 und false wie 0.
#include <iostream>
int main() {
bool paid = true;
int total = 100 + paid;
std::cout << total << '\n'; // 101
}
Das funktioniert, aber in der Regel ist das schlecht für die Lesbarkeit. Der Compiler kommt intern klar, aber die Person, die den Code liest (auch Sie in ein paar Wochen), wird nicht sehr glücklich sein.
Usual arithmetic conversions: wie der gemeinsame Typ gewählt wird
Wenn eine Operation zwei Zahlen unterschiedlichen Typs bekommt, muss der Compiler entscheiden: „In welchem Typ soll die Operation ausgeführt werden?“ Dieser Regelblock wird oft usual arithmetic conversions genannt. In den Materials der Drafts wurde sogar extra auf diesen Begriff verwiesen — das Thema ist also wirklich grundlegend.
Für unser derzeitiges Niveau reicht die praktische Faustregel:
Wenn in einem Ausdruck ein double vorkommt, dann wird die Berechnung fast immer in double ausgeführt, um den Bruchteil nicht zu verlieren.
Also:
- int + int → int
- int + double → double
- int / int → int (auch wenn Sie das Ergebnis später in ein double schreiben)
- double / int → double
4. Mischung von int und double
Falle Nr. 1: „Ich schreibe in ein double, also wird es ein Bruch“ — nein
Hier eine Demonstration, die vielen beim ersten Mal den Kopf verdreht (und das ist normal):
#include <iostream>
int main() {
int sum = 5;
int count = 2;
double avg = sum / count; // beide int -> int-Division
std::cout << avg << '\n'; // 2
}
Warum 2 und nicht 2.5? Weil sum / count als int / int berechnet wird, also ist das Ergebnis eine ganze Zahl; der Bruchteil wird abgeschnitten. Erst danach wird diese ganze Zahl ruhig in das double gelegt und ergibt z.B. 2.0. Keine Fehlermeldung — einfach nicht das, was Sie erwartet haben.
Lifehack Nr. 1: „Wie mache ich die Division zu einer Fließkommadivision“ — bringen Sie ein double in den Ausdruck
Die einfachste Methode auf unserem Niveau (ohne Casts) ist, dafür zu sorgen, dass mindestens ein Operand ein double ist. Zum Beispiel 2.0 statt 2.
#include <iostream>
int main() {
int sum = 5;
int count = 2;
double avg = sum / 2.0; // jetzt Ausdruck in double
std::cout << avg << '\n'; // 2.5
}
Der Compiler sieht hier int / double, wandelt das int in double um, rechnet in double und erhält ein Bruch-Ergebnis.
Falle Nr. 2: „stiller Datenverlust“ beim double → int
Wenn Sie eine Fließkommazahl einer Ganzzahl-Variable zuweisen, verschwindet der Dezimalteil einfach:
#include <iostream>
int main() {
double price = 19.99;
int euros = price;
std::cout << euros << '\n'; // 19
}
Das ist keine Rundung, sondern wirklich das Abschneiden des Dezimalteils. Der Compiler ist nicht verpflichtet, Sie zu retten. Er macht einfach das, was Sie geschrieben haben.
Mini‑Merkblatt
Manchmal ist es nützlich, eine kurze „Landkarte“ zu haben, um nicht zu raten.
| Ausdruck | Operandtypen | Berechnungstyp | Beispielergebnis |
|---|---|---|---|
|
int und int | |
|
|
int und double | |
|
|
int und double | |
|
|
rechts int/int | rechts , links |
|
|
rechts ist ein double | rechts , links |
|
Hier sieht man das Wichtigste: zuerst wird rechts berechnet, dann wird an den Typ links angepasst.
5. Mini‑Kassenrechner: Thema in eine Anwendung einbetten
Damit das Thema nicht im Vakuum bleibt, führen wir ein Beispielprogramm fort, das wir im Kurs entwickeln: ein Konsolen‑„Mini‑Kassenterminal“. Es beansprucht nicht, Buchhaltung zu ersetzen (echtes Geld sollte man besser nicht in double speichern — das ist eine erwachsene Diskussion), zeigt aber ideal typische Anfängerfehler.
Erster Schritt: durchschnittlichen Artikelpreis berechnen und Bug entdecken
Wir lesen die Anzahl der Artikel und die Gesamtsumme. Die Anzahl ist ganzzahlig, die Summe ist gebrochen.
#include <iostream>
int main() {
int items = 0;
double total = 0.0;
std::cin >> items >> total;
double avg = total / items;
std::cout << avg << '\n'; // z.B.: 12.5
}
Hier ist alles in Ordnung, weil total ein double ist, also wird in double geteilt.
Und jetzt zeigen wir die „schlechte“ Variante — genau die, die oft aus Gewohnheit geschrieben wird:
#include <iostream>
int main() {
int items = 0;
int total = 0;
std::cin >> items >> total;
double avg = total / items;
std::cout << avg << '\n'; // wenn total=25 items=2 -> 12
}
Wenn total = 25, items = 2, ist der „wirkliche“ Durchschnitt 12.5, aber Sie bekommen 12. Und das liegt nicht daran, dass double avg fehlerhaft ist — es liegt daran, dass total / items als ganzzahlige Division berechnet wurde.
Fix ohne Casts: einen Operand als double ausweisen
Wenn die Summe per Semantik gebrochen sein kann — speichern Sie sie als double. Aber selbst wenn die Summe ganzzahlig ist (z. B. „Euro ohne Cent“), möchten Sie vielleicht trotzdem einen gebrochenen Durchschnitt. Dann reicht es, ein double in den Ausdruck zu lassen:
#include <iostream>
int main() {
int items = 0;
int total = 0;
std::cin >> items >> total;
double avg = total / (items * 1.0);
std::cout << avg << '\n'; // wenn total=25 items=2 -> 12.5
}
Beachten Sie: (items * 1.0) wird zu double, also wird auch total / double zu einer Berechnung in double. Ja, das sieht etwas „tricky“ aus, aber auf unserem Level ist das eine normale Technik, bis wir uns explizite Casts anschauen.
Rabatt in Prozent hinzufügen: erneut Mischung von int und double
Rabatte werden oft als ganze Prozentzahlen eingegeben, die Gesamtsumme ist aber gebrochen.
#include <iostream>
int main() {
double total = 0.0;
int discountPercent = 0;
std::cin >> total >> discountPercent;
double discount = total * discountPercent / 100.0;
double finalTotal = total - discount;
std::cout << finalTotal << '\n'; // z.B.: 899.1
}
Wichtig ist hier, dass / 100.0 eine Division in double ist. Wenn stattdessen / 100 stünde, wäre total * discountPercent bereits double, und die folgende Division wäre trotzdem double / int, also double. Aber 100.0 macht die Absicht eindeutig: wir arbeiten mit Brüchen.
6. Wie man über Typen in Formeln nachdenkt
Eine sehr nützliche Gewohnheit: Stellen Sie sich bei einer Formel mental zwei Fragen.
Erste Frage: „Wo in dieser Formel soll ein Dezimalanteil auftreten?“ Wenn die Antwort „soll“ lautet, prüfen Sie, ob im Ausdruck mindestens ein double vorhanden ist. Wenn nicht — dann machen Sie sehr wahrscheinlich versehentlich ganzzahlige Arithmetik.
Zweite Frage: „Wohin schreibe ich das Ergebnis?“ Wenn Sie das Ergebnis in ein int speichern, müssen Sie damit rechnen, dass der Dezimalanteil verschwindet. Manchmal ist das in Ordnung (z. B. Anzahl von Personen), manchmal ist es ein Bug (z. B. Preis).
7. Typische Fehler
Fehler Nr. 1: einen gebrochenen Wert von int / int erwarten, weil links ein double steht.
Das ist eine der häufigsten logischen Fallen. Division wird durch die Typen der Operanden im Ausdruck rechts bestimmt, nicht durch den Typ der linken Variable. Deshalb ergibt double x = 5 / 2; 2 statt 2.5. Die Lösung: Gewöhnen Sie sich an, bei Bedarf ein double direkt im Ausdruck sicherzustellen.
Fehler Nr. 2: das Problem mit Klammern zu „behandeln“, wenn es ein Typenproblem ist.
Klammern ändern die Berechnungsreihenfolge, aber nicht die Typen der Operanden. Man kann alle Klammern perfekt setzen und trotzdem ein falsches Ergebnis wegen ganzzahliger Division erhalten. In solchen Fällen muss man die Typen ändern oder wenigstens einen Literaltyp (z. B. 2.0 statt 2).
Fehler Nr. 3: unbemerkt den Dezimalteil verlieren beim Zuweisen von double zu int.
int a = 19.99; wird zu 19 ohne Warnung auf der logischen Ebene. Der Compiler hält das für eine erlaubte Konvertierung. Wenn der Dezimalteil wichtig ist — speichern Sie in double. Wenn der Dezimalteil nicht wichtig ist, machen Sie es im Code deutlich (z. B. bewusst runden/abschneiden — das besprechen wir später mit zusätzlichen Werkzeugen).
Fehler Nr. 4: char und bool versehentlich in Arithmetik verwenden.
char und bool rutschen leicht in Ausdrücke: ein Flag wurde addiert, ein Zeichen als Zahl behandelt und plötzlich hat man ein rätselhaftes Ergebnis. Formal lässt sich das mit Promotions erklären, praktisch hilft Disziplin: Führen Sie Arithmetik nur mit Werten durch, die semantisch wirklich Zahlen sind, nicht mit Typen „für Text“ oder „für Logik“.
Fehler Nr. 5: Formeln so schreiben, dass der Leser nicht erkennt, in welchem Typ gerechnet wird.
Selbst wenn der Compiler alles „richtig“ berechnet, kann ein Mensch es missverstehen. Deshalb ist es oft besser, 100.0 statt 100 zu schreiben oder die Summe gleich als double zu speichern, damit die Formel eindeutig lesbar ist. Es geht dabei nicht um „Ästhetik“, sondern um die Verringerung zukünftiger Bugs.
GO TO FULL VERSION