Wie können Leerzeichen mit scanf eingegeben werden?

129

Verwenden Sie den folgenden Code:

char *name = malloc(sizeof(char) + 256); 

printf("What is your name? ");
scanf("%s", name);

printf("Hello %s. Nice to meet you.\n", name);

Ein Benutzer kann seinen Namen eingeben, aber wenn er einen Namen mit einem Leerzeichen wie eingibt Lucas Aardvark, wird danach scanf()einfach alles abgeschnitten Lucas. Wie scanf()erlaube ich Leerzeichen?

Kredns
quelle
9
Beachten Sie, dass "malloc (sizeof (char) * 256 + 1)" oder "malloc (256 + 1)" oder noch besser (vorausgesetzt, "name" wird streng lokal verwendet) "char name [256 + 1 ] '. Das '+1' kann als Mneumonik für den Nullterminator fungieren, der in die Zuordnung einbezogen werden muss.
Barry Kelly
@ Barry - Ich vermute, es sizeof(char) + 256war ein Tippfehler.
Chris Lutz

Antworten:

186

Personen (und insbesondere Anfänger) sollten niemals scanf("%s")oder gets()andere Funktionen verwenden, die keinen Pufferüberlaufschutz haben, es sei denn, Sie wissen mit Sicherheit, dass die Eingabe immer ein bestimmtes Format hat (und möglicherweise auch dann nicht).

Denken Sie daran, dass dies scanffür "Scan-formatiert" steht und dass kaum etwas weniger formatiert ist als vom Benutzer eingegebene Daten. Es ist ideal, wenn Sie die vollständige Kontrolle über das Eingabedatenformat haben, aber im Allgemeinen nicht für Benutzereingaben geeignet sind.

Verwenden Sie fgets()( mit Pufferüberlaufschutz), um Ihre Eingabe in eine Zeichenfolge zu übertragen und sscanf()auszuwerten. Da Sie nur das wollen, was der Benutzer ohne Analyse eingegeben hat, brauchen Sie sscanf()in diesem Fall sowieso nicht wirklich :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Maximum name size + 1. */

#define MAX_NAME_SZ 256

int main(int argC, char *argV[]) {
    /* Allocate memory and check if okay. */

    char *name = malloc(MAX_NAME_SZ);
    if (name == NULL) {
        printf("No memory\n");
        return 1;
    }

    /* Ask user for name. */

    printf("What is your name? ");

    /* Get the name, with size limit. */

    fgets(name, MAX_NAME_SZ, stdin);

    /* Remove trailing newline, if there. */

    if ((strlen(name) > 0) && (name[strlen (name) - 1] == '\n'))
        name[strlen (name) - 1] = '\0';

    /* Say hello. */

    printf("Hello %s. Nice to meet you.\n", name);

    /* Free memory and exit. */

    free (name);
    return 0;
}
paxdiablo
quelle
1
Ich wusste es nicht fgets(). Dann sieht es tatsächlich einfacher aus scanf(). +1
Kredns
7
Wenn Sie nur eine Zeile vom Benutzer erhalten möchten, ist dies einfacher. Es ist auch sicherer, da Sie Pufferüberläufe vermeiden können. Die scanf-Familie ist sehr nützlich, um einen String in verschiedene Dinge umzuwandeln (wie vier Zeichen und ein int, zum Beispiel mit "% c% c% c% c% d"), aber selbst dann sollten Sie fgets und sscanf verwenden, nicht scanf, um die Möglichkeit eines Pufferüberlaufs zu vermeiden.
Paxdiablo
4
Sie können die maximale Puffergröße im ScanF-Format festlegen. Sie können nur keine berechnete Laufzeitgröße festlegen, ohne das Format zur Laufzeit zu erstellen (es gibt nicht das Äquivalent von * für printf, * ist ein gültiger Modifikator für scanf mit einem anderen Verhalten: Unterdrücken der Zuweisung ).
AProgrammer
Beachten Sie auch, dass scanfdas Verhalten undefiniert ist, wenn die numerische Konvertierung überläuft ( N1570 7.21.6.2p10 , letzter Satz, Wortlaut seit C89 unverändert), was bedeutet, dass keine der scanfFunktionen sicher für die numerische Konvertierung nicht vertrauenswürdiger Eingaben verwendet werden kann.
zwol
@JonathanKomar und alle anderen, die dies in Zukunft lesen: Wenn Ihr Professor Ihnen sagte, dass Sie es für scanfeine Aufgabe verwenden müssen, waren sie falsch, und Sie können ihnen sagen, dass ich es gesagt habe, und wenn sie mit mir darüber streiten wollen kann meine E-Mail-Adresse leicht aus meinem Profil gefunden werden.
zwol
124

Versuchen

char str[11];
scanf("%10[0-9a-zA-Z ]", str);

Hoffentlich hilft das.

