Dr. Erhard Henkes, Stand: 22.04.2023

Zurück zum Start



Konsolen-Programme mit C++

1. Einstieg in die Programmierung

Im einfachsten Fall beschafft man sich eine IDE (integrated development environment) wie z.B. Code::Blocks oder eine kostenlose Version von MS Visual Studio (Express oder Community).

Diese IDE enthalten die notwendigen Werkzeuge Editor, Compiler und Linker. Wichtig: Auf optionale Zuschaltung der Neuerungen des C++-ISO-Standards von 2011 bei den Compilereinstellungen achten.

1.1. Was sind Konsolenprogramme?

Dieses Tutorial soll Ihnen einen Einstieg in die Erstellung von Konsolen-Programmen mit der objektorientierten Sprache C++ ermöglichen.  Konsolen-Programme sind das Gegenstück zu "Windows"-Programmen und entstammen den Anfängen der Programmierung.
Ein- und Ausgaben erfolgen hier primär textorientiert.

Obwohl diese Programme aus der "EDV-Steinzeit" stammen, sind sie zum Einstieg in C++ gut geeignet. Man konzentriert sich hier auf die eigentliche Problemlösung und die wesentlichen Programmiergrundlagen und nicht von Anfang an auf die grafische Gestaltung. 

Beginnen wir mit einem rudimentären Beispiel:

int main()
{
  return 0; // ist laut C++-ISO-Standard von 1998 nicht mehr notwendig, wird daher in Folge weggelassen.
}

Der C++-Compiler - das ist das Programm, das Ihren Sourcecode in ausführbaren Code übersetzt - sucht in C und C++ die Funktion main() als Einstiegspunkt. Vor dem main() steht ein int, das für den Variablentyp Integer (Ganzzahl) steht. Ein aktueller Compiler gibt automatisch die Null als Wert zurück, wenn das Programm normal beendet wird.

Das Ergebnis bei Ausführung der resultierenden exe-Datei sieht dann z.B. in der Konsole ("DOS-Box") von MS Windows wie folgt aus:

Was können wir auch mehr erwarten? Wenn Sie diese Ausgabe sehen, ist es Ihnen aber immerhin gelungen, mit dem Programmierwerkzeug, das Ihnen zur Verfügung steht, eine ausführbare exe-Datei zu erzeugen. 

Übrigens sehen viele Einsteiger zunächst nur ein kurzes Aufblitzen dieser Box. Daher muss man das Schließen am Programmende verhindern. Der einfachste Weg, dies zu erreichen, ist der Einbau der ursprünglich von Borland stammenden Funktion getch() bzw. _getch() aus conio.h. Dies entspricht leider nicht dem C++-Standard, ist aber unter MS Windows ziemlich praktisch.

Um portables C++ zu schreiben, verwendet man eine eigene Funktion wait() wie folgt:


#include <iostream>
#include <limits>

void wait()
{
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}

int main()
{
  wait();
}

Nun wollen wir sofort etwas mehr erreichen. Im nachfolgenden Programm deklarieren wir eine Integer-Variable namens zahl. Wir lesen einen Wert  über die Tastatur ein, den wir in dieser Variable speichern. Anschließend geben wir das Quadrat dieses Wertes auf dem Bildschirm aus: 

#include <iostream>
#include <limits>                           

using namespace std;

void wait()
{
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}

int main()
{
  int zahl;
  cin  >> zahl;                              // Eingabe
  cout << zahl * zahl << endl;               // Ausgabe 

  wait();
}

/*---------------------------------------------------------*/
//Alternative: (ohne Öffnung des namespace std)

#include <iostream>
#include <limits>                           

void wait()
{
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}

int main()
{
  int zahl ;
  std::cin  >> zahl ;                       // Eingabe
  std::cout << zahl * zahl << std::endl ;   // Ausgabe 

  wait();
}

Anstelle endl kann man auch '\n' schreiben. Es gibt aber einen Unterschied: endl entspricht '\n' (neue Zeile) plus flush (Ausgabepuffer leeren).

