C++. Сборник рецептов.

4.23. Чтение текстового файла с разделителями-запятыми.

Проблема.

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

Smith, Bill, 5/1/2002, Active.

Stanford, John, 4/5/1999, Inactive.

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

Решение.

Пример 4.32 демонстрирует, как это делается. Если читать текст в string непрерывными кусками с помощью getline (шаблон функции определен в <string>), то для анализа текста и создания структуры данных можно использовать функцию split, которая была представлена в рецепте 4.6.

Пример 4.32. Чтение файла с разделителями.

#include <iostream>

#include <fstream>

#include <string>

#include <vector>

Using namespace std;

Void split(const string& s, char c, vector<string>& v) {

 int i = 0;

 int j = s.find(c);

 while (j >= 0) {

  v.push_back(s.substr(i, j-i));

  i = ++j;

  j = s.find(c, j);

  if (j < 0) {

   v.push_back(s.substr(i, s.length()));

  }

 }

}

Void loadCSV(istream& in, vector<vector<string>*>& data) {

 vector<string>* p = NULL;

 string tmp;

 while (!in.eof()) {

  getline(in, tmp, '\n'); // Получить следующую строку.

  p = new vector<string>();

  split(tmp, '.', *p); // Использовать split из.

                       // Рецепта 4.7.

  data.push_back(p);

  cout << tmp << '\n';

  tmp.clear();

 }

}

Int main(int argc, char** argv) {

 if (argc < 2).

  return(EXIT_FAILURE);

 ifstream in(argv[1]);

 if (!in).

  return(EXIT_FAILURE);

 vector<vector<string>*> data;

 loadCSV(in, data);

 // Выполнить с данными какие-либо действия...

 for (vector<vector<string>*>::iterator p = data.begin();

  p != data end(); ++p) {

  delete *p; // Убедитесь, что p.

 }           // разыменован!

}

Обсуждение.

В примере 4.32 почти нет ничего, что еще не было бы описано, getline обсуждается в рецепте 4.19, a vector — в рецепте 4.3. Единственный фрагмент, заслуживающий упоминания, — это выделение памяти.

LoadCSV создает новый vector для каждой прочитанной строки данных и сохраняет его в другом vector, состоящем из указателей на vector. Так как память для каждого из этих векторов выделяется из кучи, кто-то должен удалить ее, и этот кто-то — это вы (а не реализация vector).

Vector ничего не знает о том, содержит ли он значение или указатель на значение или что-либо еще. Все, что он знает, — это то, что при его удалении он должен вызвать деструктор для каждого содержащегося в нем элемента. Если vector хранит объекты, то все нормально, объект будет удален правильно. Но если vector содержит указатели, то удалены будут указатели, а не объекты, на которые они указывают.

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

For (vector<vector<string>*>::iterator p = data.begin();

 p != data.end(); ++p) {

 delete *p;

}

Либо можно использовать указатель со счетчиком ссылок, такой как smart_ptr из проекта Boost, который станет частью будущего стандарта C++0x. Но реализация этого нетривиальна, так что я рекомендую почитать, что такое smart_ptr и как он работает. Для получения дополнительной информации по Boost посетите его домашнюю страницу по адресу www.boost.org.