Die Ein- und Ausgabe

In diesem Unterpunkt werden wir die Eingabe von Datenwerten durch den Benutzer eines ausführbaren C++ Programms vorstellen und die formatierte Ausgabe von Datenwerten im Terminalfenster näher betrachten. Die Programmiersprache C++ stellt mehrere integrierte Möglichkeiten der Benutzereingabe und Ausgabe bereit. Der zur cout-Ausgabe korrespondierende Eingabebefehl lautet "cin" und das folgende kleine Programm zeigt die Verwendung dieses Befehls:

EingabeInt.cpp
#include <iostream>                                            // Ein- und Ausgabebibliothek
using namespace std;                                           // Benutze den Namensraum std

int main(){                                                    // Hauptfunktion
    int zahl;                                                  // Deklaration der Integer Variable 'zahl'
    cout << "Geben Sie bitte eine ganze Zahl ein: ";           // Ausgabe eines Textes
    cin  >> zahl;                                              // Einlesen der Zahl mittels der Tastatur
    cout << "Das doppelte Ihrer Zahl ist: " << 2*zahl << endl; // Ausgabe eines Textes
}

Ein- und Ausgabe mittels "cin" und "cout"

Bevor man eine Zahleneingabe durch den Benutzer machen kann, muss die zugehörige Variable, in welcher die eingegebene Zahl gespeichert wird, deklarieren. In dem nebenstehenden Programm wird deshalb zunächst eine Integer Variable "zahl" deklariert. Der Benutzer wird dann aufgefordert eine ganze Zahl einzugeben und mittels der Zeile "cin >> zahl;" wird der vom Benutzer eingegebene Zahlwert in der Variable "zahl" gespeichert. Falls der Benutzer hier eine 'falsche', dem Typ der Variable nicht entsprechende Eingabe macht (z.B. eine Gleitkommazahl eingibt), geschieht automatisch eine Typumwandlung (implizite Typkonvertierung). Am Ende wird dann das doppelte der eingegebenen Zahl im Terminal ausgegeben.
Die nebenstehende Abbildung verdeutlicht die Funktionsweise des Programms und zeigt eine dreimalige Benutzung des kompilierten ausführbaren Programms im Linux-Terminal. Beim ersten Ausführen des Programms gibt der Benutzer die ganze Zahl '4' ein und das doppelte dieser Zahl wird richtig ausgegeben. Beim zweiten Ausführen jedoch gibt der Benutzer die Gleitkommazahl '4.9' ein und Intern geschieht eine implizite Typkonvertierung, die effektiv zu einem Abrunden der eingegebenen Zahl führt, sodass die Ausgabe des doppelten Wertes der Zahl wieder '8' ist (näheres zur impliziten und expliziten Typkonvertierung wird später im Unterpunkt Typkonvertierung... besprochen).

EingabeInt_1.cpp
#include <iostream>                                            // Ein- und Ausgabebibliothek

int main(){                                                    // Hauptfunktion
    int zahl;                                                  // Deklaration der Integer Variable 'zahl'
    printf("Geben Sie bitte eine ganze Zahl ein: ");           // Ausgabe eines Textes
    scanf("%i", &zahl);                                        // Einlesen der Zahl mittels der Tastatur
    printf("Das doppelte Ihrer Zahl ist: %i \n",2*zahl);       // Ausgabe eines Textes
}

Ein- und Ausgabe mittels "scanf(...)" und "printf(...)"

Die Ein- und Ausgabebefehle "cin" und "cout", die wir im vorigen Programm benutzt hatten, können auch mittels der Ausgabefunktionen "scanf(...)" und "printf(...)" programmiert werden. Die in der C-Standardbibliothek definierte Ein- und Ausgabefunktionen "scanf(...)" und "printf(...)" werden auch oft in C++ Programmen verwendet und im weiteren Verlauf der Vorlesung werden wir die Terminalausgabe auch meist mittels des Befehls "printf(...)" machen. Das auf der rechten Seite abgebildete Programm stellt die entsprechende scanf-printf-Version des vorigen Programms EingabeInt.cpp dar. Diesmal wird die vom Benutzer eingegebene Zahl mittels der Zeile "scanf("%i", &zahl);" eingelesen und in der Variable "zahl" gespeichert; oder genauer unter der Adresse der Variable "zahl" ("&zahl") abgelegt. Das Symbol & welches vor der Variable steht, ist der sogenannte Adressenoperator (näheres siehe Adressen, Zeiger und Referenzen). Der erste Eintrag in der scanf-Funktion ("%i") spezifiziert das Format der zu erwartenden Eingabe und beginnt stets mit dem Prozentzeichen %, wobei %i eine Integerzahl spezifiziert. Die gleiche Vorgehensweise wird danach bei der printf-Funktion verwendet, wobei nun an der Stelle %i das Resultat von 2*zahl ausgegeben wird. Die möglichen, unterschiedlichen Formatspezifikationen findet man in den Standardbibliotheken von scanf und printf (siehe auch wikipedia zu scanf und wikipedia zu printf).

