C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Шрифт:
Интервал:
8. Теперь у нас есть итератор, который будет возвращать все найденные ссылки и их описания. Мы предоставим его и итератор того же типа, созданный по умолчанию, функции print, реализованной нами ранее:
print(it, {});
}
9. Компиляция и запуск программы дадут следующий результат. Я запустил программу curl для домашней страницы ISO C++, которая просто загружает HTML-страницу из Интернета. Конечно, я мог и написать cat some_html_file.html | ./link_extraction. Использованное нами регулярное выражение довольно жестко определяет представление о том, как должны выглядеть ссылки в документе HTML. В качестве самостоятельной работы можете сделать его более обобщенным.
$ curl -s "https://isocpp.org/blog" | ./link_extraction
Sign In / Suggest an Article : https://isocpp.org/member/login
Register : https://isocpp.org/member/register
Get Started! : https://isocpp.org/get-started
Tour : https://isocpp.org/tour
C++ Super-FAQ : https://isocpp.org/faq
Blog : https://isocpp.org/blog
Forums : https://isocpp.org/forums
Standardization : https://isocpp.org/std
About : https://isocpp.org/about
Current ISO C++ status : https://isocpp.org/std/status
(...и многие другие...)
Как это работает
Регулярные выражения (или коротко regex) очень полезны. Они могут казаться очень сложными, но вам стоит изучить принципы их работы. Короткое регулярное выражение может избавить от необходимости писать множество строк кода, что пришлось бы сделать при выполнении проверки на соответствие вручную.
В данном примере мы сначала создали объект типа регулярных выражений. Мы передали его конструктору строку, которая описывает регулярное выражение. Самое простое регулярное выражение выглядит как ".", оно соответствует любому символу, поскольку точка — это специальный символ для регулярного выражения. Выражение "a" соответствует только символам 'a'. Выражение "ab*" означает «один символ а, а затем ноль или больше символов b» и т.д. Регулярные выражения — довольно обширная тема, более подробную информацию можно найти в «Википедии» и на других сайтах и в литературе.
Еще раз взглянем на регулярное выражение, соответствующее нашему представлению о ссылках HTML. Простая ссылка HTML может выглядеть как <a href="some_url.com/foo">A great link</a>. Нужно получить часть some_url.com/foo, а также A great link. Мы создали следующее регулярное выражение, которое содержит группы для соответствия подстрокам (рис. 7.2).
Полное совпадение всегда является группой 0. В данном случае это будет вся строка <a href ... </a>. Заключенная в кавычки часть href, которая содержит URL, — это группа 1. Скобки в регулярном выражении определяют такие группы. Их у нас две. Еще одна группа — фрагмент текста между тегами <a...> и </a>, содержащий описание ссылки.
Существует множество функций STL, которые принимают объекты регулярных выражений. Однако мы непосредственно использовали адаптер для итератора, работающего с токенами регулярного выражения. Он представляет собой высокоуровневую абстракцию, применяющую std::regex_search, чтобы автоматизировать работу по поиску совпадений. Мы создали его экземпляр следующим образом:
sregex_token_iterator it {begin(in), end(in), link_re, {1, 2}};
Части begin и end обозначают нашу входную строку, по которой итератор, работающий с токенами регулярного выражения, будет итерировать и искать все ссылки. link_re — это, конечно, сложное регулярное выражение, созданное нами для поиска ссылок. Часть {1, 2} — еще один непонятный фрагмент. Он дает итератору команду опускать полное совпадение и возвращать группу 1, затем инкрементировать итератор и возвращать группу 2, а позже, после очередной операции инкремента, находить следующее совпадение в строке. Это разумное поведение освобождает нас от необходимости писать ненужные строки кода.
Давайте рассмотрим еще один пример. Представим регулярное выражение "a(b*)(c*)". Оно будет соответствовать строкам, содержащим символ a, после которого идет ноль или больше символов b, а затем — ноль или больше символов c:
const string s {" abc abbccc "};
const regex re {"a(b*)(c*)"};
sregex_token_iterator it {begin(s), end(s), re, {1, 2}};
print( *it ); // выводит b
++it;
print( *it ); // выводит c
++it;
print( *it ); // выводит bb
++it;
print( *it ); // выводит ccc
Существует также класс std::regex_iterator, который генерирует подстроки, находящиеся между совпадениями с регулярными выражениями.
Удобный и красивый динамический вывод чисел на экран в зависимости от контекста
В предыдущем примере было показано, как отформатировать выходные данные с помощью потоков вывода. Во время решения данной задачи мы узнали следующее:
□ большая часть манипуляторов являются стойкими, поэтому нужно отменять их действие, чтобы не пересечься с другим несвязанным кодом, который также выводит данные на экран;
□ создавать длинные цепочки манипуляторов ввода/вывода, чтобы вывести несколько переменных с конкретным форматированием, может быть утомительно, а код получится не очень читабельным.
По этим причинам многие пользователи не любят потоки ввода/вывода и даже в С++ все еще применяют функцию print для форматирования строк.
Ниже мы увидим, как форматировать типы динамически, не прибегая к чрезмерно большому количеству манипуляторов ввода/вывода.
Как это делается
В этом примере мы реализуем класс format_guard, способный автоматически отменить любые настройки форматирования. Кроме того, напишем тип-оболочку, который может содержать любые значения, но при выводе будет применять особое форматирование, не загружая код лишними манипуляторами ввода/вывода.
1. Сначала включим некоторые заголовочные файлы и объявим об использовании пространства имен std:
#include <iostream>
#include <iomanip>
using namespace std;
2. Вспомогательный класс, очищающий состояние форматирования для потоков, называется format_guard. В его конструкторе сохраняются флаги форматирования, которые были заданы для std::cout в момент создания объекта. Его деструктор восстанавливает их состояние на момент вызова конструктора. Это, по сути, отменяет все настройки форматирования, примененные между вызовами данных методов.
class format_guard {
decltype(cout.flags()) f {cout.flags()};
public:
~format_guard() { cout.flags(f); }
};
3. Еще один небольшой вспомогательный класс называется scientific_type. Поскольку это шаблон класса, он может оборачивать любой тип, который будет представлять собой переменную-член. По сути, он ничего не делает.
template <typename T>
struct scientific_type {
T value;
explicit scientific_type(T val) : value{val} {}
};
4. Можно определить собственные настройки форматирования для любого типа, который ранее был обернут в scientific_type, поскольку в случае перегрузки оператора потока >> потоковая библиотека будет выполнять совершенно другой код при выводе подобных типов. Таким образом, можно выводить научные значения в научном представлении
Поделиться книгой в соц сетях:
Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!