Kelly Gendron
quelle
10
(1) Um Leerzeichen zu akzeptieren, müssen Sie natürlich ein Leerzeichen in die Zeichenklasse einfügen. (2) Beachten Sie, dass die 10 die maximale Anzahl von Zeichen ist, die gelesen werden, sodass str mindestens auf einen Puffer der Größe 11 zeigen muss. (3) Das letzte s hier ist keine Formatanweisung, aber scanf wird hier versuchen, es genau abzugleichen. Der Effekt wird bei einem Eintrag wie 1234567890s sichtbar, bei dem das s verbraucht wird, aber nicht wo. Ein anderer Brief wird nicht verbraucht. Wenn Sie nach dem s ein anderes Format einfügen, wird es nur gelesen, wenn ein s übereinstimmt.
AProgrammer
Ein weiteres potenzielles Problem, die Verwendung von - an einem anderen Ort als dem ersten oder dem letzten, ist die definierte Implementierung. Normalerweise wird es für Bereiche verwendet, aber was der Bereich bestimmt, hängt vom Zeichensatz ab. EBCDIC hat Löcher in den Buchstabenbereichen und selbst wenn man von ASCII abgeleitete Zeichensätze annimmt, ist es naiv zu glauben, dass alle Kleinbuchstaben im
Az-
1
"% [^ \ n]" hat das gleiche Problem wie get (), Pufferüberlauf. Mit dem zusätzlichen Haken, dass das \ n Finale nicht gelesen wird; Dies wird durch die Tatsache verdeckt, dass die meisten Formate zunächst Leerzeichen überspringen, aber [gehört nicht dazu. Ich verstehe die Instanz bei der Verwendung von scanf zum Lesen von Zeichenfolgen nicht.
AProgrammer
1
Das wurde svom Ende der Eingabezeichenfolge entfernt, da es in bestimmten Fällen sowohl überflüssig als auch falsch ist (wie in früheren Kommentaren erwähnt). [ist eher ein eigener Formatbezeichner als eine Variation des seinen.
Paxdiablo
53

In diesem Beispiel wird ein invertiertes Scanset verwendet, sodass scanf so lange Werte aufnimmt, bis es auf eine '\ n' - neue Zeile stößt, sodass auch Leerzeichen gespeichert werden

#include <stdio.h>

int main (int argc, char const *argv[])
{
    char name[20];
    scanf("%[^\n]s",name);
    printf("%s\n", name);
    return 0;
}
SVA
quelle
1
Vorsicht bei Pufferüberläufen. Wenn der Benutzer einen "Namen" mit 50 Zeichen schreibt, stürzt das Programm wahrscheinlich ab.
Brunoais
3
Wie Sie die Puffergröße kennen, können Sie verwenden %20[^\n]s, um Pufferüberläufe zu verhindern
Osvein
45 Punkte und niemand wies auf die offensichtliche Frachtkultivierung hin s!
Antti Haapala
22

Sie können dies verwenden

char name[20];
scanf("%20[^\n]", name);

Oder dieses

void getText(char *message, char *variable, int size){
    printf("\n %s: ", message);
    fgets(variable, sizeof(char) * size, stdin);
    sscanf(variable, "%[^\n]", variable);
}

char name[20];
getText("Your name", name, 20);

DEMO

Vitim.us
quelle
1
Ich habe nicht getestet, aber basierend auf anderen Antworten auf dieser Seite glaube ich, dass die richtige Puffergröße für scanf in Ihrem Beispiel wäre: scanf("%19[^\n]", name);(immer noch +1 für die prägnante Antwort)
Dr. Beco
1
Nur als Randnotiz sizeof(char)ist per Definition immer 1, so dass es nicht nötig ist, damit zu multiplizieren.
Paxdiablo
8

Verwenden Sie diese Option nicht scanf()zum Lesen von Zeichenfolgen, ohne eine Feldbreite anzugeben. Sie sollten auch die Rückgabewerte auf Fehler überprüfen:

#include <stdio.h>

#define NAME_MAX    80
#define NAME_MAX_S "80"

int main(void)
{
    static char name[NAME_MAX + 1]; // + 1 because of null
    if(scanf("%" NAME_MAX_S "[^\n]", name) != 1)
    {
        fputs("io error or premature end of line\n", stderr);
        return 1;
    }

    printf("Hello %s. Nice to meet you.\n", name);
}

Alternativ können Sie Folgendes verwenden fgets():

#include <stdio.h>

#define NAME_MAX 80

int main(void)
{
    static char name[NAME_MAX + 2]; // + 2 because of newline and null
    if(!fgets(name, sizeof(name), stdin))
    {
        fputs("io error\n", stderr);
        return 1;
    }

    // don't print newline
    printf("Hello %.*s. Nice to meet you.\n", strlen(name) - 1, name);
}
Christoph
quelle
6

Sie können die fgets()Funktion zum Lesen einer Zeichenfolge verwenden oder das scanf("%[^\n]s",name);Lesen von Zeichenfolgen beenden, wenn ein Zeilenumbruchzeichen auftritt.

Anshul garg
quelle
Vorsicht, dass dies Pufferüberläufe nicht verhindert
Brunoais
sgehört nicht dorthin
Antti Haapala
5

getline()

Jetzt ein Teil von POSIX.

Es behebt auch das Pufferzuordnungsproblem, nach dem Sie zuvor gefragt haben, obwohl Sie sich um freeden Speicher kümmern müssen .

dmckee --- Ex-Moderator Kätzchen
quelle
Standard? In der Referenz zitieren Sie: "Sowohl getline () als auch getdelim () sind GNU-Erweiterungen."
AProgrammer
1
POSIX 2008 fügt getline hinzu. Also hat GNU wen voraus und ihre Header für glibc um Version 2.9 geändert, und es verursacht Probleme für viele Projekte. Kein definitiver Link, aber schauen Sie hier: bugzilla.redhat.com/show_bug.cgi?id=493941 . Was die Online-Manpage betrifft, habe ich die erste gefunden, die Google gefunden hat.
dmckee --- Ex-Moderator Kätzchen
3

Wenn noch jemand sucht, hat Folgendes für mich funktioniert: Lesen einer beliebigen Länge von Zeichenfolgen einschließlich Leerzeichen.

Vielen Dank an viele Poster im Internet für das Teilen dieser einfachen und eleganten Lösung. Wenn es funktioniert, geht der Kredit an sie, aber alle Fehler sind meine.

char *name;
scanf ("%m[^\n]s",&name);
printf ("%s\n",name);
Immer lernen
quelle
2
Es ist erwähnenswert, dass dies eine POSIX- Erweiterung ist und nicht im ISO-Standard vorhanden ist. Der Vollständigkeit halber sollten Sie wahrscheinlich auch errnoden zugewiesenen Speicher überprüfen und bereinigen.
Paxdiablo
sgehört nicht dorthin nach dem Scanset
Antti Haapala
1

Sie können scanffür diesen Zweck mit einem kleinen Trick verwenden. Eigentlich sollten Sie Benutzereingaben zulassen, bis der Benutzer Enter ( \n) drückt . Dies berücksichtigt jedes Zeichen, einschließlich des Leerzeichens . Hier ist ein Beispiel:

int main()
{
  char string[100], c;
  int i;
  printf("Enter the string: ");
  scanf("%s", string);
  i = strlen(string);      // length of user input till first space
  do
  {
    scanf("%c", &c);
    string[i++] = c;       // reading characters after first space (including it)
  } while (c != '\n');     // until user hits Enter
  string[i - 1] = 0;       // string terminating
return 0;
}

Wie funktioniert das? Wenn der Benutzer Zeichen aus der Standardeingabe eingibt, werden diese bis zum ersten Leerzeichen in einer Zeichenfolgenvariablen gespeichert . Danach bleibt der Rest des Eintrags im Eingabestream und wartet auf den nächsten Scan. Als nächstes haben wir eine forSchleife, die char für char aus dem Eingabestream (bis \n) nimmt und sie an das Ende der Zeichenfolgenvariablen anfügt , wodurch eine vollständige Zeichenfolge gebildet wird, die der Benutzereingabe über die Tastatur entspricht.

Hoffe das hilft jemandem!

akelec
quelle
Vorbehaltlich eines Pufferüberlaufs.
Paxdiablo
0

Während Sie scanf()für solche Dinge wirklich nicht verwenden sollten , weil es viel bessere Anrufe wie gets()oder gibt getline(), kann es getan werden:

#include <stdio.h>

char* scan_line(char* buffer, int buffer_size);

char* scan_line(char* buffer, int buffer_size) {
   char* p = buffer;
   int count = 0;
   do {
       char c;
       scanf("%c", &c); // scan a single character
       // break on end of line, string terminating NUL, or end of file
       if (c == '\r' || c == '\n' || c == 0 || c == EOF) {
           *p = 0;
           break;
       }
       *p++ = c; // add the valid character into the buffer
   } while (count < buffer_size - 1);  // don't overrun the buffer
   // ensure the string is null terminated
   buffer[buffer_size - 1] = 0;
   return buffer;
}

#define MAX_SCAN_LENGTH 1024

int main()
{
   char s[MAX_SCAN_LENGTH];
   printf("Enter a string: ");
   scan_line(s, MAX_SCAN_LENGTH);
   printf("got: \"%s\"\n\n", s);
   return 0;
}
Ed Zavada
quelle
2
Es gibt einen Grund, warum getsveraltet und aus dem Standard entfernt wurde ( stackoverflow.com/questions/30890696/why-gets-is-deprecated ). Es ist noch schlimmer , scanfweil zumindest letzteres Möglichkeiten hat, es sicher zu machen.
Paxdiablo
-1
/*reading string which contains spaces*/
#include<stdio.h>
int main()
{
   char *c,*p;
   scanf("%[^\n]s",c);
   p=c;                /*since after reading then pointer points to another 
                       location iam using a second pointer to store the base 
                       address*/ 
   printf("%s",p);
   return 0;
 }
venkata sandeep
quelle
4
Können Sie erklären, warum dies die richtige Antwort ist? Bitte posten Sie keine Nur-Code-Antworten.
Theo
sgehört nicht dorthin nach dem Scanset
Antti Haapala