Как сделать так, чтобы scanf опционально игнорировал один из его спецификаторов преобразования? - PullRequest
2 голосов
/ 04 марта 2020
char value1[10];
int value2;
int value3 = 0;
if (!scanf("%s %d %d", &value1[0], &value2, &value3)) {
    scanf("%s %d", &value1[0], &value2);
}
;

Я пытаюсь использовать scanf для вставки 3 значений, но если получено 2, вместо него будет 2. Я не могу найти ответ, как использовать scanf. Я пытался использовать fgets, но значение в stdin остается.

Ответы [ 4 ]

3 голосов
/ 04 марта 2020
char value1[10]; int value2; int value3 = 0;
if (!scanf("%s %d %d", &value1[0], &value2, &value3)) { scanf("%s %d", &value1[0], &value2); } ;

Одна крошечная вещь: не используйте &value1[0], используйте value1. Массив превратится в указатель на свой первый элемент, и, условно, это value1, в котором вы хотите сохранить строку.

Во-вторых, вам нужно вызвать scanf только один раз и посмотреть на его возвращаемое значение , Это скажет вам, сколько значений было прочитано. В этом случае вам все равно, поэтому вы можете использовать:

if (scanf("%s %d %d", value1, &value2, &value3) < 2)
    { /* error handling */ }
else // we read in 2 or 3 entries, life is good
    { /* success handling */ }
1 голос
/ 04 марта 2020
scanf("%s %d %d", &value1[0], &value2, &value3)

Проблема в scanf(). Нельзя игнорировать третий спецификатор преобразования. Он по-прежнему пытается перехватить десятичный ввод для третьего аргумента, value3.

. Чтобы сначала перехватить весь ввод в виде строки, а затем разделить содержимое этой строки на каждый собственный объект, может быть лучшей альтернативой ,

fgets() на несколько безопаснее, чем scanf() при вводе пользователем, поэтому я буду использовать fgets().

char* fgets ( char * str, int num, FILE * stream );

С fgets() вам нужно определить сумму символов для чтения (num), что является отличной возможностью для обеспечения безопасности, но в этом случае мы не знаем, что пользователь потенциально может ввести как целое число для десятичных запросов ввода. Если мы укажем меньшее количество символов для цифр, остальные цифры останутся в stdin.

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

Это будут 10 цифр для общего числа «2 147 483 647» на 64-разрядных архитектурах или 5 цифр для общего числа «32 767» на 32-разрядных архитектурах. Я go с 64-битным регистром на данный момент.

Итак 10 (значение1) + 10 (значение2) + 10 (значение3) + 2 пробелы между значением1 и значением2 и между значением 2 и значением 3 + завершающий \0 для строки = 33 символов. Обратите внимание, что fgets() также читает введенную новую строку \n (но мы позже легко ее исключаем из строки), поэтому нам нужен еще один символ; всего 34 символов:

char buffer[34];
fgets(buffer,sizeof(buffer),stdin);

После этого нам нужно доказать, сколько у нас содержимого в строке, хранящейся в buffer. Мы можем это неявно доказать, посчитав пробелы:

unsigned int mark;

for(unsigned int i = 0; i < (sizeof(buffer)/sizeof(buffer[0]) - 1)); i++)
{
   if(buffer[i] == ' ')
     mark++;
}

После этого нам нужно перенести / преобразовать отдельные фрагменты содержимого в строке в buffer в соответствующие собственные объекты, используя sscanf(). Если у нас теперь есть 2 пробела в строке, мы можем использовать 3 спецификатора преобразования в команде sscanf, в противном случае у нас есть соответствующие sscanf() s:

if(mark == 2)
{
    sscanf(buffer,"%s %d %d", value1, &value2, &value3);
}
   else if(mark == 1)
   {
      sscanf(buffer,"%s %d", value1, &value2);
   }
      else if(mark == 0)
      {
         sscanf(buffer,"%s", value1);
      }
         else
         {
            printf("The input entered is not valid!\n");
            printf("Please try again!\n");
         }

Весь код будет тогда:

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

int main()
{
    char value1[10];
    int value2;
    int value3;
    unsigned int mark;
    char buffer[34];

    for(;;)
    {
       fgets(buffer,sizeof(buffer),stdin);
       buffer[strcspn(buffer, "\n")] = 0;

       mark = 0;

       for(unsigned int i = 0; i < ((sizeof(buffer)/sizeof(buffer[0]) - 1)); i++)
       {
          if(buffer[i] == ' ')
          mark++;
       }

       if(mark == 2)
       {
          sscanf(buffer,"%s %d %d", value1, &value2, &value3);
          break;
       }
          else if(mark == 1)
          {
             sscanf(buffer,"%s %d", value1, &value2);
             break;
          }
             else if(mark == 0)
             {
                sscanf(buffer,"%s", value1);
                break;
             }
                else
                {
                   printf("The input entered is not valid!\n");
                   printf("Please try again!\n");
                }
    }

    printf("value1 = %s\n", value1);

    if(mark == 1 || mark == 2)
    printf("value2 = %d\n", value2);

    if(mark == 2)
    printf("value3 = %d\n", value3);

    return 0;
}
1 голос
/ 04 марта 2020

Вы не можете scanf() более одного раза. При выполнении второго scanf() данные больше не находятся во входном буфере.

