Шаблонные функции и классы. Библиотека стандартных шаблонов — КиберПедия 

Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...

Таксономические единицы (категории) растений: Каждая система классификации состоит из определённых соподчиненных друг другу...

Шаблонные функции и классы. Библиотека стандартных шаблонов

2018-01-28 281
Шаблонные функции и классы. Библиотека стандартных шаблонов 0.00 из 5.00 0 оценок
Заказать работу

 

Шаблонные функции

Многие алгоритмы работают одинаково независимо от того, для обработки каких данных они применяются. Например, алгоритм сортировки методом пузырька будет выглядеть одинаково независимо от того, сортируем ли мы массив целых чисел, вещественных чисел, строк или объектов нашего собственного типа. Для того, чтобы не писать практически один и тот же код, отличающийся лишь типом обрабатываемых данных, в языке C++ имеется возможность создания шаблонов функций (часто также используется термин "шаблонная функция"). Шаблонная функции объявляется с помощью ключевого слова template следующим образом:

template <class|typename параметр_шаблона [, class|typename параметр_шаблона …]> дальнейшее объявление функции

В качестве параметров шаблона могут выступать:

∙ параметры-типы (их иногда называют фиктивными типами),

∙ параметры обычных типов (например, int) – см. пункт 5.6,

∙ параметры-шаблоны – см. пункт 5.4.

Для примера напишем шаблонную функцию sqr, которая возвращает квадрат своего аргумента:

 

// Пример 5.1 - простая шаблонная функция

template <class T>

T sqr (const T x) {

return x * x;

}

 

В данном примере параметр-тип (фиктивный тип) назван T (мы могли бы дать ему любое имя). При вызове шаблонной функции можно либо явно указать реальный тип параметра в угловых скобках, либо предоставить возможность компилятору определить его автоматически на основании значения аргумента функции. Например, следующие две строчки работают одинаково:

 

std::cout << sqr<int>(5) << " " << sqr<double>(3.14) << std::endl;

std::cout << sqr(5) << " " << sqr(3.14) << std::endl;

 

В первой строчке мы явно сказали, что реальными типами параметра являются int и double. Во второй строчке компилятор сам определит, что значение 5 имеет тип int, а значение 3.14 – тип double.

В этом примере компилятор сгенерирует две разных реализации (экземпляра) функции sqr – одна для типа int и другая – для типа double. Процесс создания нового экземпляра шаблонной функции или класса называется инстанцированием шаблона.

Приведём более сложный пример. Напишем шаблонную функцию count, возвращающую количество элементов в контейнере с, равных заданному значению value. В качестве контейнера может выступать, например, массив, вектор (std::vector), список (std::list) и др.

 

// Пример 5.2 - шаблонная функция count

template <class ContainerType, class ElementType>

int count(const ContainerType &c, const ElementType &value) {

int answer = 0;

for (auto &x: c)

if (x == value)

answer++;

return answer;

}

 

int main() {

std::vector<int> a = {1, 3, 7, 3, 2};

std::cout << count(a, 3) << std::endl; // будет выведен ответ 2

}

Шаблонные классы

Шаблонные классы создаются аналогично шаблонным функциям. Для примера создадим шаблонный класс PasArray, позволяющий работать с массивами в стиле языка Pascal – в таких массивах левая граница не обязана быть равна нулю (и даже может быть отрицательной). По сути, наш шаблонный класс будет представлять собой обёртку над шаблонным классом std::vector. В операторе индексирования (operator[]) вычисляется настоящая позиция элемента в векторе и возвращается ссылка на этот элемент.

 

// Пример 5.3 - шаблонный класс PasArray

template<class T>

class PasArray{

int left; // левая граница

std::vector<T> data; // вектор для хранения элементов

public:

PasArray(int left, int right):

left(left),

data (right - left + 1) {}

T& operator [] (int index) {

return data[index - left];

}

};

 

int main() {

PasArray<int> a(-5, 5);

for (int i = -5; i <= 5; i++)

a[i] = i;

}

 

Поскольку при создании объекта шаблонного класса, в отличие от функции, компилятор не может автоматически определить тип параметра, то нам пришлось указывать его явным образом в угловых скобках.

Если углубиться немного в теорию языков программирования, то можно сказать, что шаблоны языка C++ являются реализацией методологии обобщённого программирования (generic programming). Суть обобщённого программирования заключается в таком описании данных и алгоритмов, которое можно применять к различным типам данных, не меняя само это описание. Как и объектно-ориентированное программирование (ООП), обобщенное программирование (ОП) в C++ основывается на полиморфизме, только ООП основано на полиморфизме подтипов, а ОП – на параметрическом (статическом) полиморфизме.

 

