Шаблоны функция сложения для разных типов с. Прототип шаблона функции

24.06.2019
17 марта 2009 в 20:36

Трюки со специализацией шаблонов C++

  • C++

Специализация шаблонов является одной из «сложных» фичей языка с++ и использутся в основном при создании библиотек. К сожалению, некоторые особенности специализации шаблонов не очень хорошо раскрыты в популярных книгах по этому языку. Более того, даже 53 страницы официального ISO стандарта языка, посвященные шаблонам, описывают интересные детали сумбурно, оставляя многое на «догадайтесь сами - это же очевидно». Под катом я постарался ясно изложить базовые принципы специализации шаблонов и показать как эти принципы можно использовать в построении магических заклинаний.

Hello World

Как мы привыкли использовать шаблоны? Используем ключевое слово template, затем в угловых скобках имена параметров шаблона , после чего тип и имя. Для параметров также указывают что это такое: тип (typename) или значение (например, int). Тип самого шаблона может быть класс (class), структура (struct - вообщем-то тоже класс) или функция (bool foo() и так далее). Например, простейший шаблонный класс "A" можно задать вот так:

Через некоторое время мы захотим, чтобы наш класс для всех типов работал одинаково, а для какого-нибудь хитрого вроде int - по-другому. Фигня вопрос, пишем специализацию: выглядит так же как объявление но параметры шаблона в угловых скобках не указываем, вместо этого указываем конкретные аргументы шаблона после его имени:

Template<> class A< int > {}; // здесь int - это аргумент шаблона
Готово, можно писать методы и поля специальной реализации для int. Такая специализация обычно называется полной (full specialization или explicit specialization). Для большинства практических задач большего не требуется. А если требуется, то…

Специализированный шаблон - это новый шаблон

Если внимательно читать ISO стандарт С++, то можно обнаружить интересное утверждение: создав специализированный шаблонный класс мы создаем новый шаблонный класс (14.5.4.3). Что это нам дает? Специализированный шаблонный класс может содержать методы, поля или объявления типов которых нет в шаблонном классе который мы специализируем. Удобно, когда нужно чтобы метод шаблонного класса работал только для конкретной специализации - достаточно объявить метод только в этой специализации, остальное сделает компилятор:

Специализированный шаблон может иметь свои параметры шаблона

Дьявол, как известно, в деталях. То, что специализированный шаблонный класс это совсем-совсем новый и отдельный класс конечно интересно, но магии в этом мало. А магия есть в незначительном следствии - если это отдельный шаблонный класс, то он может иметь отдельные, никак не связанные с неспециализированным шаблонным классом параметры (параметры - это то, что после template в угловых скобках). Например, вот так:

Template< typename S, typename U > class A< int > {};
Правда, именно такой код компилятор не скомпилирует - новые параметры шаблона S и U мы никак не используем, что для специализированного шаблонного класса запрещено (а то что это класс специализированный компилятор понимает потому, что у него такое же имя "A" как у уже объявленного шаблонного класса). Компилятор даже специальную ошибку скажет: «explicit specialization is using partial specialization syntax, use template<> instead». Намекает, что если сказать нечего - то надо использовать template<> и не выпендриваться. Тогда для чего же в специализированном шаблонном классе можно использовать новые параметры? Ответ странный - для того, чтобы задать аргументы специализации (аргументы - это то, что после имени класса в угловых скобках). То есть специализируя шаблонный класс мы можем вместо простого и понятного int специализировать его через новые параметры :

Template< typename S, typename U > class A< std::map< S, U > > {};
Такая странная запись скомпилируется. И при использовании получившегося шаблонного класса с std::map будет использована специализация, где тип ключа std::map будет доступен как параметр нового шаблона S, а тип значения std::map как U.

