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

				
			C++17 STL Стандартная библиотека шаблонов читать книгу онлайн
С++ — объектно-ориентированный язык программирования, без которого сегодня немыслима промышленная разработка ПО. В этой замечательной книге описана работа с контейнерами, алгоритмами, вспомогательными классами, лямбда-выражениями и другими интересными инструментами, которыми богат современный С++. Освоив материал, вы сможете коренным образом пересмотреть привычный подход к программированию.
    Преимущество издания — в подробном описании стандартной библиотеки шаблонов С++, STL. Ее свежая версия была выпущена в 2017 году. В книге вы найдете более 90 максимально реалистичных примеров, которые демонстрируют всю мощь STL. Многие из них станут базовыми кирпичиками для решения более универсальных задач.
    Вооружившись этой книгой, вы сможете эффективно использовать С++17 для создания высококачественного и высокопроизводительного ПО, применимого в различных отраслях.
1. Включим несколько необходимых заголовочных файлов, а затем объявим об использовании пространства имен std по умолчанию:
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
2. Затем снова реализуем функцию std::tolower, которая уже определена в <cctype>. Уже существующая функция работает хорошо, но она не имеет модификатора constexpr. Однако отдельные строковые функции имеют этот модификатор, начиная с C++17, и мы хотим иметь возможность использовать их для нашего собственного строкового класса-типажа. Функция соотносит символы в верхнем регистре с символами в нижнем регистре и оставляет другие символы неизменными:
static constexpr char tolow(char c) {
switch (c) {
case 'A'...'Z': return c - 'A' + 'a';
default: return c;
}
}
3. Класс std::basic_string принимает три шаблонных параметра: тип основного символа, класс-типаж символа и тип allocator. В этом разделе мы изменяем только класс-типаж символа, поскольку он определяет поведение строк. Для повторной реализации только того, что должно отличаться от типичного поведения строк, мы явно наследуем от стандартного класса-типажа:
class lc_traits : public char_traits<char> {
public:
4. Наш класс принимает входные строки, но преобразует их в строки в нижнем регистре. Существует функция, которая делает это посимвольно, так что можно поместить здесь нашу функцию tolow. Она имеет модификатор constexpr, именно поэтому мы и реализовали самостоятельно функцию tolow с таким же модификатором constexpr:
static constexpr
void assign(char_type& r, const char_type& a ) {
r = tolow(a);
}
5. Еще одна функция обрабатывает копирование целой строки в отдельный участок памяти. Мы используем вызов std::transform, чтобы скопировать все символы из исходной строки в строку — место назначения, и в то же время соотносим каждый символ с его версией в нижнем регистре:
static char_type* copy(char_type* dest,
const char_type* src,
size_t count) {
transform(src, src + count, dest, tolow);
return dest;
}
};
6. Еще один типаж помогает создать строковый класс, эффективно преобразующий строки в нижний регистр. Напишем еще один типаж, который не изменяет полученную строку, но позволяет выполнять сравнение строк независимо от регистра. Снова унаследуем от существующего стандартного класса-типажа для символов и в этот раз переопределим некоторые члены функции:
class ci_traits : public char_traits<char> {
public:
7. Функция eq указывает, равны ли два символа. Реализуем такую же функцию, но будем сравнивать версии символов в нижнем регистре. При использовании этой функции символ 'A' равен символу 'a'.
static constexpr bool eq(char_type a, char_type b) {
return tolow(a) == tolow(b);
}
8. Функция lt указывает, меньше ли значение a значения b. Применим корректный логический оператор, но только после того, как оба символа будут преобразованы к нижнему регистру:
static constexpr bool lt(char_type a, char_type b) {
return tolow(a) < tolow(b);
}
9. Последние две функции работали для посимвольного ввода, а следующие две будут работать для строк. Функция compare работает по аналогии со старой функцией strncmp. Она возвращает значение 0 при условии, что обе строки равны от начала до позиции, указанной в переменной count. Если они отличаются, то функция возвращает отрицательное или положительное число, которое указывает, какая из строк была меньше с точки зрения лексикографии. Определение разности между обоими символами в каждой позиции должно, конечно же, происходить для символов в нижнем регистре. Положительный момент заключается в том, что весь код цикла является частью функции с модификатором constexpr, начиная с C++14.
static constexpr int compare(const char_type* s1,
const char_type* s2,
size_t count) {
for (; count; ++s1, ++s2, --count) {
const char_type diff (tolow(*s1) - tolow(*s2));
if (diff < 0) { return -1; }
else if (diff > 0) { return +1; }
}
return 0;
}
10. Последняя функция, которую нужно реализовать для нашего строкового класса, не зависящего от регистра, — это функция find. Для заданной входной строки p и ее длины count она определяет позицию символа ch. Затем возвращает указатель на первое включение данного символа или nullptr, если такого символа нет. Сравнение в этой функции должно выполняться с использованием функции tolow, чтобы поиск не зависел от регистра. К сожалению, мы не можем применить функцию std::find_if, поскольку она не имеет модификатора constexpr, нужно писать цикл самостоятельно.
static constexpr
const char_type* find(const char_type* p,
size_t count,
const char_type& ch) {
const char_type find_c {tolow(ch)};
for (; count != 0; --count, ++p) {
if (find_c == tolow(*p)) { return p; }
}
return nullptr;
}
};
11. О’кей, с типажами мы закончили. Теперь можно определить два новых строковых типа. lc_string означает lower-case string (строка в нижнем регистре). ci_string расшифровывается как case-insensitive string (строка, не зависящая от регистра). Оба класса отличаются от класса std::string своими классами-типажами для символов:
using lc_string = basic_string<char, lc_traits>;
using ci_string = basic_string<char, ci_traits>;
12. Чтобы позволить потокам вывода принимать эти новые классы для вывода на экран, нужно перегрузить потоковый оператор <<:
ostream& operator<<(ostream& os, const lc_string& str) {
return os.write(str.data(), str.size());
}
ostream& operator<<(ostream& os, const ci_string& str) {
return os.write(str.data(), str.size());
}
13. Теперь наконец можно начать реализовывать саму программу. Создадим экземпляр обычной строки, строки в нижнем регистре и строки, не зависящей от регистра, и сразу же выведем их на экран. Строки в нижнем регистре будут соответствовать своим названиям — их символы будут иметь нижний регистр:
int main()
{
cout << " string: "
<< string{"Foo Bar Baz"} << 'n'
<< "lc_string: "
<< lc_string{"Foo Bar Baz"} << 'n'
<< "ci_string: "