Допустим, вы хотели написать функцию для определения максимального из двух чисел. Вы можете сделать это так:
int max(int x, int y) < return (x >y) ? x : y; >
Хотя вызывающий может передавать в функцию разные значения, тип параметров фиксирован, поэтому вызывающий может передавать только значения типа int . Это означает, что эта функция хорошо работает на самом деле только для чисел int (и типов, для которых может быть выполнено расширяющее преобразование до int ).
Итак, что будет позже, когда вы захотите найти максимум из двух значений double ? Поскольку C++ требует от нас указания типов всех параметров функции, решение состоит в создании новой перегруженной версии max() с параметрами типа double :
double max(double x, double y) < return (x >y) ? x: y; >
Обратите внимание, что код для реализации версии max() для double точно такой же, как и для версии max() для int ! Фактически, эта реализация работает для многих разных типов: включая int , double , long , long double и даже новых типов, которые вы создали сами (как это сделать, мы рассмотрим в будущих уроках).
Шаблоны классов с++ примеры. Обобщенные классы. Изучение С++ для начинающих. Урок #126
Необходимость создавать перегруженные функции с одинаковой реализацией для каждого набора типов параметров, которые мы хотим поддерживать, будет головной болью при обслуживании, рецептом ошибок и явным нарушением принципа DRY (don’t repeat yourself, не повторяйся). Здесь также есть менее очевидная проблема: программист, который хочет использовать функцию max() , может пожелать вызвать ее с типом параметра, который автор max() не ожидал (и, следовательно, не написал для него перегруженную функцию).
Чего нам действительно не хватает, так это способа написать одну единственную версию max() , которая может работать с аргументами любого типа (даже с типами, о которых, возможно, не задумывались, когда писался код для max() ). Обычные функции здесь просто не подходят. К счастью, C++ поддерживает еще одно средство, которое было разработано специально для решения такого рода задач.
Добро пожаловать в мир шаблонов C++.
Введение в шаблоны C++
В C++ система шаблонов была разработана для упрощения процесса создания функций (или классов), которые могут работать с разными типами данных.
Вместо того чтобы вручную создавать группу почти идентичных функций или классов (по одному для каждого набора разных типов), мы вместо этого создаем единый шаблон. Как и обычное определение, шаблон описывает, как выглядит функция или класс. В отличие от обычного определения (где должны быть указаны все типы), в шаблоне мы можем использовать один или несколько типов-заполнителей. Тип-заполнитель представляет собой какой-либо тип, который неизвестен на момент написания шаблона, но будет предоставлен позже.
После того, как шаблон определен, компилятор может использовать его для создания необходимого количества перегруженных функций (или классов), каждая из которых использует разные реальные типы!
Конечный результат тот же – мы получаем кучу в основном идентичных функций или классов (по одному для каждого набора разных типов). Но нам нужно создать и поддерживать только один шаблон, а всю тяжелую работу за нас делает компилятор.
[C++] Шаблонные функции и шаблонные классы
Ключевые выводы
Компилятор может использовать один шаблон для создания семейства связанных функций или классов, каждый из которых использует свой набор типов.
В качестве отступления.
Поскольку концепцию шаблонов сложно описать словами, давайте попробуем провести аналогию.
Если бы вы искали слово «шаблон» в словаре, вы бы нашли определение, подобное следующему: «шаблон – это модель, которая служит образцом для создания похожих объектов». Например, один из типов шаблонов, который очень легко понять, – это трафарет. Трафарет – это тонкий кусок материала (например, кусок картона или пластика) с вырезанной в нем фигурой (например, лицо).
Поместив трафарет поверх другого объекта, а затем, распылив краску через отверстие, вы можете очень быстро воспроизвести вырезанную фигуру. Сам трафарет нужно создать только один раз, а затем его можно использовать повторно сколько угодно раз, чтобы создавать вырезанную фигуру в сколь угодно разных цветах. Более того, цвет фигуры, созданной с помощью трафарета, не нужно определять до тех пор, пока трафарет не будет использован.
Шаблон – это, по сути, трафарет для создания функций или классов. Мы создаем шаблон (наш трафарет) один раз, а затем можем использовать его столько раз, сколько необходимо, для создания функции или класса для определенного набора фактических типов. Эти фактические типы не нужно определять до фактического использования шаблона.
Поскольку фактические типы не определяются до тех пор, пока шаблон не будет использован в программе (а не при написании шаблона), автору шаблона не нужно пытаться предвидеть все фактические типы, которые могут быть использованы. Это означает, что код шаблона можно использовать с типами, которых даже не существовало на момент его написания! Мы увидим, как это пригодится позже, когда мы начнем изучать стандартную библиотеку C++, которая полностью заполнена кодом шаблонов!
Ключевые выводы
Шаблоны могут работать с типами, которых даже не существовало на момент их написания. Это помогает сделать код шаблона гибким и ориентированным на будущее!
В оставшейся части этого урока мы познакомимся и рассмотрим, как создавать шаблоны для функций, а также более подробно опишем, как они работают. Мы отложим обсуждение шаблонов классов до тех пор, пока не разберемся, что такое классы.
Шаблоны функций
Шаблон функции – это определение, подобное функции, которое используется для создания одной или нескольких перегруженных функций, каждая из которых имеет свой набор фактических типов. Это то, что позволит нам создавать функции, которые могут работать с множеством разных типов.
Когда мы создаем наш шаблон функции, для любых типов параметров, возвращаемых типов или типов, используемых в теле функции, которые мы хотим указать позже, мы используем типы-заполнители (также называемые шаблонными типами).
Шаблоны функций лучше всего изучать на примере, поэтому давайте преобразуем нашу обычную функцию max(int, int) из примера выше в шаблон функции. Это на удивление просто, и мы объясним, что происходит в процессе.
Создание шаблонной функции max
Вот снова версия max() для int :
int max(int x, int y) < return (x >y) ? x : y; >
Обратите внимание, что мы используем тип int в этой функции три раза: один раз для параметра x , один раз для параметра y и один раз для типа возвращаемого значения функции.
Чтобы создать шаблон функции, нам нужно сделать две вещи. Во-первых, мы собираемся заменить наши конкретные типы шаблонными типами. В этом случае, поскольку у нас есть только один тип, который нужно заменить ( int ), нам нужен только один шаблонный тип. Обычно для обозначения шаблонных типов используются одиночные заглавные буквы (начиная с T ).
Вот наша новая функция, которая использует один шаблонный тип:
T max(T x, T y) // не будет компилироваться, потому что мы не определили T < return (x >y) ? x : y; >
Лучшая практика
Для обозначения шаблонных типов используйте одиночные заглавные буквы (начиная с T ). Например, T , U , V и т.д.
Это хорошее начало, но этот код не скомпилируется, потому что компилятор не знает, что такое T ! И это по-прежнему обычная функция, а не шаблон функции.
Во-вторых, мы собираемся сообщить компилятору, что это шаблон функции, а T – это шаблонный тип. Это делается с помощью так называемого объявления параметра шаблона:
template // это объявление параметра шаблона T max(T x, T y) // это определение шаблона функции для max < return (x >y) ? x : y; >
Давайте подробнее рассмотрим объявление параметра шаблона. Начнем с ключевого слова template , которое сообщает компилятору, что мы создаем шаблон. Затем, в угловых скобках ( <> ) мы указываем все шаблонные типы, которые наш шаблон будет использовать. Для каждого шаблонного типа мы используем ключевое слово typename или class , за которым следует имя шаблонного типа (например, T ).
В качестве отступления.
В этом контексте нет разницы между ключевыми словами typename и class . Вы часто будете видеть, как люди используют ключевое слово class , поскольку оно было введено в язык раньше. Однако мы предпочитаем новое ключевое слово typename , потому что оно проясняет, что шаблонный тип может быть заменен любым типом (например, базовым типом), а не только типами классов.
Поскольку этот шаблон функции имеет один шаблонный тип (с именем T ), мы будем называть его max .
Связанный контент
Шаблоны функций Цель работы
2. Составить, выполнить и протестировать указанную задачу с помощью компьютера.
Порядок написания программы
Выполняем задание с использованием готового текста проекта по теме «Функции и матрицы».
1. Внимательно прочитать условие задачи и формулировку функций.
2. Для каждой функции:
Понять, какое данное мы хотим обобщить;
Меняем конкретный тип на параметризованный;
Определить, какие данные в функции связаны с параметризованным данным по типу и у них также заменить тип на параметризованный.
Замечание. В функции можно, при необходимости, вводить несколько параметризованных типов.
3. Правильно оформить тексты функций пользователя.
4. Правильно оформить прототипы функций.
5. Написать функцию mainс вызовом функций пользователя для матриц целого и вещественного типа (убедиться в правильности работы шаблона).
6. Создать проект, учитывая его особенности с использованием шаблонов. Выполнить проект.
Примечание. Вызов функцииRusперед строками-константами с русским текстом осуществляйте самостоятельно.
Примеры написания программ
Задача. Написать функцию-шаблон для вывода двумерного массива на экран.
Функция должна выводить двумерный массив любого типа, следовательно, параметризованный тип выражает тип массива
Программа будет строиться в виде проекта.
Текст заголовочного файла shablon.h будет выглядеть так:
#include //подключение системных средств для
using namespace std; //возможности использовать потоки ввода-вывода
template class T> //пишем у каждой параметризованной функции
void OutMas(T*p,int n,int m)
Источник: studfile.net
Шаблоны и шаблонные функции в C++. Введение
Давайте рассмотрим простой пример. Допустим, у нас есть функция, которая меняет местами значения двух переменных типа int:
#include void my_swap ( int second ) < int temp ( first ) ; first = second ; second = temp ; >int main ()
Теперь, допустим, у нас в функции main так же есть две переменные типа double, значения которых тоже нужно обменять. Функция для обмена значений двух переменных типа int нам не подойдет. Напишем функцию для double:
void my_swap ( double second )
И теперь перепишем main:
int main ()
Как видите, у нас алгоритм абсолютно одинаковый, отличаются лишь типы параметров и тип переменной temp. А теперь представьте, что нам еще нужны функции для short, long double, char, string и еще множества других типов. Конечно, можно просто скопировать первую функцию, и исправить типы на нужные, тогда получим новую функцию с необходимыми типами. А если функция будет не такая простая?
А вдруг потом еще обнаружится, что в первой функции была ошибка? Избежать всего этого можно, например, «шаманством» с препроцессором, но это нам ни к чему, нам помогут шаблоны.
Для начала, заглянем в википедию и посмотрим, что же такое шаблоны:
Шабло́ны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию).
https://ru.wikipedia.org/wiki/Шаблоны_C++
Итак, описание шаблона начинается с ключевого слова template за которым в угловых скобках («») следует список параметров шаблона. Далее, собственно идет объявление шаблонной сущности (например функция или класс), т. е. имеет вид:
Теперь давайте напишем шаблонную функцию my_swap. Исходя из упомянутой выше структуры объявления шаблона следует, что наша функция будет выглядеть так:
template < typename T >void my_swap ( T second )
typename в угловых скобках означает, что параметром шаблона будет тип данных. T — имя параметра шаблона. Вместо typename здесь можно использовать слово class: template В данном контексте ключевые слова typename и class эквивалентны (лично мне больше нравится typename, а кому-то class). Далее, в тексте шаблона везде, где мы используем тип T, вместо T будет проставляться необходимый нам тип.
void my_swap ( T second ) //T — тип, указанный в параметре шаблона < T temp(first) ; //временная переменная должна быть того же типа, что и параметры first = second ; second = temp ; >
теперь давайте напишем функцию main:
int main () < int a = 5 ; int b = 10 ; std::cout ( a , b ) ; std::cout ( c , d ) ; std::cout
Как видите, после имени функции в угловых скобках мы указываем тип, который нам необходим, он то и будет типом T. Шаблон — это лишь макет, по которому компилятор самостоятельно будет генерировать код. При виде такой конструкции: my_swap компилятор сам создаст функцию my_swap с необходимым типом.
Это называется инстанцирование шаблона. То есть при виде my_swap компилятор создаст функцию my_swap в которой T поменяет на int, а при виде my_swap будет создана функция с типом double. Если где-то дальше компилятор опять встретит my_swap , то он ничего генерировать не будет, т.к. код данной функции уже есть(шаблон с данным параметром уже инстанцирован).
Таким образом, если мы инстанцируем этот шаблон три раза с разными типами, то компилятор создаст три разные функции
Вывод типа шаблона исходя из параметров функции
На самом деле, мы можем вызвать функцию my_swap не указывая тип в угловых скобках. В ряде случаев компилятор может это сделать за вас.
рассмотрим вызов функции без указания типа:
int a = 5 ; int b = 10 ; my_swap ( a , b ) ;
Наша шаблонная функция принимает параметры типа T typename T >void my_swap ( T second ) //T — тип, указанный в параметре шаблона < T temp(first) ; //временная переменная должна быть того же типа, что и параметры first = second ; second = temp ; >//Функция будет принимать указатель на данные //и кол-во элементов массива данных //Сам алгоритм сортировки можете посмотреть в Интернете. //Никаких оптимизаций и проверок аргументов применять не будем, нам нужна просто демонстрация. template < class ElementType >//Использовал class, но можно и typename — без разницы void bubbleSort(ElementType * arr, size_t arrSize) < for(size_t i = 0; i < arrSize — 1; ++i) for(size_t j = 0; j < arrSize — 1; ++j) if (arr[j + 1] < arr[j]) my_swap ( arr[j] , arr[j+1] ) ; >template < typename ElementType >void out_array ( const ElementType * arr , size_t arrSize ) < for ( size_t i = 0 ; i < arrSize ; ++i ) std::cout int main () < const size_t n = 5 ; int arr1 [ n ] = < 10 , 5 , 7 , 3 , 4 >; double arr2 [ n ] = < 7.62 , 5.56 , 38.0 , 56.0 , 9.0 >; std::cout
Source arrays: 10 5 7 3 4 7.62 5.56 38 56 9 Sorted arrays: 3 4 5 7 10 5.56 7.62 9 38 56
Как видите, компилятор сам генерирует out_array для необходимого типа. Так же он сам генерирует функцию bubbleSort. А в bubbleSort у нас применяется шаблонная функция my_swap, компилятор сгенерирует и её код автоматически. Удобно, не правда ли?
Введение в шаблонные классы
Шаблонными могут быть не только функции. Рассмотрим шаблонные классы. Начнем с простого примера. Мы добавим в наш предыдущий код функцию, которая будет искать максимум и минимум в массиве. При создании функции «упираемся» в проблему — как вернуть два указателя?
Можно передать их в функцию в качестве параметров, а можно вернуть объект, который будет содержать в себе два указателя. Первый вариант при большом кол-ве возвращаемых значений приведет к заваливанию функции параметрами, поэтому я предлагаю сделать структуру:
struct my_pointer_pair < тип * first ; тип * second ; >;
А какого же типа будут указатели? Можно сделать их void*, но тогда придется постоянно кастовать их к нужному типу, и код станет похож на «Доширак». А что, если сделать эту структуру шаблонной? Попробуем:
template < typename T, typename U >struct my_pointer_pair < T * first ; U * second ; >;
Теперь компилятор при виде кода my_pointer_pair сам сгенерирует нам код структуры с соответствующими типами. В данном примере указатели у нас будут одинакового типа, но структуру мы сделаем такой, чтобы типы указателей могли быть разными. Это может быть полезно в других примерах (в данном случае я просто хотел показать, что у шаблона может быть не только один параметр).
int main () < my_pointer_pairobj = < new int(10) , new double(67.98) >;//Создаем объект типа my_pointer_pair std::cout
Компилятор не будет автоматически определять типы для шаблона класса, поэтому необходимо их указывать самостоятельно.
Теперь давайте напишем код шаблонной функции для поиска максимума и минимума:
//Шаблон наш будет с одним параметром — тип элементов массива (T) //Возвращаемое значение — объект типа my_pointer_pair < T , T >//т.е. first и second в my_pointer_pair будут иметь тип T*. template < typename T >my_pointer_pair < T , T >my_minmax_elements ( T * arr , size_t arrSize ) < my_pointer_pair< T , T >result = < 0 , 0 >; if ( arr == 0 || arrSize < 1 ) return result ; result.first = arr ; result.second = arr ; for ( size_t i = 1 ; i < arrSize ; ++i ) < if ( arr[i] < *result.first ) result.first = arr+i ; if ( arr[i] >*result.second ) result.second = arr+i ; > return result ; >
Теперь мы можем вызывать данную функцию:
my_pointer_pair < int , int >mm = my_minmax_elements ( arr1 , n ) ;
Для классов мы должны явно указывать параметры шаблона. В стандарте C++11, устаревшее ключевое слово auto поменяло свое значение и теперь служит для автоматического вывода типа в зависимости от типа инициализатора, поэтому мы можем написать так:
auto mm = my_minmax_elements ( arr1 , n ) ;
Предлагаю написать еще одну функцию, которая будет выводить объект my_pointer_pair в стандартный поток вывода:
template < typename T1 , typename T2 >void out_pair ( const my_pointer_pair < T1 , T2 > if ( mp.first == 0 || mp.second == 0 ) std::cout int main () < const size_t n = 5 ; int arr1 [ n ] = < 10 , 5 , 7 , 3 , 4 >; double arr2 [ n ] = < 7.62 , 5.56 , 38.0 , 56.0 , 9.0 >; std::cout
Arrays: 10 5 7 3 4 7.62 5.56 38 56 9 min = 3 max = 10 min = 5.56 max = 56
Шаблоны и STL
В комплекте с компилятором Вам предоставляется стандартная библиотека шаблонов (Standart Template Library). Она содержит множество шаблонных функций и классов. Например, класс двусвязного списка(list), класс «пара» (pair), функция обмена двух переменных(swap), функции сортировок, динамически расширяемый массив(vector) и т.д. Всё это — шаблоны и Вы можете их использовать. Для небольшого примера возьмем std::vector:
#include #include #include int main () < std::vector arr; arr.push_back ( 5 ) ; //Добавляем элемент в конец arr.push_back ( 7 ) ; arr.push_back ( 3 ) ; arr.push_back ( 8 ) ; std::cout
Заметьте, когда писали std::vector, авторы понятия не имели, элементы какого типа Вы будете хранить.
Шаблоны это слишком большой и мощный инструмент и описать всё в одной статье не представляется возможным. Это было лишь небольшое введение в мир шаблонов. Углубляясь в шаблоны, Вы поразитесь тому, какой мощный это инструмент и какие возможности он предоставляет.
P.S. высказывайте мнение о статье, критику, дополнения/исправления и интересующие вопросы в комментариях.
P.P.S. Просьба вопросы «консоль закрывается, что делать?», «русский язык не показывает. Что делать?», «как работает сортировка?», «что такое size_t», «что такое std::» и им подобные задавать либо в гугл, либо искать на данном сайте в других статьях. Не нужно захламлять комментарии этой чепухой. Если Вы этого не знаете, то может лучше сначала подтянуть свои знания?
Источник: code-live.ru