Существует текстовый файл следующего формата: Код (Text): ASCII_представление_хэша_1 = строка_1 ASCII_представление_хэша_2 = строка_2 ... ASCII_представление_хэша_N = строка_N причём (основная загвоздка здесь), строка_i может быть пустой, а может быть очень длинной. Фактически, формат совпадает с форматом *.ini файлов (за исключением наличия в оных секций). Требуется загрузить этот файл в контейнер map(вообще это не столь важно, куда загружать). Если использовать istream>>, то не понятно, как обходиться со знаком "=" и пустой строкой после "=". Точнее, понятно - после stream >> _string обрезать ненужные символы в начале строки. Переход от C++ к C, и использование fscanf, fgets предполагает наличие буфера определённого размера, а, как сказано выше, строка после знака "=" может быть очень длинной, т.е. это не лучший вариант. Можно читать побайтно с поиском '\n' и парсить полученную строку, но это неэффективно(?). Кто может предложить наиболее изящный метод решения этой задачи?
Не понял, что именно смущает. Без буфера все равно не обойтись (можно выделить память в куче). Вариантов ровно два: 1. читать в большой (макс. размера) буфер (gets) и отбросить лишнее и 2. на первом проходе выяснить размер (strchr), на втором проходе прочитать нужную часть и вручную сдвинуть указатель за конец строки (удобно при мэппинге файла в память). IMHO, первое проще и быстрее + гарантирует правильную трансляцию символов конца строки (встречаются дурные файлы, где OD, OA идут поодиночке и парами/тройками в непредсказуемых комбинациях). К слову, fgets и собственный парсинг всегда предпочтительнее, чем fscanf и кропотливая ловля ошибок ввода/форматирования в мегабайтных файлах. Потоки в C++, на мой взгляд, удобны для сериализации, но не для форматного I/O.
gets -- это вообще не вариант. Забудьте про эту функцию. Любое использование этой функции -- это гарантированный баг в программе типа "переполнение буфера". Если очень хочется, используйте fgets. Насчёт форматного i/o в C++ я не осведомлён. Но могу предложить такой вариант: прочитать всю строку в динамически выделяемый буфер*, а потом вручную разобрать. *) в STL где-то есть функция getline. Либо можно написать вручную на malloc/realloc. Но если вручную, то тогда уж проще сразу разбирать строку и выделять память под хеш и значение, а не под всю строку. Какая разница, после чего ловить ошибки форматирования в мегабайтных файлах? Найдёшь ли ты эту ошибку scanf'ом или вручную -- всё равно её исправлять придётся.
r90 getline решила спасибо за наводку. Вдогонку вопрос: есть ли в STL функция которая удалит все wite-space в начале строки? Можно использовать string::find_first_not_of(" \t") + string::erase(), но, может есть уже нечто подобное?
KeSqueer Если есть возможность использовать boost, то для первой задачи подошёл бы boost::tokenizer (или при более сложных конструкциях парсер на основе boost::qi || boost::karma, пишется легко, компактный, очень гибкий и быстрый ). Для работы со строками (в частности удаление пробелов и подобное) хорошо подходит boost::algorithm и в частности boost::trim || boost::replace || boost::erase. Если же ты сам этот файл составляешь,, записываешь и читаешь, то проще юзать boost::serialization, в котором существуют сериалайзеры для всех стандартных контейнеров.
W4FhLF Давно смотрел в сторону boost, но мне эта библиотека казалась слишком сложной, в C++ я всё же новичек ещё. Видимо придётся. По задаче, наверное, boost::serialization подойдёт как раз.
Ну вряд ли буст будет слишком сложен, если ты юзаешь STL. Тем более сейчас в буста неплохая справка и в гугле полно примеров. Сериализация std::map в бинарном формате и обратно будет выглядеть следующим образом: Код (Text): #include <iostream> #include <fstream> #include <iterator> #include <algorithm> #include <functional> #include <numeric> #include <map> #include <boost/bind.hpp> #include <boost/assign/std/map.hpp> #include <boost/archive/xml_iarchive.hpp> #include <boost/archive/xml_oarchive.hpp> #include <boost/serialization/map.hpp> using namespace boost::assign; int main(int argc, char* argv[]) { typedef std::map<std::string, std::string> StringMap; // Save { StringMap smap; insert(smap)("Hash1", "String1")("Hash2", "String2") ("Hash3", "String3")("Hash4", "String4"); std::ofstream ofs("serializedMap.bin", std::ios::binary); boost::archive::binary_oarchive oa(ofs); oa & smap; } // Load { StringMap smap; std::ifstream ifs("serializedMap.bin", std::ios::binary); boost::archive::binary_iarchive ia(ifs); ia & smap; // Let's see the result std::ostream_iterator<string> oit(cout, "\n"); std::transform(smap.begin(), smap.end(), oit, bind(&StringMap::value_type::second, _1)); } }
W4FhLF Прокомментируйте, пожалуйста, строки: Код (Text): insert(smap)("Hash1", "String1")("Hash2", "String2") ("Hash3", "String3")("Hash4", "String4"); и Код (Text): std::transform(smap.begin(), smap.end(), oit, bind(&StringMap::value_type::second, _1));
Первое - это feature из boost::assign. Эквивалентно: Код (Text): smap.insert(std::make_pair("Hash1", "String1")); smap.insert(std::make_pair("Hash2", "String2")); Запись с использованием boost::assign короче и нагляднее. Второе - это простой лямба-функтор. Берём поочерёдно все value из std::map (т.е. std::pair::second) и присваиваем созданому потоковому итератору oit, который выводит всё в поток cout через разделители "\n". Это вместо цикла. На самом деле это всё навороты, можно обойтись и без этого.
KeSqueer Все уже придумано до нас. Код (Text): boost::property_tree::ptree tree; boost::property_tree::ini_parser::read_ini("config.ini", tree);