Mehrere Anweisungen werden hier zu einer Anweisungsfolge verknüpft, in dem sie nacheinander geschrieben werden.
Dies ist eine einfache Form von Kontrollstruktur, nämlich die sequenzielle Ausführung.

Zunächst wollen wir verstehen, wie eine exe-Datei aus unserem Sourcecode entsteht.
Hierzu verwenden wir (wahrscheinlich unbewusst) drei Werkzeuge:

Precompiler / Präprozessor
Compiler
Linker

Der Precompiler / Präprozessor durchforstet den Sourcecode, um spezielle Anweisungen auszuführen. Diese Anweisungen beginnen mit #.
In unserem Beispiel erkennt der Precompiler #include <iostream>  und setzt an dieser Stelle den entsprechenden Sourcecode ein. Auf diese Weise wird der gesamte Sourcecode bearbeitet. Der resultierende Sourcecode (cpp) wird dem Compiler übergeben. Dieser erzeugt eine Objektdatei (o). Diese wird vom Linker in eine lauffähige Programmdatei (z.B. exe) überführt.

In C++ verfügt man über sogenannte "namespaces". Namensräume sollen dem Programmierer helfen, bei gleichen Namen für Variablen, Klassen etc. Konflikte zu vermeiden. Der Standard-Namespace wird mit std bezeichnet. Gibt man den Namensraum nicht frei, so muss man den Namensraum zusätzlich angeben, also std::cout oder std::cin schreiben.

Betrachten wir zur Veranschaulichung folgendes C++-Programm:


#include <iostream>
#include <limits>

void wait()
{
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}


namespace MyCpp { double var; }

float var;

int main()
{
  int var;
  std::cout << "globale Variable     : "  << ::var      << std::endl; // 0
  std::cout << "lokale Variable      : "  << var        << std::endl;
  std::cout << "Variable im Namespace: "  << MyCpp::var << std::endl; // 0

  wait();
}


Hier wird drei Mal eine Variable namens var ausgegeben. Die Variable innerhalb main() hat dort ihren begrenzten Scope (Sichtbarkeitsbereich). Um die globale Variable var aufzurufen, setzt man :: davor. Wir können nun ebenfalls in einem eigenen Namensbereich (hier: MyCpp) eine Variable var erzeugen. Hierbei muss man diese Variable durch Voranstellen der Bezeichnung des Namensbereiches und den Operator :: ansprechen.

cout, cin, endl gehören zum Namensbereich std. Daher wird dieser Namen vorangestellt. Wollen Sie das vermeiden, gibt es zwei Möglichkeiten:
a) Man gibt den gesamten Namensbereich std durch  using namespace std;  frei.
b) Man macht die verwendeten Elemente des Namensbereiches std gezielt bekannt:  using std::cout;  usw.


1.2. Variablen und einfache Datentypen

Mit int zahl deklarieren wir eine Integer-Variable. Wir reservieren bei einem 32-Bit-Rechner 4 Byte Speicherplatz für eine vorzeichenbehaftete Ganzzahl. 4 Byte entsprechen 4 * 8 = 32 Bit.
Damit können wir binär einen Ganzzahlenbereich von 2 hoch 32 =  4 294 967 296 abdecken.

Dieser Bereich ist für int (32 Bit) heutzutage - 2 147 483 648 ... 0 ... 2 147 483 647.

Es gibt auch die Möglichkeit nur positive Ganzzahlen und die Null zuzulassen. Hierfür stellt man ein unsigned vor das int:
unsigned int  umfaßt  0 ... 4 294 967 295 (32-Bit-Maschinen).

Sie sehen, dass int automatisch signed int bedeutet. Solche Details findet man üblicherweise in der Datei limits.h.
Dort findet man die Grenzen für die einzelnen Variablentypen: char, short, int, long, ...
 

...

