fgets не работает после scanf

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

void delspace(char *str);

int main() {
    int i, loops;
    char s1[101], s2[101];

    scanf("%d", &loops);

    while (loops--) {
        fgets(s1, 101, stdin);
        fgets(s2, 101, stdin);
        s1[strlen(s1)] = '\0';
        s2[strlen(s2)] = '\0';

        if (s1[0] == '\n' && s2[0] == '\n') {
            printf("YES\n");
            continue;
        }

        delspace(s1);
        delspace(s2);

        for (i = 0; s1[i] != '\0'; i++)
            s1[i] = tolower(s1[i]);

        for (i = 0; s2[i] != '\0'; i++)
            s2[i] = tolower(s2[i]);

        if (strcmp(s1, s2) == 0) {
            printf("YES\n");
        }
        else {
            printf("NO\n");
        }
    }

    return 0;
}

void delspace(char* str) {
    int i = 0;
    int j = 0;
    char sTmp[strlen(str)];

    while (str[i++] != '\0') {
        if (str[i] != ' ') {
            sTmp[j++] = str[i];
        }
    }
    sTmp[j] = '\0';
    strcpy(str, sTmp);
}

После того, как я ввел «петли», «s1» автоматически была назначена пустая строка. Как это происходит? Я уверен, что моя клавиатура работает нормально.


person Vayn    schedule 06.05.2011    source источник
comment
Я уверен, что моя клавиатура работает нормально. Ржу не могу!   -  person orlp    schedule 07.05.2011
comment
Некоторые варианты этого вопроса, кажется, задают здесь каждый день или около того.   -  person Jim Balter    schedule 07.05.2011
comment
Функция delspace определенно неверна! Это преобразует abc\0 в bc\0.   -  person TrueY    schedule 18.02.2016
comment
Эмпирическое правило: для интерактивного ввода всегда читайте строки.   -  person Karoly Horvath    schedule 03.09.2016
comment
Связано: stackoverflow.com/questions/5240789/   -  person melpomene    schedule 01.10.2019


Ответы (9)


scanf() читает именно то, что вы просили, оставляя следующую \n с конца этой строки в буфере, где ее будет читать fgets(). Либо сделайте что-нибудь, чтобы использовать новую строку, либо (мое предпочтительное решение) fgets(), а затем sscanf() из этой строки.

person geekosaur    schedule 06.05.2011
comment
Если я хочу использовать scanf(), как я могу использовать новую строку? Спасибо. - person Vayn; 07.05.2011
comment
Используйте что-то вроде %*[^\n]%*c в конце формата, чтобы пропустить любые символы до новой строки, за которыми следует сама новая строка. - person geekosaur; 07.05.2011
comment
@geekosaur: Простое добавление %*[^\n]%*c в конец формата на самом деле не сработает, если НЕТ символов после предыдущего прочитанного и перед новой строкой, так как тогда %*[^\n] не совпадет, поэтому %*c будет пропущен, а новая строка останется на входе. Вам нужно сделать %*c в отдельном вызове scanf, чтобы заставить его работать. - person Chris Dodd; 16.05.2016

scanf оставляет пробелы во входном буфере, включая символы новой строки. Чтобы использовать fgets для чтения следующей строки, вам нужно вручную удалить оставшуюся часть текущей строки:

int c;
do{
    c = getchar();
}while(c != EOF && c != '\n');
person hugomg    schedule 07.05.2011
comment
Да, это тоже сработает, но лично я просто использую fgets, а затем sscanf (как предложил geekosaur), потому что он одинаково работает для всех видов данных... а не только для отдельных символов. И мне нравятся общие, многоцелевые решения. Ваше здоровье. Кейт. - person corlettk; 07.05.2011
comment
Это работает для большего, чем символ, хотя - для этого и нужен цикл;) - person hugomg; 07.05.2011
comment
while(c != EOF && c != '\n'); не годится. c выходит за рамки. - person chux - Reinstate Monica; 23.02.2018
comment
Я собирался извиниться, сказав, что я, должно быть, смешивал правила области видимости C и Lua, но, очевидно, этот ответ был получен до того, как я изучил Lua, хахаха. - person hugomg; 23.02.2018

