Еще одна глава позади. Пора закрепить пройденный материал.
Теория
Шаблоны позволяют написать одну версию функции или класса, которая будет работать с разными типами данных. Функция или класс, реализованная через шаблон, с фактическим (одним) типом данных называется экземпляром.
Все шаблоны функций или классов должны начинаться с ключевого слова template и объявления параметров шаблона. В объявлении параметров шаблона указываются параметры типа и параметры non-type.
Параметр типа шаблона — это параметр, который отвечает за типы данных, с которыми будет работать шаблон, обычно его называют T
, T1
, T2
или другими (одиночными) буквами (например, S
).
Параметром non–type может быть переменная интегрального типа данных (например, char, bool, int, long, short), указатель/ссылка на функцию или на метод/объект класса, std::nullptr_t.
Разделение определения шаблонов класса и его методов по разным файлам не работает как с обычными классами — вы не можете поместить определение шаблона класса в заголовочный файл, а определение шаблонов методов этого класса в отдельный файл .cpp. Как правило, лучше всё хранить в заголовочном файле с определениями шаблонов методов под определением шаблона класса.
Явная специализация шаблона используется для определения реализации, отличающейся от общей, функции или класса при работе с определенным типом данных. Если все параметры специализации шаблона явно определены, то это полная специализация. Классы также поддерживают частичную специализацию, при которой не все параметры шаблона должны быть явно определены. В C++14 частичная специализация шаблонов функций запрещена.
Многие классы в Стандартной библиотеке C++ (такие как std::array и std::vector) используют шаблоны. Шаблоны часто применяются для реализации контейнерных классов, которые можно один раз написать и использовать с любыми типами данных.
Тест
Задание №1
Предположим, что нам нужно передавать данные парами. Реализуйте шаблон класса Pair1, который позволяет пользователю передавать данные одного типа парами. Следующий код:
1 2 3 4 5 6 7 8 9 10 |
int main() { Pair1<int> p1(6, 9); std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n'; const Pair1<double> p2(3.4, 7.8); std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n'; return 0; } |
Должен выдавать следующий результат:
Pair: 6 9
Pair: 3.4 7.8
Ответ №1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> template <class T> class Pair1 { private: T m_a; T m_b; public: Pair1(const T& a, const T& b) : m_a(a), m_b(b) { } T& first() { return m_a; } const T& first() const { return m_a; } T& second() { return m_b; } const T& second() const { return m_b; } }; int main() { Pair1<int> p1(6, 9); std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n'; const Pair1<double> p2(3.4, 7.8); std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n'; return 0; } |
Задание №2
Реализуйте класс Pair, который позволяет пользователю использовать разные типы данных в передаваемых парах. Следующий код:
1 2 3 4 5 6 7 8 9 10 |
int main() { Pair<int, double> p1(6, 7.8); std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n'; const Pair<double, int> p2(3.4, 5); std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n'; return 0; } |
Должен выдавать следующий результат:
Pair: 6 7.8
Pair: 3.4 5
Подсказка: Для определения шаблона с использованием двух разных типов, просто разделите параметры типа шаблона запятой.
Ответ №2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> template <class T, class S> class Pair { private: T m_a; S m_b; public: Pair(const T& a, const S& b) : m_a(a), m_b(b) { } T& first() { return m_a; } const T& first() const { return m_a; } S& second() { return m_b; } const S& second() const { return m_b; } }; int main() { Pair<int, double> p1(6, 7.8); std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n'; const Pair<double, int> p2(3.4, 5); std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n'; return 0; } |
Задание №3
Напишите шаблон класса StringValuePair, в котором первое значение всегда является типа string, а второе может быть любого типа. Этот шаблон класса должен наследовать частично специализированный класс Pair (в котором первый параметр типа std::string, а второй — «любой тип данных»). Следующий код:
1 2 3 4 5 6 7 |
int main() { StringValuePair<int> svp("Amazing", 7); std::cout << "Pair: " << svp.first() << ' ' << svp.second() << '\n'; return 0; } |
Должен выдавать следующий результат:
Pair: Amazing 7
Подсказка: При вызове конструктора класса Pair из конструктора класса StringValuePair, не забудьте указать, что параметры относятся к классу Pair.
Ответ №3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> #include <string> template <class T, class S> class Pair { private: T m_a; S m_b; public: Pair(const T& a, const S& b) : m_a(a), m_b(b) { } T& first() { return m_a; } const T& first() const { return m_a; } S& second() { return m_b; } const S& second() const { return m_b; } }; template <class S> class StringValuePair : public Pair<std::string, S> { public: StringValuePair(const std::string& key, const S& value) : Pair<std::string, S>(key, value) { } }; int main() { StringValuePair<int> svp("Amazing", 7); std::cout << "Pair: " << svp.first() << ' ' << svp.second() << '\n'; return 0; } |
Ответ на 1-е задание:
Ответ на 2-е задание:
С 3-м заданием был затык с вызовом конструктора Pair, но в итоге
разобрался:
Мой вариант:
В задании 3 написано: "Этот шаблон класса должен наследовать частично специализированный класс Pair". В оригинальном решении нет этого шаблона.
Мой вариант:
Дублирование кода — не есть хорошо. К тому же, в данном случае, абсолютно нету смысла в частичной специализации для string, ведь общий шаблон сгенерил бы абсолютно такой же код.