¿Cómo permite que se ingresen espacios usando scanf?


129

Usando el siguiente código:

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

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

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

Un usuario puede ingresar su nombre, pero cuando ingresa un nombre con un espacio como Lucas Aardvark, scanf()simplemente corta todo después Lucas. ¿Cómo hago para scanf()permitir espacios?


9
Tenga en cuenta que más idiomático es 'malloc (sizeof (char) * 256 + 1)', o 'malloc (256 + 1)', o incluso mejor (suponiendo que 'nombre' se usará estrictamente localmente) 'char name [256 + 1 ] '. El '+1' puede actuar como un mneumónico para el terminador nulo, que debe incluirse en la asignación.
Barry Kelly el

@Barry: sospecho que sizeof(char) + 256fue un error tipográfico.
Chris Lutz

Respuestas:


186

Las personas (y especialmente los principiantes) nunca deben usar scanf("%s")o gets()cualquier otra función que no tenga protección de desbordamiento de búfer, a menos que sepa con certeza que la entrada siempre tendrá un formato específico (y tal vez ni siquiera entonces).

Recuerde que scanfsignifica "escaneo formateado" y hay un poco menos formateado que los datos ingresados ​​por el usuario. Es ideal si tiene un control total del formato de datos de entrada, pero generalmente no es adecuado para la entrada del usuario.

Use fgets()(que tiene protección de desbordamiento de búfer) para obtener su entrada en una cadena y sscanf()evaluarla. Dado que solo desea lo que el usuario ingresó sin analizar, de sscanf()todos modos no necesita en este caso:

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

1
No sabía acerca fgets(). En realidad, parece más fácil de usar entonces scanf(). +1
Kredns

77
Si solo desea obtener una línea del usuario, es más fácil. También es más seguro ya que puede evitar desbordamientos de búfer. La familia scanf es realmente útil para convertir una cadena en cosas diferentes (como cuatro caracteres y un int, por ejemplo, con "% c% c% c% c% d") pero, aun así, debería usar fgets y sscanf, no scanf, para evitar la posibilidad de desbordamiento del búfer.
paxdiablo

44
Puede poner el tamaño máximo del búfer en formato scanf, simplemente no puede poner uno calculado en tiempo de ejecución sin generar el formato en tiempo de ejecución (no hay el equivalente de * para printf, * es un modificador válido para scanf con otro comportamiento: suprimir la asignación )
Programador del

Tenga en cuenta también que scanftiene un comportamiento indefinido si la conversión numérica se desborda ( N1570 7.21.6.2p10 , última oración, redacción sin cambios desde C89), lo que significa que ninguna de las scanffunciones puede usarse de manera segura para la conversión numérica de entradas no confiables.
zwol

@JonathanKomar y cualquier otra persona que lea esto en el futuro: si su profesor le dijo que tenía que usarlo scanfen una tarea, se equivocaron al hacerlo, y puede decirles que lo dije, y si quieren discutir conmigo al respecto , mi dirección de correo electrónico se encuentra fácilmente desde mi perfil.
zwol

124

Tratar

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

Espero que ayude.


10
(1) Obviamente para aceptar espacios, debes poner un espacio en la clase de personaje. (2) Tenga en cuenta que el 10 es el número máximo de caracteres que se leerán, por lo que str debe apuntar a un búfer de tamaño 11 como mínimo. (3) La última s aquí no es una directiva de formato, pero scanf intentará que coincida exactamente. El efecto será visible en una entrada como 1234567890s donde se consumirán los s pero no se colocará donde. No se consumirá otra carta. Si coloca otro formato después de la s, se leerá solo si hay una s que coincida.
Programador del

Otro problema potencial, el uso de - en otro lugar que el primero o el último es la implementación definida. Por lo general, se usa para rangos, pero lo que designe el rango depende del juego de caracteres. EBCDIC tiene agujeros en los rangos de letras e incluso, al asumir una codificación de caracteres ASCII derivada es ingenuo pensar que todas las letras minúsculas están en el rango az ...
AProgrammer

