Реализация стека в виде шаблона потребовала некоторых изменений. Во-первых, в стеке с указателями (см. листинг 6.9) методы top() и рор() возвращали нуль (недопустимое значение для указателей), если стек был пуст. Такое решение подходит для указателей, но совершенно не годится для шаблонов — мы не можем знать, какие значения типа Т являются допустимыми, а какие — нет. Поэтому в шаблонном классе вместо возврата значения генерируется исключение Error.
Во-вторых, написан деструктор. И при реализации деструктора, и при реализации методов top () и pop () неявно предполагалось, что класс Т имеет конструктор копирования, так как этот конструктор будет вызываться в деструкторе стека, при возврате значения из методов top() ирор(),а также в теле цикла:
while(!emptyO) { Т t = рор(); }
Кроме того, класс Т должен иметь конструктор по умолчанию, который будет вызываться в методе push () при создании нового элемента стека. Неявные предположения такого рода при создании шаблонных классов программист обязательно должен иметь в виду. Использование шаблонного стека демонстрирует программа-клиент. Сначала создается стек с числами типа double:
TStack<double> t;
Обнаружив такую запись, компилятор создает из шаблонного класса конкретный класс, подставив на место Т тип double (листинг 11.2). Процесс создания конкретного класса из шаблона путем подстановки аргументов называется ин-станцированием шаблона (см. п. 14.7 в [1]). ПРИМЕЧАНИЕ
. Термин «инсталлирование» (instantiation) не слишком понятен, хотя стал уже практически общепринятым. К сожалению, именно этот термин использовался при переводе самой важной книги о шаблонах [28] и при переводе «библии по С++» [2]. В русском языке наиболее близко передает смысл этого понятия термин «конкретизация».
Листинг 11.2. Инстанцированный шаблон
class TStack { struct Elem {double data: Elem *next;
Elem (const double& d. Elem *p) :data(d). next(p) {} };
Elem * Head; int count;
TStack(const TStack &);
TStack& operator=(const TStack &); public: class Error: public std::exception {};
TStackO: Head(0), count(0) {}
-TStackO { while( lemptyO) pop(); }void push(const double& d) { Head = new Elem(d, Head); ++count; } double top() const {if (lemptyO) return Head->dat else throw ErrorO ; // имя корректируется компилятором // подставпен тип double // подставлен тип double
// указатель на вершину // счетчик элементов // закрыли копирование // закрыли присваивание // исключение - пустой стек // конструктор // деструктор! // подставпен тип double // новый элемент в стек // увеличиваем счетчик
II подставлен тип double
Листинг 11.2 (продолжение)
void pop() {if (emptyO) throw Error(); double top = Head->data; // подставлен тип double
Elem *oldHead = Head; Head = Head->next; delete oldHead;
--count; // уменьшаем счетчик } bool emptyO const // есть ли элементы в стеке {return Head==0; } int countO const // количество элементов в стеке {return count; } };
Именно этот класс после подстановки транслируется и попадает в работающую программу. Именно он и используется при объявлении объекта-стека t в программе-клиенте (см. листинг 11.1). Далее выполняется обычная работа с этим стеком, в частности аргументы-числа метода push() переводятся по умолчанию в double.
Обратите внимание, что имя инстанцированного класса выделено курсивом. Дело в том, что компилятор корректирует имя инстанцированного класса аналогично тому, как он поступает с именами функций при перегрузке. Зачем же это нужно? Когда компилятор встречает другое объявление, то снова инстанцируется конкретный класс-стек, например, для работы со строками используется такое объявление:
TStack<string> S;
Очевидно, имена у классов TStack<double> и TStack<string> должны различаться, иначе возникает ошибка повторного определения.
Таким образом, каждое объявление объекта-стека с аргументом-типом приводит к появлению в программе соответствующего инстанцированного класса. Если наследование и композиция служат для многократного использования объектного кода, то шаблоны, как мы видим, позволяют многократно использовать исходные тексты. В качестве шаблонного типа мы можем задавать и указатели. Например, мы вполне можем написать в программе следующее объявление:
TStack<void *> st;
По этому объявлению создается класс, практически совпадающий с первым универсальным стеком (см. листинг 6.9) — добавлен только деструктор. И работать с таким стеком нужно точно так же, как и с определенным вручную:
st.push(new double(21)); // помещаем в стек числа
st.push(new double(22)) st.push(new double(23))
while (!st.empty()) // пока стек не пустой { cout << *(double *)st.top() << endl; // выводим число с вершины double *р = (double *)st.pop(); // удаляем элемент из стека
delete р; // возвращаем память
Опубликовал Kest
December 06 2013 00:02:50 ·
0 Комментариев ·
3632 Прочтений ·
• Не нашли ответ на свой вопрос? Тогда задайте вопрос в комментариях или на форуме! •
Комментарии
Нет комментариев.
Добавить комментарий
Рейтинги
Рейтинг доступен только для пользователей.
Пожалуйста, залогиньтесь или зарегистрируйтесь для голосования.
Нет данных для оценки.
Гость
Вы не зарегистрированны? Нажмите здесь для регистрации.