Форум программистов, компьютерный форум, киберфорум
fasked
Войти
Регистрация
Восстановить пароль
Карта форума Блоги Сообщество Поиск Заказать работу  
Рейтинг: 5.00. Голосов: 16.

Несколько слов о чтении данных с консоли и языке Си

Запись от fasked размещена 08.04.2012 в 13:21
Обновил(-а) fasked 04.07.2012 в 18:30 (Обновил и дополнил текст)

Безопасный ввод данных на Си

Цель данной заметки - показать начинающим, как правильно получать данные, вводимые с консоли. Я довольно часто вижу, что студентам дают задания, в которые входит пункт о проверке ввода. Большинство способов, предлагаемых на форуме, не работают. Почему люди отдают не полностью работающее решение? Во-первых, потому что это Ваша задача. А во-вторых, потому что реализовать полностью безопасный ввод на Си - не совсем тривиальная задача.

Если Вам необходимо проверять вводимые пользователем данные на корректность, то эта заметка может оказаться полезной для Вас. Также по ходу мы разберем самые распространенные ошибки, связанные с темой заметки.

Что мы рассмотрим?
  1. Общие сведения о библиотеке ввода/вывода в Си
  2. Ввод целого числа, используя scanf
  3. Предварительное чтение в буфер
  4. Интерактивная консоль или "prompt"
  5. Пример безопасного чтения double

Когда-то я долго стучался головой об стену и клавиатуру, чтобы получить безопасный ввод данных. Теперь я хочу поделиться набитыми шишками.

1. Библиотека ввода/вывода Си

"Ввод и вывод" не являются частью самого языка Си. Тем не менее стандартная библиотека libc предоставляет достаточное количество средств для взаимодействия программы с внешней средой. Функции, предоставляемые библиотекой ввода и вывода, могут быть очень простыми, например getchar и putchar, и более сложными, такими как scanf и printf, включающими в себя еще один маленький язык для обозначения типов. И все они используют так называемые потоки ввода-вывода (input/output streams). Потоки это по сути просто последовательный набор байтов.

Цитата:
Не путать с многопоточностью.
Существует два англоязычных термина: и . Оба термина в русскоязычной литературе принято переводить одним словом "поток".
Тем не менее, несмотря на всю простоту идеи потоков, существует множество подводных камней при работе с ними. Некоторые из них мы и рассмотрим прямо сейчас.

2. Чтение данных с консоли, используя scanf

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

Итак, scanf принимает форматную строку и указатели на переменные. Именно указатели. Следовательно, чтобы прочитать число, достаточно написать такой код:

C
1
2
3
4
int size = 0;
 
printf("Введите размер: ")
scanf ("%d", &size);
Как я уже говорил выше, это нормально, если Вы пишете что-то, где проверка ввода данных не стоит основной задачей, что не страшно сломать. И данный код будет работать, но только до тех пор, пока пользователь на самом деле вводит числа. Что же будет, если пользователь введет букву или восклицательный знак? Давайте посмотрим.

C
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main() 
{
        int size = 0;
 
        printf("Введите размер: ");
        scanf ("%d", &size);
 
        printf("---> Вы ввели: %d\n", size);
        return 0;
}
И вот результат:

Код:
Введите размер: 25
---> Вы ввели: 25

Введите размер: hello
---> Вы ввели: 0
В простой программе можно было бы просто прекратить работу, в случае ошибки. Это наиболее простой выход из ситуации. Функция scanf возвращает количество элементов, которые ей удалось прочитать. В нашем случае мы читаем только один элемент %d, следовательно и сравнивать надо с единицей.

C
1
2
3
4
5
6
7
for (i = 0; i < size; ++i) {
        printf("> ");
        if (scanf ("%d", &data[i]) != 1) {
                printf("Упс, ошибочка вышла. До свидания!\n");
                exit(1);
        }
}
Однако мы стремимся к другому. Так мы поступить не можем. Мы хотим сообщить пользователю о возможной ошибке и предложить повторить ввод. Проблема в том, что слово "hello" остается в потоке. И ни одно следующее число не будет прочитано. Да-да, потому что "hello" так и будет там.

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

