Zum Inhalt springen

C++-Programmierung/ Einführung in C++/ Rechnen mit unterschiedlichen Datentypen

Aus Wikibooks


Sie kennen nun die Datentypen in C++ und haben auch schon mit int-Variablen gerechnet. In diesem Kapitel erfahren Sie, wie man mit Variablen unterschiedlichen Typs rechnet. Es geht also weniger um das Ergebnis selbst, als viel mehr darum, wie der Ergebnisdatentyp lautet.

Ganzzahlen

[Bearbeiten]

Das Rechnen mit Ganzzahlen ist leicht zu begreifen. Die „kleinen“ Datentypen werden als int behandelt. Bei den größeren entscheidet der größte Datentyp über den Ergebnistyp. Die folgende Liste zeigt die Zusammenhänge:

char        + char           => int              |    wchar_t        + char           => int
char        + wchar_t        => int              |    wchar_t        + wchar_t        => int
char        + signed char    => int              |    wchar_t        + signed char    => int
char        + unsigned char  => int              |    wchar_t        + unsigned char  => int
char        + short          => int              |    wchar_t        + short          => int
char        + unsigned short => int              |    wchar_t        + unsigned short => int
char        + int            => int              |    wchar_t        + int            => int
char        + unsigned int   => unsigned int     |    wchar_t        + unsigned int   => unsigned int
char        + long           => long             |    wchar_t        + long           => long
char        + unsigned long  => unsigned long    |    wchar_t        + unsigned long  => unsigned long

signed char + char           => int              |    unsigned char  + char           => int
signed char + wchar_t        => int              |    unsigned char  + wchar_t        => int
signed char + signed char    => int              |    unsigned char  + signed char    => int
signed char + unsigned char  => int              |    unsigned char  + unsigned char  => int
signed char + short          => int              |    unsigned char  + short          => int
signed char + unsigned short => int              |    unsigned char  + unsigned short => int
signed char + int            => int              |    unsigned char  + int            => int
signed char + unsigned int   => unsigned int     |    unsigned char  + unsigned int   => unsigned int
signed char + long           => long             |    unsigned char  + long           => long
signed char + unsigned long  => unsigned long    |    unsigned char  + unsigned long  => unsigned long

short       + char           => int              |    unsigned short + char           => int
short       + wchar_t        => int              |    unsigned short + wchar_t        => int
short       + signed char    => int              |    unsigned short + signed char    => int
short       + unsigned char  => int              |    unsigned short + unsigned char  => int
short       + short          => int              |    unsigned short + short          => int
short       + unsigned short => int              |    unsigned short + unsigned short => int
short       + int            => int              |    unsigned short + int            => int
short       + unsigned int   => unsigned int     |    unsigned short + unsigned int   => unsigned int
short       + long           => long             |    unsigned short + long           => long
short       + unsigned long  => unsigned long    |    unsigned short + unsigned long  => unsigned long

int         + char           => int              |    unsigned int   + char           => unsigned int
int         + wchar_t        => int              |    unsigned int   + wchar_t        => unsigned int
int         + signed char    => int              |    unsigned int   + signed char    => unsigned int
int         + unsigned char  => int              |    unsigned int   + unsigned char  => unsigned int
int         + short          => int              |    unsigned int   + short          => unsigned int
int         + unsigned short => int              |    unsigned int   + unsigned short => unsigned int
int         + int            => int              |    unsigned int   + int            => unsigned int
int         + unsigned int   => unsigned int     |    unsigned int   + unsigned int   => unsigned int
int         + long           => long             |    unsigned int   + long           => long oder unsigned long
int         + unsigned long  => unsigned long    |    unsigned int   + unsigned long  => unsigned long

