2009-08-08 3 views
99

utilizzando il seguente codice:Come ti permettono gli spazi per essere inseriti usando scanf?

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

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

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

Un utente può inserire il proprio nome, ma quando entrano in un nome con uno spazio come Lucas Aardvark, scanf() taglia appena fuori tutto dopo Lucas. Come faccio scanf() permetto spazi

+8

noti che più idiomatica è 'malloc (sizeof (char) * 256 + 1)', o 'malloc (256 + 1)', o meglio ancora (supponendo 'nome' sarà usato rigorosamente localmente) ' nome char [256 + 1] '. Il '+1' può agire come un mneumonico per il terminatore nullo, che deve essere incluso nell'assegnazione. –

+0

@Barry - Sospetto 'sizeof (char) + 256' era un errore di battitura. –

+3

Eventuali duplicati di [Lettura stringa dall'ingresso con carattere di spazio?] (Https://stackoverflow.com/questions/6282198/reading-string-from-input-with-space-character) –

risposta

155

persone (e soprattutto principianti) non devono mai usare scanf("%s") o gets() o alcune altre funzioni che non dispongono di una protezione buffer overflow, se non si sa per certo che l'ingresso sarà sempre di un formato specifico (e forse neppure poi).

ricordare rispetto scanf sta per "scansione formattato" e c'è ben poco meno formattati di dati inseriti dall'utente. E 'l'ideale se si ha il controllo totale del formato dei dati in ingresso, ma in generale non idonei per l'input dell'utente.

Usa fgets() (che ha protezione buffer overflow) per ottenere il vostro ingresso in una stringa e sscanf() per valutarla. Dal momento che si desidera solo ciò che l'utente ha inserito, senza analisi, non si ha realmente bisogno sscanf() in questo caso, comunque:

#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; 
} 
+0

Non sapevo su 'fgets()'. Sembra davvero più facile da usare quindi 'scanf()'. +1 – Kredns

+7

Se vuoi semplicemente ottenere una linea da parte dell'utente, è più facile. È anche più sicuro poiché puoi evitare overflow del buffer. La famiglia scanf è davvero utile per trasformare una stringa in cose diverse (come quattro caratteri e un int per esempio con "% c% c% c% c% d") ma, anche allora, dovresti usare fgets e sscanf, non scanf, per evitare la possibilità di overflow del buffer. – paxdiablo

+4

È possibile inserire la dimensione massima del buffer nel formato scanf, non è possibile inserire il calcolo runtime senza costruire il formato in fase di esecuzione (non esiste l'equivalente di * per printf, * è un modificatore valido per scanf con un altro comportamento: soppressione assegnazione). – AProgrammer

105

Prova

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

Speranza che aiuta.

+7

Io non sapevo nemmeno 'scanf() 'ha accettato regex! Grazie!!!!! – Kredns

+50

nota, non esegue regex generali, solo classi di caratteri – rampion

+1

@rampion: nota a margine eccellente (ma è comunque interessante). – Kredns

7

Non utilizzare scanf() di leggere le stringhe senza specificare una larghezza di campo. Si dovrebbe anche controllare i valori di ritorno per gli errori:

#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); 
} 

In alternativa, utilizzare 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); 
} 
4

getline()

Ora parte di POSIX, nessuno-la-meno.

Inoltre si occupa del problema di allocazione del buffer che hai chiesto in precedenza, anche se si deve prendere cura di free ing memoria.

+0

Standard? Nel riferimento citi: "Sia getline() che getdelim() sono estensioni GNU." – AProgrammer

+1

POSIX 2008 aggiunge getline. Quindi GNU è andato avanti e ha cambiato le loro intestazioni per glibc attorno alla versione 2.9, e sta causando problemi a molti progetti. Non un link definitivo, ma guarda qui: https://bugzilla.redhat.com/show_bug.cgi?id=493941. Per quanto riguarda la pagina man on-line, ho afferrato il primo google trovato. – dmckee

39

Questo esempio utilizza uno scanset invertito, in modo da scanf continua a prendere in valori fino a quando non incontra un '\ n' - ritorno a capo, quindi gli spazi vengono salvati così

#include <stdio.h> 

int main (int argc, char const *argv[]) 
{ 
    char name[20]; 
    scanf("%[^\n]s",name); 
    printf("%s\n", name); 
    return 0; 
} 
+1

Attenzione con buffer overflow. Se l'utente scrive un "nome" con 50 caratteri, il programma probabilmente si bloccherà. – brunoais

+0

Come si conosce la dimensione del buffer, è possibile utilizzare '% 20 [^ \ n] s' per evitare buffer overflow – Spookbuster

6

È possibile utilizzare la funzione fgets() a leggere una stringa o utilizzare scanf("%[^\n]s",name); modo lettura stringa terminerà nell'incontrare un carattere di nuova riga.

+0

Attenzione che questo non impedisce buffer overflow – brunoais

17

È possibile utilizzare questo

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

O questo

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

+1

Non ho testato, ma sulla base di altre risposte in questa stessa pagina, Credo che la dimensione del buffer corretta per scanf nel tuo esempio sarebbe: 'scanf ("% 19 [^ \ n] ", nome);' (ancora +1 per la risposta concisa) –

+0

Proprio come una nota a margine, 'sizeof (char) 'è per definizione * sempre * 1, quindi non è necessario moltiplicare per questo. – paxdiablo

1

È possibile utilizzare scanf per questo scopo con un piccolo trucco. In realtà, è necessario consentire l'input dell'utente fino a quando l'utente non seleziona Invio (\n). Questo prenderà in considerazione tutti i personaggi, tra cui spazio. Ecco un esempio:

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; 
} 

Come funziona? Quando l'utente immette caratteri dall'input standard, saranno memorizzati in stringa variabile fino primo spazio vuoto. Dopodiché, il resto della voce rimarrà nel flusso di input e attenderà il successivo scanf. Quindi, abbiamo un ciclo for che prende char di char dal flusso di ingresso (fino \n) e apends a fine stringa variabile, formando così una stringa completa stesso come input utente dalla tastiera.

Spero che questo vi aiuterà qualcuno!

+0

Soggetto a buffer overflow. – paxdiablo

-3

utilizzare il seguente codice per leggere stringhe con spazi:
scanf("[%s/n]",str_ptr);

0

Anche se in realtà non dovrebbe usare scanf() per questo genere di cose, perché ci sono le chiamate molto meglio, come gets() o getline(), può essere fatto:

#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; 
} 
+0

C'è un * motivo * perché 'get' è stato deprecato e rimosso (https://stackoverflow.com/questions/30890696/why-gets-is-deprecated) dallo standard. È anche * peggio * che 'scanf' perché almeno quest'ultimo ha dei modi per renderlo sicuro. – paxdiablo

3

Se qualcuno è ancora alla ricerca, ecco cosa ha funzionato per me - di leggere una lunghezza arbitraria di corda inclusi gli spazi.

Grazie a molti manifesti sul web per la condivisione di questa semplice & soluzione elegante. Se funziona il merito va a loro ma ogni errore è mio.

char *name; 
scanf ("%m[^\n]s",&name); 
printf ("%s\n",name); 
+2

Vale la pena notare che questa è un'estensione * POSIX * e non esiste nello standard ISO. Per completezza, probabilmente dovresti anche controllare 'errno' e pulire anche la memoria allocata. – paxdiablo