Логичным было бы попробовать убрать его из потока. Как это сделать? Нам поможет магия scanf!

C
1
2
3
4
5
6
7
8
9
for (i = 0; i < size; ++i) {
        printf("%d > ", i);
        if (scanf ("%d", &data[i]) != 1) {
                printf("Упс, ошибочка вышла. Пожалуйста, попробуйте еще раз.\n");
 
                scanf("%*[^\n]");
                --i;
        }
}
Разберем абракадабру %*[^\n] чуть более подробно. Символ * означает, что мы хотим прочитать переменную из потока, но не хотим ее куда-либо сохранять. Так можно поступать со всеми форматами. Например, %*d - будет значить, что мы пропускаем число из потока. Именно поэтому мы не передаем в scanf указатель. Символы же [^\n] означают, что мы хотим прочитать именно строку и именно до конца, до символа перевода строки. Аналогично, в квадратных скобках можно писать любые другие форматы: %[0-9] - будет читать только числа.
Таким образом мы выбрасываем из потока все, что было в нем до того, как пользователь нажал клавишу Enter. Уменьшим счетчик, чтобы пользователь попробовал повторить ввод того же самого значения.

Данный метод работает, однако, у него есть недостаток, основанный на специфике работы потоков:

Цитата:
Если ввести "45abc", то scanf успешно прочтёт 45, затем на следующей итерации ввода прочтёт "abc". Что есть не совсем логично с точки зрения пользователя, потому что ошибочный ввод данных A повлечёт за собой ошибочный ввод данных B без возможности что-либо сделать. Например, сначала просят ввести число, а затем строку. Введя "45abc", пользователь введёт сразу два поля данных.
Объяснение не совсем очевидно, поэтому я приведу код программы, которая сможет наглядно продемонстрировать ошибку.

Код:
#include <stdio.h>

int main()
{
        int age = 0;
        char name[32] = "";

        printf("Введите свой год рождения: ");
        if (scanf ("%d", &age) != 1) {
            printf("Что-то пошло не так.\n");
            return 1;
        }

        printf("Введите свое имя: ");
        if (scanf ("%s", name) != 1) {
            printf("Что-то пошло не так.");
            return 1;
        }

        printf("Вы ввели: %s %d год", name, age);
        return 0;
}
На предложение ввести год рождения пользователь пишет "1989 блаблабла". В потоке остается строка "блаблабла", которую считывает следующий вызов scanf - в результате вместо имени и получает "блаблабла".

Код:
Введите свой год рождения: 1989 блаблабла
Введите свое имя: Вы ввели: блаблабла 1989 год
Конечно, можно использовать конструкцию scanf("%*[^\n]") перед каждым чтением с консоли для очистки потока, однако на мой взгляд, программа вообще не должна пропускать подобные случаи.

С другой стороны мы можем отловить символ перевода строки:

C
1
2
3
4
5
6
7
8
9
10
for (i = 0; i < size; ++i) {
        char eol = 0;
        printf("%d > ", i);
        if (scanf ("%d%c", &data[i], &eol) != 2 || eol != '\n') {
                printf("Упс, ошибочка вышла. Пожалуйста, попробуйте еще раз.\n");
 
                scanf("%*[^\n]");
                --i;
        }
}
Весьма надежно, но чуть более странно. При таком подходе, пользователь введет символ пробела после числа (я, например, очень часто так делаю), а программа ответит ошибкой.

Использование scanf приносит немало мучений при организации безопасного и отзывчивого интерфейса для ввода данных. Именно поэтому советуют считывать всю строку в буфер и только потом анализировать его. Такой подход применяется в уже лишь чуть-чуть серьезных приложениях. И его-то мы и обсудим далее.

3. Чтение данных с консоли, используя буфер

Это наиболее широко применяемая техника для обработки ввода данных. Вместо использования форматного ввода мы просто читаем всю строку в буфер. А прочитать строку не так уж и тяжело:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define LINESIZE 20
 
