Хобрук: Ваш путь к мастерству в программировании

Использование fscanf() для чтения файла со строками из 3 чисел в каждой, почему %d%d%d%*c работает так же хорошо, как %d%d%d?

Я знаю, что спецификатор формата %d при использовании здесь в fscanf() считывает целое число и игнорирует предшествующие ему пробелы, включая новую строку (я проверил это). Но в моей следующей программе, которая использует fscanf() для чтения из файла из нескольких строки с 3 целыми числами каждая, строка формата "%d%d%d%*c" работает так же хорошо, как "%d%d%d".

Почему это так? Поскольку fscanf() используется с %d в качестве первого спецификатора формата в строке спецификатора формата, игнорируются все пробелы, предшествующие целому числу, почему дополнительный %*c, используемый в качестве последнего спецификатора, не вызывает ошибок или побочных эффектов? Если бы спецификатор %d не игнорировал новую строку после каждой группы из 3 чисел в строке, тогда %*c имел бы смысл, поскольку он съедал бы новую строку. Но почему он работает без ошибок или побочных эффектов, даже если fscanf() игнорирует пробел для %d по умолчанию? Не следует ли fscanf() останавливать сканирование, когда %*c не может найти символ для еды, и есть несоответствие между спецификатором и вводом? Разве fscanf() не должен останавливаться при несоответствии, как это делает scanf()?

РЕДАКТИРОВАТЬ: Это работает, даже если я использую "%*c%d%d%d"!!Разве сканирование и обработка последующих символов не должны останавливаться при несоответствии между спецификатором формата и вводом в начале? >

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


int main ()
{
int n1,n2,n3;
FILE *fp;
fp=fopen("D:\\data.txt","r");

if(fp==NULL)
{
printf("Error");
exit(-1);
}

while(fscanf(fp,"%d%d%d%*c",&n1,&n2,&n3)!=EOF) //Works as good as line below
//while(fscanf(fp,"%d%d%d",&n1,&n2,&n3)!=EOF)
printf("%d,%d,%d\n",n1,n2,n3);
fclose(fp);

}

Вот формат данных в моем файле data.txt:

243 343 434
393 322 439
984 143 943
438 243 938

Вывод:

243 343 434
393 322 439
984 143 943
438 243 938

  • См. комментарий, связанный с Используйте fscanf() для чтения из заданной строки. Он точно охватывает эту ситуацию. 15.05.2013
  • @JonathanLeffler Да, это то, что я хочу спросить. Вы сказали %d skips white space to the first digit. Это подтверждает то, что я сказал изначально в своем вопросе. %d должен заботиться о пробелах, включая новую строку. Так почему же %*c не используется где-либо , даже в начале строки спецификатора формата вызывает проблему? 15.05.2013

Ответы:


1

Рассмотрим этот вариант программы в вопросе:

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

int main(int argc, char **argv)
{
    char *file = "D:\\data.txt";
    FILE *fp;
    char *formats[] =
    {
    "%d%d%d%*c",
    "%d%d%d",
    "%*c%d%d%d",
    };

    if (argc > 1)
        file = argv[1];

    for (int i = 0; i < 3; i++)
    {
        if ((fp = fopen(file, "r")) == 0)
        {
            fprintf(stderr, "Failed to open file %s\n", file);
            break;
        }
        printf("Format: %s\n", formats[i]);
        int n1,n2,n3;
        while (fscanf(fp, formats[i], &n1, &n2, &n3) == 3)
            printf("%d, %d, %d\n", n1, n2, n3);
        fclose(fp);
    }
    return 0;
}

Повторяющиеся открытия неэффективны, но здесь это не проблема. Гораздо важнее четкость и демонстрация поведения.

Он написан для (а) использования имени файла, указанного в командной строке, поэтому мне не нужно возиться с такими именами, как D:\data.txt, которые очень неудобно создавать в системах Unix, и (б) показывает три используемых формата.

Учитывая файл данных из вопроса:

243 343 434
393 322 439
984 143 943
438 243 938