#define SHRT_MIN    (-32768)        /* minimum (signed) short value */
#define SHRT_MAX      32767         /* maximum (signed) short value */
#define USHRT_MAX     0xffff        /* maximum unsigned short value */
#define INT_MIN     (-2147483647 - 1) /* minimum (signed) int value */
#define INT_MAX       2147483647    /* maximum (signed) int value */
#define UINT_MAX      0xffffffff    /* maximum unsigned int value */
#define LONG_MIN    (-2147483647L - 1) /* minimum (signed) long value */
#define LONG_MAX      2147483647L   /* maximum (signed) long value */
#define ULONG_MAX     0xffffffffUL  /* maximum unsigned long value */

#if     _INTEGRAL_MAX_BITS >= 8
#define _I8_MIN     (-127i8 - 1)    /* minimum signed 8 bit value */
#define _I8_MAX       127i8         /* maximum signed 8 bit value */
#define _UI8_MAX      0xffui8       /* maximum unsigned 8 bit value */
#endif

#if     _INTEGRAL_MAX_BITS >= 16
#define _I16_MIN    (-32767i16 - 1) /* minimum signed 16 bit value */
#define _I16_MAX      32767i16      /* maximum signed 16 bit value */
#define _UI16_MAX     0xffffui16    /* maximum unsigned 16 bit value */
#endif

#if     _INTEGRAL_MAX_BITS >= 32
#define _I32_MIN    (-2147483647i32 - 1) /* minimum signed 32 bit value */
#define _I32_MAX      2147483647i32 /* maximum signed 32 bit value */
#define _UI32_MAX     0xffffffffui32 /* maximum unsigned 32 bit value */
#endif

#if     _INTEGRAL_MAX_BITS >= 64
/* minimum signed 64 bit value */
#define _I64_MIN    (-9223372036854775807i64 - 1)
/* maximum signed 64 bit value */
#define _I64_MAX      9223372036854775807i64
/* maximum unsigned 64 bit value */
#define _UI64_MAX     0xffffffffffffffffui64
#endif

#if     _INTEGRAL_MAX_BITS >= 128
/* minimum signed 128 bit value */
#define _I128_MIN   (-170141183460469231731687303715884105727i128 - 1)
/* maximum signed 128 bit value */
#define _I128_MAX     170141183460469231731687303715884105727i128
/* maximum unsigned 128 bit value */
#define _UI128_MAX    0xffffffffffffffffffffffffffffffffui128
#endif

...


1.3. Ein- und Ausgabe mit cin und cout, Selektion mit if/else

cin ist das Standardeingabe-Objekt (Tastatur) und cout das Standardausgabe-Objekt (Bildschirm). Man bezeichnet diese Objekte auch als Streams. Der Doppelpfeil zeigt jeweils die Richtung des Datenflusses an.

Damit nach Ausgabe des Ausdruckes zahl*zahl ein Zeilenvorschub erfolgt wird entweder endl oder das Sonderzeichen '\n' gesendet.
endl leert den Ausgabepuffer (entspricht flush und setzt ein "newline"). Hier finden Sie einige Möglichkeiten für solche Sonderzeichen, die noch aus der Zeit der Programmiersprache C stammen:
 
\n Zeilenvorschub  RETURN bei der Tastatureingabe
\r Carriage Return Zeilenende besteht aus \n\r 
\b Backspace letztes Zeichen wird gelöscht 
\t Tabulator Tabulator-Sprung
\f formfeed Druckerausgabe: Seitenvorschub
\0 NULL String-Endmarkierung
\\ Backslash  erzeugt ein \ auf dem Bildschirm 
\' Hochkomma  erzeugt ein ' auf dem Bildschirm
\" Anführungszeichen erzeugt ein " auf dem Bildschirm

Wenn Sie nun die Zahl 12 eingeben, erhalten Sie die entsprechende Quadratzahl 144:

An unserem einfachen Quadratzahl-Programm können Sie bereits eine Menge testen.