Это более простое решение

scanf("%d",&loops);
while ((getchar()) != '\n'); //This will consume the '\n' char
//now you're free to use fgets
fgets(string,sizeof(string),stdin);
person Shubham Pawar    schedule 05.11.2019

Вайн,

Geekoaur хорошо ответил на ваш вопрос, я просто указываю на еще одну «проблему» с вашим кодом.

Строка s1[strlen(s1)] = '\0'; является no-op, если s1 уже корректно завершается нулем ДО того, как она будет выполнена.

Но если s1 НЕ уже правильно завершается нулем ДО того, как эта строка будет выполнена (и вам не повезло), это вызовет:

  • SIGSEGV в системе POSIX (*nix).
  • GPF в Windows.

Это связано с тем, что strlen в основном находит индекс существующего нулевого терминатора и возвращает его! Вот правильная, неоптимизированная реализация strlen:

int strlen(char *string) {
    int i = 0;
    while(string[i] != '\0') {
        ++i;
    }
    return i;
}

Итак... Если вы ДЕЙСТВИТЕЛЬНО беспокоитесь о том, что строки НЕ заканчиваются нулем, вы должны сделать что-то вроде:

  • string[sizeof(string)]='\0'; для локальных автоматических строк (где компилятор "знает" размер строки);
  • или string[SIZE_OF_STRING] для всех других строк, где SIZE_OF_STRING (чаще всего) представляет собой константу #define или переменную, которую вы поддерживаете специально для хранения текущего размера (а не длины) динамически выделяемой строки.

