Введите Quit, чтобы завершить работу с программой на C

#c #string

#c #строка

Вопрос:

Мне было интересно, в чем проблема с моей программой. Я не могу заставить программу завершиться, когда я набираю quit. Вот что у меня есть:

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

int main(void) {

    char string[200];

    printf("Enter a bunch of words: ");
    do
    {
        scanf("%[^n]c", string);


    }while(strcmp(string,"quit")!=0);

    return 0;
}
  

Комментарии:

1. Требуются ли от вас спецификации к упражнению, которое вы выполняете для использования scanf ? Если нет, я бы порекомендовал другой метод.

2. @GovindParmar да, я должен использовать scanf, мы не можем использовать ничего, чему еще не научились.

3. @UnholySheep нет, это не так. это формат сканирования строки, которая состоит из любых символов, кроме новой строки, за которой следует литерал c .

4. Почему вы используете scanf("%[^n]c", string); вместо fgets (string, sizeof string, stdin) ? (и затем strncmp (string, "quit", 4); )

5. @DavidC.Rankin Это часть школьного задания, и я не могу использовать ничего, чему еще не учили, поэтому мне приходится использовать scanf

Ответ №1:

Две ваши самые большие проблемы — это две наиболее распространенные проблемы, которые преследуют начинающих программистов на C из-за их использования scanf :

  1. Вы используете строку неправильного формата; и
  2. Вам не удается проверить возврат scanf .

Давайте сначала разберемся с основными вещами:

 scanf("%[^n]c", string);
  

В вашей строке формата "%[^n]c" используется спецификатор формата символьного класса "%[...]" для чтения текста string . Затем следует "c" — который будет соответствовать только литералу 'c' в конце вашей входной строки. Это не может произойти так, как написано, потому что "%[^n]" будут прочитаны все символы, которые не являются the, 'n' оставляя только 'n' для чтения — который НЕ соответствует 'c' .

Кроме того, "%[...]" спецификатор вместе с "%c" спецификатором НЕ используют начальный пробел ( 'n' являющийся пробелом). Таким образом, оставив 'n' непрочитанным в stdin вашем следующем вызове scanf сбой, потому что "%[^n]" не будет прочитан 'n' , и он не совпадает 'c' , что приводит к ошибке сопоставления, 'n' остается непрочитанным в stdin , и ситуация быстро выходит из-под контроля.

Чтобы решить все проблемы, вам нужно помнить (2.) вышеизложенное, а также использовать модификатор ширины поля для защиты границ массива string , и затем вы должны прочитать и сохранить символы, следующие за извлеченными и введенными string , чтобы подтвердить, что была прочитана полная строка ввода — и если нет, то вы несете ответственность за удаление всех лишних символов, которые остаются в stdin перед попыткой следующего чтения.

Для начала вы можете использовать строку формата с надлежащим ограничением, которая включает в себя space в начале, что приведет scanf к удалению всех начальных пробелов, например

     " 9[^n]%c"
  