int main()
{
        char line[LINESIZE] = "";
 
        if (gets(line) == NULL) {
                /* Произошла ошибка или был прочитан EOF */
        }
        else {
                /* Здесь можно проанализировать строку */
        }
 
        return 0;
}
Это общий подход. Но что здесь неправильно? Дело в том, что функция gets не учитывает размер буфера. Если ввести строку длиннее, чем LINESIZE, случится ужасное. В 'man gets' по этому поводу так и написано: никогда не используй gets!

Код:
BUGS
       Never use gets().  Because it is impossible to tell without knowing the
       data in advance how many  characters  gets()  will  read,  and  because
       gets() will continue to store characters past the end of the buffer, it
       is extremely dangerous to use.  It has  been  used  to  break  computer
       security.  Use fgets() instead.
Мы последуем совету и воспользуемся функцией fgets. Но она, в отличие от gets, помещает в буфер еще и символ перевода строки, а также в обязательном порядке завершает строку символом '\0'. Попробуем его удалить?

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define LINESIZE 20
 
int main()
{
        char line[LINESIZE] = "";
 
        if (fgets(line, LINESIZE, stdin) == NULL) {
                /* Произошла ошибка или был прочитан EOF */
        }
        else {
                /* Удаляем символ конца строки */
                line[strlen(line) - 1] = '\0';
 
                /* Здесь можно проанализировать строку */
        }
 
        return 0;
}
Это неправильно! Буфер может и не содержать символ конца строки, если пользователь ввел строку длиннее, чем LINESIZE. Тогда мы удалим символ, который важен для нас. Исправляем.

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

C
1
2
3
4
5
6
7
8
9
10
11
12
        if (fgets(line, LINESIZE, stdin) == NULL) {
                /* Произошла ошибка или был прочитан EOF */
        }
        else {
                /* Удаляем символ конца строки */
                size_t last = strlen(line) - 1;
 
                if (line[last] == '\n')
                    line[last] = '\0';
 
                /* Здесь можно проанализировать строку */
        }
4. Интерактивная консоль или prompt

Обычно программы не просто ждут, чтобы пользователь начал ввод данных. Как правило, они печатают какое-то приглашение на ввод, вроде как "Введите Ваше имя". Это приглашение называется prompt. Казалось бы, а что здесь может быть сложного? Дело в том, что стандартные потоки ввода/вывода буферизуются. Если Вы написали printf - это не значит, что приглашение появится именно в этот момент, когда выполнилась функция printf. Строка может остаться в буфере до лучших времен. Тем не менее программа уже будет ожидать ввода данных, а пользователь ничего об этом еще не знает.

Это решается просто. Используйте функцию flush каждый раз, когда Вы хотите вывести данные на экран немедленно.

C
1
2
3
4
printf("Enter your name please: ");
fflush(stdout);
 
/* Обработка ввода */
Цитата:
Если Вы раньше никогда не использовали flush и все работало вполне ожидаемо, то возможно в версии стандартной библиотеки, которую используете Вы, flush выполняется автоматически при вызове scanf. Стандартом языка такое поведение не гарантируется, следовательно и не стоит на него расчитывать.
Собственно это те рекомендации, на которые я хотел обратить Ваше внимание. Программа, которая работает с пользователем, должна быть интерактивна.
  • Пользователь должен знать, что именно программа от него ожидает.
    • Печатайте приглашение на ввод данных.
    • Остерегайтесь буферизации.
  • Указывайте пользователю на ошибки.
    • Если пользователь ошибся при вводе - программа не должна молчать.
    • И наоборот, если программа может сама исправить ошибку - лучше не беспокоить злых юзверей.

Зачем нужна качественная обратная связь? Давайте просто посмотрим на сообщения об ошибках таких популярных инструментов как gcc и clang.

C
1
2
3
int main() {
    return 0
}
Код:
main.c: In function ‘main’:
main.c:5:1: error: expected ‘;’ before ‘}’ token
Код:
main.c:4:13: error: expected ';' after return statement
    return 0
            ^
            ;