И если вы ДЕЙСТВИТЕЛЬНО, ДЕЙСТВИТЕЛЬНО, ДЕЙСТВИТЕЛЬНО беспокоитесь о том, что строки не заканчиваются нулем (например, вы имеете дело с «грязными» библиотечными методами (например, с ATMI Tuxedo), вы ТАКЖЕ «очищаете» свой «вернуть строки» перед передачей их подозрительным библиотечным методам с помощью:

  • до:memset(string, NULL, SIZE_OF_STRING);
  • вызвать: DirtyFunction(/*out*/string);
  • после: string[SIZE_OF_STRING]='\0'

Найти SIG11 очень сложно, потому что (если только вы не «подцепите» их с помощью signal-processor и сказать иначе, они приводят к тому, что Unix принудительно завершает вашу программу, поэтому вы не можете ничего записать (постфактум), чтобы выяснить, где, черт возьми, сделал-это-пришло-от... особенно учитывая, что во многих случаях строка кода, которая выдает SIG11, не является фактической причиной потери строки своего нулевого терминатора.

Это имеет для вас смысл?

Здоровья, приятель. Кейт.

PS: ПРЕДУПРЕЖДЕНИЕ: strncpy НЕ всегда завершается нулем... вы, вероятно, имели в виду strlcpy вместо этого. Я усвоил это на собственном горьком опыте... когда рухнул платеж на 60 миллионов долларов.


ИЗМЕНИТЬ:

К вашему сведению: вот «безопасная» (неоптимизированная) версия strlen, которую я назову strnlen (думаю, она должна быть в stdlib. Эх).

// retuns the length of the string (capped at size-1)
int strnlen(char *string, int size) {
    int i = 0;
    while( i<size && string[i]!='\0' ) {
        ++i;
    }
    return i;
}
person corlettk    schedule 07.05.2011
comment
Я новичок в C, и в учебнике мало говорится о строках и безопасности. Возможно, мой учебник недостаточно хорош :( Спасибо за профессиональный совет :) - person Vayn; 07.05.2011
comment
Не делайте string[sizeof(string)]='\0'! Это напишет \0 ПОСЛЕ выделенной памяти! sizeof(s1) равно 101, а индекс s1 изменяется от 0 до 100! Та же проблема возникает с strnlen. Если \0 нет, то он вернет size, а не size - 1, как указано в комментарии! - person TrueY; 18.02.2016

Я знаю, что это очень старо. Я новичок в c и хотел проверить свой метод, который использует getchar:

#include <stdio.h>

int main()
{

    printf("Please enter your name\n");
    char string[10];

    scanf("%s", string);
    printf("Hello %s\n", string);

    //getchar();  # un commenting this line, fgets perfectly works!!
    printf("Please enter your name again\n");

    fgets ( string, 10, stdin );     

    printf("Hello again %s", string);

    getchar();
}
person Michele    schedule 07.02.2014
comment
Попробуйте ввести "Michele \n" (пробел после имени) и посмотрите, работает ли код, не комментируя эту строку. - person chux - Reinstate Monica; 23.02.2018

просто поставь scanf("%d\n",&loops);

вместо scanf("%d",&loops);

person phantom_ab    schedule 09.09.2016
comment
Хотя этот ответ охватывает важное явление исходной проблемы, он не дает некоторого понимания самой проблемы. Таким образом, это в лучшем случае полезно для решения исходной проблемы и не очень полезно в долгосрочной перспективе. Пожалуйста, добавьте еще несколько объяснений, чтобы ваш ответ был полезен для последующих читателей, ищущих похожие проблемы. - person rpy; 12.09.2016

Другой способ игнорировать следующий символ новой строки (из-за нажатия ENTER) после сканирования целого числа в циклах меток переменных:

scanf ("%d%*c", &loops);

Где, согласно справочным страницам:

* Подавляет присвоение. Последующее преобразование происходит как обычно, но без использования указателя; результат преобразования просто отбрасывается.

Это очень маловероятно, но хорошая привычка проверять ошибки во время сканирования:

errno = 0
scanf ("%d%*c", &loops);
if (errno != 0) perror ("scanf");
// act accordingly to avoid un-necessary bug in the code

Например, в вашем коде циклы — это локальная неинициализированная переменная, содержащая мусор. Если scanf не может заполнить желаемое значение, следующий цикл while может выполняться произвольно.

person ChandanK    schedule 31.12.2019
comment
Это не то, как вы проверяете ошибки из scanf - person Antti Haapala; 03.02.2021

scanf() оставить следующий \n с конца этой строки в буфере, где fgets() будет его читать. Чтобы решить эту проблему, мы должны сделать что-то, чтобы использовать новую строку. Давайте посмотрим на проблему.

Проблема

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}
Output:
Enter in scanf: Hello
Enter in fgets:

Как видите, он не показывает часть fgets. Давайте посмотрим решение.

Поместите getchar() между scanf и fgets()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}
Output:
Enter in scanf: Hello
Enter in fgets: Hello

Если возможно, используйте scanf() после fgets()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    fgets(input, 10, stdin);
    getchar();
    printf("Enter in scanf: ");
    scanf("%s", input);
}
Output:
Enter in fgets: Hello
Enter in scanf: Hello

Поставьте fget() 2 раза (этот метод иногда не работает)

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
    fgets(input, 10, stdin)
}
Output:
Enter in scanf: Hello
Enter in fgets: Hello

Вставить sscanf() из scanf()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    sscanf(hello, "%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}
Output:
Enter in sscanf: Hello
Enter in fgets: Hello
person Community    schedule 03.03.2020
comment
Не могли бы вы уточнить это подробнее? Например. Предоставление примера. - person Elis Byberi; 03.03.2020

Следующее работает, если fgets() "пропускается" после использования scanf()

Сказав:

scanf("%d", &loops);

Сказать:

char garbage[100];

fgets(garbage,100,stdin);

Это сохранит все, что осталось во входном буфере, в переменную мусора.

Это эффективно очистит входной буфер и позволит вам впоследствии использовать fgets().

РЕДАКТИРОВАТЬ: Недавно я узнал, что есть более простое решение, чем приведенное выше. Если вы скажете getchar() после scanf(), это позволит вам использовать fgets() без проблем. getchar() получит следующий символ из входного буфера, в данном случае это будет '\n'. Как только вы удалите '\n' из входного буфера, fgets должен работать нормально.

person Coder Typist    schedule 23.02.2018