Обратите внимание, что конечный символ будет сохранен, будут выполнены два преобразования, поэтому вам понадобится символьная переменная для обработки результата спецификатора окончательного преобразования, например

     do {
        char c;     /* final character read */
        int retn;   /* variable to save scanf return */
        /* prompt */
        fputs ("Enter a bunch of words ('quit' exits): ", stdout);
        /* read saving scanf return */
        retn = scanf (" 9[^n]%c", string, amp;c);
  

(примечание: приглашение было перемещено внутри do {...} while (..); цикла)

Далее вы несете ответственность за проверку возврата scanf каждый раз. Вы должны выполнить три условия

  1. (return == EOF) пользователь отменяет ввод, генерируя вручную EOF нажатием Ctrl d (или в Windows Ctrl z);
  2. (return < expected No. of conversions) вы должны обработать ошибку сопоставления или ввода и вы должны учитывать каждый символ, который может остаться в вашем буфере ввода. (обычно вы будете сканировать вперед во входном буфере до тех пор, пока не будет найдено 'n' или EOF , отбрасывая все оставшиеся посторонние символы, см. empty_stdin() Функцию в примере); и
  3. (return == expected No. of conversions) указывает на успешное чтение — затем вы должны проверить, соответствует ли ввод каким-либо дополнительным критериям (например, положительное целое число, положительная плавающая точка и т.д.).

В целом, вы могли бы обрабатывать чтение цикла с помощью scanf и поиска "quit" в качестве ключевого слова, запрашивающего выход, следующим образом:

     do {
        char c;     /* final character read */
        int retn;   /* variable to save scanf return */
        /* prompt */
        fputs ("Enter a bunch of words ('quit' exits): ", stdout);
        /* read saving scanf return */
        retn = scanf (" 9[^n]%c", string, amp;c);
        if (retn == EOF) {      /* check the return against EOF */
            fputs ("(user canceled input)n", stderr);
            return 0;
        }
        else if (retn < 2) {    /* checking both string and c read */
            fputs ("input failure.n", stderr);
            empty_stdin();
        }
        else if (c != 'n') {   /* check c is 'n', else string too long */
            fprintf (stderr, "warning: input exceeds %d characters.n",
                    MAXC - 1);
            empty_stdin();
        }
        else    /* good input, output string */
            printf ("string: %sn", string);

    } while (strcmp (string,"quit") != 0);
  

Наконец, НЕ используйте магические числа в своем коде ( 200 это магическое число). Вместо этого, если вам нужна константа, #define используйте одну (или несколько). Единственное место, где вы должны жестко указывать числа, — это, например, модификатор scanf ширины поля, который не может использовать переменную, макрос или именованную константу. Это одно из исключений из правила. Аналогично, НЕ вводите жестко имена файлов или пути. Все функции принимают аргументы, даже main() , передают необходимую информацию в вашу программу.

В целом, вы могли бы сделать что-то вроде:

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

#define MAXC 200    /* constant - maximum characters in string */ 

void empty_stdin (void)
{
    int c = getchar();
    while (c != EOF amp;amp; c != 'n')
        c = getchar();
}

int main (void) {

    char string[MAXC];    /* use constants for array bounds */

    do {
        char c;     /* final character read */
        int retn;   /* variable to save scanf return */
        /* prompt */
        fputs ("Enter a bunch of words ('quit' exits): ", stdout);
        /* read saving scanf return */
        retn = scanf (" 9[^n]%c", string, amp;c);
        if (retn == EOF) {      /* check the return against EOF */
            fputs ("(user canceled input)n", stderr);
            return 0;
        }
        else if (retn < 2) {    /* checking both string and c read */
            fputs ("input failure.n", stderr);
            empty_stdin();
        }
        else if (c != 'n') {   /* check c is 'n', else string too long */
            fprintf (stderr, "warning: input exceeds %d characters.n",
                    MAXC - 1);
            empty_stdin();
        }
        else    /* good input, output string */
            printf ("string: %sn", string);

    } while (strcmp (string,"quit") != 0);

    return 0;
}
  

Пример использования / вывода

 $ ./bin/scanf_string_quit
Enter a bunch of words ('quit' exits): Hello
string: Hello
Enter a bunch of words ('quit' exits): My dog has fleas and my cat has none.
string: My dog has fleas and my cat has none.
Enter a bunch of words ('quit' exits): quit
string: quit
  

Создание руководства EOF с Ctrl d помощью Ctrl z (или, возможно, на windoze):

 $ ./bin/scanf_string_quit
Enter a bunch of words ('quit' exits): Hello
string: Hello
Enter a bunch of words ('quit' exits): (user canceled input)
  

Сброс значения MAXC на 20 и модификатора ширины поля на scanf на 19 можно проверить обработку слишком длинных строк, например, первый ввод подходит, второй слишком длинный:

 $ ./bin/scanf_string_quit
Enter a bunch of words ('quit' exits): my dog has fleas and my cat has none.
warning: input exceeds 19 characters.
Enter a bunch of words ('quit' exits): 1234567890123456789
string: 1234567890123456789
Enter a bunch of words ('quit' exits): 12345678901234567890
warning: input exceeds 19 characters.
Enter a bunch of words ('quit' exits): quit
string: quit
  

Просмотрите все и дайте мне знать, если у вас возникнут дополнительные вопросы.

Комментарии:

1. Вау! Большое вам спасибо, я действительно ценю все детали. Я последовал совету от вас и другого пользователя, и моя программа завершается, как только я набираю команду quit. Однако это привело к новой проблеме, поскольку я не описал назначение моей программы, чтобы вы, ребята, ответили на то, что, как вы предполагали, хотела моя программа, я собираюсь опубликовать новый вопрос, который более подробно объясняет, что я хочу сделать, как только я завершу работу программы.

Ответ №2:

Приемлемо ли что-то подобное?

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

int main(void) {

    char string[200] = {0};

    printf("Enter a bunch of words: ");
    do {
        memset(string, 0, 200);
        scanf("%s", string);
    } while (strcmp(string, "quit") != 0);

    return 0;
}
  

Вы не объяснили точно, что вы собираетесь делать со string, поэтому трудно дать ответ. Однако следует отметить одну вещь: вам нужно либо что-то сделать с string (я только что обнулил это здесь), чтобы strcmp распознать «quit», либо сканировать подстроки string , потому что если все всегда добавляется, то вашей строкой будет «(…)quit», которую strcmp не распознает как «quit».

В качестве дополнительного примечания всегда инициализируйте свои массивы, иначе могут произойти плохие вещи.

Комментарии:

1. Я не могу использовать memset, я еще не изучал это на занятиях :/. НО я инициализировал массив, как вы предложили, и он работал без memset. Большое вам спасибо!

2. @anon Вы также можете просто установить для каждого индекса в string значение 0, используя for цикл, но задача, вероятно, требует большего, чем просто это. Рад, что это было полезно.

3. @Joshua Я всегда был неравнодушен к человеку с целочисленным переполнением, он был моим любимым супергероем. Это правда, что это создает такую опасность, но аноним на самом деле не определил границы поставленной перед ним задачи, поэтому я просто пытался дать самое простое предложение о чем-то, что могло бы решить проблему, с которой он, похоже, столкнулся. Безопасность памяти, похоже, не была частью этого.

Ответ №3:

Учитывая предоставленное вами скудное объяснение, наиболее простым изменением может быть изменение вашей строки сканирования, чтобы она проглатывала n вместо теоретического c , что на самом деле невозможно:

         scanf("%[^n]n", string);
  

Чтобы предотвратить переполнение буфера, вы должны указать, сколько места в вашем буфере должно храниться для ввода:

         scanf("9[^n]n", string);
  

Это безопасно, если вы знаете, что ввод никогда не превысит 199 символов. Но это слабое предположение как в теории, так и на практике. Символы после 199 сохраненных будут отсканированы как ваше следующее слово на следующей итерации, что позволит вашей программе неожиданно завершать работу, если за введенными данными следуют 199 . с, за которыми следует слово quit . Вам нужен более надежный способ просмотреть оставшуюся часть строки и отбросить ее перед чтением следующей строки.

У вас может возникнуть соблазн использовать другой %[...] для записи дополнительных символов, подобных этому:

         scanf("9[^n]%*[^n]n", string);
  

Однако это приведет к сбою в обычном случае, когда входные данные меньше или равны 199 символам. Это связано с тем, что scanf произойдет сбой, если преобразование приведет к пустым входным данным. Таким образом, n во входных данных останется застрявший.

Если вы ограничены использованием scanf , то вам нужно будет разделить сканирование на отдельные scanf вызовы, чтобы сканирование с ошибкой и без ошибок для остальной части строки можно было рассматривать как один и тот же результат, что приведет ко второму scanf проглатыванию самой новой строки.

         scanf("9[^n]%*[^n]", string);
        scanf("n");