char value1[10];
int value2, value3;
int n = scanf("%9s%d%d", value1, &value2, &value3);
// scanf() returns the number of assignments it made, or EOF in case of failure
switch (n) {
    default: fprintf(stderr, "scanf failure\n"); exit(EXIT_FAILURE);
    case 0: strcpy(value1, "default"); /* fall-through */
    case 1: value2 = 42; /* fall-through */
    case 2: value3 = -1; /* fall-through */
    case 3: break;
}
0 голосов
/ 04 марта 2020

Для гибкости в разборе ввода, предпочитайте fgets() для выборки строк ввода и sscanf() для анализа этого ввода. Это позволяет при необходимости повторять сканирование на одном и том же входе.

Для использования fgets() необходим буфер ввода. Функция fgets() принимает аргумент, описывающий размер этого входного буфера, и считывает не более одного символа меньше из входного потока. В случае, если пользовательский ввод превышает этот предел, дополнительный поток остается во входном потоке. Это может вызвать проблемы для последующих операций ввода-вывода, поэтому надежный код должен обрабатывать эти дополнительные символы (считывая и используя или отбрасывая их). Более простой код, который не нуждается в надежности, может просто выбрать буфер размера, достаточно большой, который вряд ли будет перегружен доверенными пользователями. Приведенный ниже код выбирает размер буфера BUFSIZE равным 1024, что должно быть достаточно для дополнительного места, даже если пользователь случайно вводит слишком много символов в конце ввода. Также обратите внимание, что при наличии места для этого символ \n, обозначающий конец строки, считывается из входного потока и сохраняется во входном буфере. Эту новую строку часто нужно удалять из буфера, но в случаях, связанных с sscanf(), часто никаких дополнительных действий не требуется, поскольку новая строка не должна мешать сканированию.

Функция sscanf() (фактически все fscanf() семейные функции) отвечают на директиву %n, которая записывает количество символов, считанных с ввода до появления этой директивы, в предоставленную целочисленную переменную. Для sscanf() это фактически означает, что когда в строке формата встречается %n, индекс символа, следующего за последним прочитанным символом, сохраняется в этой предоставленной целочисленной переменной.

В приведенном ниже коде используется это %n средство, сначала сделав num_assigned = sscanf(buffer, "%9s %d%n", value1, &value2, &scan_loc). Здесь сканируются первые два значения, которые всегда ожидаются, а количество символов, считанных до этой точки, сохраняется в scan_loc. Переменная num_assigned содержит количество назначений, сделанных sscanf(), которое должно быть 2. Если это не так, то программа завершает работу с сообщением об ошибке; в противном случае код продолжается, пытаясь проанализировать остальную часть входной строки. Здесь buffer указывает на первый символ массива buffer[], а buffer + scan_loc указывает на символ , следующий за последним символом, прочитанным sscanf(). То есть второй вызов на sscanf() начинается там, где закончился первый вызов. Если этот второй вызов успешно делает присвоение value3, то num_assigned увеличивается.

Обратите внимание, что sscanf() может возвращать 0 или -1, если назначение не выполнено, в зависимости от обстоятельств. По этой причине код, подобный следующему: num_assigned += sscanf(buffer + scan_loc, "%d", &value3) не годится, поскольку невыполнение назначения может привести к уменьшению num_assigned. Также обратите внимание, что fgets() не подвержен проблемам переполнения буфера, если указан правильный размер входного буфера. Если buffer является массивом, тогда sizeof buffer всегда будет обеспечивать правильный размер (если бы вместо этого buffer был указателем, размер должен был бы быть получен другим способом). Но sscanf() все еще подвержен переполнению буфера при использовании директивы %s. Всегда указывайте максимальную ширину поля при использовании %s (или, в этом случае, директивы scanset %[]), чтобы избежать переполнения буфера, когда ввод больше ожидаемого. Терминатор \0 - это всегда , записываемый sscanf() с директивой %s, поэтому для использования пространства %9s используется массив типа char value1[10] с емкостью для 10 символов.

#include <stdio.h>
#include <stdlib.h>        // for exit() and EXIT_FAILURE

#define BUFSIZE  1024

int main(void) {
    char value1[10];
    int value2 = 0;
    int value3 = 0;

    char buffer[BUFSIZE];  // generous user input buffer
    int scan_loc = 0;      // scan location in buffer
    int num_assigned = 0;  // number of values assigned by sscanf()

    // fetch user input: parse only if fgets is successful
    if (fgets(buffer, sizeof buffer, stdin) != NULL) {

        // attempt to make first two assignments
        num_assigned = sscanf(buffer, "%9s %d%n", value1, &value2, &scan_loc);
        if (num_assigned == 2) {

            // success: attempt to make third assignment
            int final_assignment = sscanf(buffer + scan_loc, "%d", &value3);
            if (final_assignment == 1) {
                ++num_assigned;
            }
        } else {
            fprintf(stderr, "Input error: %d values read\n", num_assigned);
            exit(EXIT_FAILURE);
        }
        printf("value1: %s, value2: %d", value1, value2);
        if (num_assigned == 3) {
            printf(", value3: %d\n", value3);
        } else {
            putchar('\n');
        }
    }

    return 0;
}

Образцы казней:

$ ./a.out 
test2 42
value1: test2, value2: 42
$ ./a.out 
test3 12 34
value1: test3, value2: 12, value3: 34
...