Як ви дозволяєте вводити пробіли за допомогою scanf?


129

Використовуючи наступний код:

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

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

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

Користувач може ввести своє ім'я, але коли він вводить ім'я з пробілом Lucas Aardvark , scanf()просто відсікає все після Lucas. Як зробити scanf()пропуски


9
Зауважте, що більш ідіоматичним є "malloc (sizeof (char) * 256 + 1)", або "malloc (256 + 1)", а ще краще (припускаючи, що "name" буде використовуватися суворо локально) "char name [256 + 1 ] '. '+1' може діяти як мнемонічний для нульового термінатора, який потрібно включити у розподіл.
Баррі Келлі

@Barry - підозрюю, sizeof(char) + 256був друкарський помилок .
Кріс Лутц

Відповіді:


186

Люди (і особливо початківці) ніколи не повинні вживати scanf("%s")абоgets() будь-які інші функції, які не мають захисту від переповнення буфера, якщо ви точно не знаєте, що вхід завжди буде мати певний формат (а можливо навіть і тоді).

Пам’ятайте, ніж scanfозначає «сканувати відформатований», і є цінним трохи менше форматованих даних, ніж введені користувачем дані. Ідеально, якщо ви повністю контролюєте формат вхідних даних, але, як правило, непридатні для введення користувача.

Використовуйте fgets()(який має захист від переповнення буфера), щоб отримати свій вхід у рядок та sscanf()оцінити його. Оскільки ви просто хочете, що користувач ввів без розбору, sscanf()у цьому випадку вам дійсно не потрібно :

#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
Я про це не знав fgets(). Це насправді виглядає простішим у використанні тоді scanf(). +1
Креднс

7
Якщо ви просто хочете отримати лінію від користувача, це простіше. Це також безпечніше, оскільки ви можете уникнути переповнення буфера. Сім'я Scanf дуже корисна для перетворення рядка в різні речі (наприклад, чотири символи та int, наприклад, з "% c% c% c% c% d"), але навіть тоді ви повинні використовувати fgets та sscanf, а не scanf, щоб уникнути можливості переповнення буфера.
paxdiablo

4
Ви можете розмістити максимальний розмір буфера у форматі scanf, ви просто не можете встановити обчислений час виконання, не будуючи формат під час виконання (не існує еквівалента * для printf, * є дійсним модифікатором для scanf з іншою поведінкою: придушення призначення ).
AProgrammer

Зауважимо також, що це scanfне визначене поведінка, якщо числове перетворення переповнюється ( N1570 7.21.6.2p10 , останнє речення, формулювання незмінне з C89), що означає, що жодна з scanfфункцій не може бути безпечно використана для цифрового перетворення ненадійного вводу.
zwol

@JonathanKomar та хто-небудь ще читав це в майбутньому: якщо ваш професор сказав вам, що вам доведеться використовувати scanfв завданні, вони зробили це неправильно, і ви можете сказати їм, що я це сказав, і якщо вони хочуть зі мною сперечатися , моя електронна адреса легко знайти з мого профілю.
zwol

124

Спробуйте

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

Сподіваюся, що це допомагає.


10
(1) Очевидно, щоб прийняти пробіли, вам потрібно поставити пробіл у класі символів. (2) Зауважте, що 10 - це максимальна кількість символів, які будуть прочитані, тому str має принаймні вказувати на буфер розміром 11. (3) Кінцевий s тут не є формативною директивою, але scanf намагатиметься тут точно відповідати. Ефект буде помітний у записі типу 1234567890, де s будете споживати, але не ставити куди. Інший лист не буде використано. Якщо ви поставите інший формат після s, він буде прочитаний, лише якщо є s, який повинен відповідати.
AProgrammer

Ще одна потенційна проблема, використання - в іншому місці, ніж перше чи останнє, - це визначена реалізація. Зазвичай він використовується для діапазонів, але те, що позначає діапазон, залежить від діаграми. EBCDIC має отвори в діапазоні літер, і навіть якщо вважати, що похідні ASCII схеми, наївно думати, що всі малі літери знаходяться в діапазоні az ...
AProgrammer

