19.x – Резюме к главе 19 и небольшой тест
Шаблоны позволяют нам писать функции и классы с использованием типов-заполнителей, поэтому мы можем создавать образцы идентичных версий функций и классов, использующих разные типы. Созданная функция или класс называется экземпляром функции или класса.
Все шаблоны функций и классов должны начинаться с объявления параметра шаблона, которое сообщает компилятору, что следующая функция или класс является шаблоном функции или класса. В объявлении параметров шаблона указываются параметры-типы шаблона и/или параметры-выражения. Параметры-типы шаблона – это просто типы-заполнители, обычно называемые T
, T1
, T2
или другими однобуквенными именами (например, S
). Параметры-выражения обычно являются целочисленными типами, но могут быть указателем или ссылкой на функцию, на объект класса или на функцию-член.
Разделение определения шаблона класса и определений функций-членов работает не так, как у обычных классов – вы не можете поместить определение класса в заголовок, а определения функций-членов в файл .cpp. Обычно лучше всего хранить всё в заголовочном файле, а определения функций-членов – там же, под классом.
Когда мы хотим переопределить поведение по умолчанию шаблонной функции или класса для определенного типа, можно использовать специализацию шаблона. Если переопределены все типы, это называется полной специализацией. Классы также поддерживают частичную специализацию, когда специализированы только некоторые из шаблонных параметров. Функции не могут быть частично специализированными.
Многие классы стандартной библиотеки C++, включая std::array
и std::vector
, используют шаблоны. Шаблоны часто используются для реализации классов контейнеров, поэтому контейнер можно написать один раз и использовать с любым подходящим типом.
Небольшой тест
Вопрос 1
Иногда полезно определять данные, которые передаются парами. Напишите шаблон класса с именем Pair1
, который позволяет пользователю определять один шаблонный тип, который используется для обоих значений в паре. Должна работать следующая функция:
int main()
{
Pair1<int> p1 { 5, 8 };
std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n';
const Pair1<double> p2 { 2.3, 4.5 };
std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n';
return 0;
}
Должно напечататься:
Pair: 5 8
Pair: 2.3 4.5
Ответ
#include <iostream> template <typename T> class Pair1 { private: T m_x {}; T m_y {}; public: Pair1(const T& x, const T& y) : m_x{ x }, m_y{ y } { } const T& first() const { return m_x; } const T& second() const { return m_y; } }; int main() { Pair1<int> p1 { 5, 8 }; std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n'; const Pair1<double> p2 { 2.3, 4.5 }; std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n'; return 0; }
Вопрос 2
Напишите класс Pair
, который позволяет вам указывать отдельный тип для каждого из двух значений в паре.
Примечание. Мы называем этот класс иначе, чем предыдущий, потому что C++ в настоящее время не позволяет вам «перегружать» классы, которые отличаются только количеством или типом шаблонных параметров.
Следующая программа должна заработать:
int main()
{
Pair<int, double> p1 { 5, 6.7 };
std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n';
const Pair<double, int> p2 { 2.3, 4 };
std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n';
return 0;
}
и напечатать
Pair: 5 6.7
Pair: 2.3 4
Подсказка: чтобы определить шаблон с использованием двух разных типов, в объявлении параметров шаблона разделите эти два типа запятой. Дополнительную информацию смотрите в уроке «8.13 – Шаблоны функций».
Ответ
#include <iostream> template <typename T, typename S> class Pair { private: T m_x; S m_y; public: Pair(const T& x, const S& y) : m_x(x), m_y(y) { } const T& first() const { return m_x; } const S& second() const { return m_y; } }; int main() { Pair<int, double> p1 { 5, 6.7 }; std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n'; const Pair<double, int> p2 { 2.3, 4 }; std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n'; return 0; }
Вопрос 3
Пара строка-значение – это особый тип пары, где первое значение всегда является строковым типом, а второе значение может быть любого типа. Напишите шаблон класса с именем StringValuePair
, который наследуется от частично специализированного класса Pair
(использующего std::string
в качестве первого типа и позволяющего пользователю указать второй тип).
Следующая программа должна запуститься:
int main()
{
StringValuePair<int> svp { "Hello", 5 };
std::cout << "Pair: " << svp.first() << ' ' << svp.second() << '\n';
return 0;
}
и напечатать:
Pair: Hello 5
Подсказка: когда вы вызываете конструктор Pair
из конструктора StringValuePair
, не забудьте включить параметры шаблона как часть имени класса Pair
.
Ответ
#include <iostream> #include <string> template <typename T, typename S> class Pair { private: T m_x {}; S m_y {}; public: Pair(const T& x, const S& y) : m_x { x } , m_y { y } { } const T& first() const { return m_x; } const S& second() const { return m_y; } }; template <typename 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 { "Hello", 5 }; std::cout << "Pair: " << svp.first() << ' ' << svp.second() << '\n'; return 0; }