类模板
C++ 除了支持函数模板,还支持类模板。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中,类模板的目的同样是将数据的类型参数化。
template<class T1, class T2> class 类名
{...}
- 类模板和函数模板都是以 template 开头声明模板;
- typename表明其后面的符合是一种数据类型(当然也可以使用 class),后跟类型参数;
- 类型参数不能为空,多个类型参数用逗号隔开。
一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char
等内置类型的地方,都可以用类型参数来代替。
实例:
#include <iostream>
#include<string>
using namespace std;
//类模板
template<class T1,class T2>
class Data
{
private:
T1 name;
T2 num;
public:
Data(T1 name,T2 num)
{
this->name=name;
this->num=num;
cout<<"有参构造"<<endl;
}
~Data()
{
cout<<"析构函数"<<endl;
}
void showData(void)
{
cout<<"name="<<this->name<<",num="<<this->num<<endl;
}
};
void test1()
{
//Data ob1("小明")err 类模板不允许 类型推导
Data<string,int> ob1("小明",100);
ob1.showData();
Data<int,string> ob2(200,"小华");
ob2.showData();
Data<int,int> ob3(100,300);
ob3.showData();
Data<string,string> ob4("小红","小德");
ob4.showData();
}
int main()
{
test1();
return 0;
}
总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板。
一、类模板与函数模板的区别
-
类模板没有自动类型推导的使用方式。
-
类模板在模板参数列表中可以有默认参数。
实例:
#include <iostream>
#include<string>
using namespace std;
template<class T1,class T2=int>
class Person
{
public:
T1 name;
T2 age;
public:
Person(T1 name,T2 age)
{
this->name=name;
this->age=age;
}
void showPerson()
{
cout<<"name:"<<this->name<<",age:"<<this->age<<endl;
}
};
//类模板没有自动类型推导的方式
void test1()
{
//Person p("小明",1000);err 类模板使用时候 不可以用自动类型推导
Person<string,int>p("小明",1000);//必须使用显示指定类型方式,使用类模板
p.showPerson();
}
//类模板在模板参数列表中可以有默认参数
void test2()
{
Person <string> p1("小华",2000);//类模板中的模板参数列表 可以指定默认参数
p1.showPerson();
}
int main()
{
test1();
test2();
return 0;
}
总结:
- 类模板使用只能用显示指定类型方式。
- 类模板中的模板参数列表可以有默认参数。
二、模板用作参数
模板可以包含类型参数(如typename T
)和非类型参数(如int n
)。模板还可以包含本身就是模板的参数,这种参数是模板新增的特性,用于实现STL。
头文件:
#ifndef STACKTP_H
#define STACKTP_H
template<class Type>
class Stack
{
private:
enum{Max=10};
Type items[Max];
int top;
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type & item);
bool pop(Type & item);
};
template<class Type>
Stack<Type>::Stack()
{
top=0;
}
template<class Type>
bool Stack<Type>::isempty()
{
return top==0;
}
template<class Type>
bool Stack<Type>::isfull()
{
return top==Max;
}
template<class Type>
bool Stack<Type>::push(const Type &item)
{
if(top<Max)
{
items[top++] = item;
return true;
}else
return false;
}
template<class Type>
bool Stack<Type>::pop(Type &item)
{
if(top>0)
{
item = items[--top];
return true;
}
else
return false;
}
#endif // STACKTP_H
源文件:
#include <iostream>
#include "stacktp.h"
using namespace std;
//模板参数template<typename >class Thing>
//template<typename >class类型 Thing参数。
template<template <typename T> class Thing>
class Crab
{
private:
//声明对象,
Thing<int> s1;
Thing<double> s2;
public:
Crab(){}
bool push(int a,double x){return s1.push(a) && s2.push(x);}
bool pop(int & a,double & x){return s1.pop(a) && s2.pop(x);}
};
int main()
{
//Thing<int>被实例化为Stack<int>,而Thing<double>被实例化为Stack<double>。
//模板参数Thing将被替换为声明Crab对象时被用作模板参数的模板类型
Crab<Stack> nebula;
int ni;
double nb;
cout<<"Enter int double pairs,such as 4 3.5 (0 0 to end):\n";
while(cin>>ni>>nb && ni>0 && nb>0)
{
if(!nebula.push(ni,nb))
break;
}
while(nebula.pop(ni,nb))
cout<<ni<<", "<<nb<<endl;
cout<<"Done.\n";
return 0;
}
template<template <typename T> class Thing>
//模板参数:template<typename >class Thing>
//类型 :template<typename >class
//参数:Thing
例:
Crab<King> legs;
//为了满足声明,模板参数King必须是个模板类,其声明与模板参数Thing的声明匹配:
template<typename T>
class King{...};
可以混合使用模板参数和常规参数,例:Crab类的声明可以像下面这样打头:
template<template <Typename T> class Thing,typename U,typename V>
class Crab
{
private:
Thing <U> s1;
Thing <V> s2;
....
//成员s1和s2可以存储的数据类型为泛型,而不是用硬编码指定的类型。
//这时nebula声明修改为:
Crab<Stack,int,double> nebuls;//T=Stack U=int V=double
类模板实例化出的对象,向函数传参的方式有三种:
- 指定传入的类型:直接显示对象的数据类型
- 参数模板化:将对象中的参数变化为模板进行传递
- 整个类模板化:将这个对象类型 模板化进行传递
例:
#include <iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Data
{
friend void addData(Data<string,int>&ob);
private:
T1 name;
T2 num;
public:
Data(T1 name,T2 num)
{
this->name=name;
this->num=num;
cout<<"有参构造"<<endl;
}
~Data()
{
cout<<"析构函数"<<endl;
}
void showData(void)
{
cout<<"name="<<this->name<<",num="<<this->num<<endl;
}
};
void addData(Data<string,int> &ob)
{
cout<<"普通函数"<<endl;
ob.name+="_vip";
ob.num+=200;
}
void test2()
{
Data<string,int> ob("小明",18);
addData(ob);
ob.showData();
}
int main()
{
test2();
return 0;
}
三、成员模板
模板可用结构、类或模板类的成员,实现STL的设计,必须使用这项特性。
例:模板类将另一个模板类和模板函数作为其成员。
#include <iostream>
using namespace std;
template <typename T>
class beta
{
private:
//声明私有,只能在beta中访问它。
template <typename V>
class hold
{
private:
V val;
public:
hold(V v=0):val(v){
}
void show()const{cout<<val<<endl;}
V Value()const{return val;}
};
//声明变量
hold<T> q;//T类型(beta模板参数)的hold对象;下面beta<double> guy(3.5,3);使得T为double
hold<int> n;//n是基于int类型的hold对象;
public:
beta(T t,int i):q(t),n(i){}
template<typename U>//由该方法被调用时的参数值显示确定。T类型由对象实例化类型确定。
//混合类型引起的自动类型转换以double类型计算,但返回值类型为U(即int),因此为18
U blab(U u,T t){return (n.Value()+q.Value()*u/t);}
void Show()const {q.show();n.show();}
};
int main()
{
beta<double> guy(3.5,3);
cout<<"T set to double\n";
guy.Show();
cout<<"V was set to T,which is double,then V was set to int\n";
cout<<guy.blab(10,2.3)<<endl;
cout<<"U was set to int\n";
cout<<guy.blab(10.0,2.3)<<endl;
cout<<"U was set to double\n";
cout<<"Done\n";
return 0;
}
在beta模板中声明hold类和blah方法,并在beta的外面定义
它们,然而老编译器根本不接受模板成员,而另一些编译器接受模板成员,但不接受类外面的定义。
然而,如果所用的编译器接受类外面的定义,则beta模板之外定义模板方法的代码如下:
template<typename T>
class beta
{
private:
template <typename V>
class hold;
hold<T> q;
hold<int> n;
public:
beta(T t, int i):q(t),n(i){}
template<typename U>
U blab(U u, T t);
void Show() const{q.show();n.show();}
};
template <typename T>
template<typename V>
class beta<T>::hold
{
private:
V val;
public:
hold(V v=0):val(v){}
void show()const{cout<<val<<endl;}
V Value()const{return val;}
};
注:将T、V和U用作模板参数。因为模板是嵌套的,因此必须使用下面语法:
template <typename T>
template<typename V>
//不能使用下面
template<typename T,typename V>
定义必须指出hold和blab是beta<T>
类的成员,这是通过使用作用域解析运算符完成的。
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建。
- 类模板中的成员函数在调用时才创建。
例:
#include<iostream>
using namespace std;
class Person1
{
public:
void showPerson1()
{
cout<<"Person1 show"<<endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout<<"Person2 show"<<endl;
}
};
template<class T>
class MyClass
{
public:
T ob;
//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成的
void fun1(){ob.showPerson1();}
void fun2(){ob.showPerson2();}
};
void test1()
{
MyClass<Person1>m;
m.fun1();
//m.fun2();//编译会出错,说明函数调用才会创建成员函数
}
int main()
{
test1();
return 0;
}
总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建。
四、类模板成员函数在类外实现
#include<iostream>
#include<string>
using namespace std;
//严格说:类模板的类型 不是Person 而是Person<T1,T2>
template<class T1,class T2>
class Person
{
public:
T1 name;
T2 age;
public:
//类内声明
Person(T1 name,T2 age);
void showPerson(void);
};
//类外定义
template<class T1,class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{
this->name=name;
this->age=age;
cout<<"有参构造"<<endl;
}
//成员函数 类外实现
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
cout<<"name="<<name<<",age="<<age<<endl;
}
int main()
{
Person<string,int> ob1("小法",18);
ob1.showPerson();
Person<int,int> ob2(12,14);
ob2.showPerson();
return 0;
}
总结:类模板中成员函数类外实现时,需要加上模板参数列表
五、类模板与继承
当类模板碰到继承时,需要注意下面几点:
- 当派生继承的基类是一个类模板,派生类在声明的时候,要指定出基类中T的类型
- 如果不指定,编译器无法给派生类分配内存
- 如果想灵活指定基类中T的类型,派生类也需变为类模板
#include<iostream>
#include<string>
using namespace std;
//类模板
template<class T>
class Base
{
private:
T num;
public:
Base(T num)
{
this->num = num;
cout << "有参构造" << endl;
}
~Base()
{
cout << "析构函数" << endl;
}
void showNum(void)
{
cout << num << endl;
}
};
//类模板 派生普通类必须给基类 指定T类型
class Son1 :public Base<int>
{
public:
Son1(int a) :Base<int>(a)
{
cout << "Son1的构造函数" << endl;
}
};
class Son2 :public Base<string>
{
public:
Son2(string a) :Base<string>(a)
{
cout << "Son2的构造函数" << endl;
}
};
int main()
{
Son1 ob(100);
ob.showNum();
Son2 ob2("小马");
ob2.showNum();
return 0;
}
六、类模板分文件实现
类模板中成员函数创建时机是在调用阶段,导致分文件编写链接不到。解决:
- 直接包含.cpp源文件:方法:创建源文件,person.cpp文件,person.h头文件,将头文件#include"person.h"换成.cpp
头文件:
#ifndef PERSON_H
#define PERSON_H
#include<iostream>
#include<string>
using namespace std;
template<class T1, class T2>
class Person
{
public:
T1 name;
T2 age;
public:
//类内声明
Person(T1 name, T2 age);
void showPerson(void);
};
#endif // PERSON_H
main.cpp文件:
#include <iostream>
#include<string>
#include"person.cpp"//include标准是包含头文件 基本上不会包含.cpp
using namespace std;
int main(int argc, char *argv[])
{
//因为类模板进行两次编译 1:本身,2:类模板成员函数调用
//c++,c但是单独编译的
//如果类模板的.cpp和.h分文件,错误原因在第二次
// 解决问题1:将上面的头文件#include"person.h"换成.cpp
//解决问题2:如果不包含.cpp,应该将.cpp和.h放在一起
Person<string, int> ob("小德", 12);
ob.showPerson();
return 0;
}
person.cpp文件:
#include"person.h"
//类外定义
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->name = name;
this->age = age;
cout << "构造函数" << endl;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "name=" << name << ",age:" << age << endl;
}
- 将声明和实现写到同一个文件,并更改后缀.hpp,hpp是约定的名称,并不是强制的因为include标准是包含头文件 基本上不会包含.cpp
头文件:
#ifndef PERSON_H
#define PERSON_H
#include<iostream>
#include<string>
using namespace std;
template<class T1, class T2>
class Person
{
public:
T1 name;
T2 age;
public:
//类内声明
Person(T1 name, T2 age);
void showPerson(void);
};
//类外定义
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->name = name;
this->age = age;
cout << "构造函数" << endl;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "name=" << name << ",age:" << age << endl;
}
#endif // PERSON_H
main文件:
#include <iostream>
#include<string>
//#include"person.cpp"//include标准是包含头文件 基本上不会包含.cpp
#include"person.hpp"
using namespace std;
int main(int argc, char *argv[])
{
//分文件实例化对象将报错 不识别
//因为类模板进行两次编译 1:本身,2:类模板成员函数调用
//c++,c但是单独编译的
//如果类模板的.cpp和.h分文件,错误原因在第二次
// 解决问题1:将上面的头文件#include"person.h"换成.cpp
//解决问题2:如果不包含.cpp,应该将.cpp和.h放在一起
Person<string, int> ob("小德", 12);
ob.showPerson();
return 0;
}
七、类模板与友元
模板的友元分3类:
- 非模板友元。
- 约束模板友元,即友元的类型取决于被实例化时的类型;
- 非约束模板友元,即友元的所有具体化是类的每一个具体化的友元。
①非模板友元
//非模板友元。
#include <iostream>
using namespace std;
template<typename T>
class HasFriend
{
private:
T item;
static int ct;//静态成员共享
public:
HasFriend(const T & i):item(i){ct++;}
~HasFriend(){ct--;}
//使counts()函数成为模板所有实例化的友元
//将是类hasFriend<int>和HasFriend<string>的友元。
friend void counts();
//要提供模板参数,必须指明具体化。
//声明一个特定类型的对象时,将生成的具体化:HasFriend<int> hf;
//所以必须向下面这样声明:
friend void reports(HasFriend<T> &);
//也就是说,带HasFriend<int>参数的report( )将成为HasFriend<int>类的友元。
// 同样,带HasFriend<double>参数的report()将是report()的一个重载版本它是Hasfriend<double>类的友元。
};
template <typename T>
int HasFriend<T>::ct=0;
void counts()
{
cout<<"int count:"<<HasFriend<int>::ct<<";";
cout<<"double count:"<<HasFriend<int>::ct<<endl;
}
//注意,report()本身并不是模板函数,而只是使用 一个模板作参数。
//这意味着必须为要使用的友元定义显示具体化:void reports(HasFriend<int> &hf)
void reports(HasFriend<int> &hf)
{
cout<<"HasFriend<int>:"<<hf.item<<endl;
}
void reports(HasFriend<double> &hf)
{
cout<<"HasFriend<double>:"<<hf.item<<endl;
}
int main()
{
cout<<"No objects declared:";
counts();
HasFriend<int> hfi1(10);
cout<<"After hfi1 declared:";
counts();
HasFriend<int> hfi2(20);
cout<<"After hfi2 declared:";
counts();
HasFriend<double> hfdb(10.5);
cout<<"After hfdb declared:";
counts();
reports(hfi1);
reports(hfi2);
reports(hfdb);
return 0;
}
注:
counts()
函数不是通过对象调用的(它是友元,不是成员函数),也没有对象参数,那么它如何访问HasFriend对象呢?有很多种可能性。
- 它可以访问全局对象;
- 可以使用全局指针访问非全局对象;
- 可以创建自己的对象;
- 可以访问独立于对象的模板类的静态数据成员。
②、模板类的约束模板友元函数
可以修改上一个实例,使友元函数本身成为模板,具体说,为约束模板友元作准备,要使类的每一个具体化都获得与友元匹配的具体化。这比非模板友元复杂些。包含3步:
- 在类定义的前面声明每个模板函数。
- 在函数中再次将模板声明为友元,这些语句根据类模板参数的类型声明具体化。
- 为友元提供模板定义。
#include <iostream>
using namespace std;
//第一步:在类定义的前面声明每个模板函数。
template <typename T>void counts();
template <typename T>void report(T &);
template<typename TT>
class HasFriendT
{
private:
TT item;
static int ct;
public:
HasFriendT(const TT & i):item(i){ct++;}
~HasFriendT(){ct--;}
//第二步:在函数中再次将模板声明为友元,这些语句根据类模板参数的类型声明具体化。
//声明中的<>指出这是模板具体化。对于report(),<>可以为空,因为可以从函数参数推断出如下模板类型参数:HasFriendT<TT>;counts没有参数必须使用模板参数<TT>来执行具体化。
friend void counts<TT>();
friend void report<>(HasFriendT<TT> &);
};
template <typename T>
int HasFriendT<T>::ct=0;
//第三步:为友元提供模板定义
template <typename T>
void counts()
{
cout<<"int count:"<<sizeof(HasFriendT<T>)<<";";
cout<<"double count:"<<HasFriendT<T>::ct<<endl;
}
template <typename T>
void report(T & hf)
{
cout<<hf.item<<endl;
}
int main()
{
counts<int>();
HasFriendT<int> hfi1(10);
HasFriendT<int> hfi2(20);
HasFriendT<double> hfdb(10.5);
report(hfi1);
report(hfi2);
report(hfdb);
cout<<"counts<int>() output:\n";
counts<int>();//指明具体化
//方法2:report<HasFriendT<int> >(hfi1>;
cout<<"counts<double>() output:\n";
counts<double>();//指明具体化
return 0;
}
正如您看到的, counts<double>
和 counts<int>
报告的模板大小不同,这表明每种 T
类型都有自己的友元函数 count()
。
③、模板类的非约束模板友元函数
前一 节中的约束模板友元函数是在类外面声明的模板的具体化。int类具体化获得 int函数具体化,依此类推。
通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。
#include <iostream>
using namespace std;
template<typename T>
class ManyFriend
{
private:
T item;
public:
ManyFriend(const T & i):item(i){}
//对于非约束友元,友元模板类型参数与模板类类型参数是不同的:
template<typename C,typename D> friend void show2(C &,D &);
};
//void show2 <ManyFriend<int> &, ManyFriend<int> &>
// ( ManyFriend<int> &c, ManyFriend<int> & d);等价于下面(因为是ManyFriend具体化友元,所以能够访问所有具体化的item成员,但它只访问了ManyFriend<int>对象。):
template<typename C,typename D> void show2(C & c,D & d)
{
cout<<c.item<<", "<<d.item<<endl;
}
//show(hfd,hfi2)与下面具体化匹配:
//void show2 <ManyFriend<double> &, ManyFriend<int> &>
// ( ManyFriend<double> &c, ManyFriend<int> & d)
//也是ManyFriend具体化的友元,并访问了ManyFriend<int>对象的item成员和ManyFriend<double>对象的item成员。
int main()
{
ManyFriend<int> hfi1(10);
ManyFriend<int> hfi2(20);
ManyFriend<double> hfdb(10.5);
cout<<"hfi1,hfi2:";
show2(hfi1,hfi2);
cout<<"hfdb hfi2:";
show2(hfdb,hfi2);
return 0;
}
八、类模板案例
头文件:
#ifndef ARRAY_H
#define ARRAY_H
#include<iostream>
#include<vector>
using namespace std;
template<class T>
class MyArray
{
private:
T *addr;
int capacity;//容量
int size;//大小
public:
MyArray(int capacity);
MyArray(const MyArray &ob);
~MyArray();
//尾插法
void push_back(const T &val);
//遍历数组
void printArray(void);
};
#endif // ARRAY_H
template<class T>
MyArray<T>::MyArray(int capacity)
{
//对于自定义数据类型 new Person[10];数组中的每个元素 都会调用无参构造
this->addr=new T[capacity];
this->capacity=capacity;
this->size=0;
}
template<class T>
MyArray<T>::MyArray(const MyArray &ob)
{
this->capacity=ob.capacity;
this->addr=new T[this->capacity];
int i=0;
for(i=0;i<ob.size;i++)
{
this->addr[i]=ob.addr[i];
}
this->size=ob.size;
}
template<class T>
MyArray<T>::~MyArray()
{
if(this->addr !=NULL)
{
delete [] this->addr;
this->addr=NULL;
}
}
template<class T>
void MyArray<T>::push_back(const T &val)
{
//数组的实际个数size不能超过capacity
if(this->size==this->capacity)
{
cout<<"容器已满"<<endl;
return;
}
this->addr[this->size]=val;
this->size++;
}
template<class T>
void MyArray<T>::printArray()
{
int i=0;
for(i=0;i<this->size;i++)
{
//如果输出的是自定义数据 Person必须重载
cout<<this->addr[i]<<" ";
}
cout<<endl;
}
源文件mian.cpp:
#include <iostream>
#include<string>
#include"array.hpp"
using namespace std;
class Person{
friend ostream&operator <<(ostream &out,const Person &ob);
private:
string name;
int age;
public:
Person()
{}
Person(string name,int age)
{
this->name=name;
this->age=age;
}
};
ostream & operator <<(ostream &out,const Person &ob)
{
out<<"name="<<ob.name<<",age="<<ob.age<<endl;
return out;
}
int main()
{
MyArray<char> ob(10);
ob.push_back('a');
ob.push_back('b');
ob.push_back('c');
ob.push_back('d');
ob.printArray();
MyArray<int> ob2(10);
ob2.push_back(10);
ob2.push_back(20);
ob2.push_back(30);
ob2.push_back(40);
ob2.printArray();
//存放自定义 数据
MyArray<Person> ob3(10);
ob3.push_back(Person("德玛",18));
ob3.push_back(Person("风男",19));
ob3.push_back(Person("小法",20));
ob3.push_back(Person("瞎子",21));
ob3.printArray();
return 0;
}
九、指针栈
string po;
//替换成:
char * po;
//表明使用char指针而不是string对象来接收输入,但是结果会失败,因为仅仅创建指针,没有创建用于保存输入字符串的空间。
string po;
//替换成:
//char po[40];
//表明输入的字符串分配了空间。
//po的类型为`char *`,因此可以被放在栈中。
但数组完全与下面的pop()
方法冲突:
template<class T>
bool Stack<T>::pop(T & item)
{
if(top>0)
{
item=items[--top];
return true;
}
else
return false;
}
首先,引用变量item必须引用某种类型的左值,而不是数组名。其次,代码假设可以给item赋值。即使item能够引用数组,也不能为数组名赋值。因此会失败。
string po;
//替换为:
char * po= new char[40];
//为输入的字符串分配了空间。
//po是变量,因此与pop()的代码兼容。
//遇到最基本的问题:只有一个po变量,该变量总是指向相同的内存单元
正确使用指针栈方法:
-
让调用程序提供一个指针数组,其中每个指针都指向不同的字符串。
-
把这些指针放在栈中是有意义的,因为每个指针都指向不同的字符串。
-
注意,创建不同指针是调用程序的职责,而不是栈的职责。栈的任务是管理指针,而不是创建指针。
十、数组模板示例和非类型参数及模板多功能性
1.数组模板示例和非类型参数
-
模板常用作容器类,这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型。
-
下面探讨一些非类型(或非表达式)参数以及如何使用数组来处理继承族。
-
允许指定数组大小的简单数组模板方法:方法1:在类中使用动态数组和构造函数参数来提供元素数目。方法2:使用模板参数来提供参数来常规数组的大小。
实例:
#ifndef ARRAYTP_H
#define ARRAYTP_H
#include<iostream>
#include<cstdlib>
using namespace std;
template <class T,int n>
class ArrayTP
{
private:
T ar[n];
public:
ArrayTP(){}
explicit ArrayTP(const T &v);
virtual T & operator [](int i);
virtual T operator [](int i)const;
};
template <class T,int n>
ArrayTP<T,n>::ArrayTP(const T &v)
{
for(int i=0;i<n;i++)
ar[i]=v;
}
template<class T,int n>
T &ArrayTP<T,n>::operator [](int i)
{
if(i<0 || i>=n)
{
cerr<<"Error in array limits:"<<i
<<"is out of range\n";
}
return ar[i];
}
template<class T,int n>
T ArrayTP<T,n>::operator[](int i)const
{
if(i<0||i>=n)
{
cerr<<"Enter in array limit:"<<i
<<"is out of range\n";
}
return ar[i];
}
#endif // ARRAYTP_H
注:
- 表达式有允许限制,表达式参数可以是整型,枚举,引用或指针,表明
double m
是不合法的,但double * rm
和double *pm
是合法的。 - 模板代码不能修改参数的值,也不能使用参数的地址。所以,在ArrayTP模板中不能使用诸如
n++
和&n
等表达式。 - 实例化模板时,用作表达式参数的值必须是常量表达式。
2.模板多功能性
可以将用于常规类的技术用于模板类,模板类可用作基类,也可以用作组件类,还可以用作其他模板的类型参数。
例如,可以使用数组模板实现栈模板,也可以使用数组来构造数组:数组元素是基于栈模板的栈。
template <class T>
class Array
{
private:
T entry;
...
};
template <classs T>
class GrowArray:public Array<T>{...};
template<class T>
class Stack
{
Array<Tp> ar;//使用Array<>作为组件
...
};
...
Array<Stack<int> >asi;//int堆栈的数组
在最后一条语句中,c++98,要求使用至少一个空白字符将两个>符合分开,以免与运算符>>混淆。c++11不要求这样做。
3.递归使用模板
递归使用模板,例如:ArrayTP<Array<int,5>,10>twodee;
这使得twodee是一个包含10个元素的数组,其中每个元素都是一个包含5个int元素的数组。与之等价的常规数组声明如下:
int twodee[10][5];
在模板语法中,维的顺序与等价的二维数组相反。
下面程序,使用了这种方法,同时使用ArrayTP模板创建了一维数组,来分别保存这10组(每组包含5个数)的总数和平均值。方法调用cout.width(2)
以两个字符的宽度显示下一个条目(如果整个数字的宽度不超过两个字符)。例:
#include"arraytp.h"
#include <iostream>
using namespace std;
int main(void)
{
ArrayTP<int,10>sums;
ArrayTP<double,10>aves;
ArrayTP<ArrayTP<int,5>,10>twodee;
int i,j;
for(i=0;i<10;i++)
{
sums[i]=0;
for(j=0;j<5;j++)
{
twodee[i][j]=(i+1)*(j+1);
sums[i]+=twodee[i][j];
}
aves[i]=(double) sums[i]/10;
}
for(i=0;i<10;i++)
{
for(j=0;j<5;j++)
{
cout.width(2);
cout<<twodee[i][j]<<' ';
}
cout<<":sum=";
cout.width(3);
cout<<sums[i]<<",average="<<aves[i]<<endl;
}
cout<<"Done.\n";
return 0;
}
4.使用多个类型参数
模板可以包含多个类型参数,例如:假设希望类可以保存两种值,则可用创建并使用Pair模板来保存两个不同的值(标准模板库提供了类似的模板,名为pair)。
下面程序演示了。其中,方法first()const和second()const
报告存储的指值,由于这两个方法返回Pair数据成员的引用。因此让你能够通过赋值重新设置存储的值。
例:
#include <iostream>
#include<string>
using namespace std;
template <class T1,class T2>
class Pair
{
private:
T1 a;
T2 b;
public:
T1 & first();
T2 & second();
T1 first()const{return a;}
T2 second()const{return b;}
Pair(const T1 & aval,const T2 &bval):a(aval),b(bval){}
Pair(){}
};
template<class T1,class T2>
T1 & Pair<T1,T2>::first()
{
return a;
}
template<class T1,class T2>
T2 & Pair<T1,T2>::second()
{
return b;
}
int main()
{
//必须使用Pair<string,int>来调用构造函数,并将它作为sizeof的参数,因为类名为Pair<string,int>,而不是Pair
Pair<string,int> ratings[4]=
{
Pair<string,int>("The Purpled Duck",5),
Pair<string,int>("Jaquie's Frisco AI Fresco",4),
Pair<string,int>("Cafe Souffle",5),
Pair<string,int>("Bertie's Eats",3)
};
int joints=sizeof(ratings)/sizeof(Pair<string,int>);
cout<<"Rating:\t Eatery\n";
for(int i=0;i<joints;i++)
cout<<ratings[i].second()<<":\t"
<<ratings[i].first()<<endl;
cout<<"Oops! Revised rating:\n";
ratings[3].first()="Berting's Fab Eats";
ratings[3].second()=6;
cout<<ratings[3].second()<<":\t"
<<ratings[3].first()<<endl;
return 0;
}
注:Pair<char*,double>
是另一个完全不同的类的名称。
默认类型模板参数,类模板的另一项新特性是,可以为类型参数提供默认值:
template<class T1,class T2=int>class Topo{...};
//省略T2的值,编译器将使用int:
Topo<double,double> m1;//T:double,T2:double
Topo<double>m2;//T:double,T2:int
虽然可以为类模板类型参数提供默认值,但不能为函数模板提供默认值。然而,可以为非类型参数提供默认值,这对于类模板和函数模板都是适合的。
十一、模板的具体化
- 类模板与函数模板很相似,因为可以隐式实例化,显示实例化和显示具体化,统称具体化。
- 模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明。
- 项目3
1.隐式实例化
声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义。
ArrayTP<int,100> stuff;//隐式实例化
//编译器在需要对象之前,不会生成类的隐式实例化:
ArrayTP<double,30> *pt;//指针,还不需要对象
pt = new ArrayTP<double,30>;//现在需要一个对象;导致编译器生成类定义,并根据该定义创建一个对象。
2.显示实例化
当使用关键字template
并指出所需类型来声明类时,编译器将生成类声明的显示实例化。
声明必须位于模板定义所在的名称空间中,例如:下面的声明将ArrayTP<string,100>
声明为一个类:
template class ArrayTP<string 100>;//生成ArrayTP<string,100>类
这种情况,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成模板来生成具体化。
3.显示具体化
显式具体化是特定类型(用于替换模板中的泛型)的定义,有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同,在这种情况下,可以创建显式具体化。
例如,假设已经为用于表示排序后数组的类(元素在加入时被排序)定义了一个模板:
template<typename T>
class SortedArray
{
...
};
假设模板使用>
运算符来对值进行比较:
- 对于数字,这管用;
- 如果T表示一种类,则只要定义了
T::operator>()
方法,这也管用; - 但如果T是由
const char*
表示的字符串,这将不管用。
实际上,模板倒是可以正常工作,但字符串将按地址(按照字母顺序)排序。这要求类定义使用strcmp()
,而不是>
来对值进行比较。
在这种情况下,可以提供一个显式模板具体化,这将采用为具体类型定义的模板,而不是为泛型定义的模板。当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。
- 定义:
template <> class Classname<specialized-type-name>{...};
- 早期形式:
class Classname<specialized-type-name>{...};
例:使用新的表示法提供一个专供const char *
类型使用的SortedArray模板:
template<> class SortedArray<const char char *>
{...};
其中将使用strcmp(而不是>)
比较数组值,当请求const char *
类型的SortArray模板时,编译器将使用上述专用的定义,而不是通用的模板定义:
SortArray<int> scores;//一般定义
SortArray<const char *> dates;//使用显示具体化定义
4.部分具体化
部分限制模板的通用性,例:部分具体化可以给类型参数之一指定具体的类型:
//通用模板
template <class T1,class T2>class Pair {...};
//部分具体化
template<class T1> class Pair<T1,int>{...};
如果指定所有的类型,则<>内将为空,这将导致显示具体化:
template<> class Pair<int,int>{...};
如果多个模板可供选择,编译器将使用具体化化程度最高模板,例:
Pair<double,double> p1;//通用模板
Pair<double,int> p2;//使用 Pair<T1,int>
Pair<int,int>p3;//使用 Pair<int,int>
也可以通过指针提供特殊版本来部分具体化现有的模板:
template<class T>//通用版本
class Feeb{...};
template <class T*>//指针部分具体化
class Feeb{...};
如果提供的类型不是指针,则编译器将使用通用版本;如果提供指针,则编译器将使用指针具体化版本:
Feeb<char >fb1;//使用通用模板,T:char
Feeb<char *> fb2;//使用 Feeb T* 具体化 T:char
如果没有进行部分具体化,则第二个声明将使用通用模板,将T转换为char *
类型,如果进行了部分具体化,则第二个声明将使用具体化模板,将T转换为char。部分具体化特性能设置各种限制。例:
//通用模板
template <class T1,class T2,class T3> class Trio{...};
//T3设置为T2的具体化
template<class T1,class T2> class Trio<T1,T2,T3>{...};
//T3和T2设置为T1*具体化
template<class T1> class Trio<T1,T1*,T1*>{...};
上述声明,编译器将作出如下选择:
Trio<int, short,char *> t1;//通用
Trio<int,short> t2;//Trio<T1,T2,T3>
Trio<char,char *,char *> t3;//Trio<T1,T1*,T1*>
十二、模板别名
使用typedef
为模板具体化指定别名:
//定义三个typedef别名
typedef array<double,12>arrd;
typedef array<int,12>arri;
typedef array<string,12>arrst;
arrd gallons;
arri days;
arrst months;
c++新增的使用模板提供一系列别名,如:
template <class T>
arrtype=array<T,12>;//创建多个别名模板
这将arrtype定义为一个模板别名,可使用它来指定类型,如下:
ayytype<double>gallons;
arrtype<int>age;
arrtype<string>months;
总之,arrtype<T>
表示类型array<T,12>
。
c++11允许将语法using=用于非模板。用于非模板时,这种语法与常规typedef等价:
typedef const char * pc1;
using pc2=const char *;
typedef const int *(*pc1)[10];
using pa2=const int*(*)[10];