Zunächst geben wir 10 000 oder -10 000 ein und erhalten mathematisch korrekt die Quadratzahl 100 000 000.
Anders sieht es bei 100 000 aus. Hier erhalten wir nicht 10 000 000 000, sondern völlig falsch 1 410 065 408.
Das liegt daran, daß bei 2 147 483 647 plus 1 die Zählweise wieder von vorne beginnt, also bei - 2 147 483 648.
Man spricht hier von einem "Überlauf". Genau genommen liegt es daran, dass das höchste Bit das Vorzeichen signalisiert. Ist es gesetzt, ist die Zahl negativ, wenn nein, dann ist sie positiv. Solche Integer-Typen nennt man signed. Bei unsigned Typen gibt es kein Vorzeichenbit, damit kann man Ganzzahlen von 0 bis 2 ^ 32 - 1 darstellen. 

Testen Sie es selbst aus, indem Sie die Programmzeile:

cout << zahl * zahl << endl;

durch folgende Zeile ersetzen:

cout << zahl+1 << endl;

Für den Variablentyp Integer (int) spendiert ein PC typischerweise 4 Byte ( = 32 bit ). Der Datentyp int entspricht der "Maschinenbreite", die ist noch weitgehend 32 bit mit der Tendenz nach 64 bit. 

Wenn man keine Zahl, sondern z.B. den String (Zeichenfolge) "Hallo" eintippt, dann erhält man 0 (Null) als Ergebnis. Das ist genau genommen auch nicht korrekt, aber immerhin noch erträglich.

Es liegt also an Ihnen als Programmierer, diesen "Blödsinn" zu beherrschen und entsprechende Begrenzungen bei der Entgegennahme und Auswertung von Eingaben durchzuführen.

Nehmen wir den konkreten Fall. Die größte Ganzzahl, die wir mit int (4 Byte) darstellen können, ist 2 147 483 647.
Die Quadratwurzel hieraus ist abgerundet 46340. Also dürfen wir keine Eingabe größer als 46340 oder kleiner als -46340 akzeptieren.
Hierzu verwendet man z.B. eine if / else - Kontrollstruktur, einen Größenvergleich und eine logische "ODER"-Verknüpfung:

 
 
#include <iostream>
#include <limits>
using namespace std;

void wait()
{
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}


int main()
{
  int zahl;
  cin  >> zahl;
  if( zahl > 46340 || zahl < -46340 ) cout << "Zahl zu gross" << endl;
  else cout << zahl*zahl << endl;

  wait();
}

Die if / else - Kontrollstruktur funktioniert nach folgendem Prinzip:
 

if( Bedingung )
{
    Block 1

}

else
{
    Block 2
}

Da Grössenvergleiche und logische Verknüpfungen häufig vorkommen, hier eine Übersicht:
 
== gleich 
!= ungleich 
> größer 
>= größer oder gleich 
kleiner 
=<  kleiner oder gleich 
&& logisches UND
||  logisches ODER
Negation 

Nun fehlt noch die Anzeige für den Benutzer, was eigentlich passieren soll bzw. passiert ist.
Das erledigt man bei Konsolenanwendungen in C++ durch cout-Anweisungen (Ausgabe auf das Standardausgabe-Objekt):
 
#include <iostream>
#include <limits>
using namespace std;

void wait()
{
  std::cin.clear();
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  std::cin.get();
}


int main()
{
 int zahl;
 cout << "Bitte Zahl eingeben: ";
 cin  >> zahl;
 if( zahl > 46340 || zahl < -46340 ) cout << "Zahl zu gross" << endl;
 else 
 {
  cout << "Quadratzahl: " << zahl*zahl << endl;
 }

 wait();
}

Experimentieren Sie an dieser Stelle bitte eifrig mit eigenen Ideen, z.B. verschiedene Umrechnungen (kW/PS, kcal/kJ, °C/°F/K, Währungen, ...).
Nur durch eigene Versuche erhalten Sie Routine beim Erstellen des Sourcecodes.
 

Zurück zum Start