Такая специализация шаблона, при которой задается новый список параметров и через эти параметры задаются аргументы для специализации называется частичной специализацией (partial specialization). Почему «частичной»? Видимо потому, что изначально задумывалась как синтаксис для специализации шаблона не по всем аргументам. Пример, где шаблонный класс с двумя параметрами специализируется только по одному из них (специализация будет работать когда первый аргумент, T, будет указан как int. При этом второй аргумент может быть любым - для этого в частичной специализации введен новый параметр U и указан в списке аргументов для специализации):

Template< typename T, typename S > class B {}; template< typename U > class B< int, U > {};

Магические последствия частичной специализации

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

Template< typename S, typename U > class A< S(*)(U) > {};
А если в специализированном шаблоне объявить typedef или static const int (пользуясь тем, что это новый шаблон), то можно использовать его для извлечения нужной информации из типа. Например, мы используем шаблонный класс для хранения объектов и хотим получить размер переданного объекта или 0, если это указатель. В две строчки:

Template< typename T > struct Get { const static int Size = sizeof(T); }; template< typename S > struct Get< S* > { const static int Size = 0; }; Get< int >::Size // например, 4 Get< int* >::Size // 0 - нашли указатель:)
Магия этого типа используется в основном в библиотеках: stl, boost, loki и так далее. Конечно, при высокоуровневом программировании использовать такие фокусы череповато - думаю, все помнят конструкцию для получения размера массива:). Но в библиотеках частичная специализация позволяет относительно просто реализовывать делегаты, события, сложные контейнеры и прочие иногда очень нужные и полезные вещи.

Коллеги, если найдете ошибку (а я, к сожалению, не гуру - могу ошибаться) или у Вас есть критика, вопросы али дополнения к вышеизложенному - буду рад комментариям.

Update: Обещанное продолжение

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

Любой шаблон начинается со слова template , будь то шаблон функции или шаблон класса. После ключевого слова template идут угловые скобки — < > , в которых перечисляется список параметров шаблона. Каждому параметру должно предшествовать зарезервированное слово class или typename . Отсутствие этих ключевых слов будет расцениваться компилятором как . Некоторые примеры объявления шаблонов:

Template

Template

Template

Ключевое слово typename говорит о том, что в шаблоне будет использоваться встроенный тип данных, такой как: int , double , float , char и т. д. А ключевое слово class сообщает компилятору, что в шаблоне функции в качестве параметра будут использоваться пользовательские типы данных, то есть классы. Но не в коем случае не путайте параметр шаблона и шаблон класса. Если нам надо создать шаблон класса, с одним параметром типа int и char , шаблон класса будет выглядеть так:

Template

где T — это параметр шаблона класса, который может принимать любой из встроенных типов данных, то, что нам и нужно.

А если параметр шаблона класса должен пользовательского типа, например типа Array , где Array — это класс, описывающий массив, шаблон класса будет иметь следующий вид:

