C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов читать книгу онлайн
С++ — объектно-ориентированный язык программирования, без которого сегодня немыслима промышленная разработка ПО. В этой замечательной книге описана работа с контейнерами, алгоритмами, вспомогательными классами, лямбда-выражениями и другими интересными инструментами, которыми богат современный С++. Освоив материал, вы сможете коренным образом пересмотреть привычный подход к программированию.
Преимущество издания — в подробном описании стандартной библиотеки шаблонов С++, STL. Ее свежая версия была выпущена в 2017 году. В книге вы найдете более 90 максимально реалистичных примеров, которые демонстрируют всю мощь STL. Многие из них станут базовыми кирпичиками для решения более универсальных задач.
Вооружившись этой книгой, вы сможете эффективно использовать С++17 для создания высококачественного и высокопроизводительного ПО, применимого в различных отраслях.
В этом примере мы реализуем точно такое же приложение. Наш небольшой клон grep будет принимать шаблон из командной строки, а затем выполнять рекурсивный поиск в том каталоге, где мы находимся на момент запуска приложения. Он выведет имена всех файлов, соответствующих нашему шаблону. Проверка на совпадение с шаблоном будет применяться построчно, так что можно вывести на экран еще и номера строк в файле, соответствующих шаблону.
Как это делается
В этом разделе мы реализуем небольшой инструмент, который выполняет поиск предоставленных пользователем текстовых шаблонов в файлах. Инструмент работает точно так же, как и инструмент UNIX grep, но для простоты будет не таким зрелым и эффективным.
1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространств имен std и filesystem:
#include <iostream>
#include <fstream>
#include <regex>
#include <vector>
#include <string>
#include <filesystem>
using namespace std;
using namespace filesystem;
2. Потом реализуем вспомогательную функцию. Она принимает путь к файлу и объект, содержащий регулярное выражение, описывающее искомый шаблон. Затем создаем экземпляр вектора, который будет включать пары, состоящие из номера строк и их содержимого. Кроме того, создадим экземпляр объекта файлового потока ввода, из которого будем считывать содержимое и сравнивать его с шаблоном строка за строкой:
static vector<pair<size_t, string>>
matches(const path &p, const regex &re)
{
vector<pair<size_t, string>> d;
ifstream is {p.c_str()};
3. Пройдем по файлу строка за строкой с помощью функции getline. Функция regex_search возвращает значение true при условии, что строка содержит наш шаблон. Если это именно так, то поместим в вектор номер строки и саму строку. Наконец, вернем все найденные совпадения:
string s;
for (size_t line {1}; getline(is, s); ++line) {
if (regex_search(begin(s), end(s), re)) {
d.emplace_back(line, move(s));
}
}
return d;
}
4. В функции main сначала проверим, предоставил ли пользователь аргумент командной строки, который можно задействовать как шаблон. Если нет, то сгенерируем ошибку:
int main(int argc, char *argv[])
{
if (argc != 2) {
cout << "Usage: " << argv[0] << " <pattern>n";
return 1;
}
5. Далее создадим объект регулярного выражения из входного шаблона. Невозможность создать такое регулярное выражение приведет к генерации исключения. При генерации исключения поймаем его и сгенерируем ошибку:
regex pattern;
try { pattern = regex{argv[1]}; }
catch (const regex_error &e) {
cout << "Invalid regular expression provided.n";
return 1;
}
6. Наконец, можно проитерировать по файловой системе и поискать совпадения с шаблоном. Воспользуемся итератором recursive_directory_iterator для итерации по всем файлам рабочего каталога. Он работает точно так же, как и итератор directory_iterator из предыдущего примера, но заходит еще и в подкаталоги. Таким образом, не нужно управлять рекурсией. Для каждой записи вызываем вспомогательную функцию matches:
for (const auto &entry :
recursive_directory_iterator{current_path()}) {
auto ms (matches(entry.path(), pattern));
7. Для каждого совпадения (если они есть) выводим путь к файлу, номер строки и содержимое строки, содержащей совпадение:
for (const auto &[number, content] : ms) {
cout << entry.path().c_str() << ":" << number
<< " - " << content << 'n';
}
}
}
8. Подготовим файл с именем "foobar.txt", содержащий тестовые строки, по которым можно выполнить поиск:
foo
bar
baz
9. Компиляция и запуск программы дадут следующий результат. Я запустил приложение в каталоге /Users/tfc/testdir моего ноутбука и сначала передал ему шаблон "bar". Внутри этого каталога приложение нашло вторую строку в нашем файле "foobar.txt" и другом файле "text1.txt", который находится в каталоге testdir/dir1:
$ ./grepper bar
/Users/tfc/testdir/dir1/text1.txt:1 - foo bar bla blubb
/Users/tfc/testdir/foobar.txt:2 - bar
10. При повторном запуске приложения с шаблоном "baz" оно находит третью строку в нашем примере текстового файла:
$ ./grepper baz
/Users/tfc/testdir/foobar.txt:3 - baz
Как это работает
Создание и использование регулярного выражения с целью фильтрации содержимого файлов — основная цель данного примера. Однако рассмотрим итератор recursive_directory_iterator, поскольку этот особый класс итераторов мы применяли для фильтрации файлов, по которым итерируем рекурсивно.
Как и directory_iterator, recursive_directory_iterator итерирует по элементам каталога. Он делает это рекурсивно, согласно своему названию. При встрече с элементом файловой системы, который является каталогом, он вернет экземпляр типа directory_entry для данного пути, а затем зайдет в него, чтобы проитерировать по его потомкам.
Итератор recursive_directory_iterator имеет несколько интересных функций-членов.
□ depth() — говорит, на сколько уровней итератор спустился в подкаталоге.
□ recursion_pending() — сообщает, будет ли итератор спускаться дальше после элемента, на который он указывает в данный момент.
□ disable_recursion_pending() — эту функцию можно вызвать, чтобы помешать итератору спуститься в следующий подкаталог, если сейчас он указывает на каталог, в который можно спуститься. Это значит, что вызов указанного метода ничего не даст, если мы совершим данное действие слишком рано.
□ pop() — эта функция прерывает работу на текущем уровне и поднимает итератор на один уровень вверх в иерархии каталогов для продолжения работы.
Дополнительная информация
Еще одной важной деталью, о которой нужно знать, выступает класс-перечисление directory_options. Конструктор класса recursive_directory_iterator принимает значение этого типа в качестве второго аргумента. Значением по умолчанию, которое мы использовали неявно, является directory_options::none. Другие его значения выглядят следующим образом:
□ follow_directory_symlink — позволяет рекурсивному итератору следовать по символьным ссылкам на каталоги;
□ skip_permission_denied — указывает итератору пропускать каталоги, которые в противном случае вернут ошибку, поскольку файловая система не дает прав на доступ к ним.
Эти настройки можно объединять с помощью оператора |.
Инструмент для автоматического переименования файлов
На создание этого примера меня сподвигла ситуация, в которую я попадаю довольно часто. Скажем,