In dem folgenden Programm werden einige Formatspezifikationen und Anwendungen der Ausgabefunktion "printf(...)" dargestellt:

Printf.cpp
#include <iostream>                                     // Ein- und Ausgabebibliothek

int main(){                                             // Hauptfunktion
    bool b = true;                                      // Definition der boolschen Variable 'b'
    char zeichen = 'G';                                 // Definition der char Variable 'zeichen'
    int ganze_zahl = -30256;                            // Definition der int Variable 'ganze_zahl'
    float reelle_zahl = 2.4573;                         // Definition der float Variable 'reelle_zahl'                  
    double reelle_zahl_doppeltgenau = 2453.4573545742;  // Definition der double Variable 'reelle_zahl_doppeltgenau'
    long double reelle_zahl_long = 7653.3467587475842L; // Definition der long double Variable 'reelle_zahl_long' 
    
    printf("Die Variable 'b' hat den Wert %i und für sie wurde ein Speicherplatz von %li Byte im Hauptspeicher reserviert. \n",b ,sizeof(b)); 
    printf("Die Variable 'zeichen' hat den Wert %c und für sie wurde ein Speicherplatz von %li Byte im Hauptspeicher reserviert. \n \n",zeichen ,sizeof(zeichen)); 
    
    printf("Die drei reellen Variablen Werte lauten: \n"); 
    printf("  reelle_zahl    reelle_zahl_doppeltgenau    reelle_zahl_long \n"); 
    printf("%13.4f %27.10f %19.13Lf \n",reelle_zahl, reelle_zahl_doppeltgenau, reelle_zahl_long);
    
    printf("\n... und in exponentieller Schreibweise: \n"); 
    printf("  reelle_zahl    reelle_zahl_doppeltgenau    reelle_zahl_long \n"); 
    printf("%13.4e %27.10e %19.13Le \n",reelle_zahl, reelle_zahl_doppeltgenau, reelle_zahl_long);
}

Es werden zunächst diverse Variablen unterschiedlicher Datentypen deklariert (bool b; , char zeichen; , ...) und ihnen gleichzeitig ein spezifischer Wert zugewiesen (Initialisierung). Der Wert der boolschen Variable 'b' wird als Integer Wert ausgegeben (%i), wobei durch die zuvor erfolgte "True"-Initialisierung diese Variable den Wert "1" haben wird. Es wird zusätzlich der reservierte Speicherplatz dieser Variable in Byte ausgegeben. Da der zurückgegebene Typ der durch "sizeof(b)" ermittelten Speicherplatzbelegung "long unsigned int" ist, muss man bei der Ausgabe den Formatbezeichner %li verwenden.
Danach wird der Wert der Variable 'zeichen' mittels %c ausgegeben und am Ende zwei Zeilenumbrüche verwendet (\n \n). Die drei reellwertigen Zahlenvariablen werden dann in einer speziellen Formatierung ausgegeben: printf("%13.4f %27.10f %19.13Lf \n",reelle_zahl, reelle_zahl_doppeltgenau, reelle_zahl_long);
So wird z.B. für die float-Variable "reelle_zahl" der Formatspezifizierer %13.4f verwendet. Das f am Ende des Ausdruckes kennzeichnet, dass es sich um eine Gleitkommazahl handelt, die auch in Komma-Schreibweise ausgegeben werden soll. Die davor geschriebene Zahl (13.4) bedeutet, dass 13 Leerzeichen für die Ausgabe reserviert und 4 Nachkommastellen ausgegeben werden sollen. Die Anzahl der reservierten Leerzeichen wurde so gewählt, dass der Wert der Zahl gerade rechtsbündig unter dem in der vorigen Zeile ausgegebenen Variablennamen stehen soll. In gleicher Weise wurden die zweite und dritte reelle Zahl ausgegeben, wobei hier eine unterschiedliche Anzahl der Nachkommastellen gewählt wurde und der Formatbezeichner bei der Variable "reelle_zahl_long" als eine 'lange' Gleitkommazahl Lf spezifiziert wurde, da ihr Typ long double ist. Die letzten drei printf(...) Anweisungen stellen ebenfalls die Werte der drei reellwertigen Variablen dar, dies jedoch in einer exponentiellen Darstellung (mittels e und Le).
Kompiliert man das Programm und führt es im Terminal aus, so erhält man die folgende Ausgabe:

Die Ein- und Ausgabebefehle "scanf(...)" und "printf(...)" besitzen noch viele weitere Formatspezifikationen:
Eingabe mittels scanf(...) und Ausgabe mittels printf(...)

