При закрытии подписчики были переданы в рассылку "Программирование на Visual С++" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
| Информационный Канал Subscribe.Ru |
|
C++ для всех. Выпуск 7 Шаблонные классы. Объявление шаблонных классов |
Здравствуйте, уважаемые подписчики
Думаю не ошибусь если скажу, что шаблоны и шаблонные классы в частности, являются самой горяче обсуждаемой темой в большинстве С++ журналов и конференций. С помощью шаблонов творят просто невообразимые вещи. Но об этом позже,- сейчас нас интересуют базовые понятия.
Как и шаблонные функции, шаблонные классы могут применятся для обработки разнотипных данных. Наибольшее применение получили в контейнерных классах, и как замечательный пример - дружно смотрим в сторону стандартной библиотеки STL.
Итак начнем. Сначала декларация:
template<class T>
class A
{
public:
A(T *p=0):ptype(p){}
~A(){ delete ptype; }
void checkValue(const T&){}
const T* getValue()const{ return ptype; }
private:
T *ptype;
};
Теперь мы можем использовать вышеприведенный класс с любым типом:
struct STest{
int val;
double x;
};
int main()
{
A<int> a_int;
A<int*> a_pointer_int;
A<STest> *pa = new A<STest>(new STest());
...
delete pa;
return 0;
}
Шаблоны могут иметь несколько параметров, также в качестве параметров могут передаваться другие шаблоны. Для упрощения восприятия шаблонам можно давать эквивалентные имена с помощью ключевого слова typedef. Так, например широко используемый в стандартной STL тип std::string объявляется с помощью typedef:
namespace std{
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
}
Такая возможность позволяет значительно упростить внешний вид параметров. Вместо
a)
int main()
{
A<std::basic_string<char> > a_int;
return 0;
}
можно использовать более изящное решение:
б)
int main()
{
A<std::string> a_int;
return 0;
}
В примере a) прошу обратить внимание на способ записи параметров-шаблонов - между конечными угловыми скобками > > делается пробел, иначе будет ошибка компиляции. А теперь с несколькими параметрами:
template<class T1,class T2,class T3>
class A
{
public:
A(const T1&,const T2&,const T3&){}
};
int main()
{
A<int,double,double*> a(10,20,new double(30));
return 0;
}
Также шаблоны могут содержать уже определенные аргументы. Так поступают при необходимости получить определенное значение конкретного типа:
template<class T,int v> void func(T t)
{
std::cerr << "T: " << t << '\n';
std::cerr << "int: " << v << '\n';
}
int main()
{
func<double,20>(100.23);
return 0;
}
Для шаблонов с определенными аргументами добавлю, что в эти аргументы не являются модифицируемыми, т.е. такие действия не пройдут:
template<class T,int v> void func(T t)
{
v++; //ошибка на этапе компиляции, - v не модифицирумый тип
int x = &v; //аналогичная ошибка
int &x = v; //аналогичная ошибка
const int &x = v; //правильно
};
Кроме того, для немодифицируемых аргументов условия сравнения нужно брать в скобки, т.к. первый встреченный '>' будет рассматриваться как конец объявления шаблона:
template<int i = 10>20> class A{}; //ошибка
template<int i = (10>20)> class A{}; //нормально
Согласно стандарту (14.1.7) нельзя также объявлять шаблон с параметром-значением типа double, class или void, хотя некоторые компиляторы делают ограничения только на class:
struct STest{};
template<int d> class A; //нормально
template<STest v> class B; //ошибка компиляции
template<void d> class C; //ошибка компиляции
template<void* d> class D; //нормально
template<double d> class E; //ошибка компиляции
template<double *d> class F; //нормально
Шаблонный тип может иметь тип-параметр по умолчанию. В ниже приведенном примере определение интерфейс переменных a и a1 равноценен:
template<class T = int>
class A {};
int main()
{
A<> a;
A<int> a1;
return 0;
}
Параметр-тип по умолчанию может быть либо последним либо все аргументы шаблона обязаны иметь определенные типы по умолчанию:
template<class T=int,class T2> class A{}; //ошибка компиляции - нет второго параметра по умолчанию
template<class T,class T2=int> class B{}; //нормально
template<class T=double,class T2=int> class C{}; //нормально
В то же время, возможно объявить шаблон за несколько заходов:
template<class T,class T2=double> class A; template<class T=int,class T2> class A;
Это равноценно такому объявлению:
template<class T=int,class T2=double> class A;
Причем объявление действует в контексте, пока не будет дополнено дополнительным объявлением, т.е.:
template<class T,class T2=double> class A;
void func1()
{
A<> *a; //ошибка компиляции - отсутствует обязательный параметр
A<int> *b; //нормально
}
template<class T=int,class T2> class A;
void func2()
{
A<> *a; //нормально
}
template<class T,class T2> class A{};
Само собой разумеется, внутри шаблонного класса могут быть определены другие шаблонные классы (вложенные классы) и шаблонные функции:
template<class T>
class A
{
public:
template<class X> void anyFunc(const X &x){ A1<X> v; }
protected:
template<class T1>
class A1
{
protected:
T1 *p_t1;
T *p_t;
};
};
int main()
{
A<double> a;
return 0;
}
Порядок шаблонных аргументов в объявлении и определении обязан совпадать:
template<class T1,class T2>
class A
{
void func1();
void func2();
};
template<class T1,class T2> void A<T1,T2>::func1(){} //нормально
template<class T1,class T2> void A<T2,T1>::func2(){} //ошибка компиляции
Имена шаблонных типов перекрывают глобальные типы поэтому для доступа к глобальным типам и переменным следует использовать оператор ::
class T{};
int val;
template<class T,T val> void calcValue(T t)
{
T t1 = val; //здесь работают шаблонные параметры
::T t2 = ::val; //здесь глобальный тип и переменная
}
Пожалуй, на сегодня хватит. Далее я приведу текст простого шаблонного класса списка указателей, - посмотрите на локальные классы и на способ объявления и определения функций. Что бы Вы могли изменить? Как и почему?
/****************************************************************************************
ptrlist.h - description
-------------------
begin : Чтв Июл 24 2003
copyright : (C) 2003 by Yuri Gordienko
email : iqsoft@cg.ukrtel.net
*****************************************************************************************/
#include <stdlib.h>
template<class type>
class TPtrList
{
protected:
struct SNode{
type *data;
SNode *next;
};
public:
class Iterator{
friend class TPtrList;
public:
Iterator():curr(0){}
Iterator(const Iterator &s):curr(s.curr){}
bool isValid()const{ return curr!=0; }
Iterator& operator++(){ if(curr) curr = curr->next; return *this; }
const Iterator operator++(int){ Iterator tmp(element); ++*this; return tmp; }
type* operator->()const{ return curr ? curr->data : 0; }
type* operator*()const{ return curr ? curr->data : 0; }
protected:
Iterator(SNode *n):curr(n){}
private:
SNode *curr;
};
TPtrList(bool autodelete=false):is_autodelete(autodelete),ifirst(0),icurr(0),count_i(0){}
TPtrList(const TPtrList<type> &s):ifirst(0),icurr(0){}
virtual ~TPtrList(){ clear(); }
bool autoDelete()const{ return is_autodelete; }
void setAutoDelete(bool ad){ is_autodelete=ad; }
int count()const{ return count_i; }
bool isEmpty()const{ return !ifirst; }
void clear();
bool removeCurrent(){ return icurr ? remove(icurr->data) : false; }
void append(type *d);
bool remove(type *d);
type* find(type *d)const;
type* first();
type* last();
type* next();
Iterator begin()const{ return Iterator(ifirst); }
Iterator end()const{ return Iterator(0); }
const TPtrList& operator=(const TPtrList &s);
private:
SNode *ifirst,*ilast;
SNode *icurr;
int count_i;
bool is_autodelete;
};
template<class type>
void TPtrList<type>::clear()
{
SNode *p;
while(ifirst){
p = ifirst->next;
if(is_autodelete) delete ifirst->data;
free(ifirst);
ifirst = p;
}
icurr = 0;
count_i = 0;
}
template<class type>
void TPtrList<type>::append(type *d)
{
if(ifirst){
ilast->next = (SNode*)malloc(sizeof(SNode));
ilast = ilast->next;
}
else{
ifirst = (SNode*)malloc(sizeof(SNode));
ilast = ifirst;
}
ilast->data = d;
ilast->next = 0;
++count_i;
}
template<class type>
bool TPtrList<type>::remove(type *d)
{
SNode *p = ifirst;
SNode *p_old = 0;
while(p){
if(p->data==d){
if(is_autodelete) delete p->data;
if(p->next){
SNode *p1 = p->next;
memcpy(p,p1,sizeof(SNode));
p = p1;
}
else{
if(icurr==p) icurr = 0;
if(p_old)
p_old->next = 0;
else
ifirst = 0;
ilast = p_old;
}
free(p);
--count_i;
return true;
}
p_old = p;
p = p->next;
}
return false;
}
template<class type>
type* TPtrList<type>::find(type *d)const
{
SNode *p = ifirst;
while(p){
if(p->data==d) return p->data;
p = p->next;
}
return 0;
}
template<class type>
inline type* TPtrList<type>::first()
{
icurr = ifirst;
return icurr ? icurr->data : 0;
}
template<class type>
inline type* TPtrList<type>::last()
{
icurr = ifirst ? ilast : 0;
return icurr ? icurr->data : 0;
}
template<class type>
inline type* TPtrList<type>::next()
{
if(icurr){
icurr = icurr->next;
if(icurr) return icurr->data;
}
return 0;
}
template<class type>
const TPtrList<type>& TPtrList<type>::operator=(const TPtrList<type> &s)
{
if(this!=&s){
if(ifirst) clear();
SNode *p = s.ifirst;
ifirst = ilast = 0;
while(p){
if(!ifirst){
ifirst = (SNode*)malloc(sizeof(SNode));
ilast = ifirst;
}
else{
ilast->next = (SNode*)malloc(sizeof(SNode));
ilast = ilast->next;
}
ilast->data = p->data;
p = p->next;
}
if(ilast) ilast->next = 0;
is_autodelete = false;
count_i = s.count_i;
}
return *this;
}
int main()
{
TPtrList<int> vlist(true);
//заполнили
int i;
for(i=0;i<10;++i)
vlist.append(new int(i));
//обойдем и напечатаем через итератор
TPtrList<int>::Iterator it;
for(it=vlist.begin();it.isValid();++it)
printf("%d\n",**it);
//обойдем и изменим значение через функцию
for(int *p=vlist.first();p;p=vlist.next())
*p = i--;
//обойдем и напечатаем через итератор
for(it=vlist.begin();it.isValid();++it)
printf("%d\n",**it);
return 0;
}
На сегодня все. Пожелания, предложения, вопросы прошу на iqsoft@cg.ukrtel.net
В следующем выпуске мы подробно рассмотрим этот пример и детально поговорим о вложенных шаблонных классах, итераторах и шаблонных friend-классах.
(с) Юрий Гордиенко
| http://subscribe.ru/
E-mail: ask@subscribe.ru |
Отписаться
Убрать рекламу |
| В избранное | ||