Icon Ressource

[C] Die spinnen, die Eingabefunktionen. Essensreste im stdin ...

Da in C Vorlesungen offensichtlich chronisch die scanf() Funktion für Benutzereingaben gelehrt wird, gibt es ebenso chronisch immer wieder dasselbe Problem: Bei aufeinanderfolgenden Aufrufen werden Eingabefunktionen vermeintlich übergangen.

Stellen wir die Symptome mit einem kleinen Beispiel nach:
C:
//Taschenrechner
#include <stdio.h>

int main(void)
{
  int a = 0, b = 0, op = 0;

  fputs("Gib die erste Zahl ein: ", stdout);
  scanf("%d", &a);

  fputs("Gib den Operator ein: ", stdout);
  op = getchar();

  fputs("Gib die zweite Zahl ein: ", stdout);
  scanf("%d", &b);

  switch (op)
  {
    case '+':
      printf("Die Summe betraegt %d.\n", a + b);
      break;

    case '-':
      printf("Die Differenz betraegt %d.\n", a - b);
      break;

    case '*':
      printf("Das Produkt betraegt %d.\n", a * b);
      break;

    case '/':
      printf("Der Quotient betraegt %lf.\n", (double)a / b);
      break;

    default:
      puts("Ich kann leider nur die Grundrechenoperationen.");
  }

  return 0;
}
Sieht auf den ersten Blick gut und richtig aus. Lassen wir es mal rennen ...
Code:
Gib die erste Zahl ein: 2
Gib den Operator ein: Gib die zweite Zahl ein:
Nanu? Die 2 konnte ich erfolgreich eingeben. Nach dem Drücken auf Enter hatte ich aber nicht die geringste Chance den Operator einzugeben. Was ist passiert?
Es gibt noch ungelesene Reste der vorherigen Eingabe im stdin. Das Drücken der Entertaste hat ein '\n' Zeichen erzeugt. Das konnte aber nicht mit zur Zahl konvertiert werden, verbleibt deshalb im stdin und wartet darauf gelesen zu werden. Da als nächstes die getchar() Funktion folgt, wird das '\n' von dieser gelesen und gibt diesen Wert zurück. Somit geht's ad hoc weiter mit der nächsten Eingabefunktion.
Wir haben also gelernt, dass alles was nicht zu einer Zahl konvertiert werden konnte im stdin bleibt (allgemein gesprochen, alles für das es im Formatstring bei scanf() keinen Bezeichner gibt) und wir wissen dass Eingabefunktionen erst dann zurückgeben, wenn sie ein '\n' gelesen haben. Das können wir nutzen. Wir lesen so lange char-weise aus dem stdin, bis das '\n' gelesen wurde.
while (getchar() != '\n');
Bauen wir diese Zeile mal nach jeder Eingabefunktion ein ...
C:
//Taschenrechner
#include <stdio.h>

int main(void)
{
  int a = 0, b = 0, op = 0;

  fputs("Gib die erste Zahl ein: ", stdout);
  scanf("%d", &a);
  while (getchar() != '\n');

  fputs("Gib den Operator ein: ", stdout);
  op = getchar();
  while (getchar() != '\n');

  fputs("Gib die zweite Zahl ein: ", stdout);
  scanf("%d", &b);
  while (getchar() != '\n');

  switch (op)
  {
    case '+':
      printf("Die Summe betraegt %d.\n", a + b);
      break;

    case '-':
      printf("Die Differenz betraegt %d.\n", a - b);
      break;

    case '*':
      printf("Das Produkt betraegt %d.\n", a * b);
      break;

    case '/':
      printf("Der Quotient betraegt %lf.\n", (double)a / b);
      break;

    default:
      puts("Ich kann leider nur die Grundrechenoperationen.");
  }

  return 0;
}
Test:
Code:
Gib die erste Zahl ein: 2
Gib den Operator ein: *
Gib die zweite Zahl ein: 3
Das Produkt betraegt 6.
Scheint zu funktionieren. Aber halt, da gibt es eine Ausnahme. Wenn ich vergesse den Operator einzugeben und nur Enter drücke, dann wird das '\n' der Variablen op zugewiesen. Nun ist das stdin aber bereits leer und die while Schleife findet nichts zum lesen. Erst bei erneutem Drücken auf Enter läuft das Programm weiter zum zweiten scanf(). Nicht schlimm, aber unschön. Indem man überprüft, welchen Wert variable op hat, lässt sich das noch etwas optimieren. (Zeile 14)
C:
//Taschenrechner
#include <stdio.h>

int main(void)
{
  int a = 0, b = 0, op = 0;

  fputs("Gib die erste Zahl ein: ", stdout);
  scanf("%d", &a);
  while (getchar() != '\n');

  fputs("Gib den Operator ein: ", stdout);
  op = getchar();
  while (op != '\n' && getchar() != '\n');

  fputs("Gib die zweite Zahl ein: ", stdout);
  scanf("%d", &b);
  while (getchar() != '\n');

  switch (op)
  {
    case '+':
      printf("Die Summe betraegt %d.\n", a + b);
      break;

    case '-':
      printf("Die Differenz betraegt %d.\n", a - b);
      break;

    case '*':
      printf("Das Produkt betraegt %d.\n", a * b);
      break;

    case '/':
      printf("Der Quotient betraegt %lf.\n", (double)a / b);
      break;

    default:
      puts("Ich kann leider nur die Grundrechenoperationen.");
  }

  return 0;
}
War's das nun, oder gibt es noch mehr zu beachten? Naja, in den meisten Fällen ist das so okay. Aber ja, der Vollständigkeit wegen sollte ich noch einen Schritt weiter gehen. Es gibt Situationen wo beim Aufruf eines Konsoleprogramms ein Stream an das stdin des Programms umgeleitet wird. Das passiert üblicherweise in der Kommandozeile mit den Zeichen | bzw. <. Bei diesen Streams kann es sein, dass es keinen Zeilenumbruch gibt. In diesem Fall sind ein paar zusätzliche Prüfungen erforderlich. Beispiel:
C:
int ch = getchar();
while (ch != '\n' && ch != EOF && getchar() != '\n' && !feof(stdin));

Last but not least ...
Der eine oder andere wird über diesen Beitrag stolpern und sich denken
"Alles Unsinn was der alte Mann hier schreibt. Ich hab einmal kurz bei Google gesucht, fflush(stdin); gefunden, ausprobiert, funktioniert in allen oben genannten Fällen."
Jein. Die fflush() Funktion ist für Ausgabestreams gedacht. Sie dient dazu, noch gepufferte Inhalte zu schreiben. Die Anwendung von fflush() auf Eingabestreams erzeugt laut C-Standard undefiniertes Verhalten. Einige Implementationen (bspw. auf Windows) erlauben aber die Verwendung von fflush() auch zum Leeren von Eingabestreams. Wer allerdings plattformunabhängigen Code schreiben will, sollte fflush() nie auf Eingabestreams, also auch nicht auf das stdin anwenden.
Autor
german
Aufrufe
1.939
Erstellt am
Letzte Bearbeitung
Bewertung
5,00 Stern(e) 2 Bewertung(en)

Weitere Ressourcen von german

Zurück
Oben Unten