Am Ende dieser Vorlesung möchte ich Ihnen noch eine hilfreiche Bibliothek vorstellen, die die numerischen Limitierungen bei numerischen Berechnungen in Form eines Templates bereitstellt (was ein Template eigentlich ist, werden wir erst später kennenlernen). Im Unterpunkt Datentypen und Variablen hatten wir gesehen, dass die Zahlenmenge ℝ$_C$ der mit einem C++ Programm abbildbaren Zahlen von ihrem grundsätzlichen Wesen her eine diskrete Abfolge von Zahlen und somit nicht-kontinuierlich ist. Die numerischen Grenzen, und somit der eigentliche Zahlenraum ℝ$_C$, hängt von dem gewählten Datentyp der definierten Variable ab. Die für die jeweiligen Typen definierten numerischen Grenzwerte sind in den Spezialisierungen des Templates "numeric_limits" enthalten, das sich in der Standardbibliothek <limits> befindet. Mittels "numeric_limits" lassen sich Fragen wie "Was ist die größte int-Zahl?" oder "Was ist die kleinste positive double-Zahl?" und vieles mehr beantworten (siehe Template "numeric_limits").
Das folgende C++ Programm stellt ein Anwendungsbeispiel dieses Templates dar und verwendet zusätzlich einen hilfreichen Trick der formatierten Ausgabe mittels der "printf(...)"-Funktion.

NumLim.cpp
// Numerische Grenzwerte des Templates "numeric_limits"
#include <iostream>     // Ein- und Ausgabebibliothek
#include <limits>       // Bibliothek mit std::numeric_limits
using namespace std;    // Benutze den Namensraum std

int main(){
    printf("Einige Grenzwerte des Datentyps int: \n");
    printf("%20s %20s \n","Groesste Zahl","Kleinste Zahl");
    printf("%20i %20i \n\n",numeric_limits<int>::max(),numeric_limits<int>::min());
    
    printf("Einige Grenzwerte des Datentyps long: \n");
    printf("%25s %25s \n","Groesste Zahl","Kleinste Zahl");
    printf("%25li %25li \n\n",numeric_limits<long>::max(),numeric_limits<long>::min());
    
    printf("Einige Grenzwerte des Datentyps double: \n");
    printf("%30s %30s %30s %30s %30s \n","Groesste Zahl","Kleinste Zahl","Kleinste positive Zahl","Anzahl der Nachkommastellen","Maschinen epsilon");
    printf("%30.15e %30.15e %30.15e ",numeric_limits<double>::max(),numeric_limits<double>::lowest(),numeric_limits<double>::min());
    printf("%30i %30.15e \n\n",numeric_limits<double>::digits10,numeric_limits<double>::epsilon());
    
    printf("Einige Grenzwerte des Datentyps long double: \n");
    printf("%30s %30s %30s %30s %30s \n","Groesste Zahl","Kleinste Zahl","Kleinste positive Zahl","Anzahl der Nachkommastellen","Maschinen epsilon");
    printf("%30.18Le %30.18Le %30.18Le ",numeric_limits<long double>::max(),numeric_limits<long double>::lowest(),numeric_limits<long double>::min());
    printf("%30i %30.18Le \n",numeric_limits<long double>::digits10,numeric_limits<long double>::epsilon());
}

Es werden einige Grenzwerte des Datentyps int, long, double und long double ausgegeben. Im Speziellen wird für die ganzzahligen Datentypen die größte und kleinste Zahl und für die reellwertigen Datentypen die größte und kleinste Zahl, die kleinste positive Zahl, die Anzahl der Nachkommastellen und das sogenannte "Maschinen-Epsilon" ausgegeben. Das Maschinen-Epsilon $\epsilon_M$ eines Gleitkommazahlen-Typs stellt die Differenz zwischen 1 und dem kleinsten darstellbaren Wert größer als 1 dar und somit ist der Wert ($1+\epsilon_M$) die nächstliegende Zahl, die größer als 1 ist.
Die Ausgabe mittels der "printf(...)"-Funktion benutzt hier einen hilfreichen Formatierungstrick um die dargestellten Zahlenwerte rechtsbündig unter ihre Beschreibung zu platzieren. Hierbei wird für die ausgegebene Zeile der Beschreibung die gleiche Anzahl von ausgegebenen Leerstellen reserviert, wie bei der Ausgabezeile der Datenwerte. Z.B. wird bei der Beschreibung der ganzzahligen Grenzwerte der Ausgabebefehl
"printf("%20s %20s \n","Groesste Zahl","Kleinste Zahl");"
benutzt. Mittels des Formatbezeichners %20s werden 20 Leerstellen reserviert und es wird der String der eigentlichen Bezeichnung separat nach dem Komma aufgelistet. Kompiliert man das Programm und führt es im Terminal aus, so erhält man die folgende Ausgabe: