Подтвердить что ты не робот

Как читать числа из файла ASCII (С++)

Мне нужно прочитать в файлах данных, которые выглядят так:

* SZA: 10.00
 2.648  2.648  2.648  2.648  2.648  2.648  2.648  2.649  2.650  2.650
 2.652  2.653  2.652  2.653  2.654  2.654  2.654  2.654  2.654  2.654
 2.654  2.654  2.654  2.655  2.656  2.656  2.657  2.657  2.657  2.656
 2.656  2.655  2.655  2.653  2.653  2.653  2.654  2.658  2.669  2.669
 2.667  2.666  2.666  2.664  2.663  2.663  2.663  2.662  2.663  2.663
 2.663  2.663  2.663  2.663  2.662  2.660  2.656  2.657  2.657  2.657
 2.654  2.653  2.652  2.651  2.648  2.647  2.646  2.642  2.641  2.637
 2.636  2.636  2.634  2.635  2.635  2.635  2.635  2.634  2.633  2.633
 2.633  2.634  2.634  2.635  2.637  2.638  2.637  2.639  2.640  2.640
 2.639  2.640  2.640  2.639  2.639  2.638  2.640  2.640  2.638  2.639
 2.638  2.638  2.638  2.638  2.637  2.637  2.637  2.634  2.635  2.636
 2.637  2.639  2.641  2.641  2.643  2.643  2.643  2.642  2.643  2.642
 2.641  2.642  2.642  2.643  2.645  2.645  2.645  2.645

Какой самый элегантный способ прочитать этот файл в массиве float?

Я знаю, как читать каждую строчку в строке, и я знаю, как преобразовать строку в float с помощью atof(). Но как я могу сделать все остальное самым легким?

Я слышал о строковых буферах, может это мне помочь?

4b9b3361

Ответ 1

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

std::vector<float> readFile(std::istream& is)
{
  char chdummy;
  is >> std::ws >> chdummy >> std::ws; 
  if(!is || chdummy != '*') error();
  std::string strdummy;
  std::getline(is,strdummy,':');
  if(!is || strdummy != "SZA") error();

  std::vector<float> result;
  for(;;)
  {
    float number;
    if( !is>>number ) break;
    result.push_back(number);
  }
  if( !is.eof() ) error();

  return result;
}

Почему float, BTW? Обычно double намного лучше.

Изменить, поскольку было поставлено под сомнение, является ли возвращение копии vector хорошей идеей:

Для первого решения я, безусловно, сделаю очевидное. Функция читает файл в vector, и наиболее очевидной задачей для функции является возвращение ее результата. Является ли это результатом заметного замедления, зависит от множества вещей (размер вектора, как часто вызывается функция и откуда, от скорости чтения этого диска, может ли компилятор применять RVO). Я бы не хотел испортить очевидное решение с оптимизацией, но если профилирование действительно показывает, что это должно замедляться, вектор должен быть передан в неконстантной ссылке.

(Также обратите внимание, что С++ 1x с поддержкой rvalue, надеюсь, скоро будет доступен с помощью компилятора рядом с вами, сделает это обсуждение спорным, так как это предотвратит копирование вектора при возврате из функции.)

Ответ 2

String Toolkit Library (Strtk) имеет следующее решение вашей проблемы:

#include <iostream>
#include <string>
#include <deque>
#include <iterator>

#include "strtk.hpp"

int main()
{
    std::deque<float> flist;
    strtk::for_each_line("file.txt",
                         [&flist](const std::string& line)
                         { strtk::parse(line," ",flist); }
                         );
    std::copy(flist.begin(),flist.end(),
              std::ostream_iterator<float>(std::cout,"\t"));
    return 0;
}

Дополнительные примеры можно найти в С++ String Toolkit (StrTk) Tokenizer.

Ответ 3

Я бы сделал что-то вроде этого:

std::ifstream input("input.txt");
std::vector<float> floats;
std::string header;
std::getline(input, header); // read in the "* SZA: 10.00" line
if(header_is_correct(header)) {
    float value;
    // while we could successfully read in a float from the file...
    while(input >> value) {
        // store it in the vector.
        floats.push_back(value);
    }
}

ПРИМЕЧАНИЕ. header_is_correct(header) - это просто пример, вам нужно будет провести проверку ошибок для этой первой строки вручную.

Ответ 4

Простое решение с использованием алгоритмов STL:

#include <vector>
#include <iostream>
#include <string>
#include <iterator>

struct data
{
   float first; // in case it is required, and assuming it is 
                // different from the rest
   std::vector<float> values;
};

data read_file( std::istream& in )
{
   std::string tmp;
   data d;
   in >> tmp >> tmp >> d.first;
   if ( !in ) throw std::runtime_error( "Failed to parse line" );

   std::copy( std::istream_iterator<float>( in ), std::istream_iterator<float>(),
         std::back_inserter<float>(d.values) );

   return data;
}

Если вам действительно нужно использовать массив, вы должны сначала выделить его (динамически или статически, если знаете размер), а затем вы можете использовать тот же алгоритм копирования

// parsing the first line would be equivalent
float data[128]; // assuming 128 elements known at compile time
std::copy( std::istream_iterator<float>(is), std::istream_iterator<float>(), 
      data );

Но я бы рекомендовал использовать std::vector даже в этом случае, если вам нужно передать данные в функцию, которая принимает массив, вы всегда можете передать ее как указатель на первый элемент:

void f( float* data, int size );
int main()
{
   std::vector<float> v; // and populate
   f( &v[0], v.size() ); // memory is guaranteed to be contiguous
}