Оба инструмента идентифицируют место возникновения ошибки номером строки и номером символа от начала строки. Указывают тип ошибки и возможное решение. Даже здесь лично мне больше по душе второй вариант.
Представьте, если бы компилятор Вам говорил только "где-то произошла какая-то ошибка"

UPDATE: В следующем примере я делаю акцент на детализацию ошибок, на идентификацию типа ошибки, на диагностику. Для такого простейшего случая я попытаюсь дать пользователю как можно больше информации об ошибке. Холивар на тему "Зачем это надо?" можно найти в комментариях.

5. Пример безопасного чтения double

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

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
 
/* Размер буфера */
#define CLI_BUFFER_SIZE 5
 
/* Читает double из консоли, возвращет 0 в случае успеха */
int cli_read_double(double *value, const char *prompt) 
{
    size_t length = 0;
    char *end = NULL;
    char buf[CLI_BUFFER_SIZE] = "";
 
    /* Проверка параметров */
    assert(value);
    assert(prompt);
 
    /* Приглашение */
    printf("%s: ", prompt);
    fflush(stdout);
 
    /* Чтение в буфер */
    if (!fgets(buf, sizeof(buf), stdin)) {
        return 1;
    }
 
    /* Удаление символа перевода строки */
    length = strlen(buf);
    if (buf[length - 1] == '\n') {
        buf[--length] = '\0';
 
        /* Перевод из строки в число */
        errno = 0;
        *value = strtod(buf, &end);
 
        /* Обработка ошибок */ 
        if (length == 0) {
            fprintf(stderr, "Ошибка: введена пустая строка.\n");
            return 1;
        }
        if (errno != 0 || *end != '\0') {
            fprintf(stderr, "Ошибка: некорректный символ.\n");
            fprintf(stderr, "\t%s\n" , buf);
            fprintf(stderr, "\t%*c\n", (int)(end - buf) + 1, '^');
            return 1;
        }
    }
    else {
        /* Строка прочитана не полностью
           Пропустить остаток строки      */
        scanf("%*[^\n]");
        scanf("%*c");
        fprintf(stderr, "Ошибка: не вводите больше чем %d символа(ов).\n", CLI_BUFFER_SIZE - 2);
 
        return 1;
    } 
 
    return 0;
}
 
int main() 
{
    int status = 0;
    double value = 0;
 
    do {
        status = cli_read_double(&value, "Введите вещественное число");
        if (status != 0) {
            printf("Пожалуйста, попробуйте еще раз.\n");
        }
    } while (status != 0);
 
    printf("-----> Вы ввели: %lf\n", value);
    return 0;
}

Примеры работы программы:
  • Ввод пустой строки (в случае, когда пользователь просто нажимает enter)

    Код:
    Введите вещественное число:
    Ошибка: введена пустая строка
    Пожалуйста, попробуйте еще раз.
    Пользователю сообщается конкретная ошибка, поэтому он может быстро исправиться.
  • Ввод недопустимого символа (запятая вместо точки в качестве разделителя целой и дробной частей)

    Код:
    Введите вещественное число: 1,3
    Ошибка: некорректный символ.
            1,3
             ^
    Пожалуйста, попробуйте еще раз.
    Опять же, пользователь получает информацию о том, в каком именно символе он допустил ошибку. Заметим, что в программе в зависимости от ошибочного символа можно сделать некоторые предположения о характере ошибки. Например, если символ является запятой (как в этом случае) можно еще более детализировать ошибку - например, "Возможно Вы используете в качестве разделителя запятую. Используте точку."
  • Ввод слишком большого числа

    Код:
    Введите вещественное число: 124.12
    Ошибка: не вводите больше чем 3 символа(ов).
    Пожалуйста, попробуйте еще раз.
  • Корректный ввод

    Код:
    Введите вещественное число: 1.5
    -----> Вы ввели: 1.500000

