Warum ist scanf
schlecht?
Das Hauptproblem ist, dass scanf
es nie beabsichtigt war, Benutzereingaben zu verarbeiten. Es soll mit "perfekt" formatierten Daten verwendet werden. Ich habe das Wort "perfekt" zitiert, weil es nicht ganz wahr ist. Es ist jedoch nicht dafür ausgelegt, Daten zu analysieren, die so unzuverlässig sind wie Benutzereingaben. Benutzereingaben sind von Natur aus nicht vorhersehbar. Benutzer verstehen Anweisungen falsch, machen Tippfehler, drücken versehentlich die Eingabetaste, bevor sie fertig sind usw. Man könnte sich vernünftigerweise fragen, warum eine Funktion, die nicht für Benutzereingaben verwendet werden sollte, von liest stdin
. Wenn Sie ein erfahrener * nix-Benutzer sind, ist die Erklärung keine Überraschung, kann jedoch Windows-Benutzer verwirren. In * nix-Systemen ist es sehr üblich, Programme zu erstellen, die über Piping funktionieren.stdout
stdin
des zweiten. Auf diese Weise können Sie sicherstellen, dass Ausgabe und Eingabe vorhersehbar sind. Unter diesen Umständen scanf
funktioniert tatsächlich gut. Wenn Sie jedoch mit unvorhersehbaren Eingaben arbeiten, riskieren Sie alle möglichen Probleme.
Warum gibt es keine benutzerfreundlichen Standardfunktionen für Benutzereingaben? Man kann hier nur raten, aber ich gehe davon aus, dass alte Hardcore-C-Hacker einfach dachten, dass die vorhandenen Funktionen gut genug waren, obwohl sie sehr klobig sind. Wenn Sie sich typische Terminalanwendungen ansehen, lesen sie nur sehr selten Benutzereingaben von stdin
. Meistens übergeben Sie alle Benutzereingaben als Befehlszeilenargumente. Sicher, es gibt Ausnahmen, aber für die meisten Anwendungen ist die Benutzereingabe eine sehr untergeordnete Sache.
Also was kannst du tun?
Mein Favorit ist fgets
in Kombination mit sscanf
. Ich habe einmal eine Antwort darauf geschrieben, aber ich werde den vollständigen Code erneut veröffentlichen. Hier ist ein Beispiel mit anständiger (aber nicht perfekter) Fehlerprüfung und -analyse. Es ist gut genug für Debugging-Zwecke.
Hinweis
Ich mag es nicht besonders, den Benutzer zu bitten, zwei verschiedene Dinge in einer einzigen Zeile einzugeben. Das mache ich nur, wenn sie auf natürliche Weise zueinander gehören. Wie zum Beispiel printf("Enter the price in the format <dollars>.<cent>: ")
und dann verwenden sscanf(buffer "%d.%d", &dollar, ¢)
. Ich würde niemals so etwas tun printf("Enter height and base of the triangle: ")
. Der Hauptzweck der folgenden Verwendung fgets
besteht darin, die Eingaben zu kapseln, um sicherzustellen, dass eine Eingabe die nächste nicht beeinflusst.
#define bsize 100
void error_function(const char *buffer, int no_conversions) {
fprintf(stderr, "An error occurred. You entered:\n%s\n", buffer);
fprintf(stderr, "%d successful conversions", no_conversions);
exit(EXIT_FAILURE);
}
char c, buffer[bsize];
int x,y;
float f, g;
int r;
printf("Enter two integers: ");
fflush(stdout); // Make sure that the printf is executed before reading
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);
// Unless the input buffer was to small we can be sure that stdin is empty
// when we come here.
printf("Enter two floats: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);
// Reading single characters can be especially tricky if the input buffer
// is not emptied before. But since we're using fgets, we're safe.
printf("Enter a char: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%c", &c)) != 1) error_function(buffer, r);
printf("You entered %d %d %f %c\n", x, y, f, c);
Wenn Sie viele davon ausführen, kann ich empfehlen, einen Wrapper zu erstellen, der immer leert:
int printfflush (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
fflush(stdout);
va_end (arg);
return done;
}```
Wenn Sie dies tun, wird ein häufiges Problem behoben, nämlich die nachfolgende neue Zeile, die mit der Verschachtelungseingabe in Konflikt geraten kann. Aber es gibt noch ein anderes Problem: Wenn die Leitung länger als ist bsize
. Sie können das mit überprüfen if(buffer[strlen(buffer)-1] != '\n')
. Wenn Sie den Zeilenumbruch entfernen möchten, können Sie dies mit tun buffer[strcspn(buffer, "\n")] = 0
.
Im Allgemeinen würde ich raten, nicht zu erwarten, dass der Benutzer Eingaben in einem seltsamen Format eingibt, das Sie in verschiedenen Variablen analysieren sollten. Wenn Sie die Variablen height
und zuweisen möchten, width
fragen Sie nicht gleichzeitig nach beiden. Ermöglichen Sie dem Benutzer, zwischen ihnen die Eingabetaste zu drücken. Auch dieser Ansatz ist in gewisser Hinsicht sehr natürlich. Sie werden die Eingabe stdin
erst erhalten, wenn Sie die Eingabetaste drücken. Warum also nicht immer die gesamte Zeile lesen? Dies kann natürlich immer noch zu Problemen führen, wenn die Zeile länger als der Puffer ist. Habe ich daran gedacht zu erwähnen, dass Benutzereingaben in C klobig sind? :) :)
Um Probleme mit Zeilen zu vermeiden, die länger als der Puffer sind, können Sie eine Funktion verwenden, die automatisch einen Puffer mit der entsprechenden Größe zuweist getline()
. Der Nachteil ist, dass Sie free
das Ergebnis anschließend benötigen .
Das Spiel steigern
Wenn Sie es ernst meinen, Programme in C mit Benutzereingaben zu erstellen, würde ich empfehlen, sich eine Bibliothek wie anzusehen ncurses
. Denn dann möchten Sie wahrscheinlich auch Anwendungen mit einigen Terminalgrafiken erstellen. Leider verlieren Sie dabei etwas an Portabilität, aber Sie können die Benutzereingaben weitaus besser steuern. So können Sie beispielsweise einen Tastendruck sofort lesen, anstatt darauf zu warten, dass der Benutzer die Eingabetaste drückt.
(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2
der nachfolgende nicht numerische Text nicht als schlecht erkannt wird.fgets()
dem"1 2 junk"
,if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) {
etwas falsch nicht mit Eingang berichten , obwohl es „Junk“ hat.scanf
soll mit perfekt formatierten Daten verwendet werden Aber auch das stimmt nicht. Neben dem von @chux erwähnten Problem mit "Junk" gibt es auch die Tatsache, dass ein Format wie"%d %d %d"
gerne Eingaben aus einer, zwei oder drei Zeilen liest (oder sogar mehr, wenn dazwischen Leerzeilen liegen), dass es keine gibt Die Möglichkeit, eine zweizeilige Eingabe zu erzwingen (sagen wir), indem Sie etwas Ähnliches"%d\n%d %d"
usw. tun , istscanf
möglicherweise für formatierte Stream- Eingaben geeignet , aber für nichts zeilenbasiertes überhaupt nicht gut.scanf
ist großartig, wenn Sie wissen, dass Ihre Eingabe immer gut strukturiert ist und sich gut benimmt. Andernfalls...IMO, hier sind die größten Probleme mit
scanf
:Risiko eines Pufferüberlaufs - Wenn Sie keine Feldbreite für die
%s
und die%[
Konvertierungsspezifizierer angeben, besteht das Risiko eines Pufferüberlaufs (Sie versuchen, mehr Eingaben zu lesen, als ein Puffer für die Größe hat). Leider gibt es keine gute Möglichkeit, dies als Argument anzugeben (wie beiprintf
) - Sie müssen es entweder als Teil des Konvertierungsspezifizierers fest codieren oder einige Makroshenanigans ausführen.Akzeptiert Eingaben, sollte abgelehnt werden - Wenn Sie einen Eingang mit der gerade lesen
%d
Konvertierungsspezifizierer und Sie geben so etwas wie12w4
, man würde erwartenscanf
, dass die Eingabe zu verwerfen, aber es funktioniert nicht - es erfolgreich konvertiert und Abtretungsempfänger des12
, so dassw4
in dem Eingangsstrom die nächste Lesung zu beschmutzen.Also, was solltest du stattdessen verwenden?
Normalerweise empfehle ich, alle interaktiven Eingaben als Text
fgets
zu lesen. Mit dieser Option können Sie eine maximale Anzahl von Zeichen festlegen, die gleichzeitig gelesen werden sollen, damit Sie einen Pufferüberlauf auf einfache Weise verhindern können:Eine Besonderheit
fgets
ist, dass die nachfolgende neue Zeile im Puffer gespeichert wird, wenn Platz vorhanden ist. Sie können also auf einfache Weise überprüfen, ob jemand mehr Eingaben eingegeben hat, als Sie erwartet hatten:Wie Sie damit umgehen, liegt bei Ihnen - Sie können entweder die gesamte Eingabe sofort ablehnen und alle verbleibenden Eingaben schlürfen mit
getchar
:Oder Sie können die bisher erhaltenen Eingaben verarbeiten und erneut lesen. Dies hängt von dem Problem ab, das Sie lösen möchten.
Um die Eingabe zu tokenisieren (auf der Grundlage eines oder mehrerer Trennzeichen aufzuteilen), können Sie sie verwenden
strtok
, aber Vorsicht -strtok
ändert ihre Eingabe (sie überschreibt Trennzeichen mit dem String-Terminator), und Sie können ihren Status nicht beibehalten (dh Sie können ' t Eine Zeichenfolge teilweise tokenisieren, dann eine andere Zeichenfolge tokenisieren und dort weitermachen, wo Sie in der ursprünglichen Zeichenfolge aufgehört haben. Es gibt eine Variante,strtok_s
die den Status des Tokenizers beibehält, aber die Implementierung von AFAIK ist optional (Sie müssen überprüfen,__STDC_LIB_EXT1__
ob diese definiert ist, um festzustellen, ob sie verfügbar ist).Sobald Sie Ihre Eingabe getoken haben und Zeichenfolgen in Zahlen konvertieren müssen (dh
"1234"
=>1234
), haben Sie Optionen.strtol
undstrtod
konvertiert Zeichenfolgendarstellungen von Ganzzahlen und reellen Zahlen in ihre jeweiligen Typen. Sie ermöglichen es Ihnen auch, das12w4
oben erwähnte Problem zu lösen - eines ihrer Argumente ist ein Zeiger auf das erste Zeichen, das nicht in die Zeichenfolge konvertiert wurde:quelle
%*[%\n]
nützlich, um später in der Antwort mit überlangen Linien umzugehen).snprintf()
) ,.isspace()
- es werden nicht signierte Zeichen akzeptiert , die als dargestelltint
werden. Sie müssen also umwandelnunsigned char
, um UB auf Plattformen zu vermeiden, auf denenchar
signiert ist.In dieser Antwort gehe ich davon aus, dass Sie Textzeilen lesen und interpretieren . Vielleicht fordern Sie den Benutzer auf, der etwas eingibt und RETURN drückt. Oder Sie lesen Zeilen mit strukturiertem Text aus einer Datendatei.
Da Sie Textzeilen lesen, ist es sinnvoll, Ihren Code um eine Bibliotheksfunktion herum zu organisieren, die eine Textzeile liest. Die Standardfunktion ist
fgets()
, obwohl es andere gibt (einschließlichgetline
). Und dann ist der nächste Schritt, diese Textzeile irgendwie zu interpretieren.Hier ist das Grundrezept für den Aufruf
fgets
zum Lesen einer Textzeile:Dies liest einfach eine Textzeile ein und druckt sie wieder aus. Wie geschrieben hat es ein paar Einschränkungen, die wir in einer Minute erreichen werden. Es hat auch eine sehr gute Funktion: Diese Zahl 512, an die wir als zweites Argument übergeben haben,
fgets
ist die Größe des Arrays, in dasline
wirfgets
einlesen möchten. Diese Tatsache - dass wir erkennen können,fgets
wie viel es lesen darf - bedeutet, dass wir sicher sein können, dassfgets
das Array nicht überläuft , indem wir zu viel hineinlesen.Jetzt wissen wir also, wie man eine Textzeile liest, aber was ist, wenn wir wirklich eine Ganzzahl oder eine Gleitkommazahl oder ein einzelnes Zeichen oder ein einzelnes Wort lesen möchten? (Das heißt, was ist, wenn der
scanf
Anruf wir versuchen , auf zu verbessern war wie ein Formatbezeichner mit%d
,%f
,%c
, oder%s
?)Es ist einfach, eine Textzeile - eine Zeichenfolge - als eines dieser Dinge neu zu interpretieren. Um eine Zeichenfolge in eine Ganzzahl umzuwandeln, ist der einfachste (wenn auch unvollständige) Weg, dies zu tun, der Aufruf
atoi()
. Um in eine Gleitkommazahl umzuwandeln, gibt esatof()
. (Und es gibt auch bessere Möglichkeiten, wie wir gleich sehen werden.) Hier ein sehr einfaches Beispiel:Wenn Sie möchten, dass der Benutzer ein einzelnes Zeichen eingibt (möglicherweise
y
odern
als Ja / Nein-Antwort), können Sie buchstäblich nur das erste Zeichen der Zeile wie folgt abrufen:(Dies ignoriert natürlich die Möglichkeit, dass der Benutzer eine Antwort mit mehreren Zeichen eingegeben hat. Alle zusätzlichen Zeichen, die eingegeben wurden, werden stillschweigend ignoriert.)
Wenn Sie möchten, dass der Benutzer eine Zeichenfolge eingibt, die definitiv kein Leerzeichen enthält, wenn Sie die Eingabezeile behandeln möchten
Da die Zeichenfolge
"hello"
von etwas anderem gefolgt wird (was dasscanf
Format%s
getan hätte), ist es in diesem Fall nicht ganz so einfach, die Zeile auf diese Weise neu zu interpretieren, also die Antwort darauf Ein Teil der Frage muss etwas warten.Aber zuerst möchte ich auf drei Dinge zurückkommen, die ich übersprungen habe.
(1) Wir haben angerufen
in das Array einzulesen
line
, und wo 512 die Größe des Arrays istline
,fgets
weiß also, dass es nicht überlaufen soll. Um sicherzustellen, dass 512 die richtige Nummer ist (insbesondere um zu überprüfen, ob möglicherweise jemand das Programm optimiert hat, um die Größe zu ändern), müssen Sie zurücklesen, woline
immer dies deklariert wurde. Das ist ein Ärgernis, daher gibt es zwei viel bessere Möglichkeiten, um die Größen synchron zu halten. Sie können (a) den Präprozessor verwenden, um einen Namen für die Größe zu erstellen:Oder (b) verwenden Sie den
sizeof
Operator von C :(2) Das zweite Problem ist, dass wir nicht nach Fehlern gesucht haben. Wenn Sie Eingaben lesen, sollten Sie immer nach Fehlern suchen. Wenn
fgets
die von Ihnen angeforderte Textzeile aus irgendeinem Grund nicht gelesen werden kann, wird dies durch die Rückgabe eines Nullzeigers angezeigt. Also hätten wir Dinge wie tun sollenSchließlich gibt es das Problem, dass zum Lesen einer Textzeile
fgets
Zeichen gelesen und in Ihr Array eingefügt werden, bis das\n
Zeichen gefunden wird, das die Zeile beendet, und das\n
Zeichen auch in Ihr Array eingefügt wird . Sie können dies sehen, wenn Sie unser früheres Beispiel geringfügig ändern:Wenn ich dies ausführe und "Steve" eingebe, wenn es mich dazu auffordert, wird es ausgedruckt
Das
"
in der zweiten Zeile ist, weil die Zeichenfolge, die es gelesen und wieder ausgedruckt hat, tatsächlich war"Steve\n"
.Manchmal spielt diese zusätzliche Zeile keine Rolle (wie bei unserem Anruf
atoi
oderatof
, da beide zusätzliche nicht numerische Eingaben nach der Nummer ignorieren), aber manchmal ist sie sehr wichtig. So oft wollen wir diese neue Zeile entfernen. Es gibt verschiedene Möglichkeiten, die ich in einer Minute erreichen werde. (Ich weiß, dass ich das viel gesagt habe. Aber ich werde auf all diese Dinge zurückkommen, das verspreche ich.)An diesem Punkt denken Sie vielleicht: "Ich dachte, Sie sagten, es
scanf
sei nicht gut, und dieser andere Weg wäre so viel besser. Aberfgets
es fängt an, wie ein Ärgernis auszusehen. Das Anrufenscanf
war so einfach ! Kann ich es nicht weiter benutzen?" ""Sicher, Sie können weiter verwenden
scanf
, wenn Sie möchten. (Und für wirklich einfache Dinge ist es in gewisser Weise einfacher.) Aber bitte kommen Sie nicht zu mir, wenn es Ihnen aufgrund einer seiner 17 Macken und Schwächen versagt oder aufgrund Ihrer Eingabe in eine Endlosschleife gerät nicht erwartet, oder wenn Sie nicht herausfinden können, wie man es benutzt, um etwas komplizierteres zu tun. Und werfen wir einen Blick auffgets
die tatsächlichen Belästigungen:Sie müssen immer die Arraygröße angeben. Nun, das ist natürlich überhaupt kein Ärgernis - das ist eine Funktion, denn Pufferüberlauf ist eine wirklich schlechte Sache.
Sie müssen den Rückgabewert überprüfen. Eigentlich ist das eine Wäsche, denn um
scanf
richtig zu verwenden , muss man auch den Rückgabewert überprüfen.Sie müssen den
\n
Rücken abstreifen . Ich gebe zu, das ist ein echtes Ärgernis. Ich wünschte, es gäbe eine Standardfunktion, auf die ich Sie hinweisen könnte, die dieses kleine Problem nicht hatte. (Bitte niemand ansprechengets
.) Aber im Vergleich zuscanf's
17 verschiedenen Belästigungen werde ich diese eine Belästigung einesfgets
jeden Tages nehmen.So , wie Sie Ihnen die Newline - Streifen? Drei Wege:
(a) Offensichtlicher Weg:
(b) Kniffliger und kompakter Weg:
Leider funktioniert dieser nicht immer.
(c) Ein anderer kompakter und leicht dunkler Weg:
Und jetzt, da das nicht im Weg ist, können wir zu einer anderen Sache zurückkehren, die ich übersprungen habe: den Unvollkommenheiten von
atoi()
undatof()
. Das Problem bei diesen ist, dass sie Ihnen keinen nützlichen Hinweis auf Erfolg oder Misserfolg geben: Sie ignorieren nachfolgende nicht numerische Eingaben stillschweigend und geben stillschweigend 0 zurück, wenn überhaupt keine numerische Eingabe vorhanden ist. Die bevorzugten Alternativen - die auch bestimmte andere Vorteile haben - sindstrtol
undstrtod
.strtol
Sie können auch eine andere Basis als 10 verwenden, was bedeutet, dass Sie den Effekt (unter anderem)%o
oder%x
mit erzielen könnenscanf
. Aber zu zeigen, wie man diese Funktionen richtig einsetzt, ist eine Geschichte für sich und würde zu sehr von dem ablenken, was sich bereits in eine ziemlich fragmentierte Erzählung verwandelt. Deshalb werde ich jetzt nichts mehr darüber sagen.Der Rest der Haupterzählung betrifft Eingaben, die Sie möglicherweise analysieren möchten und die komplizierter sind als nur eine einzelne Zahl oder ein einzelnes Zeichen. Was ist, wenn Sie eine Zeile lesen möchten, die zwei Zahlen oder mehrere durch Leerzeichen getrennte Wörter oder eine bestimmte Interpunktion enthält? Hier werden die Dinge interessant, und dort wurden die Dinge wahrscheinlich kompliziert, wenn Sie versuchten, Dinge mit zu tun
scanf
, und wo es jetzt, da Sie eine Textzeile sauber gelesen haben, weitaus mehr Optionen gibtfgets
, obwohl die ganze Geschichte über all diese Optionen könnte wahrscheinlich ein Buch füllen, also werden wir hier nur die Oberfläche kratzen können.Meine Lieblingstechnik besteht darin, die Zeile in durch Leerzeichen getrennte "Wörter" aufzuteilen und dann mit jedem "Wort" etwas weiter zu machen. Eine Hauptstandardfunktion hierfür ist
strtok
(die auch ihre Probleme hat und die auch eine ganze separate Diskussion bewertet). Meine eigene Präferenz ist eine dedizierte Funktion zum Erstellen eines Arrays von Zeigern auf jedes auseinandergebrochene "Wort", eine Funktion, die ich in diesen Kursnotizen beschreibe . Auf jeden Fall , wenn Sie „Wörter“ haben, können Sie weiter jeden verarbeiten, vielleicht mit den gleichenatoi
/atof
/strtol
/strtod
Funktionen haben wir bereits betrachtet.Paradoxerweise besteht eine
scanf
andere gute Möglichkeit, mit der Textzeile umzugehen, mit der wir gerade gelesen haben,fgets
darin, sie weiterzugeben , obwohl wir hier ziemlich viel Zeit und Mühe aufgewendet haben, um herauszufinden, wie wir uns entfernen könnensscanf
. Auf diese Weise erhalten Sie die meisten Vorteilescanf
, jedoch ohne die meisten Nachteile.Wenn Ihre Eingabesyntax besonders kompliziert ist, kann es angebracht sein, eine "Regexp" -Bibliothek zu verwenden, um sie zu analysieren.
Schließlich können Sie die für Sie geeigneten Ad-hoc- Parsing-Lösungen verwenden. Sie können Zeichen
char *
für Zeichen durch die Zeile bewegen, indem Sie mit einem Zeiger nach den erwarteten Zeichen suchen. Oder Sie können mit Funktionen wiestrchr
oderstrrchr
, oderstrspn
oderstrcspn
oder nach bestimmten Zeichen suchenstrpbrk
. Oder Sie können / convert analysieren und überspringen Gruppen von Ziffernzeichen , die mitstrtol
oderstrtod
Funktionen , die wir über früher übersprungen.Es gibt natürlich noch viel mehr zu sagen, aber hoffentlich bringt Ihnen diese Einführung den Einstieg.
quelle
sizeof (line)
und nicht nur zum Schreibensizeof line
? Ersteres lässt es so aussehen, als wäreline
es ein Typname!sscanf
als Konvertierungs-Engine, aber das Sammeln (und möglicherweise Massieren) der Eingabe mit einem anderen Werkzeug. Aber vielleicht im Zusammenhang erwähnenswertgetline
.fscanf
die tatsächlichen Belästigungen" sprechen , meinen Sie dasfgets
? Und das Ärgernis Nr. 3 ärgert mich wirklich, insbesondere angesichts der Tatsache, dassscanf
ein nutzloser Zeiger auf den Puffer zurückgegeben wird, anstatt die Anzahl der eingegebenen Zeichen zurückzugeben (was das Entfernen der neuen Zeile viel sauberer machen würde).sizeof
Stils. Für mich ist es einfach, sich daran zu erinnern, wann Sie die Eltern brauchen: Ich denke,(type)
es ist wie eine Besetzung ohne Wert (weil wir nur an dem Typ interessiert sind). Eine andere Sache: Sie sagen, dassstrtok(line, "\n")
das nicht immer funktioniert, aber es ist nicht offensichtlich, wann es nicht funktioniert. Ich vermute, Sie denken an den Fall, in dem die Zeile länger als der Puffer war, also haben wir keine neue Zeile und gebenstrtok()
null zurück? Es ist wirklich schade,fgets()
dass kein nützlicherer Wert zurückgegeben wird, sodass wir wissen können, ob die neue Zeile vorhanden ist oder nicht.Statt
scanf(some_format, ...)
, solltenfgets()
mitsscanf(buffer, some_format_and %n, ...)
Mithilfe von
" %n"
kann der Code einfach erkennen, ob das gesamte Format erfolgreich gescannt wurde und am Ende kein zusätzlicher Nicht-Leerraum-Junk vorhanden war.quelle
Geben wir die Anforderungen für das Parsen wie folgt an:
Eine gültige Eingabe muss akzeptiert (und in eine andere Form konvertiert) werden.
Eine ungültige Eingabe muss zurückgewiesen werden
Wenn eine Eingabe abgelehnt wird, muss dem Benutzer eine beschreibende Nachricht zur Verfügung gestellt werden, die erklärt (in klarer Sprache "leicht verständlich für normale Personen, die keine Programmierer sind"), warum sie abgelehnt wurde (damit die Benutzer herausfinden können, wie das Problem behoben werden kann Problem)
Um die Dinge sehr einfach zu halten, sollten Sie eine einzelne einfache Dezimalzahl (die vom Benutzer eingegeben wurde) und nichts anderes analysieren. Mögliche Gründe für die Ablehnung der Benutzereingabe sind:
Definieren wir auch "Eingabe enthielt nicht akzeptable Zeichen" richtig. und sag das:
5" wird als "5" behandelt)
Daraus können wir feststellen, dass die folgenden Fehlermeldungen benötigt werden:
An diesem Punkt können wir sehen, dass eine geeignete Funktion zum Konvertieren einer Zeichenfolge in eine Ganzzahl zwischen sehr unterschiedlichen Fehlertypen unterscheiden muss. und dass so etwas wie "
scanf()
" oder "atoi()
" oder "strtoll()
" völlig wertlos ist, weil sie Ihnen keinen Hinweis darauf geben, was mit der Eingabe falsch war (und eine völlig irrelevante und unangemessene Definition dessen verwenden, was gültig ist / nicht " Eingang").Beginnen wir stattdessen damit, etwas zu schreiben, das nicht nutzlos ist:
Um die angegebenen Anforderungen zu erfüllen; Diese
convertStringToInteger()
Funktion besteht wahrscheinlich aus mehreren hundert Codezeilen für sich.Dies war nur "Parsen einer einzelnen einfachen Dezimalzahl". Stellen Sie sich vor, Sie möchten etwas Komplexes analysieren. wie eine Liste von "Name, Straße, Telefonnummer, E-Mail-Adresse" -Strukturen; oder vielleicht wie eine Programmiersprache. In diesen Fällen müssen Sie möglicherweise Tausende von Codezeilen schreiben, um eine Analyse zu erstellen, die kein verkrüppelter Witz ist.
Mit anderen Worten...
Schreiben Sie selbst (möglicherweise Tausende von Zeilen) Code, um Ihren Anforderungen zu entsprechen.
quelle
Hier ist ein Beispiel für die Verwendung
flex
zum Scannen einer einfachen Eingabe, in diesem Fall einer Datei mit ASCII-Gleitkommazahlen, die entweder im US- (n,nnn.dd
) oder im europäischen (n.nnn,dd
) Format vorliegen können . Dies wird nur aus einem viel größeren Programm kopiert, daher gibt es möglicherweise einige ungelöste Verweise:quelle
Andere Antworten enthalten die richtigen Details auf niedriger Ebene, daher beschränke ich mich auf eine höhere Ebene: Analysieren Sie zunächst, wie jede Eingabezeile aussehen soll. Versuchen Sie, die Eingabe mit einer formalen Syntax zu beschreiben - mit etwas Glück können Sie feststellen, dass sie mit einer regulären Grammatik oder zumindest einer kontextfreien Grammatik beschrieben werden kann . Wenn eine reguläre Grammatik ausreicht, können Sie eine Finite-State-Maschine codierenHiermit wird jede Befehlszeile zeichenweise erkannt und interpretiert. Ihr Code liest dann eine Zeile (wie in anderen Antworten erläutert) und scannt dann die Zeichen im Puffer durch die Zustandsmaschine. In bestimmten Zuständen halten Sie an und konvertieren den bisher gescannten Teilstring in eine Zahl oder was auch immer. Sie können wahrscheinlich "Ihre eigenen rollen", wenn es so einfach ist; Wenn Sie feststellen, dass Sie eine vollständige kontextfreie Grammatik benötigen, sollten Sie besser herausfinden, wie vorhandene Parsing-Tools (re:
lex
und /yacc
oder deren Varianten) verwendet werden.quelle
errno == EOVERFLOW
nach der Verwendungstrtoll
) möglich.