Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kernigan_B__Payk_R_Praktika_programmirovania.pdf
Скачиваний:
78
Добавлен:
18.03.2016
Размер:
2.53 Mб
Скачать

Реализация на C++

В этом разделе мы напишем версию библиотеки CSV на C++, в которой постараемся преодолеть некоторые ограничения, имеющиеся в С~версии. Нам придется внести некоторые изменения в спецификацию, главным из которых станет то, что функции будут теперь обрабатывать строки C++ вместо массивов символов С. Использование строк C++ автоматически решит некоторые проблемы, связанные с хранением данных, поскольку управлением памятью вместо нас займутся библиотечные функции. Так, в частности, функции работы с полями будут возвращать строки, которые затем могут изменяться вызывающей стороной, — проект получится более гибким, чем в предыдущей версии.

Класс Csv определяет внешние спецификации, изящно скрывая при этом переменные и функции реализации. Поскольку объект класса исчерпывающе описывает все состояния экземпляра, мы можем обрабатывать сразу несколько переменных Csv, при этом каждая из них будет абсолютно независима, так что одновременно могут обрабатываться сразу несколько входных потоков CSV.

class Csv { // читает и разбирает CSV // пример ввода: "LIT, 86. 25, "11/4/1998", "2:19РМ", +4. 0625

public:

Csv(istream& fin = cin, string sep = ",") : fin(fin), fieldsep(sep) {}

int getline(string&); string getfield(int n);

int getnfieldO const { return nfield; } private:

istream& fin; // указатель на файл ввода string line; // вводимая строка vector<string> field; // строки полей

int nfield; // количество полей

string fieldsep; // символы разделителей int split();

int endofline(char);

int advplain(const string& line, string& fid, int);

int advquoted(const string& line, string& fid, int); };

Для конструктора определены параметры, принимаемые по умолчанию, — такой объект Csv будет читать из стандартного входного потока и использовать обычный символ-разделитель; эти параметры можно изменить, задав другие значения в явном виде.

Для работы со строками класс использует не строки С, а стандартные С++-классы st ring и vector. Для типа st ring не существует невозможного состояния — "пустая" строка означает всего лишь строку нулевой длины, и нет никакого значения, эквивалентного по своему смыслу NULL, который бы мы использовали как сигнал достижения конца файла. Таким образом, Csv: :getline возвращает введенную строку через аргумент, передаваемый по ссылке, используя возвращаемое значение для сообщений о конце файла или ошибках.

//getline: получает одну строку,

//по мере необходимости наращивает размер

int Csv::getline(string& str)

{

char c;

for (line = ""; fin.get(c) && !endofline(c); ) line += c; split(); str = line; return !fin.eof();

}

Операция += здесь переопределяется, чтобы добавлять символ в строку.

Несколько меньше изменений потребуется вносить в endof line. Нам точно так же придется считывать ввод посимвольно, поскольку ни одна из стандартных функций ввода не может обработать все многообразие вариантов.

// endofline: ищет и удаляет \r', \n, \r\n или EOF int Csv::endofline(char с)

{

int eol;

eol = (c==Ar' || c=='\n'); if (с == V) { , fin.get(c); if (!fin.eof() && с != '\n',) fin.putback(c);

// слишком много прочитали > return eol;

}

А вот как выглядит новая версия функции split:

// split: разделяет строку на поля int Csv::split()

{

string fid; int i, j; nfield = 0;

if (line.lengthO == 0) return 0; i = 0;

do

{

if (i < line.length() && line[i] == '".')

j = advquoted(line, fid, ++i); // пропуск кавычки else

j = advplain(line, fid, i); if (nfield >= field.size()) field.push^back(fld); else

field[nfield] = fid; nfield++; i = j + 1;

}

while (j < line.length()); return nfield;

}

Поскольку strcspn не работает со строками C++, нам надо изменить и split, и advquoted. Новая версия advquoted для поиска следующего вхождения символаразделителя использует стандартную С++-функ-цию find_first_of. Вызов s. find_first_of (fieldsep, j) ищет в строке s первое вхождение любого символа из fieldsep, начиная с позиции ]. Если вхождение найдено не было, возвращается индекс, лежащий за концом строки, так что нам надо будет вернуть его обратно в должный диапазон. Внутренний цикл for в advquoted добавляет в поле fid все символы, расположенные до ближайшего разделителя.

//advquoted: для полей, заключенных в кавычки;

//возвращает индекс следующего разделителя

int Csv::advquoted(const string& s, string& fid, int i)

{

int j; fid = "";

for (j = i; j < s.length(); j++)

{

if (S[j] == "&& s[++j] != ")

{

int k = s.find_first_of(fieldsep, j); if (k > s.length())

// разделитель не найден

k = s.length(); for (k -= j; k-- > 0; ) fid += s[j++];

break;

}

Функция find_first_of используется также и в новой функции advplain, которая обрабатывает обычные, не заключенные в кавычки поля. Еще раз подчеркнем, что необходимость в этом обусловлена тем, что функции языка С вроде strcspn не могут быть применены к строкам C++, которые представляют собой совершенно особый тип данных.

//advplain: для полей, не заключенных в кавычки,

//возвращает индекс следующего разделителя

int Csv::advplain(const strings s, strings fid, int i)

{

int j;

j = s.find_first_of(fieldsep, i);

//поиск разделителя if (j > s.lengthO)

//разделитель не найден

j = s.lengthO; fid = string(s, i, j-i); return j;

}

И снова, как и в предыдущей версии, Csv::getfield абсолютно тривиальна, a Csv: :getnfield настолько коротка, что воплощена прямо в описании класса.

// getfield: возвращает n-e поле string Csv::getfield(int n) {

if (n < 0 n >= nfield) return ""; else return field[n];

}

Тестовая программа представляет собой несколько упрощенный йа-риант предыдущей версии:

// Csvtest main: тестирует класс Csv int main(void)

{

string line; Csv csv;

while (csv.getline(line) != 0)

{

cout « "Строка = '"

« line <<"'\n"; for (int i = 0; i < csv.getnfieldQ; i++)

cout « "Поле[" « i « "] = '" « csv.getfield(i) « '"\n";

}

return 0;

}

Использование библиотеки в C++ незначительно отличается от версии на С. В зависимости от компилятора новая версия в сравнении с С-версией дает замедление от 40 % до четырех раз на большом файле из 30 000 строк примерно по 25 полей на строку. Как мы уже выясняли при оценке быстродействия программы markov, подобный разброс зависит от степени проработанности используемых библиотек. Последнее, что остается добавить: исходный код версии C++ получился примерно на 20 % короче.

Упражнение 4-5

Введите в версию C++ оператор [ ], чтобы к полям можно было обращаться как к csv[i].

Упражнение 4-6

Напишите библиотеку CSV на Java, а затем сравните все три версии с точки зрения простоты и ясности, надежности и скорости.

Упражнение 4-7

Перепишите C++ версию кода CSV с использованием класса STL iterator.

Упражнение 4-8

Версия на C++ предоставляет возможность нескольким независимым экземплярам Csv работать одновременно, никак не мешая друг другу, — в этом выразилось важное достоинство инкапсуляции всего состояния объекта, экземпляры которого можно порождать многократно. Измените версию на С так, чтобы добиться подобного эффекта; для этого замените глобальные структуры данных структурами, выделение памяти для которых и инициализация осуществляются явным образом с помощью отдельной функции csvnew.