Вывод программы:

Format: %d%d%d%*c
243, 343, 434
393, 322, 439
984, 143, 943
438, 243, 938
Format: %d%d%d
243, 343, 434
393, 322, 439
984, 143, 943
438, 243, 938
Format: %*c%d%d%d
43, 343, 434
393, 322, 439
984, 143, 943
438, 243, 938

Обратите внимание, что первая цифра первого числа используется %*c, когда это первая часть формата. После того, как первые 3 числа прочитаны, %*c читает новую строку после третьего числа в строке, затем %d пропускает дальнейшие пробелы (за исключением того, что их нет) и считывает число.

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


Часть обсуждаемого кода в соответствующем вопросе Используйте fscanf() для чтения из заданной строки< /а> было:

fscanf(f, "%*d %*d %*d%*c");
fscanf(f, "%d%d%d", &num1, &num2, &num3);

Я отметил, что код должен проверять возвращаемое значение из fscanf(). Однако с тремя спецификациями преобразования %*d вы можете получить возвращаемое значение EOF, если встретите EOF до достижения указанной строки. К сожалению, вы никак не можете узнать, что первая строка содержит букву вместо цифры, пока не выполните вторую fscanf(). Вы также должны проверить второй fscanf(); вы можете получить EOF, или 0, или 1, или 2 (все из которых указывают на проблемы), или вы можете получить 3, указывающее на успех с 3 конверсиями. Обратите внимание, что добавление \n к формату означает, что пустые строки будут пропущены, но это все равно должно было произойти; %d пропускает пробел до первой цифры.

Есть ли какой-либо другой способ, которым мы можем читать, но игнорировать целые строки, как я неуклюже сделал с fscanf(f,"%*d%*d%*d")? Является ли использование %*[^\n] самым близким способом, который можно сделать для этого?

Лучший способ пропустить целые строки — использовать fgets(), как в последней версии кода в моем ответе. Очевидно, что существует вероятность того, что он неправильно подсчитает строки, если какая-либо из этих строк длиннее 4095 байт. OTOH, это довольно маловероятно.

У меня сейчас замешательство, и я не хочу задавать его вопросом. Итак, можете ли вы сказать мне следующее: fscanf() автоматически игнорирует пробелы, поэтому после первой строки, когда три целых числа читаются и игнорируются в соответствии с моим спецификатором %*d%*d%*d, я ожидаю, что fscanf() также будет игнорировать новую строку, когда она начнет читать в следующем запуске цикла. . Но почему мои дополнительные %*c или \n не вызывают проблем, и программа работает нормально, когда я использую %*d%*d%*d%*c или %*d%*d%*d\n в своем коде?

Вы не можете сказать, где что-то пошло не так с этими форматами; вы можете обнаружить EOF, но в противном случае fscanf() вернет 0. Однако, поскольку %*d пропускает начальные пробелы, включая новые строки, не имеет большого значения, читаете ли вы новую строку после третьего числа с %*c или нет, и когда вы там есть \n, это пробел, поэтому чтение пропускает новую строку и любые конечные или начальные пробелы, останавливаясь, когда достигает символа, отличного от пробела. Конечно, у вас также может быть перевод строки в середине трех чисел или у вас может быть более трех чисел в строке.

Обратите внимание, что конечный \n в формате особенно странен, когда пользователь печатает на терминале. Пользователь нажимает клавишу возврата и продолжает нажимать клавишу возврата, но программа не продолжается до тех пор, пока пользователь не введет непустой символ. Вот почему fscanf() так сложно использовать, когда данные ненадежны. Когда это надежно, это легко, но если что-то пойдет не так, диагностика и восстановление будут болезненными. Вот почему лучше использовать fgets() и sscanf(); у вас есть контроль над тем, что анализируется, вы можете повторить попытку с другим форматом, если хотите, и вы можете сообщить всю строку, а не только то, что fscanf() не удалось интерпретировать.