1
"% [^ \ n]" має таку ж проблему, що і переповнення буфера get (). З додатковим уловом, що \ n фінал не читається; це буде приховано тим фактом, що більшість форматів починаються з пропускання білих пробілів, але [не один з них. Я не розумію екземпляр, коли використовується scanf для читання рядків.
AProgrammer

1
Вилучено sз кінця вхідного рядка, оскільки воно є і зайвим, і некоректним у певних випадках (як зазначено у попередніх коментарях). [це власний специфікатор формату, а не деякий варіант sодного.
paxdiablo

54

У цьому прикладі використовується перевернутий скансет, тому scanf продовжує приймати значення, поки не зустріне '\ n' - новий рядок, тому пробіли також будуть збережені

#include <stdio.h>

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

1
Обережно з переливами буфера. Якщо користувач запише "ім'я" з 50 символами, програма, ймовірно, вийде з ладу.
brunoais

3
Оскільки ви знаєте розмір буфера, ви можете використовувати його %20[^\n]sдля запобігання переповнення буфера
osvein

45 балів, і ніхто не вказав на очевидну вантажну вершину наявності sтам!
Антті

22

Ви можете використовувати це

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

Або це

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
Я не перевіряв, але грунтуючись на інших відповідях на цій самій сторінці, я вважаю, що правильним розміром буфера для scanf у вашому прикладі буде: scanf("%19[^\n]", name);(все-таки +1 для стислої відповіді)
Д-р Беко

1
Як бічна примітка, sizeof(char)за визначенням завжди 1, тому множити на неї не потрібно.
paxdiablo

8

Не використовуйте scanf()для читання рядків, не вказуючи ширину поля. Ви також повинні перевірити значення повернення на наявність помилок:

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

Крім того, використовуйте 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

Ви можете використовувати fgets()функцію для читання рядка або використовувати scanf("%[^\n]s",name);так, щоб читання рядків припинилося, коли зустрінеться символ нового рядка.



sне належить туди
Антті

5

getline()

Тепер частина POSIX - не менша.

Він також вирішує проблему розподілу буфера, про яку ви питали раніше, хоча вам слід подбати про freeвикористання пам'яті.


Стандартний? У посиланні ви цитуєте: "І getline (), і getdelim () - це розширення GNU."
AProgrammer

1
POSIX 2008 додає getline. Тож GNU випереджала та змінила заголовки на glibc біля версії 2.9, і це спричиняє проблеми для багатьох проектів. Не остаточне посилання, але дивіться тут: bugzilla.redhat.com/show_bug.cgi?id=493941 . Щодо сторінки он-лайн man, я схопив першу знайдену Google.
dmckee --- кошеня колишнього модератора

3

Якщо хтось ще дивиться, ось що для мене спрацювало - читати довільну довжину рядка, що включає пробіли.

Завдяки багатьом афішам в Інтернеті за те, що вони поділилися цим простим та елегантним рішенням. Якщо це працює, кредит їм належить, але помилки є моїми.

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

2
Варто зазначити, що це розширення POSIX і не існує в стандарті ISO. Для повноти, ймовірно, ви також повинні перевіряти errnoта очищати виділену пам'ять.
paxdiablo

sне належить туди після сканування
Антті

1

Ви можете використовувати scanfдля цієї мети з невеликою хитрістю. Насправді ви повинні дозволити користувачеві ввести, поки користувач не натисне Enter ( \n). Це врахує кожен персонаж, включаючи пробіл . Ось приклад:

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

Як це працює? Коли користувач вводить символи зі стандартного введення, вони зберігатимуться в рядковій змінній до першого порожнього пробілу. Після цього решта запису залишиться у вхідному потоці та чекати наступного сканування. Далі ми маємо forцикл, який бере char by char від вхідного потоку (till \n) і додає їх до кінця рядка змінної , утворюючи, таким чином, повний рядок, такий же, як користувальницькі дані з клавіатури.

Сподіваюся, що це комусь допоможе!


Підлягає переповненню буфера.
paxdiablo

0

Хоча ви дійсно не повинні використовувати scanf()для подібних речей, оскільки є набагато кращі дзвінки, такі як, gets()або getline()це можна зробити:

#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
Існує причина, чому getsїї застаріли та видалено ( stackoverflow.com/questions/30890696/why-gets-is-deprecated ) зі стандарту. Ще гірше , scanfтому що принаймні останні мають способи зробити це безпечним.
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;
 }

4
Чи можете ви пояснити, чому це правильна відповідь? Будь ласка, не публікуйте відповідей, що стосуються лише коду.
Тео

sне належить туди після сканування
Антті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.