C++ / Eingabe
“Reading input is often the messiest part of a program.” — Bjarne Stroustrup
“Output is easy, but input is filled with pitfalls that await the unwary due to the gap between the actual and the expected types of incoming data.” — Jon Pearce
Einlesen mit >>
In C++ kann mit dem Wirkoperator ">>" aus einem Eingabestrom ein Wert gelesen und in eine Variable geschrieben werden. Dabei wird für normale Benutzereingaben von der Konsole der Eingabestrom »::std::cin« verwendet.
Eingabe.cpp
#include <iostream> // std::cout, std::cin
#include <ostream> // <<
#include <istream> // >>
int main()
{ std::cout << "Programm zur Berechnung der Summe zweier Zahlen\n";
std::cout << "Erster Summand?\n"; double summand0; std::cin >> summand0;
std::cout << "Zweiter Summand?\n"; double summand1; std::cin >> summand1;
double const summe{ summand0 + summand1 };
std::cout << "Summe= " << summe << '\n'; }
Das hier vorgestellte Verfahren ist nicht ganz perfekt. Wird vom Benutzer statt einer Zahl ein Text eingegeben, so erfolgt keine sinnvolle Fehlerbehandlung. In dieser Lektion sollen aber Details des Verfahrens der Eingabe noch nicht behandelt werden. Programme für den Einsatz verwenden ohnehin oft andere Verfahren zum Einlesen. Das hier vorgestellt Verfahren des Einlesens mit ">>" von der Konsole ist aber für den Anfang am einfachsten.
Man beachte daß der Name "summe" eine Konstante bezeichnet, da der Name "summe" innerhalb seines Bereiches nur einmal initialisiert wird und danach nicht verändert wird, ist dieses Vorgehen empfehlenswert. Der Quelltext wird leichter lesbar, da es sofort klar ist, daß sich der Wert der Konstanten "summe" nicht mehr verändert. Fehler durch eine versehentliche Veränderung können vom Übersetzer erkannt und gemeldet werden.
Der Tastaturpuffer
main.cpp
#include <cstdio> // ::std::putchar, ::std::getchar
int main()
{ ::std::putchar( ::std::getchar() );
::std::putchar( ::std::getchar() );
::std::putchar( ::std::getchar() ); }- Konsole
ab↵
ab¶
Einfache Erwartungsschleifen
#include <iostream>
#include <ostream>
#include <istream>
#include <string>int main()
{ { double x; do ::std::cin >> x; while( x != 5 ); ::std::cout << x << '\n'; }
{ char x; do ::std::cin >> x; while( x != 'w' ); ::std::cout << x << '\n'; }
{ ::std::string x; do ::std::cin >> x; while( x != "Ende" ); ::std::cout << x << '\n'; }}
Alle Bezeichner, die wie eine Konstante verwendet werden, sollten auch als Konstante deklariert werden.
#include <iostream>
#include <ostream>
#include <istream>
#include <limits>int main() { double x {}; bool ok {}; do
{ ::std::cout << "Number? ";
if( ::std::cin >> x )ok = true, ::std::cout << x << '\n';
else
{ ::std::cerr << "?REDO FROM START\n";
::std::cin.clear();
::std::cin.ignore
( (::std::numeric_limits< ::std::streamsize >::max)(), '\n' ); }}
while( !ok );
::std::cout << "read " << x << '\n'; }
(Der »static_cast< bool >« im obigen Programm war in älteren C++ -Versionen nicht nötig, da der verwendete Umwandlungsoperator damals noch nicht mit »explicit« gekennzeichnet war.)
(Die Klammern um »::std::numeric_limits< ::std::streamsize >::max« verhindern eine eventuelle Expansion von »max()« als Makro.)
- Entsprechendes BASIC-Programm
10 INPUT "NUMBER"; X
20 PRINT "READ "; X
Einlesen von Strings
- Einlesen eines Namens
#include <iostream> // ::std::cin
#include <istream> // >>
#include <string> // ::std::string(...)
::std::cout << "name :";
::std::string name; // Definition und Fehlinitialisierung als leerer Text
::std::cin >> name; // Ausgabepuffer leeren, Leerraum ignorieren,
// dann Lesen bis zum ersten folgenden Leerraum- Wegen automatischer Leerung des Ausgabepuffers beim Wechsel zum Einlesen, ist keine explizite Leerung mit »<< ::std::endl«, »<< ::std::flush« oder »::std::cout.flush()« nötig.
Strings einlesen: Keine Pufferreservierung noetig, wie fehlertraechtig in C!
#include <iostream> // ::std::cin
#include <istream> // >>
#include <string> // ::std::string
Teilnehmerfrage Solange Einlesen bis keine Zahl eingegeben wird
#include <iostream> // ::std::cout
#include <ostream> // ::std::ostream (<<)
#include <sstream>
#include <string>
int main()
{ ::std::string s;
for( bool looping = true; looping; )
{ ::std::cin >> s;
::std::istringstream stream( s );
double x;
if( stream >> x )::std::cout << x + 1 << "\n";
else looping = false; }
::std::cout << "Keine Zahl!\n"; }
Die do-Schleife zur Eingabeprüfung
Bei der Eingabeprüfung mit while muß »std::cin >> i;« zwiefach im Quelltext stehen.
Eingabeprüfung3
std::cout << "Bitte eine positive Zahl eingeben!\n";
std::cin >> i;
while( i <= 0 )
{ std::cout << "Fehler: Diese Zahl war nicht positiv!\n";
std::cout << "Bitte eine positive Zahl eingeben:\n";
std::cin >> i; }
Durch eine do-Schleife kann dies vermieden werden:
Eingabeprüfung2
do
{ std::cout << "Bitte eine positive Zahl eingeben!\n";
std::cin >> i; }
while( i <= 0 );
Wenn ein Text zur Eingabeaufforderung vorangestellt wird, dann ist dieser aber im Wiederholungsfall auch immer gleich, was etwas irritierend sein kann.
Soll eine spezielle Fehlermeldung für den Fall der Wiederholung ausgegeben werden, dann kann dies doch wieder mit einer while-Anweisung geschehen.
Eingabeprüfung3
std::cout << "Bitte eine positive Zahl eingeben!\n";
std::cin >> i;
while( i <= 0 )
{ std::cout << "Fehler: Diese Zahl war nicht positiv!\n";
std::cout << "Bitte eine positive Zahl eingeben:\n";
std::cin >> i; }
Wenn die do-Anweisung dafür verwendet wird, dann muß jetzt eine Prüfung wiederholt werden.
Eingabeprüfung4
do
{ std::cout << "Bitte eine positive Zahl eingeben!\n";
std::cin >> i;
if( i <= 0 )
std::cout << "Fehler: Diese Zahl war nicht positiv!\n"; }
while( i <= 0 );
Die Verwendung der do-Anweisung muß also in realistischen Situationen nicht immer eine Vereinfachung bedeuten, wenn eine Prüfung erstmalig nach einem zum wiederholenden Vorgang erfolgen soll. Darüberhinaus kann der Abschluß mit dem Schlüsselwort "while" unter bestimmten Umständen leicht mit einer while-Anweisung verwechselt werden. So kann die Zeile "while( i <= 0 );" auch als vollständige while-Anweisung interpretiert werden, wenn nicht gesehen wird, daß irgendwo darüber das Schlüsselwort "do" steht.
Die Verwendung einer Schleifenvariablen erlaubt eine garantiert einmalige Ausführung vor dem ersten Test auch mit einer while-Schleife, ohne daß die Anweisung zum Einlesen oder der Test wiederholt notiert werden muß.
Eingabeprüfung5
looping = true; while( looping )
{ std::cout << "Bitte eine positive Zahl eingeben!\n";
std::cin >> i;
if( i > 0 )looping = false; else
{ std::cout << "Fehler: Diese Zahl war nicht positiv!\n"; }
}
Ein Texteditor in C++
Der folgende Texteditor wurde vor allem so geschrieben, daß der Quelltext möglichst kurz ist. Andere Eigenschaften dieses Programms sind aber noch verbesserungsfähig.
main.cpp
#include <iostream>
#include <ostream>
#include <istream>
#include <cstdlib>int arg( ::std::string & s ){ return stoi( s.substr( 1 )); }
int main()
{ ::std::cout << "i1 insert \"1\"\n+1 move cursor right\n "
::std::string s{ "alpha" }, command; int pos = 0; while( 1 )
{ ::std::cout << s << '\n' << ::std::string( pos, ' ' ) << '^' << '\n';
::std::cin >> command; switch( command.at( 0 ))
{ case '+': pos += arg( command ); break;
case 'i': s.insert( pos, command.substr( 1 )); break; }}}- Konsole
i1 insert "1"
+1 move cursor right
alpha
^
+2
alpha
^
i__
al__pha
^
»<<« und »getline«
»<<« liest Wörter, »getline« liest Zeilen
getline und Abstraktion der Implementation eines Stromes
main.cpp
#include <iostream>
#include <istream>
#include <ostream>
#include <regex>
#include <sstream>
#include <string>
void ok(){ ::std::cerr << "ok" << '\n'; }
void bad
( ::std::string const & e )
{ ::std::cerr << "bad " << e << '\n'; exit( 99 ); }
void chk
( ::std::string const & s,
::std::string const & p, ::std::string const & e )
{ if( !::std::regex_search( s, ::std::regex( p.c_str() ) ))bad( e ); }
void expect_that_line( ::std::basic_istream<char> & i )
{ ::std::string s; ::std::getline( i, s );
chk( s, R"(^\d+)", "0" );
chk( s, R"(^\d+ )", "1" );
chk( s, R"(^\d+ \d+)", "2" );
chk( s, R"(^\d+ \d+ )", "3" );
chk( s, R"(^\d+ \d+ [^ ]*)", "4" );
chk( s, R"(^\d+ \d+ [^ ]* )", "5" );
chk( s, R"(^\d+ \d+ [^ ]* [^ ]*)", "5" );
chk( s, R"(^\d+ \d+ [^ ]* [^ ]* )", "6" );
chk( s, R"(^\d+ \d+ [^ ]* [^ ]* $)", "7" );
ok(); }
int main()
{ ::std::stringstream s( "1 23 a b \n" );
expect_that_line( s ); }
back_inserter für Ströme
Beispiel 6 aus 49
main.cpp
#include <algorithm> // ::std::random_shuffle, ::std::copy_n
#include <array>
#include <iostream>
#include <iterator> // ::std::ostream_iterator
#include <numeric> // ::std::iota
#include <ostream>
#include <random>
int main()
{ using num = int;
using size_type = ::std::vector< num >::size_type;
auto output { ::std::ostream_iterator< int >( ::std::cout, " " )};constexpr size_type total_size = 49;
constexpr size_type selection_size = 6;{ ::std::random_device device {};
if( device.entropy() == 0.0 )::std::cout << "Deterministisches ::std::random_device.\n";
if( device.entropy() < 30.0 )::std::cout << "Zu wenig Entropie.\n"; }::std::mt19937 algorithm{ ::std::random_device{}() };
::std::array< num, total_size >a;
for( auto i { 0 }; i < 3; ++i )
{ { constexpr num offset = 1; ::std::iota( begin( a ), end( a ), offset ); }
copy_n( begin( a ), selection_size, output ); ::std::cout << '\n';
shuffle( begin( a ), end( a ), algorithm );
copy_n( begin( a ), selection_size, output ); ::std::cout << '\n'; }}- Protokoll
Deterministisches ::std::random_device.
Zu wenig Entropie.
1 2 3 4 5 6
11 48 36 22 19 4
1 2 3 4 5 6
49 18 1 19 13 16
1 2 3 4 5 6
3 15 40 10 11 34
Wird »algorithm« durch »::std::mt19937{}« ersetzt, würde sich immer die gleiche Auswahl ergeben, da der Generator jedes Mal auf denselben Anfangszustand zurückgesetzt werden würde.
Wird »::std::mt19937{ ::std::random_device{}() }« durch »::std::mt19937{}« ersetzt, würden sich bei jedem Programmstart dieselben Ziehungen ergeben.
»::std::mt19937{ ::std::random_device{}() }« war 2016 unter MinGW gestört (Entropie gleich 0, formal korrekt). Es konnte ersatzweise »::std::mt19937( ::std::time( 0 )* ::std::time( 0 ))« verwendet werden (sourceforge.net/p/mingw-w64/bugs/338/).
main.cpp
#include <algorithm> // ::std::random_shuffle, ::std::copy_n
#include <array>
#include <iostream>
#include <iterator> // ::std::ostream_iterator
#include <numeric> // ::std::iota
#include <ostream>
#include <random>
int main()
{ using num = int;
using size_type = ::std::vector< num >::size_type;
auto output { ::std::ostream_iterator< int >( ::std::cout, " " )};constexpr size_type total_size = 49;
constexpr size_type selection_size = 6;::std::mt19937 algorithm{ ::std::random_device{}() };
::std::array< num, total_size >numbers;
{ constexpr num offset = 1; ::std::iota( begin( numbers ), end( numbers ), offset ); }
shuffle( begin( numbers ), end( numbers ), algorithm );
copy_n( begin( numbers ), selection_size, output ); ::std::cout << '\n'; }- Protokoll
45 19 34 2 26 24
F:\r\j\autocomp\main.cpp:19:3: warning: uninitialized record type: 'numbers' [cppcoreguidelines-pro-type-member-init]
::std::array< num, total_size >numbers;
^
{}
Häufiger Fehler Endet jede Anweisung mit einem Semikolon?
Die Aussage „Jede Anweisung muß mit einem Semikolon enden. “ ist eine der häufigsten Falschmeldungen über C++. Die Verbundanweisung (englisch: “compound statement ”) ist eine Anweisung (englisch: “statement ”). Die Verbundanweisung endet aber nicht mit einem Semikolon, sondern mit einer geschweiften Klammer zu »}«!
Die falsche Aussage „Jede Anweisung muß mit einem Semikolon enden. “ stammt vermutlich daher, daß jede Auswertungsanweisung mit einem Semikolon endet und der Verbreiter dieser Aussage noch keine anderen Arten von Anweisung kennengelernt hat. Richtig wäre die Aussage „Jede Auswertungsanweisung muß mit einem Semikolon enden. “
Im Abschnitt 6p1 von ISO/IEC 14882:2011 wird bestätigt, daß die Verbundanweisung eine Anweisung ist (“statement: … compound-statement ”).
Im Grunde kann man viele C++ -Programme auch so umschreiben, daß sie kein Semikolon mehr enthalten. Dies zeigt das folgende Programmbeispiel, das allerdings zu diesem Zweck ausnahmsweise einmal vorgreift und die im Lehrgang noch nicht behandelte if-Anweisung verwendet. Dieses Programm wertet den Ausdruck »::std::putchar( 67 )« auf, ohne daß es ein Semikolon enthält. Es enthält aber durchaus Anweisungen, nur eben solche ohne Semikolon
main.cpp
#include <cstdio> /* ::std::putchar */
int main(){ if( ::std::putchar( 67 )){} }
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<int main()
{ if( ::std::cout << 65 << "\n" ){} }::std::cout
65
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<int main()
{ if( ::std::cout << 65 << "\n" ){}
if( ::std::cout << 65 << "\n" ){}
if( ::std::cout << 65 << "\n" ){} }::std::cout
65
65
65
Auch die C++ -Spezifikation erklärt ausdrücklich, daß es eine Anweisung gibt, die mit einer geschweiften Klammer endet.
Die geschweiften Klammern bilden tatsächlich mit ihrem Inhalt zusammen selber ebenfalls eine Anweisung: Die Verbundanweisung.
- n3290
- 8.4 Function definitions [dcl.fct.def] 8.4.1 In general [dcl.fct.def.general] (...)
- function-body : (...) compound-statement
- n3290
- A.5 Statements [gram.stmt]
- statement : (...) compound-statement (...)
- compound-statement : (...) { statement-seq (...) }
Übungsaufgaben
- ? Eingabeprüfung
- Schreiben Sie ein Program, das eine Schulnote von 1 bis 6 einliest und eine Erklärung (beispielsweise »sehr gut« für »1«) ausgibt und endet. Falls der Benutzer keine Schulnote eingibt, soll er eine entsprechende Information erhalten und dann wieder eine Zahl eingeben können (dies soll gegebenenfalls auch mehrfach wiederholt werden, solange bis der Benutzer eine gültige Schulnote eingibt und das Programm dann deren Erklärung ausgibt und endet.)
- ? Der Benutzer soll eine Zahl erraten
- Der Computer soll eine Zufallszahl zwischen 1 und 100 wählen. Diese soll dem Benutzer aber zunächst nicht bekanntgegeben werden. Der Benutzer soll solange Zahlen eingeben, bis er die vom Computer gewählte Zahl getroffen hat. Nach jeder Eingabe erfährt der Benutzer, ob die eingegeben Zahl größer als die vom Computer gewählt Zahl, kleiner als die vom Computer gewählte Zahl, oder der vom Computer gewählten Zahl gleich ist.
- ? Der Computer soll eine Zahl erraten
- Der Bediener soll eine Zahl zwischen 1 und 100 wählen. Diese soll dem Computer aber zunächst nicht bekanntgegeben werden. Der Computer soll solange Zahlen ausgeben, bis er die vom Bediener gewählte Zahl getroffen hat. Nach jeder Eingabe erfährt der Computer, ob die ausgegeben Zahl größer als die vom Bediener gewählt Zahl, kleiner als die vom Bediener gewählte Zahl, oder der vom Bediener gewählten Zahl gleich ist.
- ? ROT13
- Ein Programm soll die ROT13-Verschlüsselung einer eingebenenen Zeile ausgeben.
- ? Editor erweitern
- Erweitern Sie den Editor um ein Kommando »-«, um die Schreibmarke nach links zu bewegen, und um ein Kommando »e1« um ein Zeichen bei der Schreibmarke zu löschen (oder entsprechend mehr Zeichen bei einer anderen Ziffer).
- Konsole
i1 insert "1"
+1 move cursor right
-1 move cursor left
e1 erase 1 character
alpha
^
+2
alpha
^
i__
al__pha
^
-1
al__pha
^
e2
a_pha
^
Aussprachehinweise *
cin “see in ”, Von “character input ”,