¿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?

Kredns
fuente
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;
}
paxdiablo
fuente
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.

Kelly Gendron
fuente
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;
}
SVA
fuente
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

Vitim.us
fuente
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);
}
Christoph
fuente
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.

Anshul garg
fuente
Tenga
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.

dmckee --- gatito ex moderador
fuente
¿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);
Siempre aprendiendo
fuente
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!

akelec
fuente
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;
}
Ed Zavada
fuente
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;
 }
venkata sandeep
fuente
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