Template class Name { //тело шаблона класса };

C этим вам лучше разобраться изначально, чтобы потом не возникало никаких ошибок, даже, если шаблон класса написан правильно.

Давайте создадим шаблон класса Стек, где , в которой хранятся однотипные элементы данных. В стек можно помещать и извлекать данные. Добавляемый элемент в стек, помещается в вершину стека. Удаляются элементы стека, начиная с его вершины. В шаблоне класса Stack необходимо создать основные методы:

  • Push — добавить элемент в стек;
  • Pop — удалить элемент из стека
  • printStack — вывод стека на экран;

Итак реализуем эти три метода, в итоге получим самый простой класс, реализующий работу структуры стек. Не забываем про конструкторы и деструкторы. Смотрим код ниже.

#include "stdafx.h" #include template << "Заталкиваем элементы в стек: "; int ct = 0; while (ct++ != 5) { int temp; cin >> << "\nУдаляем два элемента из стека:\n"; myStack.pop(); // удаляем элемент из стека myStack.pop(); // удаляем элемент из стека myStack.printStack(); // вывод стека на экран return 0; } // конструктор template Stack::Stack(int s) { size = s > Stack bool Stack bool Stack void Stack::printStack() { for (int ix = size -1; ix >= 0; ix--) cout << "|" << setw(4) << stackPtr << endl; }

// код Code::Blocks

// код Dev-C++

#include using namespace std; #include template class Stack { private: T *stackPtr; // указатель на стек int size; // размер стека T top; // вершина стека public: Stack(int = 10);// по умолчанию размер стека равен 10 элементам ~Stack(); // деструктор bool push(const T); // поместить элемент в стек bool pop(); // удалить из стека элемент void printStack(); }; int main() { Stack myStack(5); // заполняем стек cout << "Заталкиваем элементы в стек: "; int ct = 0; while (ct++ != 5) { int temp; cin >> temp; myStack.push(temp); } myStack.printStack(); // вывод стека на экран cout << "\nУдаляем два элемента из стека:\n"; myStack.pop(); // удаляем элемент из стека myStack.pop(); // удаляем элемент из стека myStack.printStack(); // вывод стека на экран return 0; } // конструктор template Stack::Stack(int s) { size = s > 0 ? s: 10; // инициализировать размер стека stackPtr = new T; // выделить память под стек top = -1; // значение -1 говорит о том, что стек пуст } // деструктор template Stack::~Stack() { delete stackPtr; // удаляем стек } // элемент функция класса Stack для помещения элемента в стек // возвращаемое значение - true, операция успешно завершена // false, элемент в стек не добавлен template bool Stack::push(const T value) { if (top == size - 1) return false; // стек полон top++; stackPtr = value; // помещаем элемент в стек return true; // успешное выполнение операции } // элемент функция класса Stack для удаления элемента из стек // возвращаемое значение - true, операция успешно завершена // false, стек пуст template bool Stack::pop() { if (top == - 1) return false; // стек пуст stackPtr = 0; // удаляем элемент из стека top--; return true; // успешное выполнение операции } // вывод стека на экран template void Stack::printStack() { for (int ix = size -1; ix >= 0; ix--) cout << "|" << setw(4) << stackPtr << endl; }

Как видите шаблон класса Stack объявлен и определен в файле с main -функцией. Конечно же такой способ утилизации шаблонов никуда не годится, но для примера сойдет. В строках 7 — 20 объявлен интерфейс шаблона класса. Объявление класса выполняется привычным для нас образом, а перед классом находится объявление шаблона, в строке 7. При объявлении шаблона класса, всегда используйте такой синтаксис.

Строки 47 — 100 содержат элемент-функции шаблона класса Stack, причем перед каждой функцией необходимо объявлять шаблон, точно такой же, как и перед классом — template . То есть получается, элемент-функции шаблона класса, объявляются точно также, как и обычные шаблоны функций. Если бы мы описали реализацию методов внутри класса, то заголовок шаблона — template для каждой функции прописывать не надо.

Чтобы привязать каждую элемент-функцию к шаблону класса, как обычно используем бинарную операцию разрешения области действия — :: с именем шаблона класса — Stack . Что мы и сделали в строках 49, 58, 68, 83, 96.

Обратите внимание на объявление объекта myStack шаблона класса Stack в функции main , строка 24. В угловых скобочка необходимо явно указывать используемый тип данных, в шаблонах функций этого делать не нужно было. Далее в main запускаются некоторые функции, которые демонстрируют работу шаблона класса Stack . Результат работы программы смотрим ниже.

Заталкиваем элементы в стек: 12 3456 768 5 4564 |4564 | 5 | 768 |3456 | 12 Удаляем два элемента из стека: | 0 | 0 | 768 |3456 | 12

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

template void Sort(T array, Tsize size);

Имена формальных параметров шаблона могут не совпадать в предварительном объявлении и определении шаблона. Так, например, в следующем фрагменте и прототип шаблона, и определение шаблона относятся к одной и той же функции:

template T max(T, T);

template

Type max(Type a, Type b)

if (a > b) return a; else return b;

Использование шаблона функции

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

Например, в следующем примере функция min() конкретизируется дважды: один раз типом int и один раз типом double:

template

Type min(Type a, Type b)

if (a < b) return a; else return b;

int x = 4, y = 5, z;

double t = 6.56, r = 3.07, p;

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

Специализированная функция шаблона – это обычная функция, имя которой совпадает с именем функции в шаблоне, но которая определяется для параметров специфических типов. Специализированные функции шаблона определяют в том случае, когда обобщенный шаблон не годится для некоторого типа данных. Например, функция шаблона min

template

Type min(Type a, Type b)

if (a < b) return a; else return b;

не может применяться для строк (для типа char*) , так как генерируемый компилятором код будет просто сравнивать их положение в памяти (адреса). Для корректного сравнения строк можно определить специализированную функцию:

char* min(char* s1, char* s2)

if (strcmp(s1,s2)>0) return s2; else return s1;

Тогда обращаться к такой функции можно так же, как и к функции шаблона:

int i1 = 3, i2 = 5;

cout << “max int = ” << max(i1, i2) << endl;

char* s1 = “Golden Eagle”;

char* s2 = “Perigrine Falcon”;

cout << “max str = “ << max(s1, s2) << endl;

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

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

Синтаксис шаблона класса

template <<список аргументов шаблона>>

class <имя класса>

<тело класса>

За ключевым словом template следуют один или несколько аргументов (параметров), заключенных в угловые скобки и отделяемых друг от друга запятыми. Каждый аргумент может представлять собой ключевое слово class, за которым следует идентификатор, обозначающий параметризованный тип. Список аргументов шаблона не может быть пустым.

Затем следует определение класса. Оно аналогично определению обычного класса, за исключением того, что использует список аргументов шаблона.

Параметры шаблона, состоящие из ключевого слова class и следующего за ним идентификатора, часто называют параметрами типа . Другими словами, они информируют компилятор, что шаблон предполагает тип в качестве аргумента.

До настоящего момента обсуждались вопросы построения шаблонных классов на основе предопределенных (включенных в состав библиотеки классов) шаблонов классов. В этом разделе на простом примере обсуждается техника объявления собственных шаблонов классов и шаблонов функций.

Шаблонный класс обеспечивает стандартную реализацию дополнительной функциональности на основе ранее объявленных подстановочных классов.

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

Для формирования ограничений на подстановочные классы в C# используется механизм ограничителей параметров шаблона - он вводится при объявлении шаблона с помощью ключевого слова where , за которым могут располагаться имя параметра типа и список типов класса или интерфейса либо конструктор – ограничение new() :

Using System; using System.Collections; using System.Collections.Generic; namespace PatternArrays { //========== Это заголовок шаблона класса W ========== // Шаблон класса своими руками. T – параметр шаблона. // Шаблонный класс – это класс-шаблон, который детализируется // подстановочным классом. // При создании шаблонного класса вхождения параметра шаблона // (в данном случае это T) замещаются именем подстановочного // класса. Разработчик шаблона класса может выдвигать требования // относительно характеристик подстановочного класса. // Для этого используются специальные языковые конструкции, // называемые ОГРАНИЧИТЕЛЯМИ ПАРАМЕТРА ШАБЛОНА. // ОГРАНИЧИТЕЛЬ ПАРАМЕТРА ШАБЛОНА формулирует требования для // подстановочного класса. class W where T: IComparable, new() // Ограничитель параметра шаблона new() – особый // ограничитель. // Во-первых, в списке ограничителей шаблона // он всегда последний. // Во-вторых, этот ограничитель НЕ ограничивает. // Он ОБЕСПЕЧИВАЕТ обязательное выполнение явно // заданного конструктора умолчания для // подстановочного класса в шаблонном // классе. Это единственный способ заставить // выполниться конструктор умолчания // подстановочного класса при создании // объекта шаблонного класса. // Ограничитель подстановочного класса. // Шаблонный класс строится на основе шаблона и множества // подстановочных классов, которыми замещаются параметры // шаблона. Таким образом ограничители подстановочного // класса формулируют требования по поводу "родословной" // подстановочного класса. // В данном случае претендент на замещение параметра T // в шаблоне W обязательно должен наследовать интерфейс // IComparable. { // Вот место, которое предназначено объекту подстановочного класса. // Объект - представитель шаблонного класса включает объект, // представляющий подстановочный класс. public T t; // Конструктор шаблона. // Вот по какой схеме производится встраивание объекта-представителя // подстановочного класса. Всего лишь для того, чтобы эта схема // построения объекта - представителя шаблонного класса работала, // в объявлении шаблона должен присутствовать ограничитель параметра // шаблона new(). Его отсутствие приводит к возникновению ошибки // компиляции. С каких это пор необходимое требование стали // называть ограничением? public W() { t = new T(); } // Сравнение объектов в шаблоне. Обращение к функциям сравнения // регламентировано стандартными интерфейсами. // Полиморфизм через интерфейсы в действии. public int wCompare(T t) { return ((IComparable)this.t).CompareTo(t); } // А вот замечательный шаблон функции. // Он реализован в рамках класса-шаблона W. // Эта функция предназначена для формирования шаблонных очередей // из входных массивов объектов - представителей подстановочного // класса, представленного параметром шаблона Z. // Между прочим, такое обозначение параметра ничуть не хуже любого // другого. Более того, если бы здесь было использовано старое // обозначение параметра, транслятор выступил бы с предупреждением // по поводу того, что две разных сущности (параметр шаблона для // шаблона класса и параметр шаблона для параметра функции) // в рамках одного и того же объявления имеют одинаковые обозначения. public void QueueFormer(Queue queue, params Z values) { foreach (Z z in values) { queue.Enqueue(z); } } } //=============================================================== // Вот классы-кандидаты на подстановку в шаблон. // Первый класс подходит, а второй – не подходит! // Все решается на этапе трансляции. //=============================================================== class xPoints: IComparable { // Объект-генератор "случайных" чисел. static Random rnd = new Random(); public int x; public int y; public xPoints() { x = rnd.Next(0, 100); y = rnd.Next(0, 100); } // Ничто не может помешать классу иметь // различные версии конструкторов! public xPoints(int x, int y) { this.x = x; this.y = y; } // Вычисляется расстояние от начала координат. public int R { get { return (int)(Math.Sqrt(x * x + y * y)); } } // После реализации соответствующего интерфейса объект-КОМПАРЕР // обеспечивает реализацию алгоритма сравнения. public int CompareTo(object p) { return (this.R - ((xPoints)p).R); } } class yPoints { // Объект-генератор "случайных" чисел. static Random rnd = new Random(); public int x; public int y; public yPoints() { x = rnd.Next(0, 100); y = rnd.Next(0, 100); } // Шаблон функции в рамках объявления "обычного" класса. // Функция предназначена для формирования шаблонных магазинов // из входных массивов объектов - представителей подстановочного // класса, представленного параметром шаблона T. public void StackFormer(Stack stack, params T values) { foreach (T t in values) { stack.Push(t); } } } //============================================================== class Class1 { static void Main(string args) { W xw0 = new W(); W xw1 = new W(); // Объекты - представители шаблонного класса можно сравнивать // в результате // реализации интерфейса IComparable. if (xw0.wCompare(xw1.t) == 0) Console.WriteLine("Yes"); else Console.WriteLine("No"); // В силу ограничений параметра шаблона T, следующий код // для подстановочного // класса в принципе нереализуем.============================= //W yw0 = new W(); //W yw1 = new W(); //if (yw0.ww(yw1.t) == 0) Console.WriteLine("Yes"); //else Console.WriteLine("No"); //===================================================== // Демонстрация использования шаблона функции. // На основе подстановочного класса сформировали // шаблонную функцию для подстановочного класса, // которая обслуживает шаблонные очереди, формируемые на основе // предопределенного шаблона класса Queue<...>. Queue xpQueue = new Queue(); xw1.QueueFormer(xpQueue, new xPoints(0, 9), new xPoints(1, 8), new xPoints(2, 7), new xPoints(3, 6), new xPoints(4, 5), new xPoints(5, 4), new xPoints(6, 3), new xPoints(7, 2), new xPoints(8, 1), new xPoints(9, 0)); // Шаблоны классов и шаблоны функций концептуально не связаны. // В C# это самостоятельные и независимые конструкции. // Шаблон функции может быть объявлен где угодно - // в шаблоне класса и в рамках объявления "обычного" класса. // При объявлении шаблона функции ограничения на свойства // подстановочного класса, представленного параметром шаблона, // не упоминаются. // В силу ограничений на параметр шаблона класс yPoints в принципе // не может быть использован для построения шаблонного класса // на основе шаблона class W. // Однако этот же самый класс может быть использован для построения // шаблонной функции в шаблонном классе, созданном на основе // подстановочного класса xPoints! Queue ypQueue = new Queue(); xw1.QueueFormer(ypQueue, new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints()); // А вот применение шаблона функции, объявленного в "обычном" классе. // Создали объект класса, содержащего шаблон функции // по обслуживанию очередей. yPoints yp = new yPoints(); // Ссоздали шаблонный стек и воспользовались шаблоном функции, // объявленной yPoints в классе. Stack xpStack = new Stack(); yp.StackFormer(xpStack, new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints()); } } } Листинг 13.2.

Пример использования шаблонов: сортировка

Старая задача, новые решения с использованием предопределенных шаблонов классов и интерфейсов...

Using System; using System.Collections; using System.Collections.Generic; namespace PatternArrays { // Данные для массива элементов. // Подлежат сортировке в составе шаблонного массива методом Sort. class Points { public int x; public int y; public Points(int key1, int key2) { x = key1; y = key2; } // Вычисляется расстояние от начала координат. public int R { get { return (int)(Math.Sqrt(x * x + y * y)); } } } // ...ШАБЛОННЫЙ КОМПАРЕР на основе шаблона интерфейса... class myComparer: IComparer { // Предлагаемый ШАБЛОННЫЙ метод сравнения возвращает разность расстояний // двух точек (вычисляется по теореме Пифагора) от начала координат // – точки с координатами (0,0). Чем ближе точки к началу координат // – тем они меньше. Не требуется никаких явных приведений типа. // Шаблон настроен на работу с классом Points. int IComparerCompare(Points p1, Points p2) { return (p1.R - p2.R); } } // После реализации соответствующего ШАБЛОННОГО интерфейса // объект-КОМПАРЕР обеспечивает реализацию стандартного алгоритма // сортировки. class Class1 { static void Main(string args) { // Объект-генератор "случайных" чисел. Random rnd = new Random(); int i; // Очередь Points. QueueQP = new Queue(); // Шаблонный перечислитель. Предназначен для обслуживания // шаблонной очереди элементов класса Points. IEnumeratorEnP; // Сортировка поддерживается классом Array. Points pp; // Создали Компарер, способный сравнивать пары // объектов - представителей класса Points. myComparer c = new myComparer(); Console.WriteLine("========================================"); // Проинициализировали массив объектов - представителей класса Points. for (i = 0; i < 10; i++) { qP.Enqueue(new Points(rnd.Next(0, 10), rnd.Next(0, 10))); } enP = ((IEnumerable)(qP)).GetEnumerator(); for (i = 0; enP.MoveNext(); i++) { Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y); } // Сортируются элементы массива типа Points, который формируется на // основе шаблонной очереди. // Условием успешной сортировки элементов массива является // реализация интерфейса IComparer. Если Компарер не сумеет // справиться с поставленной задачей – будет возбуждено исключение. // На основе очереди построили массив. pp = qP.ToArray(); // А саму очередь можно почистить! qP.Clear(); try { Array.Sort(pp, c); } catch (Exception ex) { Console.WriteLine(ex); } // Сортировка произведена, очередь восстановлена. for (i = 0; i < 10; i++) qP.Enqueue(pp[i]); Console.WriteLine("========================================"); enP = ((IEnumerable)(qP)).GetEnumerator(); for (i = 0; enP.MoveNext(); i++) { Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y); } Console.WriteLine("========================================"); } } } Листинг 13.3.

Nullable-типы

Nullable-типы (простые Nullable-типы) представляют собой расширения простых типов. Их объявления принадлежат пространству имен System.Nullable .

Это шаблонные типы, то есть типы, построенные в результате детализации шаблонов. Шаблон Nullable<> используется для расширения простых типов, которые по своей сути являются структурами. Для обозначения Nullable шаблонных (построенных на основе шаблона) типов используются две нотации.

Функция-шаблон определяет общий набор операций, который будет применен к данным различ­ных типов. Используя этот механизм, можно применять некоторые общие алгоритмы к широко­му кругу данных. Как известно, многие алгоритмы логически одинаковы вне зависимости от типа данных, с которыми они оперируют. Например, алгоритм быстрой сортировки Quicksort один и тот же и для массива целых чисел, и для массива чисел с плавающей запятой. Отличается только тип данных, подлежащих сортировке. При помощи создания функции-шаблона (generic function) можно определить сущность алгоритма безотносительно к типу данных. После этого компилятор автоматически генерирует корректный код для того типа данных, для которого создается данная конкретная реализация функции на этапе компиляции. По существу, когда создается функция- шаблон, создается функция, которая может автоматически перегружать сама себя.

Функции-шаблоны создаются с использованием ключевого слова template (шаблон). Обычное значение слова «шаблон» достаточно полно отражает его использование в С++. Шаблон исполь­зуется для создания каркаса функции, оставляя компилятору реализацию подробностей. Общая форма функции-шаблона имеет следующий вид:

template возвращаемый_тип имя_функции(список параметров)
{
// тело функции
}

Здесь птип является параметром-типом, «держателем места» (placeholder) для имени типа дан­ных, которое используется функцией. Этот параметр-тип может быть использован в определении функции. Однако это только «держатель места», который будет автоматически заменен компи­лятором на фактический тип данных во время создания конкретной версии функции.

Ниже приведен короткий пример, в котором создается функция-шаблон, имеющая два пара­метра. Эта функция меняет между собой величины значений этих параметров. Поскольку общий процесс обмена значениями между двумя переменными не зависит от их типа, то он естествен­ным способом может быть реализован с помощью функции-шаблона.

// пример шаблона функции
#include
// шаблон функции
template void swap(X &a, X &b)
{
X temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int i=10, j = 20;
float x=10.1, у= 23.3;
char a="x", b="z";
cout << "Original i, j: " << i << " " << j << endl;
cout << "Original x, y: " << x << " " << у << endl;
cout << "Original a, b: " << a << " " << b << endl;
swap(i, j); // обмен целых
swap(x, у); // обмен вещественных значений
swap(a, b); // обмен символов
cout << "Swapped i, j: " << i << " " << j << endl;
cout << "Swapped x, y: " << x << " " << у << endl;
cout << "Swapped a, b: " << a << " " << b << endl;
return 0;
}

Рассмотрим эту программу более внимательно. Строка

Template void swap (X &а, X &b)

Указывает компилятору, что создается шаблон. Здесь X - шаблон типа, используемый в качестве параметра-типа. Далее следует объявление функции swap() с использованием типа данных X для тех параметров, которые будут обмениваться значениями. В функции main() функция swap() вы­зывается с передачей ей данных трех различных типов: целых чисел, чисел с плавающей запятой и символов. Поскольку функция swap() является функцией-шаблоном, то компилятор автоматичес­ки создаст три разные версии функции swap() - одну для работы с целыми числами, другую для работы с числами с плавающей запятой и, наконец, третью для работы с переменными символь­ного типа.

Похожие статьи