Обратите внимание, что %c%*c) не пропускает пробел; поэтому %*c в конце формата считывает (и отбрасывает) символ после прочитанного числа. Если это новая строка, то этот символ читается и игнорируется. Набор сканирования %[...] — это другая спецификация преобразования, которая не пропускает пробелы; все другие стандартные спецификации преобразования пропускают начальные пробелы.

15.05.2013
  • Рад, что вы присоединились к ответу!! 15.05.2013
  • Давайте опустим часть %*d здесь, это не имеет значения, поскольку здесь мы используем %d%d%d, а %*c здесь реальная проблема... например, почему его присутствие не влияет на код. 15.05.2013
  • Что заставило раскрутиться удалить свой ответ? В обсуждении было так много вещей, которые нужно было заархивировать. 15.05.2013
  • Он получил два отрицательных голоса (ни от меня), и мне не совсем понятно, почему, но я могу сочувствовать удалению ответа с двумя отрицательными голосами. Обратите внимание, что этот вопрос и десятки связанных с ним вопросов проясняют, почему обработка всех деталей scanf() и родственников так сложна. Это действительно сложные функции для обучения новичков! 15.05.2013
  • @Rüppell'sVulture Я сдался. :) Кажется, я полностью упустил суть, и, как сказал Джонатан, я действительно не хочу оставлять ответы, за которые проголосовали против, но не был уверен, что мне следует сделать, чтобы это исправить. 15.05.2013
  • @JonathanLeffler Требуется 100 000+ парней с репутацией, чтобы проголосовать против еще 100 000+ репутаций, ребята, так что, поскольку все дело в клубе, к которому я не принадлежу или вряд ли буду принадлежать, я должен перестать думать об этом .. LOL .. 15.05.2013
  • @Rüppell'sVulture А? Я не думаю, что есть такое ограничение, я думаю (надеюсь!) любой может проголосовать за меня, если он не согласен с моими ответами. По крайней мере, я не знаю о таких ограничениях. 15.05.2013
  • @unwind Я имею в виду, что я даже не проголосовал за 10 000+ парней во время моего пребывания в SO, так что забудьте о 100 000+ ответах людей. парень 50k+, чтобы обнаружить их ошибки в первую очередь. 15.05.2013

  • 2

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

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

    15.05.2013
    Новые материалы

    5 проектов на Python, которые нужно создать прямо сейчас!
    Добро пожаловать! Python — один из моих любимых языков программирования. Если вы новичок в этом языке, перейдите по ссылке ниже, чтобы узнать о нем больше:

    Dall-E 2: недавние исследования показывают недостатки в искусстве, созданном искусственным интеллектом
    DALL-E 2 — это всеобщее внимание в индустрии искусственного интеллекта. Люди в списке ожидания пытаются заполучить продукт. Что это означает для развития креативной индустрии? О применении ИИ в..

    «Очень простой» эволюционный подход к обучению с подкреплением
    В прошлом семестре я посетил лекцию по обучению с подкреплением (RL) в моем университете. Честно говоря, я присоединился к нему официально, но я редко ходил на лекции, потому что в целом я нахожу..

    Освоение информационного поиска: создание интеллектуальных поисковых систем (глава 1)
    Глава 1. Поиск по ключевым словам: основы информационного поиска Справочная глава: «Оценка моделей поиска информации: подробное руководство по показателям производительности » Глава 1: «Поиск..

    Фишинг — Упаковано и зашифровано
    Будучи старшим ИТ-специалистом в небольшой фирме, я могу делать много разных вещей. Одна из этих вещей: специалист по кибербезопасности. Мне нравится это делать, потому что в настоящее время я..

    ВЫ РЕГРЕСС ЭТО?
    Чтобы понять, когда использовать регрессионный анализ, мы должны сначала понять, что именно он делает. Вот простой ответ, который появляется, когда вы используете Google: Регрессионный..

    Не зря же это называют интеллектом
    Стек — C#, Oracle Опыт — 4 года Работа — Разведывательный корпус Мне пора служить Может быть, я немного приукрашиваю себя, но там, где я живу, есть обязательная военная служба на 3..