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

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

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



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

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


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

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



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




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



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



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

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




Использование библиотеки в 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.