long        + char           => long             |    unsigned long  + char           => unsigned long
long        + wchar_t        => long             |    unsigned long  + wchar_t        => unsigned long
long        + signed char    => long             |    unsigned long  + signed char    => unsigned long
long        + unsigned char  => long             |    unsigned long  + unsigned char  => unsigned long
long        + short          => long             |    unsigned long  + short          => unsigned long
long        + unsigned short => long             |    unsigned long  + unsigned short => unsigned long
long        + int            => long             |    unsigned long  + int            => unsigned long
long        + unsigned int   => unsigned long    |    unsigned long  + unsigned int   => unsigned long
long        + long           => long             |    unsigned long  + long           => unsigned long
long        + unsigned long  => unsigned long    |    unsigned long  + unsigned long  => unsigned long


Zugegebenermaßen wirkt dies erst einmal erschlagend aber es ist eigentlich nicht schwierig zu begreifen. Bei jeder Rechenoperation hat jeder der 2 Operanden, sowie das Ergebnis der Rechnung, einen Datentyp:

#include <iostream>

int main(){
    char  zahl1=22;
    short zahl2=40;

    std::cout << zahl1 * zahl2 << std::endl; // 22   * 40    =  880
                                             // char + short => int
}

Gleitkommarechnen

[Bearbeiten]

Beim Rechnen mit Gleitkommazahlen gelten im Grunde die gleichen Regeln wie bei Ganzzahlen. Der Ergebnistyp entspricht auch hier dem des Operanden mit dem „größeren“ Typ. Die aufsteigende Reihenfolge lautet: float, double, long double. Es gilt also:

float       + float       => float
float       + double      => double
float       + long double => long double

double      + float       => double
double      + double      => double
double      + long double => long double

long double + float       => long double
long double + double      => long double
long double + long double => long double

Casting

[Bearbeiten]

Casting bedeutet in diesem Zusammenhang die Umwandlung eines Datentyps in einen anderen. Diese Typumwandlung kann sowohl automatisch (implizit) stattfinden, als auch vom Programmierer angegeben (explizit) werden.

Implizite Typumwandlung

[Bearbeiten]

Mit impliziter Typumwandlung hatten Sie bereits reichlich zu tun, denn es kann ausschließlich mit Zahlen gerechnet werden, die den gleichen Typ besitzen.

Beispiele:

char  + int          => int          | int          + int          => int
short + unsigned int => unsigned int | unsigned int + unsigned int => unsigned int
float + double       => double       | double       + double       => double

Umformungsregeln

[Bearbeiten]

Viele binäre Operatoren, die arithmetische oder Aufzählungsoperanden erwarten, verursachen Umwandlungen und ergeben Ergebnistypen auf ähnliche Weise. Der Zweck ist, einen gemeinsamen Ergebnistyp zu finden. Dieses Muster wird "die üblichen arithmetischen Umwandlungen" genannt, die folgendermaßen definiert sind:

„Gleitkomma geht vor“:

  • Wenn ein Operand vom Typ long double ist, dann wird der andere zu long double konvertiert.
  • Andernfalls, wenn ein Operand vom Typ double ist, dann wird der andere zu double konvertiert.
  • Andernfalls, wenn ein Operand vom Typ float ist, dann wird der andere zu float konvertiert.

Ist kein Gleitkommatyp beteiligt, dann werden folgende Ganzzahl-Umwandlungen auf beide Operanden angewendet:

  • Wenn ein Operand vom Typ unsigned long ist, dann wird der andere zu unsigned long konvertiert.
  • Andernfalls, wenn ein Operand vom Typ long und der andere vom Typ unsigned int, dann wird, falls ein long alle Werte eines unsigned int darstellen kann, der unsigned int-Operand zu long konvertiert; andernfalls werden beide Operanden zu unsigned long konvertiert.
  • Andernfalls, wenn ein Operand vom Typ long ist, dann wird der andere zu long konvertiert.
  • Andernfalls, wenn ein Operand vom Typ unsigned int ist, dann wird der andere zu unsigned int konvertiert.

Hinweis: Der einzig verbleibende Fall ist, dass beide Operanden vom Typ int sind.

