c++ Чтение чисел из текстовых файлов, игнорируя комментарии

Итак, я видел много решений на этом сайте и руководств по чтению из текстового файла на C++, но еще не нашел решения моей проблемы. Я новичок в C++, поэтому я думаю, что у меня возникли проблемы с составлением документации, чтобы понять все это.

То, что я пытаюсь сделать, это прочитать номера текстовых файлов, игнорируя комментарии в файле, обозначенные знаком «#». Таким образом, пример файла будет выглядеть так:

#here is my comment
20 30 40 50
#this is my last comment
60 70 80 90

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

/////////////////////// Read the file ///////////////////////
std::string line;
if (input_file.is_open())
{
    //While we can still read the file
    while (std::getline(input_file, line))
    {
        std::istringstream iss(line);
        float num; // The number in the line

        //while the iss is a number 
        while ((iss >> num))
        {
            //look at the number
        }
    }
}

else
{
    std::cout << "Unable to open file";
}
/////////////////////// done reading file /////////////////

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


person Ninja_Panda    schedule 09.11.2012    source источник
comment
line.assign(line.substr(0,line.find('#'))); (как первый оператор в цикле while) был бы одним из способов быстрого внесения необходимых изменений.   -  person jogojapan    schedule 09.11.2012
comment
Это очень, очень просто. Вы говорите, что недостаточно хорошо понимаете приведенный выше код, чтобы модифицировать его. Я думаю, вам нужно потратить некоторое время на то, чтобы понять это, прежде чем пытаться что-то еще.   -  person john    schedule 09.11.2012
comment
Вы пробовали это с комментариями, присутствующими в файле? Как написано, код будет игнорировать любую часть строки после первой части, которая не является допустимым числом, включая комментарии.   -  person Bart van Ingen Schenau    schedule 09.11.2012
comment
Итак, я думаю, что @BartvanIngenSchenau прав, что было моей интуицией поначалу, но я получаю какое-то странное поведение, которое теперь, я думаю, не связано с синтаксическим анализом. Чего я здесь не показываю, так это того, что я использую ввод файла для рисования набора геометрических фигур, и иногда я рисую красную линию на экране. Поэтому я подумал, что это может быть потому, что он делает что-то странное и читает комментарии, но теперь я думаю, что это что-то другое. Итак, я собираюсь изучить некоторые другие элементы, спасибо всем.   -  person Ninja_Panda    schedule 10.11.2012


Ответы (3)


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

while (std::getline(input_file, line))
{
    if (line[0] != "#" )
    {
        std::istringstream iss(line);
        float num; // The number in the line

        //while the iss is a number 
        while ((iss >> num))
        {
            //look at the number
        }
    }
}

Однако целесообразно обрезать строку начальных и конечных пробелов, как показано здесь, например: Удалить пробелы из std::string в C++

person Chris    schedule 09.11.2012
comment
Если это не первый символ, то можно использовать std::find, чтобы найти его, и std::string::erase, чтобы удалить его и все последующие. - person James Kanze; 09.11.2012
comment
Видите ли, я пробовал подобное выражение if раньше, и я получаю сообщение об ошибке: comparison between pointer and integer ('int' and 'const char*'). - person Ninja_Panda; 10.11.2012
comment
Если вы замените getline(input_file, line) на getline(input_file >> std::ws, line), ваши строки комментариев могут содержать начальные пробелы. - person Micha Wiedenmann; 12.11.2012

Если это всего лишь одно из применений, для линейно-ориентированного ввода, такого как ваш, самое простое решение - просто удалить комментарий из строки, которую вы только что прочитали:

line.erase( std::find( line.begin(), line.end(), '#' ), line.end() );

Более общим решением было бы использование фильтрующего потокового буфера, например:

