从今天开始就进入泛型程序设计了。。。
一、函数模板
函数模板格式(其中class可以替换为typename):
template <class 类型参数1, class 类型参数2, ...>
返回值类型 模板名(参数名)
{
函数体;
}
例如:
#include<iostream>
using namespace std;
template <typename T> //typename可以替换为class
void cswap(T &x, T &y) //交换数值
{
T tmp = x;
x = y;
y = tmp;
}
template <typename T>
T test(T o)
{
return o+1;
}
int main()
{
int n = 1, m = 2;
cswap(n, m); //模板被替换为cswap(int &x, int &y),这叫做模板的实例化
cout<<n<<" "<<m<<endl; //输出2 1
double a = 1.1, b = 2.2;
cswap(a, b); //模板被替换为cswap(double &x, double &y)
cout<<a<<" "<<b<<endl; //输出2.2 1.1
cout<<test(1)<<endl; //模板自动检测T为int类型,输出2
cout<<test(1.2)<<endl; //模板自动检测T为double类型,输出2.2
cout<<test<double>(3)<<endl;//可以明确模板应该用哪种类型
return 0;
}
函数模板是可以重载的,形参表不一样即可。在多个函数和函数模板名字相同的情况下,一条函数的调用语句遵循如下顺序:
- 参数完全匹配的普通函数
- 参数完全匹配的模板函数
- 实参经过自动类型转换后能够匹配的普通函数
- 都找不到,编译出错
下面是一个例子:
#include<iostream>
using namespace std;
template <class T>
T test(T a, T b)
{
cout<<"test1"<<endl;
}
template <class T, class T1>
T test(T a, T1 b)
{
cout<<"test2"<<endl;
}
double test(double a, double b)
{
cout<<"test3"<<endl;
}
int main()
{
test(1, 2);//调用第一个模板
test(1, 2.0);//调用第二个模板
test(1.0, 2);//调用第二个模板
test(1.0, 2.0);//调用普通函数
return 0;
}
输出结果是:
test1
test2
test2
test3
下面来做一个实验:如果把第二个模板去掉,按照我们之前说的规则,程序将默认test(1, 2.0);
和test(1.0, 2);
调用普通函数,也就是输出三次test3
。如果把第二个模板和普通函数去掉,编译将出错,出错的将会是test(1, 2.0);
和test(1.0, 2);
这两条语句,因为编译器不知道该把1换成double类型,还是把2.0换成int类型,出现了二义性,因此编译出错。
二、类模板
类模板格式(其中class可以替换为typename):
template <class 类型参数1, class 类型参数2, ...>
class 类模板名
{
成员函数和成员变量
}
//如果是在类定义外部定义成员函数的,按照下面格式:
template <class 类型参数1, class 类型参数2, ...>
返回值类型 类模板名<class 类型参数1, class 类型参数2, ...>::成员函数名(参数名)
{
do sth;
}
//如果是类内部定义的,那么按普通函数那种写法即可
由类模板实例化得到的类叫做模板类。
例如:
#include<iostream>
using namespace std;
template <class T> //定义了一个类模板
class A{
public:
T n;
template<class T2>
T2 test(T2 a);
};
//按照格式,如果在类外面定义成员函数要多声明一次类模板的类型参数表
//如果是写在类定义内部的,就不需要多声明了,直接按照普通函数写法即可
//试过了,合并写成template<class T, class T2>是不行的
template <class T>
template <class T2>
T2 A<T>::test(T2 a) //记得类模板名后面加上类型参数表!!!
{
return a+1;
}
int main()
{
A <int>p; //创建对象时必须写类型参数表,要一一对应
//这样就得到一个由类模板实例化的模板类
cout<<p.test(1)<<endl; //成员函数模板被实例化
return 0;
}
注意一下上面一些细节即可。
三、类模板与继承
有以下几种情况:
1.类模板→派生→类模板
template<class T1, class T2>
class A{
T1 n;
T2 m;
};
template<class T1, class T2>
class B:public A<T2, T1>{
T1 n1;
T2 m1;
};
template<class T>
class C:public B<T, T>{
T n2;
T m2;
};
int main()
{
B <int, double>b;
C <int>c;
return 0;
}
十分easy,把所有T、T1、T2换成我们相应写的类型就可以了。生成对象b时,类B实例化成:class B<int, double>
,类A实例化成:class A<double, int>
。生成对象c时,类C实例化成:class C<int>
,类B实例化成:class B<int, int>
,类A实例化成:class A<int, int>
。
2.模板类→派生→类模板
这个其实就是类A派生类B的时候,类B把类A的类型参数表给填了,也即是类A被实例化,变成模板类。
template<class T1, class T2>
class A{
T1 n;
T2 m;
};
template<class T3, class T4>
class B:public A<int, double>{ //类A实例化后得到模板类:A<int, double>
//因此说模板类派生出了类B
T n1;
T m1;
};
int main()
{
B <int>b;//这条语句会导致生成两个模板类:A<int, double>和B<int>
return 0;
}
3.普通类→派生→类模板
这个就更easy了,直接看下面的例子:
class A{
int n;
};
template<class T>
class B:public A{
T m;
};
int main(){
B <double>b;
return 0;
}
4.模板类→派生→普通类
其实就是把上面的例子反过来而已:
template<class T>
class A{
T n;
};
class B:public A<double>{
int m;
};
int main(){
B b;
return 0;
}
上面这四种情况不用死记硬背,用模板类还是类模板继承看需求。
四、类模板与友元
1.函数、类、类的成员函数作为类模板的友元
这个还用说???
void test(){ }
class A{ };
class B{
public:
void fun(){ }
};
template<class T>
class C{
friend void test();
friend class A;
friend void B::fun();
};
int main(){
C <int>c;
return 0;
}
2.函数模板作为类模板的友元
把上面的改动一下就是了。
template <class T1>
void test(T1 t){ }
class A{ };
class B{
public:
void fun(){ }
};
template<class T>
class C{
template <class T1>
friend void test(T1 t);
friend class A;
friend void B::fun();
};
3.函数模板作为类的友元
template<class T>
void fun(T n){ }
class A{
public:
template<class T>
friend void fun(T n){ }
};
4.类模板作为类模板的友元
这个稍微复杂一些:
#include<iostream>
using namespace std;
template<class T2>
class B{
public:
T2 m;
template <class T1>
friend class A;//类模板A声明为友元
};
template<class T1>
class A{
public:
T1 n;
template <class T2>
void test(B <T2>b)//类型参数未知,可以写成<T2>b
{
b.m = 100;//一旦声明为友元,就可以访问类B的成员了
cout<<b.m<<endl;
}
};
int main()
{
B <int>b;//把T2换成int
A <int>a;//把T1换成int
//还可以写成A <B<int> >a,注意,>>中间要有空格才能通过编译
//意思是用B<int>替换成A模板中的T
//这种写法有些奇怪,不是很懂
a.test(b);//输出100
return 0;
}
五、类模板中的静态成员
例子:
#include<iostream>
using namespace std;
template<class T>
class A{
private:
T n;
public:
static T sum;
A(T _n) { n=_n; sum=sum+n; }
~A() { sum=sum-n; }
static void Print(){ cout<<sum<<endl; }
//复习:静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数
};
template<> int A<int>::sum = 0;//对静态成员变量在类外部加以声明是必须的
//Dev C++把这个template<>去掉之后编译会出错
//VS可以把template<>去掉,而编译不出错
template<> double A<double>::sum = 0;
int main()
{
A <int>a1(2);
A <int>a2(3);
a1.Print();
A <double>a3(2.2);
A <double>a4(3.6);
a3.Print();
return 0;
}
输出结果:
5
5.8
从这个输出结果可以看出,A<int>
和A<double>
是两个不同的类,它们不会共享同一个静态成员变量sum
。说白了,还是那句话,静态成员变量是属于一个类的,不属于某一个对象。
大概就写到这吧。