Почему scanf принимает символы для спецификатора формата "% f"? - PullRequest
0 голосов
/ 11 ноября 2018

Я готовлю простую программу для своих занятий и обнаружил, что scanf не так строг, как я ожидал:

int main()
{
  // Define variables
  float alpha;
  float b;
  float result;

  // Get user input
  printf("Type alpha and b. Space separated float numbers only.\n");
  scanf("%f %f", &alpha, &b);
  printf("%f %f\n", alpha, b);
  return 0;
}

Возвращает:

Type alpha and b. Space separated float numbers only.
a 0
-29768832.000000 0.000000

Где я могу прочитать о его поведении?

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

P. S. Я использую gcc v5 в Ubuntu.

Ответы [ 4 ]

0 голосов
/ 11 ноября 2018

Как @ Джон Боллинджер ( ответ ) и @ chux ( ответ ) любезно объяснил вопрос сам по себе, на самом деле scanf не принимает символы вместо плавающих, но опускает их. Чтобы поймать это поведение, я изменил код следующим образом:

+ #include <errno.h> // Error codes constants

int main()
{
  // Define variables
  float alpha;
  float b;
+ int matches;
  float result;

  // Get user input
  printf("Type alpha and b. Space separated float numbers only.\n");
± matches = scanf("%f %f", &alpha, &b);

+ if (matches != 2) {
+   printf("ERROR: Space separated float numbers only expected.\n");
+   return EINVAL;
+ }

  printf("%f %f\n", alpha, b);
  return 0;
}

Подходит только для простых программ обучения, которые вы пишете во время обучения в CS!

В противном случае проверьте ответ @ chux's для более производительного решения.

0 голосов
/ 11 ноября 2018

Я готовлю простую программу для своих занятий и обнаружил, что scanf не так строго, как я ожидал

Нет, вы не сделали.

Учитывая это scanf вызов:

  float alpha;
  float b;

  scanf("%f %f", &alpha, &b);

... и этот вход:

a 0

... scanf имеет сбой сопоставления, когда он встречает 'a' при попытке сканирования значения с плавающей запятой и еще не отсканировал никаких десятичных цифр. Он останавливается в этой точке (и оставляет «a» в потоке для последующего чтения) и, как всегда, возвращает количество успешно отсканированных полей ввода (0).

Где я могу прочитать о его поведении?

Вы можете попробовать запустить команду man scanf в командной строке (Linux, OSX, ...) или ввести то же самое, что и поиск Google. Или гуглите "scanf docs" или "scanf manual" или подобное. Или посмотрите официальное руководство для вашей конкретной библиотеки C. Для стандартных библиотечных функций стандарт является отличным справочником, но ваша конкретная реализация может иметь расширения, не задокументированные там, или иногда может даже незначительно отличаться от стандарта.

Как я могу сделать ввод строгим со стандартными функциями и бросить исключение, если не было ни целых, ни чисел?

C не имеет исключений. Он сообщает вам об ошибках через возвращаемые значения функции, по большей части, хотя некоторые сторонние библиотеки могут делать это немного по-другому. Но вы должны сделать свою часть, проверив возвращаемые значения и ответив соответствующим образом. В этом конкретном случае вы должны избегать чтения значений alpha и b. Поскольку эти переменные не были инициализированы и им впоследствии не были присвоены какие-либо значения, чтение их значений приводит к неопределенному поведению .

0 голосов
/ 11 ноября 2018

Ответы других, особенно @ John Bollinger , хорошо объясняют, почему код не соответствует цели.

Примечание: вход может преобразовывать в int, float, ни то, ни другое, или оба.

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

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

Проверить строго довольно сложно. *scanf("%d",...) имеет неопределенное поведение при переполнении. *scanf("%f",...) имеет аналогичный UB, особенно когда float не поддерживает бесконечность / не число.


Чтобы проверить, преобразована ли строка в допустимое значение int

bool my_isint(const char *s) {
  // Add to dis-allow leading white-space
  if (isspace((unsigned char) *s)) return false;

  char *endptr; 
  base = 0; // use base = 0 to only allow base 10 input
  errno = 0;
  long val = strtol(s, &endptr, base); 
  if (s == endptr) return false; // no conversion

  if (errno == ERANGE) return false; // too big for long
  if (val > INT_MAX || val < INT_MIN) return false; // too big for int

  // Add to allow trailing white-space
  while (isspace((unsigned char) *endptr)) endptr++;

  // Add to dis-allow trailing junk
  while (*endptr) return false; // trailing junk

  return true;
}

Проверка того, преобразуется ли строка в допустимое значение float, похоже на проверку double, когда float поддерживает бесконечность / не-число. Еще позже - ГТГ.

0 голосов
/ 11 ноября 2018

fgets может использоваться для чтения строки.Разобрать строку с sscanf.sscanf вернет количество успешно отсканированных элементов.Это также захватывает характер после второго плавания.Если это не новая строка, цикл продолжается до тех пор, пока не будут отсканированы два числа с плавающей запятой, за которыми следует новая строка.

#include <stdio.h>
#include <stdlib.h>

int main( void) {
    char input[200] = "";
    char nl = 0;
    int items = 3;
    float alpha;
    float b;
    float result;

    do {
        printf("Type alpha and b. Space separated float numbers only.\n");
        if ( fgets ( input, sizeof input, stdin)) {
            // Get user input
            items = sscanf ( input, "%f %f%c", &alpha, &b, &nl);
        }
        else {
            fprintf ( stderr, "fgets EOF\n");
            return 0;
        }
    } while ( 3 != items || '\n' != nl);
    printf("%f %f\n", alpha, b);
    return 0;
}
...