class FilterCommentsStreambuf : public std::streambuf
{
    std::istream& myOwner;
    std::streambuf* mySource;
    char myCommentChar;
    char myBuffer;

protected:
    int underflow()
    {
        int const eof = std::traits_type::eof();
        int results = mySource->sbumpc();
        if ( results == myCommentChar ) {
            while ( results != eof && results != '\n') {
                results = mySource->sbumpc(0;
            }
        }
        if ( results != eof ) {
            myBuffer = results;
            setg( &myBuffer, &myBuffer, &myBuffer + 1 );
        }
        return results;
    }

public:
    FilterCommentsStreambuf( std::istream& source,
                             char comment = '#' )
        : myOwner( source )
        , mySource( source.rdbuf() )
        , myCommentChar( comment )
    {
        myOwner.rdbuf( this );
    }
    ~FilterCommentsStreambuf()
    {
        myOwner.rdbuf( mySource );
    }
};

В этом случае вы могли бы даже отказаться от getline:

FilterCommentsStreambuf filter( input_file );
double num;
while ( input_file >> num || !input_file.eof() ) {
    if ( ! input_file ) {
        //  Formatting error, output error message, clear the
        //  error, and resynchronize the input---probably by
        //  ignore'ing until end of line.
    } else {
        //  Do something with the number...
    }
}

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

person James Kanze    schedule 09.11.2012

Альтернативой «чтению строки и анализу ее как строки» может быть использование самого потока в качестве входящего буфера:

while(input_file)
{
    int n = 0;

    char c; 
    input_file >> c; // will skip spaces ad read the first non-blank

    if(c == '#')
    {
        while(c!='\n' && input_file) input_file.get(c);
        continue; //may be not soooo beautiful, but does not introduce useless dynamic memory
    }

    //c is part of something else but comment, so give it back to parse it as number
    input_file.unget(); //< this is what all the fuss is about!
    if(input_file >> n)
    { 
        // look at the nunber
        continue;
    }

    // something else, but not an integer is there ....
    // if you cannot recover the lopop will exit 
}
person Emilio Garavaglia    schedule 09.11.2012
comment
Вот хороший пример того, как писать нечитаемый код. Не говоря уже о том, что последнее if неверно. (Если вы зайдете так далеко, это всегда будет правдой, если только не произошла аппаратная ошибка.) - person James Kanze; 09.11.2012
comment
@JamesKanze: в соответствии со стандартом бит ошибки (а не плохой бит, это другое) устанавливается, когда операция извлечения завершается неудачно (например, потому что вы ожидаете прочитать число, но e - это нечисловая цифра на входе). Дело здесь не в том, чтобы закрыть код и позволить ему открыться для дальнейшего анализа дополнительных случаев. Я изменил стиль кода, но дело здесь не в том, чтобы сделать его элегантным, а в том, чтобы избежать введения некоторых ненужных динамических распределений памяти/деаллоков (типичных для string-s и sringstream-s). - person Emilio Garavaglia; 09.11.2012
comment
В исходном коде вы не проверяли failbit до тех пор, пока не произошел сбой ввода (input_file >> n оценено как ложное). И если ввод невозможен, необходимо установить либо failbit, либо badbit; badbit устанавливается тогда и только тогда, когда есть исключение из streambuf (которого в большинстве случаев никогда не бывает). Итак, когда вы тестировали failbit, он почти наверняка был установлен. После того, как произошел сбой, вы можете протестировать eof(), чтобы решить, была ли это из-за того, что больше нечего было читать, или из-за ошибки во входном формате (оба из этих причин приводят к установке failbit ). - person James Kanze; 09.11.2012
comment
Использование std::string обычно не вызывает накладных расходов, которые можно измерить по сравнению с накладными расходами при чтении из файла. - person James Kanze; 09.11.2012
comment
И, наконец, цикл из двадцати строк с continue повсюду совершенно нечитаем. (На самом деле я не могу придумать ни одного контекста, в котором continue приводил бы к читаемому коду.) - person James Kanze; 09.11.2012
comment
@JamesKanze: да, именно поэтому я удалил if, не имея другого значения, так как я не могу представить, что предлагает другая возможная обработка в случае ввода, отличного от int (может быть, пропустить строку или просто до первого пробела... Это зависит от того, какая семантика скрыта за этими числами). В этом я согласен. Я просто указал, что это не может быть просто аппаратный сбой в соответствии с вашим первым комментарием. Как вы лучше описали в своем ответе. Спасибо, что указали на это! - person Emilio Garavaglia; 09.11.2012
comment
@JamesKanze: continue похож на break, на возврат, на goto. По моему опыту, они всегда нечитаемы для тех, кто не хочет их читать, и прекрасно читаемы для всех, кто их знает. Немного менее религиозный подход может помочь вам понять код, который использует стиль, который вам не нравится больше всего. Продолжить — это вполне законный механизм, позволяющий избежать глубокой вложенности и введения поддельных состояний. Есть случаи, когда это добавляет ценности. - person Emilio Garavaglia; 09.11.2012
comment
@JamesKanze: файл не обязательно всегда привязан к файлу на диске. пусть ОП решит компромисс. - person Emilio Garavaglia; 09.11.2012
comment
Если вы чувствуете потребность в continue, ваши циклы и ваша функция слишком сложны. мне потребовалось некоторое время, чтобы понять, как работает ваш код, и еще больше времени, чтобы понять, что он неверен. continue хорош для обфускации, но не более того. - person James Kanze; 09.11.2012
comment
И если вы беспокоитесь о динамическом размещении в string и т. Д., См. Мое второе решение. На сайте нет std::string. И это очень читабельно, если вы знакомы с тем, как работает streambuf. (Это не иначе, и я бы посчитал такое знание достаточно продвинутым C++; я не думаю, что это одна из первых вещей, которые должен изучать программист.) - person James Kanze; 09.11.2012
comment
@ДжеймсКанзе: +1. спрашивать о continue все равно, что спрашивать о месте, где вы ставите фигурные скобки. Просто религия. У каждого свое. Использование определенного streambuf интересно, но я не нахожу его настолько читабельным. Не из-за стиля кодирования, а потому, что он переходит в обычно скрытую сторону потокового ввода-вывода. (Кто знает, почему здесь используется underflow? и sbumpc?) Но для очень общего кода это определенно путь (вы даже можете связать разные препроцессоры, так что это может быть очень гибким) - person Emilio Garavaglia; 09.11.2012
comment
Использование пользовательского streambuf можно прочитать, если вы знаете протокол streambuf. Однако знание протокола streambuf не является базовым для C++, и многие очень хорошие программисты на C++ не знакомы с ним. (С другой стороны, его стоит изучить, так как он открывает двери для нескольких полезных паттернов.) - person James Kanze; 12.11.2012