Diese Regeln wurden so aufgestellt, dass dabei stets ein Datentyp in einen anderen Datentyp mit "größerem" Wertebereich umgewandelt wird. Das stellt sicher, dass bei der Typumwandlung keine Wertverluste durch Überläufe entstehen. Es können allerdings bei der Umwandlung von Ganzzahlen in float-Werte Rundungsfehler auftreten:

Bei dem Beispiel wird von float mit 32 Bit ausgegangen.


std::cout << "17000000 + 1.0f = " << std::fixed << 17000000 + 1.0f << std::endl;
Ausgabe:
17000000 + 1.0f = 17000000.000000

Für die Berechnung werden zunächst beide Operanden in den Datentyp float konvertiert und anschließend addiert. Das Ergebnis ist wiederum ein float und somit aber nicht in der Lage, Zahlen in der Größenordnung von 17 Millionen mit der nötigen Genauigkeit zu speichern, um zwischen 17000000 und 17000001 zu unterscheiden. Das Ergebnis der Addition ist daher wieder 17000000.

Explizite Typumwandlung

[Bearbeiten]

In C++ gibt es dafür zwei Möglichkeiten. Zum einen den aus C übernommenen Cast (Typ)Wert und zum anderen die vier (neuen) C++ Casts.

static_cast< Zieltyp >(Variable)
const_cast< Zieltyp >(Variable)
dynamic_cast< Zieltyp >(Variable)
reinterpret_cast< Zieltyp >(Variable)
Tipp

Die Leerzeichen zwischen dem Zieltyp und den spitzen Klammern sind nicht zwingend erforderlich, Sie sollten sich diese Notation jedoch angewöhnen. Speziell wenn Sie später mit Templates oder Namensräumen arbeiten, ist es nützlich, Datentypen ein wenig von ihrer Umgebung zu isolieren. Sie werden an den entsprechenden Stellen noch auf die ansonsten möglichen Doppeldeutigkeiten hingewiesen.

Thema wird später näher erläutert…

Im Moment benötigen Sie nur den static_cast. Was genau die Unterschiede zwischen diesen Casts sind und wann man welchen einsetzt, erfahren Sie im Kapitel Casts. Auf C-Casts wird in diesem Kapitel ebenfalls eingegangen, merken Sie sich jedoch schon jetzt, dass Sie diese nicht einsetzen sollten. Natürlich müssen Sie sie als C++-Programmierer dennoch kennen, falls Sie einmal auf einen solchen stoßen sollten.

Ganzzahlen und Gleitkommazahlen

[Bearbeiten]

Wird mit einer Ganzzahl und einer Gleitkommazahl gerechnet, so ist das Ergebnis vom gleichen Typ wie die Gleitkommazahl.

Rechnen mit Zeichen

[Bearbeiten]

Mit Zeichen zu rechnen, ist besonders praktisch. Um beispielsweise das gesamte Alphabet auszugeben, zählen Sie einfach vom Buchstaben 'A' bis einschließlich 'Z':

#include <iostream>

int main(){
    for(char i = 'A'; i <= 'Z'; ++i){
        std::cout << i;
    }
}
Ausgabe:
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Für eine Erklärung des obigen Quellcodes lesen Sie bitte das Kapitel Schleifen.

Wenn Sie binäre Operatoren auf Zeichen anwenden, ist das Ergebnis (mindestens) vom Typ int. Im folgenden Beispiel wird statt eines Buchstabens der dazugehörige ASCII-Wert ausgegeben. Um also wieder ein Zeichen auszugeben, müssen Sie das Ergebnis wieder in den Zeichentyp casten. (Beachten Sie im folgenden Beispiel, dass die Variable i – im Gegensatz zum vorherigen Beispiel – nicht vom Typ char ist):

#include <iostream>

int main(){
    char zeichen = 'A';

    for(int i = 0; i < 26; ++i){
        std::cout << zeichen + i << ' ';               // Ergebnis int
    }

    std::cout << std::endl;

    for(int i = 0; i < 26; ++i){
        std::cout << static_cast< char >(zeichen + i); // Ergebnis char
    }
}
Ausgabe:
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
ABCDEFGHIJKLMNOPQRSTUVWXYZ