Теперь Вы можете использовать этот пример как базовый для своих будущих программ.
Показов 186662 Комментарии 28
Всего комментариев 28
Комментарии
  1. Старый комментарий
    [B]fasked[/B] единственными граблями в чтении double явлется облом программы при вводе параметра с типом double на старых компиляторах без начальной инициализации этого параметра любым стохастическим числом. Также есть один подводный камень у printf связанный с даблом - это тот факт что при печати с ключом %lf , будет печататься всего 6-ть символов после запятой (вывод дефалтится до флоат). Так вот этот момент обходим посредством спецификатора длинны формата %.11f
    Лично от себя добавлю что "безопасный ввод" это огромная гора кода, которая может быть заменана лаконично простой конструкцией
    [C]#include <stdio.h>

    int main()
    {
    char bufStub = 0;
    double param = 0;//Вот важный момент начальная инициализация!
    while(1)
    {
    bufStub = 0;
    printf("Enter double : ");
    if(!scanf("%lf",&param))
    {
    printf("Wrong input\n");
    //Данный цикл отработает всегда
    while(bufStub != '\n')
    scanf("%c",&bufStub);
    }
    else
    printf("Your input %.11f\n",param);//Тоже мало кто знает что %.11f позволятет
    //обходить изъян связанный с дефалтным отображением дабла как float
    }
    return 0;
    }[/C]
    PS:Зачем городить какуе-то байду если можно эффективно писать в 3 строчки...
    Отработку можешь посмотреть здесь [url]https://www.cyberforum.ru/blogs/34326/blog279.html[/url]
    Запись от -=ЮрА=- размещена 08.04.2012 в 14:16 -=ЮрА=- вне форума
    Обновил(-а) -=ЮрА=- 08.04.2012 в 14:26
  2. Старый комментарий
    Аватар для fasked
    Цитата:
    Сообщение от -=ЮрА=- Просмотреть комментарий
    Лично от себя добавлю что "безопасный ввод" это огромная гора кода, которая может быть заменана лаконично простой конструкцией
    C
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    #include <stdio.h>
     
    int main()
    {
        char bufStub = 0;
        double param = 0;//Вот важный момент начальная инициализация!
        while(1)
        {
            bufStub = 0;
            printf("Enter double : ");
            if(!scanf("%lf",&param))
            {
                printf("Wrong input\n");
                //Данный цикл отработает всегда
                while(bufStub != '\n')
                    scanf("%c",&bufStub);
            }
            else
                printf("Your input %.11f\n",param);//Тоже мало кто знает что %.11f позволятет 
            //обходить изъян связанный с дефалтным отображением дабла как float
        }
        return 0;
    }
    PS:Зачем городить какуе-то байду если можно эффективно писать в 3 строчки...
    Этот пример странно себя ведет, если я ввожу всякую чушь:
    Код:
    Enter double : 12f
    Your input 12.00000000000
    Enter double : Wrong input
    Enter double :
    Код:
    Enter double : 123123.123123.123
    Your input 123123.12312300000
    Enter double : Your input 0.12300000000
    Enter double :
    Код:
    Enter double : 123.f.12
    Your input 123.00000000000
    Enter double : Wrong input
    Enter double :
    И, наконец, я говорю об интерактивности. Мой пример показывает, какая именно произошла ошибка. Где именно ошибся пользователь. В этом же примере один ответ на все - "Wrong input".

    К тому же у меня есть подозрение, что мой способ очистки потока таки производительнее и как минимум лаконичнее, чем этот:
    C
    1
    2
    3
    
                //Данный цикл отработает всегда
                while(bufStub != '\n')
                    scanf("%c",&bufStub
    Запись от fasked размещена 08.04.2012 в 14:24 fasked вне форума
    Обновил(-а) fasked 08.04.2012 в 14:45
  3. Старый комментарий
    [B]fasked[/B] ну мы что маленькие, ты прекрасно понимаешь что мой код отрабатывал так потому как был отстроен на 1-у ошибку ввода. Чтож да ты прав ошибок может быть множество.
    Ниже код с полной фиксацией "плохого ввода". На счёт интерактивности, ввод может быть успешным либо нет и городить гору кода чтобы написать Ввелось "не так потому что тот кто вводил редиска" или "не так потому что тот кто вводил не вышел руками" как то сомнительно.
    Чтож я доработал код
    [C]#include <stdio.h>

    int main()
    {
    char bufStub = 0;
    double param = 0;//Вот важный момент начальная инициализация!
    while(1)
    {
    bufStub = 0;
    printf("Enter double : ");
    if(scanf("%lf%c",&param,&bufStub) != 2)
    printf("Wrong input\n");
    //Данный цикл отработает всегда
    if(bufStub == '\n')
    printf("Your input %.11f\n",param);//Тоже мало кто знает что %.11f позволятет
    //обходить изъян связанный с дефалтным отображением дабла как float
    else//Очистка от неверного ввода
    {
    //Данный цикл отработает всегда
    printf("Wrong input\n");
    while(bufStub != '\n')
    scanf("%c",&bufStub);
    }
    }
    return 0;
    }[/C]
    Ниже отработка под твои неверные вводы
    [C]Enter double : 12f
    Wrong input
    Enter double : 123123.123123.123
    Wrong input
    Enter double : 123.f.12
    Wrong input
    Enter double : 123.777
    Your input 123.77700000000
    Enter double :[/C]
    В любом случае это не гора кода и "для маленьких" он гораздо понятней будет...
    Запись от -=ЮрА=- размещена 08.04.2012 в 16:24 -=ЮрА=- вне форума
  4. Старый комментарий
    Аватар для fasked
    Цитата:
    Сообщение от -=ЮрА=- Просмотреть комментарий
    ну мы что маленькие, ты прекрасно понимаешь что мой код отрабатывал так потому как был отстроен на 1-у ошибку ввода. Чтож да ты прав ошибок может быть множество.
    Ниже код с полной фиксацией "плохого ввода".
    Код:
    Enter double : aaaa
    Wrong input
    Wrong input
    Цитата:
    Сообщение от -=ЮрА=- Просмотреть комментарий
    На счёт интерактивности, ввод может быть успешным либо нет и городить гору кода чтобы написать Ввелось "не так потому что тот кто вводил редиска" или "не так потому что тот кто вводил не вышел руками" как то сомнительно.
    Как сказать, как сказать...
    Цитата:
    Сообщение от -=ЮрА=- Просмотреть комментарий
    В любом случае это не гора кода и "для маленьких" он гораздо понятней будет...
    Может быть, но я ставил другие цели. Я не говорю, что я собирался написать непонятный код, а то, что я писал код, который покажет ошибку. Я также упоминал, что подход, который предлагаю я, используется в более-менее серьезных приложениях. Если прочитать заметку целиком, то можно увидеть момент, где я предлагал остановиться на достаточно простом варианте.
    Запись от fasked размещена 08.04.2012 в 16:31 fasked вне форума
  5. Старый комментарий
    Аватар для Evg
    Там, где "не путать с многопоточностью" можно пояснить, что в английском есть два термина. "Stream" - поток ввода-вывода. "Thread" - поток как ветвь исполнения программы. Просто эти два термина по русски переводятся одним словом

    > "Проблема в том, что слово "hello" остается в потоке"

    Этот момент надо объяснить поподробнее. Потому что тем, кто не знает, это совсем неочевидно. Да и до конца раздела не помешало бы чуть более подробно разжевать

    > "Буфер может и не содержать символ конца строки, если пользователь ввел строку длиннее, чем LINESIZE. Тогда мы удалим символ, который важен для нас"

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

    -------------------------------------

    Юра наваял гневный ответ чемберлену: https://www.cyberforum.ru/blogs/34326/blog279.html
    Как обычно это был ответ человека, который "чукча не читатель, чукча писатель", но тем не менее эту проблему заметил в том числе и я. В разделе "Пример безопасного чтения double" надо явным образом указать, почему разводится такой здоровенный код ради ввода double'а, хотя в начале статьи у тебя это делалось намного короче. Ты Юре об этом явно написал, но он этого не увидел. А это говорит о том, что причина плохо пояснена

    В общем статья очень правильная и очень полезная для начинающих. Но статья написана как краткий вывод из некоей сентенции без нормального объяснения самОй сентенции. А вот на объяснение как раз-таки стоит потратить время. Потому что читающий должен чётко понимать, какие проблемы решаются и какая конкретная постановка задачи стоит. Т.е. нужно сделать хороший упор на то, что помимо безопасного ввода выполняется ещё и качественная обратная связь (детализация ошибки ввода).
    Запись от Evg размещена 08.04.2012 в 17:18 Evg вне форума
  6. Старый комментарий
    Аватар для Evg
    Ссылку на твою статью добавил в свою статью с свалкой полезных ссылок
    Запись от Evg размещена 08.04.2012 в 17:20 Evg вне форума
  7. Старый комментарий
    Аватар для fasked
    Цитата:
    Сообщение от Evg Просмотреть комментарий
    В общем статья очень правильная и очень полезная для начинающих. Но статья написана как краткий вывод из некоей сентенции без нормального объяснения самОй сентенции. А вот на объяснение как раз-таки стоит потратить время. Потому что читающий должен чётко понимать, какие проблемы решаются и какая конкретная постановка задачи стоит.
    Благодарю за внимание и разумную критику. Замечанию учту и постараюсь в ближайшее время внести поправки.
    Цитата:
    Сообщение от Evg Просмотреть комментарий
    Ссылку на твою статью добавил в свою статью с свалкой полезных ссылок
    Запись от fasked размещена 08.04.2012 в 17:28 fasked вне форума
  8. Старый комментарий
    Аватар для Evg
    Раздел "Чтение данных с консоли, используя scanf". Если ввести "45abc", то scanf успешно прочтёт 45, затем на следующей итерации ввода прочтёт "abc". Что есть не совсем логично с точки зрения пользователя, потому что ошибочный ввод данных A повлечёт за собой кривое вбитие данных B без возможности что-либо сделать. Например, сначала просят ввести число, а затем строку. Введя "45abc" пользователь введёт сразу два поля данных. И в этом случае для короткого примера (т.е. без детализации ошибок) логичным было воспользоваться фичей от Юры (с контролем за переводом строки)
    Запись от Evg размещена 08.04.2012 в 17:48 Evg вне форума
  9. Старый комментарий
    [QUOTE]Enter double : aaaa
    Wrong input
    Wrong input[/QUOTE] - Ииии???
    Вывод прекратился или что???
    Enter double : dddsd
    Wrong input
    Wrong input
    Enter double :

    Да переставь условные операторы, не я это должен делать, я показал что можно обойтись без горы кода и на уровне "чукчи показал" как это сделать элегантно, остально лишь детали реализации...
    Запись от -=ЮрА=- размещена 08.04.2012 в 19:48 -=ЮрА=- вне форума
  10. Старый комментарий
    Аватар для Evg
    Юра, ты всё-таки ради разнообразия вникай в то, что тебе пишут. Человек, условно говоря, показал код программы-калькулятора на несколько килобайт, а ты говоришь, зачем такая гора кода, когда я могу просто написать код в одну строку "int x = 10 + 20 * 30;" и незачем для этого писать кучу кода
    Запись от Evg размещена 08.04.2012 в 22:54 Evg вне форума
  11. Старый комментарий
    Для ознакомления
    [url]https://www.cyberforum.ru/blogs/34326/blog279.html#comment1451[/url]
    Запись от -=ЮрА=- размещена 10.04.2012 в 20:07 -=ЮрА=- вне форума
  12. Старый комментарий
    Аватар для Evg
    В конце данного поста пример на тему "ради чего мы так геморроимся"
    https://www.cyberforum.ru/blog... omment1474
    Запись от Evg размещена 11.04.2012 в 16:58 Evg вне форума
  13. Старый комментарий
    Аватар для programina
    Классная статья
    Запись от programina размещена 06.06.2012 в 20:04 programina вне форума
  14. Старый комментарий
    Аватар для Evg
    > А во-вторых, потому что реализовать полностью безопасный ввод на Си не совсем тривиальная задача

    Там тире должно быть

    > И все они используют так называемые потоки

    Думаю, лучше написать как "И все они используют так называемые потоки ввода-вывода (input-output stream)"

    > Существует два англоязычных термина: thread и stream

    Редиректор формуа косячит, а потому хз куда эти ссылки ведут

    > Логика функции scanf устроена весьма просто

    Если это новый абзац, то надо перевод строки воткнуть.

    > Когда происходит ошибка несоотвествия

    несоответствия

    > Если ввести "45abc", то scanf успешно прочтёт 45

    Это мой собственный текст, но я с трудом его осилил. Думаю, полезно было бы продемонстрировать на конкретном примере

    > Такой подход применяется в уже лишь чуть-чуть серьезных приложениях

    Шо такое "в уже лишь чуть-чуть"

    > Но она, в отличие от gets, помещает в буфер еще и символ перевода строки

    И в обязательном порядке завершает строку символом \0

    > Следует отметить, что если логика программы требует получения всей строки целиком

    Опять абзац

    > Пример безопасного чтения double

    Полезно было бы отобразить пример работы программы
    Запись от Evg размещена 06.06.2012 в 20:43 Evg вне форума
  15. Старый комментарий
    Аватар для fasked
    Evg, пофиксил все, кроме ссылок и примеров. Добавлю чуть позже.
    Запись от fasked размещена 21.06.2012 в 19:11 fasked вне форума
  16. Старый комментарий
    Аватар для Evg
    https://www.cyberforum.ru/c-be... 75050.html

    У человека проблема - ему нужно считать один символ. И такая постановка довольно-таки стандартная, в том смысле, что я часто видел, как люди об это спотыкаются. Неплохо бы добавить пример для решения этой проблемы.

    И неплохо бы пронумеровать разделы, чтобы можно было людей отсылать на статью со словами типа "прочти раздел N3"
    Запись от Evg размещена 22.06.2012 в 13:51 Evg вне форума
  17. Старый комментарий
    Аватар для fasked
    Цитата:
    Сообщение от Evg Просмотреть комментарий
    https://www.cyberforum.ru/c-be... 75050.html
    У человека проблема - ему нужно считать один символ. И такая постановка довольно-таки стандартная, в том смысле, что я часто видел, как люди об это спотыкаются. Неплохо бы добавить пример для решения этой проблемы.
    А смысл есть ли? Суть та же самая.

    Цитата:
    Сообщение от Evg Просмотреть комментарий
    И неплохо бы пронумеровать разделы, чтобы можно было людей отсылать на статью со словами типа "прочти раздел N3"
    Номера добавил.
    Запись от fasked размещена 04.07.2012 в 18:38 fasked вне форума
  18. Старый комментарий
    Аватар для Evg
    Цитата:
    Сообщение от fasked Просмотреть комментарий
    А смысл есть ли? Суть та же самая
    Смысла нет для того, кто всё знает. Для тех, кто прочёл статью с условно нулевым уровнем входа - смысл есть. Спроецировать одно на другое для начинающего не всегда будет тривиально. Хотя я бы сделал немного наоборот. В качестве первого пояснения добавил бы пояснение на примере ввода yes/no, а пример ввода double'а, как более сложный, был логическим продолжением. Но. наверное, и вправду незачем сейчас что-либо перелопачивать
    Запись от Evg размещена 04.07.2012 в 19:25 Evg вне форума
  19. Старый комментарий
    [url]https://www.cyberforum.ru/c-beginners/thread630861.html#post3318854[/url]
    Запись от alkagolik размещена 01.08.2012 в 20:22 alkagolik вне форума
  20. Старый комментарий
    Аватар для #pragma

    Вопрос по логике построения кода

    fasked, а почему ты выбрал в функции безопасного ввода double именно
    C
    1
    2
    3
    
        /* Проверка параметров */
        assert(value);
        assert(prompt);
    ? Почему не проверку на NULL и передачу управления вызывающей функции с выводом сообщения об ошибке? Например, программа могла выделять динамическую память, и перед выходом из программы её стоит освобождать, или не нужно?

    Не по теме:

    Пост полезный, очень детальный подход к проблеме.

    Запись от #pragma размещена 06.08.2012 в 23:33 #pragma вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru