Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2vcTnguyvU

.pdf
Скачиваний:
6
Добавлен:
15.04.2023
Размер:
955.27 Кб
Скачать

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

Шаблон класса можно специализировать для конкретных аргументов шаблона. Специализированные шаблоны классов позволяют оптимизировать реализации для конкретных типов, или корректировать неверное поведение определенных типов для инстанцирования шаблона класса. Однако при специализации шаблона класса необходимо специализировать все его функции-члены. Хотя можно специализировать и отдельную функцию-член, но после этого нельзя будет специализировать целый класс. Чтобы специализировать шаблон класса, следует объявить класс, предварив его конструкцией template<> , и указать типы, для которых специализируется шаблон класса. Типы используются в качестве аргументов шаблона и задаются непосредственно после имени класса.

template <>

class Stack<std::string> { } ;

Для таких специализаций любая функция-член должна определяться как "обычная" функция-член с заменой каждого включения Т специализированным типом.

void Stack<std::string>::push(std::string const& elem) { elems.push_back(elem) ;}

// Добавление копии элемента в конец массива

 

Далее приведен завершенный пример специализации Stack

для типа

std::string.

 

#include <cstdlib>

 

#include <iostream>

 

#include <vector>

 

#include <stdexcept>

 

#include <deque>

 

#include <string>

 

using namespace std;

 

template < class T> class Stack {

 

private:

 

std::vector<T> elems;

 

public:

 

void push(T const&);

 

void pop();

 

T top() const;

 

bool empty() const

 

{ return elems.empty(); }

 

};

 

template <class T> void Stack<T>::push(T const& elem) {

 

10

 

elems.push_back(elem) ; }

template <class T> void Stack<T>::pop() { if (elems.empty()) {

throw std:: out_of_range (" Stack< >: : pop () : empty stack"); } elems.pop_back() ; }

template <class T> T Stack<T>::top() const { if (elems.empty())

{ throw std::out_of_range (" Stack< >: : top () : empty stack"); } return elems.back(); }

template <> class Stack<std::string> { private:

std::deque <std::string> elems; public:

void push(std::string const&); void pop(); std::string top() const;

bool empty() const { return elems.empty(); } };

void Stack<std::string>::push(std::string const& elem) { elems.push_back(elem); }

void Stack<std::string>::pop() { if (elems.empty()) {

throw std::out_of_range("Stack<std::string>::pop(): empty stack") ; } elems.pop_back(); }

std::string Stack<std::string>::top() const { if (elems.empty()) {

throw std::out_of_range("Stack<std::string>::top(): empty stack"); } return elems.back(); } //

В данном примере для управления элементами в стеке вместо вектора используется очередь с двусторонним доступом (дек). Это сделано, чтобы показать, что реализация специализации может значительно отличаться от реализации первичного шаблона.

Частичная специализация

Специализация шаблонов классов может быть частичной. Можно определить реализации для определенных типов, но при этом некоторые параметры шаблона остаются задаваемыми пользователем. Например, для шаблона класса

template<class Tl, class T2> class MyClass { } Возможны следующие частичные специализации:

• Частичная специализация: оба параметра шаблона имеют один и тот же тип template<class T> class MyClass<T,T> { };

11

Частичная специализация: тип второго параметра - int

template<class T2> class MyClass<T,int> { };

• Частичная специализация: оба параметра - указатели template<class Tl, class T2> class MyClass<Tl*,T2*> { } ;

В приведенном далее примере показано, какие шаблоны применяются при разных объявлениях.

MyClass<int , float> mif;

// Используется MyClass<Tl,T2>

MyClass<float,float> mff;

// Используется MyClass<T,T>

MyClass<float,int> mfi; // Используется MyClass<T,int>

MyClass<int*,float*> mp;

// Используется MyClass<Tl*, T2*>

Если для объявления одинаково хорошо подходит несколько частичных специализаций, получается неоднозначность, не разрешаемая компилятором. //ошибка: соответствуют MyClass<T,T> и MyClass<T,int>

MyClass<int, int > m;

//ошибка: соответствуют MyClass<T, T> и MyClass<Tl*,T2*> MyClass<int*, int*> m;

Чтобы избежать неоднозначности во втором случае, можно использовать дополнительную частичную специализацию для указателей одного и того же типа:

template<class T> class MyClass<T*,T*> { };

Аргументы шаблона, задаваемые по умолчанию

Как уже отмечалось ранее, у шаблона могут быть кроме параметровтипов, представляющих некоторый тип, параметры-константы, представляющие фиксированное константное выражение. Параметрконстанта выглядит как обычное объявление, т.е. вместо имени параметра должно быть подставлено значение константы из определения шаблона. На параметры шаблонов, не являющиеся типами, накладываются некоторые ограничения. В общем случае такими параметрами могут быть только целочисленные константы (включая перечисления) или указатели на объекты с внешним связыванием. Использование чисел с плавающей точкой и объектов с типом класса в качестве параметров шаблона не допускается. В случае использования шаблонов класса для параметров шаблона можно определять значения по умолчанию. Эти значения называются аргументами шаблона по умолчанию. Например, в классе Stack<> можно использовать второй параметр шаблона, определяющий контейнер, который применяется для хранения элементов, в качестве значения по умолчанию указывается тип std:: vector<>.

#include <vector> #include <stdexcept>

12

template <class T, class CONT = std::vector<T> > class Stack {

private:

CONT elems; // Элементы public:

void push(T const&); // Добавление элемента

void pop();

// Снятие со стека

T top() const;

// Возврат элемента с вершины стека

bool empty() const { return elems.empty();}

// Возвращает true если стек пуст

};

 

 

template<class Т, class

CONT>

void Stack<T, CONT>::push(T const& elem) {

elems.push_back(elem);

} // Добавление элемента

template<class T, class

CONT> void Stack<T,CONT>: :pop()

{ if (elems.empty()) {

throw std: : out_of _range ( " Stack: : pop () : empty stack"); } elems.pop_back(); } // Удаление последнего элемента template<class T, class CONT>

T Stack<T,CONT>: :top() const { if (elems.empty()) {

throw std::out_of_range("Stack<>::top(): empty stack"); }

return elems.back();} // Возврат копии последнего элемента Поскольку теперь два параметра шаблона, то каждое определение функциичлена должно иметь два параметра,

template<class T, class CONT>

void Stack<T,CONT>::push(T const& elem) { elems.push_back(elem); } // Добавление элемента

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

template<class Т, class CONT = std::vector<T> > class Stack {

private:

CONT elems; }// Элементы

При объявлении объекта Stack в программе можно явно указать, какой контейнер должен использоваться для хранения элементов.

Stack<int> intStack;

// Стек значений int

// Стек значений double, в котором для хранения

элементов используется

std::deque<> Stack<double,std::deque<double> >

dblStack;

 

13

 

Работа со стеком целых чисел intStack.push(7);

std::cout<< intStack.top();

Работа со стеком чисел с плавающей точкой dblStack.push(42.42);

std::cout<< dblStack.top() << std::endl; dblStack.pop () ;

dblStack.pop();

} catch(std::exception const& ex) { std::cerr << "Exception: " << ex.what ( ) << std::endl;

return EXIT_FAILURE; }// Выход из программы с указанием ошибки

}

С помощью конструкции Stack<double,std::deque<double> > объявляется стек значений double, в котором для внутренней работы с элементами используется контейнер std: :deque<>.

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

Лабораторная работа № 5 Тема: Шаблонные функции и классы

Цель: Переопределить класс Stack, включая простую шаблонную технику

Краткие теоретические сведения

Спецификация шаблонного класса Stack Объявление

// Заголовочный файл tstack.h #ifndef TEMPLATE_STACK_CLASS #define TEMPLATE_STACK_CLASS #include <iostream>