1
"% [^ \ n]" tiene el mismo problema que gets (), desbordamiento de búfer. Con la captura adicional de que el \ n final no se lee; esto estará oculto por el hecho de que la mayoría de los formatos comienzan saltando espacios en blanco, pero [no es uno de ellos. No entiendo la instancia al usar scanf para leer cadenas.
AProgrammer

1
Se eliminó sdel final de la cadena de entrada, ya que es a la vez superfluo e incorrecto en ciertos casos (como se señaló en comentarios anteriores). [es su propio especificador de formato en lugar de alguna variación del smismo.
paxdiablo

54

Este ejemplo utiliza un conjunto de escaneo invertido, por lo que scanf sigue tomando valores hasta que encuentra un '\ n' - nueva línea, para que los espacios también se guarden

#include <stdio.h>

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

1
Cuidado con desbordamientos de búfer. Si el usuario escribe un "nombre" con 50 caracteres, el programa probablemente se bloqueará.
brunoais

3
Como sabe el tamaño del búfer, puede usarlo %20[^\n]spara evitar el desbordamiento del búfer
osvein

45 puntos y nadie señaló la obvia carga de culto de tener sallí!
Antti Haapala

22

Puedes usar esto

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

O esto

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

MANIFESTACIÓN


1
No probé, pero en base a otras respuestas en esta misma página, creo que el tamaño de búfer correcto para scanf en su ejemplo sería: scanf("%19[^\n]", name);(todavía +1 para la respuesta concisa)
Dr Beco

1
Como una nota al margen, sizeof(char)es por definición siempre 1, por lo que no hay necesidad de multiplicarlo.
paxdiablo

8

No lo use scanf()para leer cadenas sin especificar un ancho de campo. También debe verificar los valores de retorno para errores:

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

Alternativamente, use 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);
}

6

Puede usar la fgets()función para leer una cadena o usarla scanf("%[^\n]s",name);para que la lectura de la cadena termine al encontrar un carácter de nueva línea.



sno pertenece allí
Antti Haapala

5

getline()

Ahora parte de POSIX, no obstante.

También se ocupa del problema de asignación de búfer sobre el que preguntó anteriormente, aunque debe ocuparse de freela memoria.


¿Estándar? En la referencia que cita: "tanto getline () como getdelim () son extensiones GNU".
AProgrammer

1
POSIX 2008 agrega getline. Entonces GNU avanzó y cambió sus encabezados para glibc alrededor de la versión 2.9, y está causando problemas para muchos proyectos. No es un enlace definitivo, pero mira aquí: bugzilla.redhat.com/show_bug.cgi?id=493941 . En cuanto a la página de manual en línea, tomé el primero que Google encontró.
dmckee --- ex-gatito moderador

3

Si alguien todavía está buscando, esto es lo que funcionó para mí: leer una longitud arbitraria de cadena que incluye espacios.

Gracias a muchos carteles en la web por compartir esta solución simple y elegante. Si funciona, el crédito recae en ellos, pero cualquier error es mío.

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

2
Vale la pena señalar que esta es una extensión POSIX y no existe en el estándar ISO. Para completar, probablemente también debería verificar errnoy limpiar la memoria asignada.
paxdiablo

sno pertenece allí después del scanset
Antti Haapala

1

Puede utilizar scanfpara este propósito con un pequeño truco. En realidad, debe permitir la entrada del usuario hasta que el usuario presione Enter ( \n). Esto considerará todos los personajes, incluido el espacio . Aquí hay un ejemplo:

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

¿Cómo funciona esto? Cuando el usuario ingresa caracteres de la entrada estándar, se almacenarán en una variable de cadena hasta el primer espacio en blanco. Después de eso, el resto de la entrada permanecerá en la secuencia de entrada y esperará el próximo scanf. A continuación, tenemos un forbucle que toma char por char del flujo de entrada (till \n) y los une al final de la variable de cadena , formando así una cadena completa igual que la entrada del usuario desde el teclado.

¡Espero que esto ayude a alguien!


Sujeto a desbordamiento de búfer.
paxdiablo

0

Si bien no deberías usarlo scanf()para este tipo de cosas, porque hay llamadas mucho mejores como gets()o getline(), se puede hacer:

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

2
Hay una razón por la cual getsfue desaprobado y eliminado ( stackoverflow.com/questions/30890696/why-gets-is-deprecated ) del estándar. Es incluso peor que scanfporque al menos el último tiene maneras de hacer más segura.
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;
 }

44
¿Puedes explicar por qué esta es la respuesta correcta? No publique respuestas de solo código.
Theo

sno pertenece allí después del scanset
Antti Haapala
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.