C++STL—类模板
四、类模板
4.1 类模板的声明
- 声明形式:
template<class 类型形参1,....> class 类模板名{...};
例如:
template<class A, class B>class CMath {
public:
A m_a;
B func(){....};
};
- 如果在类模板外实现成员函数
声明形式
template<class 类型形参1,...> 返回值类型 类模板名<类型形参1,...>::函数名(调用形参1,....){ 函数体实现; }
例如:
template<class A,class B> B CMath<A,B>::func(){
........;
}
4.2 类模板的使用
- 使用类模板必须对类模板进行实例化(产生真正的类)
类模板本身并不代表一个确定的类型(即不能用于定义对象),只有通过类 型实参实例化成真正的类后才具备类的语义(即可以定义对象)。
例如:
CMath<int,double> math;
-
类模板被实例化时类模板中的成员函数并没有实例化, 成员函数只有在被调用时才会被实例化(即产生真正成员函数)
注意:成员虚函数除外 -
某些类型虽然并没有提供类模板所需要的全部功能但照样可以实例化类模板,只要不调用那些未提供功能的成员函数即可。
clsTemplate1.cpp
#include <iostream>
using namespace std;
//类模板
//可以把template<class T>单独写一行,除却这一行其余和类的写法一致
template<class T>
class CMath{
public:
CMath(T const& t1, T const& t2):m_t1(t1),m_t2(t2){}
T add();
T add(){ return m_t1 + m_t2 ; }
private:
T m_t1;
T m_t2;
};
//如果在类模板外书写函数:类模板先声明、template<class T>不能丢、::作用域、CMath<T>中的<T>不能丢
/*template<class T>T CMath<T>::add(){
return m_t1 + m_t2;
}*/
//class CMath<int>{.....}; //CMath<int>为类名
//class CMath<double>{.....};
//class CMath<string>{.....};
int main(){
int nx=10, ny=20;
CMath<int> m1(nx,ny);
cout << m1.add() << endl;
double dx=12.3, dy=45.6;
CMath<double> m2(dx,dy);
cout << m2.add() << endl;
string sx="hello", sy=" world";
CMath<string> m3(sx,sy);
cout << m3.add() << endl;
return 0;
}
clsTemplate2.cpp
#include <iostream>
using namespace std;
class Integer{
public:
Integer(int i):m_i(i){}
//重载+运算符后即可使用类模板的add()
Integer operator+(Integer const& that)const{
return m_i + that.m_i;
}
private:
int m_i;
};
//类模板
//可以把template<class T>单独写一行,除却这一行其余和类的写法一致
template<class T>
class CMath{
public:
CMath(T const& t1, T const& t2):m_t1(t1),m_t2(t2){}
T add(){ return m_t1 + m_t2 ; }
private:
T m_t1;
T m_t2;
};
//CMath<int>编译器自动生成的只有成员变量无成员函数的类
/*
class CMath<int>{
public:
private:
int m_t1;
int m_t2;
};*/
//CMath<int> m1(nx,ny)才会进一步生成成员函数——构造函数,m1.add()生成add()成员函数
/*
class CMath<int>{
public:
CMath<int>(int const& t1, int const& t2):m_t1(t1),m_t2(t2){}
int add(){
return m_t1 + m_t2;
}
private:
int m_t1;
int m_t2;
};*/
int main(){
int nx=10, ny=20;
//CMath<int>自动引发编译器生成名为CMath<int>的类(只有成员变量无成员函数的类));CMath<int> m1(nx,ny)才会进一步生成成员函数——构造函数,m1.add()生成add()成员函数
CMath<int> m1(nx,ny);
cout << m1.add() << endl;
Integer ix=100, iy=200;
CMath<Integer> m2(ix,iy);
m2.add();//
int nx=10, ny=20;
CMath<int> m(nx,ny);
cout << m.add() << endl;
return 0;
}
4.3 类模板的静态成员
-
类模板中的静态成员即不是每个对象拥有一份
-
也不是类模板拥有一份.
-
而应该是由类模板实例化出的每一个真正的类各有一份.
-
且为该实例化类定义的所有对象共享。
static.cpp
#include <iostream>
using namespace std;
template<class T>class A{
public:
static void print(){
cout << "&m_i:" << &m_i << "," << "&m_t:" << &m_t << endl;
}
static int m_i;
static T m_t;
};
//类名A<T>引成员
template<class T>int A<T>::m_i;// = 0;
template<class T>T A<T>::m_t; //= ??
int main(){
A<int> x,y,z;
x.print();
y.print();
z.print();
A<int>::print();
cout << "--------------------" << endl;
A<double> m,n,t;
m.print();
n.print();
t.print();
A<double>::print();
return 0;
}
4.4 类模板的递归实例化
-
可以使用任何类型来实例化类模板。
-
由类模板实例化产生的类也可以用来实例化类模板自身,这种做法称之为类模板的递归实例化。
-
通过这种方法可以构建空间上具有递归特性的数据结构(例如:多维数组)。
recursion.cpp
#include <iostream>
using namespace std;
template<class T>class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
int main(){
//此处两个>>中间必须用空格隔开,否则编译器无法识别
Array<Array<int> > m;
for(int i=0; i<10; i++){
for(int j=0; j<10; j++){
m[i][j] = i+j;
}
}
for(int i=0; i<10; i++){
for(int j=0; j<10; j++){
cout << m[i][j] << ' ';
}
cout << endl;
}
/* Array<int> a;
for(int i=0; i<10; i++)
a[i] = i+1;
for(int i=0; i<10; i++)
cout << a[i] << ' ';
cout << endl;*/
return 0;
}
五、类模板扩展
5.1 全局特化
-
全类特化 :特化一个类模板可以特化该类模板所有的成员函数,相当于重新写了一个针对某种特定数据类型的具体类。
声明形式 :
template<>class 类模板名<类型参数1,...>{.....};
例如:
template<>class CMath<char*>{.....};
-
成员特化:类模板特化除了可以对整个类进行特化以外,可以只针对某部分成员函数进行特化。
声明形式:
template<>返回值类型 类模板名<类型参数1,…>::成员函数名(调用参数1,...){....}
例如 :
template<>
char *const CMath<char *const>::sum(...){....}
除去template<>
相当于写了一个真正的类。
special.cpp
#include <iostream>
#include <cstring>
using namespace std;
//类模板
template<class T>class CMath{
public:
CMath(T const& t1, T const& t2):m_t1(t1),m_t2(t2){}
T add(){
return m_t1 + m_t2;
}
private:
T m_t1;
T m_t2;
};
//成员特化
template<>
char* const CMath<char*const>::add(){
//strcat()两个字符串拼接
return strcat(m_t1, m_t2);
}
/*
//全类特化
template<>
class CMath<char* const>{
public:
CMath<char* const>(char*const& t1, char* const& t2):m_t1(t1),m_t2(t2){}
char* const add(){
return strcat(m_t1,m_t2);
}
private:
char* const m_t1;
char* const m_t2;
};*/
int main(){
int nx=10, ny=20;
CMath<int> m1(nx,ny);
cout << m1.add() << endl;
double dx=12.3, dy=45.6;
CMath<double> m2(dx,dy);
cout << m2.add() << endl;
string sx="hello", sy=" world";
CMath<string> m3(sx,sy);
cout << m3.add() << endl;
char cx[256]="hello", cy[256]=" world";
CMath<char* const> m4(cx,cy);
cout << m4.add() << endl;
return 0;
}
全类特化相对复杂,有些没必要的方法就不用特化,因此可以选择成员特化:
//成员特化
template<>
char* const CMath<char*const>::add(){
//strcat()两个字符串拼接
return strcat(m_t1, m_t2);
}
全类特化和成员特化都属于全局特化。
5.2 局部特化
类模板的局部特化,除非必要否则尽量不要特化,因为特化版本过多容易引发编译器匹配歧义。
local.cpp
#include <iostream>
using namespace std;
template<class T, class D>class CMath{
public:
static void foo(){
cout << "1:CMath<T,D>::foo" << endl;
}
};
//局部特化
template<class T>class CMath<T,short>{
public:
static void foo(){
cout << "2:CMath<T,short>::foo" << endl;
}
};
//本来T和D可一样,可不一样,此处定义为必须一样,也为特化
template<class T>class CMath<T,T>{
public:
static void foo(){
cout << "3:CMath<T,T>::foo" << endl;
}
};
//也是一种特化
template<class T, class D>class CMath<T*,D*>{
public:
static void foo(){
cout << "4:CMath<T*,D*>::foo" << endl;
}
};
int main(){
CMath<int,double>::foo();//1
CMath<int,short>::foo();//2
// CMath<short,short>::foo();//匹配歧义,在2和3之间无法抉择,编译器不知道选择哪一个
CMath<int*,double*>::foo();//1
// CMath<int*,int*>::foo();//匹配歧义,在3和4中无法抉择(若无4则选3)
return 0;
}
全局特化和局部特化的区别在于特化的类型参数,如果是所有类型参数都特化即为全局特化,否则为局部特化(template<class T>class CMath<T,short>
中的short
)。
5.3 类型形参的缺省
-
类模板的类型形参可以带缺省值。
实例化类模板时,如果提供了类型实参则用所提供的类型实参来实例化类模板,如果没有提供类型实参则用相应的类型形参的缺省类型来实例化类模板。 -
如果类模板的某个类型形参带有缺省值,那么它后面的类型形参都必须带缺省值。
defalt.cpp
#include <iostream>
#include <typeinfo>
using namespace std;
template<class T=short,class D=int>class CMath{
public:
void print(){
cout << "m_t:" << typeid(m_t).name() << ","
<< "m_d:" << typeid(m_d).name() << endl;
}
private:
T m_t;
D m_d;
};
int main(){
CMath<float,double> m;
m.print();
CMath<> m2;
m2.print();
return 0;
}
5.4 数值形的模板参数
- 类类模板的模板形参并不限于类型参数,普通数值也可以作为模板的参数。
valparam.cpp
#include <iostream>
using namespace std;
template<class T=double,size_t S=15>class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
size_t size(){
return S;
}
private:
T m_arr[S];
};
int main(){
Array<int> a;
for(int i=0; i<a.size(); i++)
a[i] = i+1;
for(int i=0; i<a.size(); i++)
cout << a[i] << ' ';
cout << endl;
return 0;
}
5.5 模板技巧
- 模板型成员变量
成员变量,但其类型是由一个类模板实例化的未知类,那么它才可以称之为模板型成员变量。
例如:
template<class T>class Arrary{…};
template<class D>class Sum{
public:
Arrary<D> m_s; //模板型成员变量
}
templateMemberVal.cpp
#include <iostream>
using namespace std;
template<class T>class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
template<class D>class Sum{//求和器
public:
Sum(Array<D>& s):m_s(s){}
D add(){//求和
D d = 0;
for(int i=0; i<10; i++){
d += m_s[i];
}
return d;
}
private:
Array<D> m_s;//模板型成员变量
};
int main(){
Array<int> a;
for(int i=0; i<10; i++)
a[i] = i+1;
Sum<int> s(a);
cout << s.add() << endl;
return 0;
}
- 模板型成员函数
别名:类模板的成员函数模板。
例如:
template<class T>class CMath{
public:
template<class D>void foo(){......}//成员函数模板
};
如果在类外实现:
template<class T>template<class D>void CMath<T>::foo(){.....}
templateMemberFunc.cpp
#include <iostream>
using namespace std;
template<class T>class CMath{
public:
template<class D>void foo();
/* template<class D>void foo(){//成员 函数模板
cout << "CMath<T>::foo<D>()" << endl;
}*/
};
template<class T>
template<class D>void CMath<T>::foo(){
cout << "CMath<T>::foo<D>()" << endl;
}
int main(){
CMath<int> m;
m.foo<double>();
return 0;
}
- 模板型成员类型
类模板中嵌套的类模板
例如:
template<class X>class A{
public:
template<class Y>class B{.....};//模板型成员类型
};
templateMemberType.cpp
#include <iostream>
using namespace std;
template<class X>class A{
public:
template<class Y>class B{
public:
template<class Z>class C;
};
};
//帽子均要带上
template<class X>
template<class Y>
template<class Z>class A<X>::B<Y>::C{
public:
template<class T>void foo(){
cout << "foo()" << endl;
}
};
int main(){
A<int>::B<double>::C<float> c;
c.foo<string>();
return 0;
}
-
模板型模板参数
类模板的模板形参也可以是类模板,可以有缺省值。例如:
template<class T> class Arrary{….};
template< template<class D>class C=Arrary >class Sum{
……
};
templateVal.cpp
#include <iostream>
using namespace std;
template<class T>class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
template<class D,template<class M>class C>class Sum{//求和器
public:
Sum(C<D>& s):m_s(s){}
D add(){//求和
D d = 0;
for(int i=0; i<10; i++){
d += m_s[i];
}
return d;
}
private:
C<D> m_s;//==>Array<D> m_s;
};
int main(){
Array<int> a;
for(int i=0; i<10; i++)
a[i] = i+1;
Sum<int,Array> s(a);
cout << s.add() << endl;
return 0;
}
六、模板典型错误
6.1 嵌套依赖
-
问题 :
由于模板要经过两次编译,在第一次编译模板的代码时,类型形参的具体类型尚不明确,编译器将把类型形参的嵌套类型理解为某个未知类型的静态成员变量,因此编译器看到使用这样的标识符声明变量时会报告错误,这就叫嵌套依赖。 -
解决方法 :
在类型形参的前面增加一个 typename 标识符,意在告诉编译器其后是一个类模板的嵌套使用。
typeName.cpp
#include <iostream>
using namespace std;
class A{
public:
class B{
public:
void foo(){
cout << "A::B::foo()" << endl;
}
};
};
template<class T>void Func(){
//T::B b;//嵌套依赖。
typename T::B b;//解决嵌套依赖。
b.foo();
}
int main(){
Func<A>();
return 0;
}
6.2 依赖模板参数访问成员函数模板
-
问题 :
利用未知类定义的对象来访问成员函数模板时,编译器在第一次编译时无法解析成员函数模板的类型参数列表的<>而报告编译错误。 -
解决方法 :
在成员函数模板之前增加template关键字,意在告诉编译器其后是一个函数模板实例,编译器就可以正确理解<>了。
#include <iostream>
using namespace std;
class A{
public:
template<class T>void foo(){
cout << "A::foo<T>()" << endl;
}
};
template<class D>void Func(){
D d;
d.template foo<int>();//依赖模板参数访问成员函数模板。
}
int main(){
Func<A>();
return 0;
}
6.3 子模板访问基模板
-
问题 :
在子类模板中访问基类模板的成员,编译器第一次编译时只在子类模板和全局域中搜索使用的标识符号,不会到基类模板中搜索. -
解决方法 :
在子类模板中可以通过使用作用于限定符或显示使用this指针。
#include <iostream>
using namespace std;
template<class T>class Base{
public:
int m_i;
void foo(){
cout << "Base<T>::foo()" << endl;
}
};
//int m_i;
//void foo(){}
template<class T,class D>class Derived : public Base<T>{
public:
// int m_i;
// void foo(){}
void bar(){
Base<T>::m_i = 100;
this->foo();
}
};
int main(){
Derived<int,double> d;
d.bar();
return 0;
}
6.4 零值初始化
-
问题 :
基本类型不存在缺省构造函数,未被初始化的局部变量都具有一个不确定的值(int a;//值不确定)
类类型由于存在缺省构造函数,在未被初始化的情况下可以有一个确定的缺省初始化状态。(Integer a;//值确定)
基于以上两点,就会在模板实现中产生不一致的语法语义。 -
解决方法 :
如果希望模板中,所有类型参数的变量,无论是类类型还是基本类型都以缺省方式获得初始化,就必须对其进行显示的缺省构造T()。
#include <iostream>
using namespace std;
class Integer{
public:
Integer():m_i(0){}
private:
int m_i;
friend ostream& operator<<(ostream& os, Integer const& that);
};
ostream& operator<<(ostream& os, Integer const& that){
return os << that.m_i;
}
template<class T>void Func(){
T t = T();//Integer() / int()
cout << "t=" << t << endl;
}
int main(){
Func<int>();
Func<Integer>();
return 0;
}
6.5 类模板中的成员虚函数
-
类模板中的普通成员函数可以是虚函数
即可以为类定义成员虚函数,和普通类的成员虚函数一样,类模板的成员虚函数也可以表现出多态性。 -
类模板中的成员函数模板不可以是虚函数
根据成员虚函数的多态机制,需要一个虚函数表(表中保存成员虚函数的入口地址),而这个表是编译器在实例化类模板时就产生,类的成员函数模板的实例化(即产生真正的函数实体)需要编译器处理完调用后才会完成,这时才出现成员虚函数的地址。 -
总结:
成员函数模板的延迟编译 阻碍了虚函数表的静态构建。
#include <iostream>
using namespace std;
template<class T>class Base{
public:
virtual void foo(){
cout << "Base<T>::foo()" << endl;
}
};
template<class T,class D>class Derived : public Base<T>{
public:
/* void foo(){
cout << "Derived<T,D>::foo()" << endl;
}*/
/* virtual template<class M>void bar(){
}*/
};
int main(){
Derived<int,int> d;
Base<int>* pBase = &d;
pBase->foo();
pBase->bar<int>();
return 0;
}