#include <stdlib.h> using namespace std;

14

const int MaxStackSize = 50; template <class T> class Stack {

private:

// закрытые данные-члены. массив размером MaxStackSize и вершина стека T stacklist[MaxStackSize];

int top; public:

// конструктор

Stack ( ); // инициализация вершины //операции модификации стека

void Push (const T& item); T Pop ( );

void ClearStack( ); // доступ к стеку

T Peek ( ) const; // методы проверки стека

int StackEmpty( ) const; int StackFull( ) const; };

Реализация класса Stack, основанного на шаблоне

Каждый метод определяется как внешняя шаблонная функция. Это требует помещения списка параметров шаблона перед каждой функцией и замены класса Stack на Stack<T>

// инициализация вершины стека

template <class T> Stack<T>::Stack ( ): top(-1) {} // поместить элемент в стек

template <class T> void Stack<T>::Push (const T& item) {

// если все индексы массива стека использованы, то выйти из программы if (top == MaxStackSize-1) {

cerr << "Стек переполнен (Stack overflow!) " << endl; exit(1); }

// инкремент вершины и копирование значения в стек top++; stacklist[top] = item; }

//извлечь значение из стека template <class T>

T Stack<T>::Pop (void) { T temp;

//если стек пуст, то завершить программу

if (top == -1)

{

cerr << "Стек

пуст (Attempt to pop an empty stack) !" << endl;

 

15

exit(1); }

temp = stacklist[top]; // записать элемент вершины

// декремент индекса top и возврат значения элемента в вершине стека top--; return temp; }

//возвратить значение в вершине стека

template <class T> T Stack<T>::Peek ( ) const { // если стек пуст, завершить программу

if (top == -1) {

cerr << "Попытка считать данные из пустого стека!" << endl; exit(1); }

return stacklist[top]; }

// тестирование стека на наличие в нем данных

template <class T> int Stack<T>::StackEmpty(void) const { // возвратить логическое значение top == -1

return top == -1; }

//проверка, заполнен ли стек template <class T>

int Stack<T>::StackFull(void) const {

//возвратить логическое значение top == MaxStackSize-1 return top == MaxStackSize-1; }

//удалить все значения из стека

template <class T> void Stack<T>::ClearStack( ) { // установить top в -1

top = -1; }

#endif // TEMPLATE_STACK_CLASS

Вычисление инфиксного выражения

Следующие выражения используют инфиксную запись с бинарными

операторами, расположенными между операндами. Пара

скобок

создает

подвыражение, вычисляемое отдельно:

 

 

 

8.5+2*3 -7*(4/3-6.25)+ 9

 

 

 

Ранг выражения. Алгоритм для вычисления инфиксного выражения

использует понятие ранга, который присваивает значение

-1, 0,

1 каждому

терму выражения.

 

 

 

Ранг операнда с плавающей точкой равен 1

 

 

 

Ранг унарных операторов +, -, *, / равен 0

 

 

 

Ранг бинарных операторов +, -, *, / равен –1

 

 

 

Ранг левой скобки равен 0

 

 

 

Когда просматриваются термы в выражении,

ранг

определяет

неправильно расположенные операнды или операторы,

которые

могут

16

 

 

 

сделать выражение неверным. С каждым термом выражения ассоциируется суммарный ранг, который является суммой ранга отдельных термов от первого символа до данного терма. Суммарный ранг используется для контроля над тем, чтобы каждый бинарный оператор имел два окружающих операнда и чтобы никакие операнды в выражении не существовали без инфиксного оператора. Например, в простом выражении 2+3

последовательные значения ранга: Сканирование 2: суммарный ранг =1 Сканирование +: суммарный ранг =1 + -1 = 0 Сканирование 3: суммарный ранг =1

Правило. Для каждого терма в выражении суммарный ранг выражения должен быть равен 0 или 1. Ранг полного выражения должен быть равен 1.

Следующие выражения являются неверными, что определяется функцией rank:

Выражение

Неверный ранг

Причина

 

 

2.5 4 + 3

Ранг 4=2

Слишком много последовательных

 

 

операндов

 

 

2.5 + * 3

Ранг *= -1

Слишком

много

последовательных

 

 

операторов

 

 

2.5+ 3 -

Конечный ранг=0

Нет одного операнда

 

Алгоритм инфиксного выражения использует стек операндов—стек значений с плавающей точкой для хранения операндов и результатов промежуточных вычислений. Второй стек, называемый стеком операторов, содержит операторы и левые скобки и позволяет реализовать порядок приоритетов. При сканировании выражения термы помещаются в соответствующие стеки. Операнд помещается в стек операндов, когда он встречается в процессе сканирования и извлекается из стека , когда он необходим для операции. Оператор помещается в стек только тогда , когда уже были оценены все операторы с более высоким и равным приоритетом, и освобождается из стека, когда наступает время его выполнения. Это происходит при вводе последующего оператора с более низким или равным приоритетом или в конце выражения. Например, 2 + 4 - 3 * 6 Ввод 2: Поместить 2 в стек операндов.

Ввод +: Поместить + в стек операторов. Ввод 4: Поместить 4 в стек операндов.

Ввод - Оператор - имеет тот же порядок приоритетов, что и оператор + в стеке. Извлечь два операнда и выполнить операцию сложения. Результат (2+4=6) поместить в обратно в стек операндов.

Затем поместить -(минус) в стек операторов.

17

Ввод 3: Поместить 3 в стек операндов.

Ввод *: Оператор * имеет более высокий приоритет, чем оператор - в стеке. Поместить * в стек операторов.

Ввод 6: Поместить 6 в стек операндов.

Выполнить: Извлечь * и выполнить операцию с операндами 6 и 3 из стека операндов. Поместить результат 18 в стек операндов.

Извлечь из стека и выполнить операцию с операндами 18 и 3 из стека операндов. 6 - 18 = -12. Это результат.

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

Символ

 

Приоритет

 

 

 

 

Входной

Стековый

Ранг

 

 

приоритет

приоритет

 

+

-

1

1

-1

(бинарный)

 

 

 

*

/

2

2

-1

(

 

3

-1

0

)

 

0

0

0

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

//Заголовочный файл mathop.h #ifndef INFIX_MATH_OPERATIONS #define INFIX_MATH_OPERATIONS

//список постоянных сообщений ошибок const int OperatorExpected = 0,

OperandExpected = 1, MissingLeftParenthesis = 2, MissingRightParenthesis = 3, InvalidInput = 4;

// обозначения правой и левой скобок const char leftparenthesis = '(',

rightparenthesis = ')';

18

// класс, управляющий операторами в стеке операторов class MathOperator {

private:

//оператор и два его значения приоритета char op;

int inputprecedence; int stackprecedence; public:

//конструктор; включает конструктор умолчания

//конструктор , инициализирующий объект MathOperator( );

MathOperator(char ch);

//функции-члены для управления оператором в стеке int operator>= (MathOperator a) const;

void Evaluate (Stack<float> &OperandStack); char GetOp( ); };

MathOperator::MathOperator( ) {}// конструктор по умолчанию

// конструктор задает как входной, так и стековый приоритет оператора MathOperator::MathOperator(char ch) {

op = ch;

// присвоить оператору

switch(op)

{

//'+' and '-' имеют входной и стековый приоритет 1 case '+':

case '-': inputprecedence = 1; stackprecedence = 1; break;

//'*' and '/' имеют входной и стековый приоритет 2 case '*':

case '/': inputprecedence = 2; stackprecedence = 2; break;

//'(' имеет входной приоритет 3, стековый приоритет -1 case '(': inputprecedence = 3;

stackprecedence = -1; break;

//')' имеет входной и стековый приоритет 0

case ')': inputprecedence = 0; stackprecedence = 0; break; }

}

19

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]