Hallo,
hab folgendes Programm in C geschrieben (Auschnitt) :
void main (void)
{
double a,b, Ergebnis;
char Rechenzeichen;
printf("Bitte die erste Variable eingeben\n");
scanf("%lf", &a);
printf("Bitte die zweite Variable eingeben\n");
scanf("%lf", &b);
printf("Bitte Rechenoperation ausw\x84hlen: ""+,-,*,/""\n");
fflush(stdin); // Tastaturpuffer leeren, da als nächstes sonst ein
"Enter" eingelesen würde
scanf("%c", &Rechenzeichen);
switch(Rechenzeichen)
....
Das hier abgebildete Programm funktioniert!
Habe beim programmieren zu nächst den "fflush(stdin);" um das "Enter"
aus dem Tastaturpuffer zu löschen vergessen.
Würde gerne wissen ,warum ich diesen unbedingt an der Stelle brauche,
bevor ich einen Wert in eine char Variable einlese, aber er nicht
notwendig
nach dem zweiten scanf-Befehl ist.
Zumindestns zeigt das Programm keine Fehlfunktion an.
Bin gespannt ;-) !
be stucki schrieb:> Hier eine Lösung (Antwort mit der Bewertung 10):>> Quit using scanf. Use fgets and the sscanf> http://stackoverflow.com/questions/6277370/replacement-of-fflushstdin
Sind "scanf" und "gets" einfach "veraltet"?
Also kann ich 1:1 die hier vorgeschlagenen Befehle benutzen ohne mir
"Sorgen" machen zu müssen?
Hier ist eine Erklärung warum fgets beser ist:
http://www.proggen.org/doku.php?id=c:lib:stdio:fgets
Unser Lehrer hat bis jetzt nur die Befehle scanf und gets inkl.
fflush(stdin)
vorgestellt. Soll ich ihn drauf hinweißen das sein Zeug veraltet ist
oder gibt es einen Grund warum er die "alten" Befehle benutzt?
Danke für die schnelle Antwort!!
gets ist veraltet und auch gefährlich, da die Größe des Eingabepuffers
nicht geprüft wird (und auch nicht kann, da sie gar nicht angegeben
wird).
Darum ist gets (seit C11) auch deprecated.
Das Verhalten von fflush() ist für stdin nicht definiert.
Unter Linux leert es nicht den Eingabepuffer.
Für dein Problem, das überlesen vom '\n', reicht ein Leerzeichen vor dem
%c im Formatstring von scanf aus.
1
scanf(" %c",&Rechenzeichen);
2
// ^ da
Das Leerzeichen bedeutet: "Überlese alle Whitespace"
scanf ist eine sehr mächtige Funktion, die dementsprechend auch
Resourcen braucht.
Bei fgets brauchst du einen ausreichend großen Speicherbereich für die
Eingabe. Dies brauchst du bei scanf nicht unbedingt.
fgets speichert allerdings das '\n' mit in der Eingabe ab.
Das gibt gerne mal Probleme beim Stringvergleich oder auch bei
Dateinamen.
Tstyle schrieb:> oder gibt es einen Grund warum er die "alten" Befehle benutzt?
Weil er nichts mehr dazu gelernt hat.
Tstyle schrieb:> Sind "scanf" und "gets" einfach "veraltet"?
Veraltet ist das falsche Wort. Die beiden Funktionen sind unsicher und
können einen Buffer-Overflow erzeugen:
1
#include<stdio.h>
2
3
intmain(void){
4
charStr[16];
5
scanf("%s",Str);
6
return0;
7
}
Wer oder was hindert den Benutzer daran, mehr als 15 Zeichen einzugeben?
Genau, niemand! scanf hat keine Ahnung über die Grösse von Str und
schreibt munter alle eingegebenen Zeichen in den Speicher.
Mit fgets kann sowas nicht passieren:
1
#include<stdio.h>
2
3
intmain(void){
4
charStr[16];
5
fgets(Str,sizeof(Str),stdin);
6
return0;
7
}
Der Benutzer kann zwar immernoch mehr als 15 Zeichen eingeben, aber dein
Speicher wird dabei nicht zerschossen, da fgets eine Längenangabe
erhalten hat. fgets liest jetzt aber nur die ersten 15 Zeichen ein. Hat
der Benutzer mehr Zeichen eingegeben, stehen diese immernoch im Input
Buffer und können später noch gelesen werden.
Tstyle schrieb:> Soll ich ihn drauf hinweißen das sein Zeug veraltet ist> oder gibt es einen Grund warum er die "alten" Befehle benutzt?
Kannst ihm ja einfach sagen, dass du im Internet gelesen hättest, dass
scanf unsicher und somit in professioneller Software nicht zu gebrauchen
sei und ihn anschliessend fragen, ob dies so richtig sei. Viele Lehrer
haben Probleme damit, wenn ihre Fähigkeiten direkt in Frage gestellt
werden. Auf die Tatsache, dass fflush(stdin) undefiniertes Verhalten
zeigt, solltest du ihn natürlich hinweisen.
bist wieder auf der sicheren Seite.
Schwieriger wird es, wenn die Länge des Puffers variabel ist.
Aber so einfach kann man fgets und scanf (mit %s) auch nicht
vergleichen.
Die haben da unterschiedliches Verhalten, was durchaus erwünscht sein
kann.
Dirk B. schrieb:> fgets speichert allerdings das '\n' mit in der Eingabe ab.> Das gibt gerne mal Probleme beim Stringvergleich oder auch bei> Dateinamen.
Ich sehe das als Vorteil:
1
char*NewLineChar=strrchr(Str,'\n');
2
if(NewLineChar==NULL){
3
/* Zeile nicht vollständig gelesen */
4
}
5
else{
6
*NewLineChar='\0';
7
/* Verarbeitung der eingelesenen Daten */
8
}
Dirk B. schrieb:> Mit#include <stdio.h>>> int main(void){> char Str[16];> scanf("%15s",Str);> return 0;> }bist wieder auf der sicheren Seite.
Bis du irgendwann die Grösse des Buffers änderst. Da arbeite ich lieber
mit dem sizeof Operator.
Dirk B. schrieb:> Aber so einfach kann man fgets und scanf (mit %s) auch nicht> vergleichen.> Die haben da unterschiedliches Verhalten, was durchaus erwünscht sein> kann.
Kannst du ein Beispiel machen? Ich habe scanf bis jetzt noch nie
vermisst, aber vielleicht kann ich noch was lernen.
Dirk B. schrieb:> #include <stdio.h>>> int main(void){> char Str[16];> scanf("%15s",Str);> return 0;> }
muss nicht am Ende des Arrays ein "\0" eingefügt werden, damit der
Compiler weiss das die Zeichenkette zu Ende ist.
Die Funktionen gets, fgets machen das ja selbstständig aber scanf?
be stucki schrieb:> Ich sehe das als Vorteil:
Ich auch. Ich wollte nur darauf hinweisen, dass man gets nicht so
einfach durch fgets ersetzen kann.
be stucki schrieb:> Kannst du ein Beispiel machen?
Nein.
Es gibt dann immer ein Beispiel, wie man es ohne scanf macht.
be stucki schrieb:> Kannst du ein Beispiel machen? Ich habe scanf bis jetzt noch nie> vermisst, aber vielleicht kann ich noch was lernen.
Sry, kann dir darauf leider keine Antwort geben. Bis jetzt haben wir
diesen Befehl zum Einlesen von Variablen des Typs int, double und char
benutzt.
Dass man es wie unten gezeigt machen kann, wusste ich bis jetzt noch
nicht.
Dirk B. schrieb:> #include <stdio.h>>> int main(void){> char Str[16];> scanf("%15s",Str);> return 0;> }
Dachte immer das man Funktionen wie gets/fgets braucht um eine
Zeichenkette einlesen zu können.
Tstyle schrieb:> Dirk B. schrieb:>> btw, wie habt ihr gelernt, double-Variablen auszugeben?>> Welchen Formatspecifier nutzt ihr dafür?>> printf("%lf", dbl);
Auch da sieht man es.
Der richtige Formatspecifier wäre das %f ohne das l (ell)
Das ist eine Besonderheit von Funktionen mit variabler Argumentenliste.
Da aber fast alle das mit %lf gemacht haben, wurde es geduldet und ist
mittlerweile auch im Standard enthalten.
Siehe http://www.cplusplus.com/reference/cstdio/printf/
oder http://en.cppreference.com/w/c/io/fprintf
Tstyle schrieb:> Dachte immer das man Funktionen wie gets/fgets braucht um eine> Zeichenkette einlesen zu können.
fgets liest eine ganze Zeile ein (oder bis der Puffer voll ist)
scanf mit %s liest bis zum nächste Whitespace (oder bis die angegebene
Länge erreicht wurde)
Es gibt dann noch %[
Damit kann man auch hübsche Dinge anstellen. Z.B gets simulieren.
1
intmain(void){
2
charStr[16];
3
scanf("%15[^\n] ",Str);
4
return0;
5
}
Bei der Größenangabe bei scanf muss man aber Bedenken, dass dort die
Anzahl der Zeichen angegeben wird. Der Platz für die '\0' muss noch
übrig bleiben.
Bei fgets wird die Größe des Puffers angegeben. Die '\0' wird
berücksichtigt.
Tstyle schrieb:> Laut Definiton von>> Dirk B. schrieb:>> oder http://en.cppreference.com/w/c/io/fprintf>> steht der f also für Gleitkommazahlen jeglichen Datentyps?
Nein.
Das steht da auch nicht.
Es gibt ja noch long double.
Dafür ist der L-Modifier. Also %Lf
In der Reference steht beim l-Modifiere double.
Allerdings steht das auch bei none
be stucki schrieb:> Leider steht immernoch in vielen Tutorials und sogar in Büchern, dass> fflush zum Leeren des Tastaturbuffers verwendet werden soll...
Ja, das sieht man recht oft. Dabei ist das ja überhaupt nicht der Sinn
von fflush(). Der Sinn ist, alle Daten, die noch im Puffer liegen, an
ihr Ziel auszugeben. Bei Eingabestreams ergibt das keinen Sinn, denn da
ist das Ziel ja mein Programm, wohin ich die Daten duch einfaches Lesen
bekomme.
Es gibt keine Funktion, um den Pufferinhalt eines Streams zu verwerfen.
Tenga schrieb:> Rolf Magnus schrieb:>> Es gibt keine Funktion, um den Pufferinhalt eines Streams zu verwerfen.>> Nicht blockierend lesen bis nichts mehr kommt.
Natürlich kann man lesen und dann selbst das Gelesene verwerfen. Das
habe ich nicht bestritten.
Tstyle schrieb:> Dirk B. schrieb:>> Der richtige Formatspecifier wäre das %f ohne das l (ell)>> In unserem Skriptum steht der Formatspecifier f für den Datentyp float.
Bei den printf-Varianten ist die Unterscheidung zwischen %f und %lf
nicht notwendig, weil ein float-Wert als Argument einer variablen
Parameterliste immer nach double konvertiert wird. Bei scanf ist sehr
wohl wichtig, dass man zwischen %f und %lf unterscheidet:
Dirk B. schrieb:> Da aber fast alle das mit %lf gemacht haben, wurde es geduldet und ist> mittlerweile auch im Standard enthalten.
Da gabs meines Wissens aber auch einen handfesteren Grund.
Die Sache ist die, das eine gleichermassen erlaubte Verwendung von %f
unf %lf obwohl eigentlich überflüssig, es einfacher macht, Formatstrings
extern zu warten. Damit kann man in einer externen Datei den zu
benutzenden Formatspezifier definieren (um ihn zb vom Programm von dort
lesen zu lassen), ohne sich um die Unterscheidung zwischen printf und
scanf kümmern zu müssen. Selbiges mit Formatspezifiern, die über #define
im Code an einer zentralen Stelle gesammelt werden (um zb zwischen %f
und %g 'umschalten' zu können).
Alles in allem denke ich, das es eine gute Entscheidung war, hier gleich
zu ziehen.