Специализация шаблонов

Иногда бывает нужно сделать так, чтобы для определённого типа данных шаблон функции или класса инстанцировался особым образом – не так, как для остальных типов. Рассмотрим пример. Пусть нам требуется написать шаблонную функцию swap, меняющую два своих аргумента местами. Вначале реализуем обычный способ замены двух переменных – с помощью вспомогательной третьей:

 

// Пример 5.4 - шаблонная функция swap

template<class T>

void swap(T &x, T&y) {

T tmp;

tmp = x; x = y; y = tmp;

}

 

Однако, для переменных целого типа существует интересный вариант замены, не требующий вспомогательной переменной. В нём используется битовая операция ^ (исключающее или). Допустим, нам по какой-то причине хочется, чтобы переменные типа int функция swap меняла местами именно таким способом. Для этого нужно выполнить специализацию этой функции для типа int:

 

// Пример 5.5 - специализация шаблона swap для типа int

template<>

void swap<int> (int &x, int &y) {

if (&x!= &y) {

x ^= y; y ^= x; x ^= y;

}

}

 

Обратите внимание на синтаксис: после слова template пишутся пустые угловые скобки, а после названия функции добавляется <int>. Примечание: для практических целей такой способ замены переменных вряд ли имеет какие-либо плюсы по сравнению с обычным.

Похожим образом выполняется и специализация шаблонных классов. Типичным примером из стандартной библиотеки является класс std::vector – он по-особому работает с типом данным bool. Хотя переменная типа bool обычно занимает один байт, но в std::vector<bool> каждый элемент занимает всего лишь один бит (это позволяет получить восьмикратную экономию памяти по сравнению с булевским массивом).

Интересно, что из-за этой особенности класса std::vector описанный в предыдущем пункте шаблонный класс PasArray не будет нормально работать с типом bool. Например, следующий код вызовет ошибку компиляции:

 

PasArray<bool> a(1, 5);

a[1] = true; // ошибка компиляции при попытке получить

// неконстантную ссылку на элемент вектора

 

Чтобы это исправить, нам нужно по-особому реализовать класс PasArray для типа bool, то есть выполнить специализацию этого шаблонного класса. Сделать это можно разными способами. Простейший вариант – вместо вектора из bool использовать вектор из char:

 

// Пример 5.6 - специализация шаблона PasArray для типа bool

template<>

class PasArray<bool>{

int left;

std::vector<char> data;

public:

PasArray(int left, int right):

left(left),

data (right - left + 1) {}

char& operator [] (int index) {

return data[index - left];

}

};

 

Заметим, что для решения конкретно этой проблемы можно обойтись и без специализации – достаточно разобраться по документации, что именно возвращает функция operator[] в классе std::vector. Данная операторная функция возвращает std::vector::reference, где reference – это ссылка на заданный элемент вектора (кроме std::vector<bool>), и это объект специального вспомогательного класса с перегруженной операцией присваивания для случая std::vector<bool>. Таким образом, нам достаточно лишь написать, что в классе PasArray функция operator[] будет возвращать тип std::vector<T>::reference. При этом отпадает необходимость в специализации, к тому же сохраняется экономия памяти, которую даёт вектор для типа bool:

 

// Пример 5.7 - реализация шаблонного класса PasArray,

// не требующая специализации для типа bool

template<class T>

class PasArray{

int left;

std::vector<T> data;

public:

PasArray(int left, int right):

left(left),

data (right - left + 1) {}

typename std::vector<T>::reference operator [] (int index)

{

return data[index - left];

}

};

 

Обратите внимание на ключевое слово typename – оно подсказывает компилятору, что запись std::vector<T>::reference означает тип (а не переменную или константу). Компилятору затруднительно определить это автоматически (так как в этом выражении используется фиктивный тип T), поэтому стандарт требует использовать слово typename в подобных случаях.

 


Поделиться с друзьями:

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰)...

История развития хранилищ для нефти: Первые склады нефти появились в XVII веке. Они представляли собой землянные ямы-амбара глубиной 4…5 м...

Поперечные профили набережных и береговой полосы: На городских территориях берегоукрепление проектируют с учетом технических и экономических требований, но особое значение придают эстетическим...

История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.028 с.