模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
什么是模板
- 类属——类型参数化,又称参数模板
使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递 - C++提供两种模板机制:
- 函数模板
- 类模板
函数模板
考虑求两参数之中大值函数:max(a , b)
对 a, b 的不同类型,都有相同的处理形式:
return(a > b) ? a : b;
用已有方法解决对不同数据类型处理:
- 宏替换
#define max(a , b)(a > b ? a : b)
问题:避开类型检查
2. 重载
问题:需要许多重载版本
3. 使用函数模板
重载函数通常基于不同的数据类型实现类似的操作
对不同数据类型的操作完全相同,用函数模板实现更为简洁方便
模板说明
声明模板中使用的类属参数。形式为(template和typename都是关键字)
template <类型形式参数表>
类型形式参数的形式为:
typename T1, typename T2,···, typename Tn
或 class T1, class T2,···, class Tn
e.g.
template <typename T>
template <typename ElementType>
template <typename NameType, typename DateType>
函数模板与模板函数
函数模板声明
template <类型形式参数表>
类型 函数名(形式参数表)
{
语句序列
}
- 函数模板定义由模板说明和函数定义组成
- 模板说明的类属参数必须在函数定义中至少出现一次
- 函数参数表中可以使用类属类型参数,也可以使用一般类型参数
e.g.
#include <iostream>
using namespace std;
template <typename T>
T max(T a, T b)
{
return a > b ? a : b;
}
void main()
{
cout << "max (3, 5) is " << max(3, 5) << endl;
cout << "max ('y', 'e') is " << max('y', 'e') << endl;
cout << "max (9.3, 0.5) is " << max(9.3, 0.5) << endl;
system("pause");
}
输出结果为:
max (3, 5) is 5
max (‘y’, ‘e’) is y
max (9.3, 0.5) is 9.3
请按任意键继续. . .
重载函数模板
有些特殊情况需要函数模板参与重载
e.g.
template <typename T>
T max(T a, T b)
{
return a > b ? a : b;
}
当存在以下调用时候:
void f(int i, char c)
{
max(i, i); // ok
max(c, c); // ok
max(i, c); // error,无法匹配
max(c, i); // error
}
这种时候就需要进行重载
template <typename T>
T max(T a, T b)
{
return a > b ? a : b;
}
int max(int a, int b) // 模板函数重载版本
{
return a > b ? a : b;
}
void f(int i, char c)
{
max(i, i); // ok
max(c, c); // ok
max(i, c); // ok ,由系统提供隐式转换
max(c, i); // ok
}
e.g.
#include <iostream>
using namespace std;
template <typename T>
T Max(const T a, const T b)
{
return a>b ? a : b;
}
template <typename T>
T Max(const T a, const T b, const T c)
{
T t;
t = Max(a, b);
return Max(t, c);
}
int Max(const int a, const char b)
{
return a>b ? a : b;
}
void main()
{
cout << "Max(3, 'a') is " << Max(3, 'a') << endl;
cout << "Max(9.3, 0.5) is " << Max(9.3, 0.5) << endl;
cout << "Max(9, 5, 23) is " << Max(9, 5, 23) << endl;
system("pause");
}
输出结果为:
Max(3, ‘a’) is 97
Max(9.3, 0.5) is 9.3
Max(9, 5, 23) is 23
请按任意键继续. . .
匹配约定:
- 寻找和使用最符合函数名和参数类型的函数,若找到则调用它
- 否则,寻找一个函数模板,将其实例化产生一个匹配的模板函数,若找到则调用它
- 否则,寻找可以通过类型转换进行参数匹配的重载函数,若找到则调用它
- 如果按以上步骤均未能找到匹配函数,则调用错误
- 如果调用有多于一个的匹配选择,则调用匹配出现二义性
e.g.
#include <iostream>
using namespace std;
int Max(int a, int b)
{
cout<<"int Max(int a, int b)"<<endl;
return a > b ? a : b;
}
template<typename T>
T Max(T a, T b)
{
cout<<"T Max(T a, T b)"<<endl;
return a > b ? a : b;
}
template<typename T>
T Max(T a, T b, T c)
{
cout<<"T Max(T a, T b, T c)"<<endl;
return Max(Max(a, b), c);
}
void main()
{
int a = 1;
int b = 2;
cout<<Max(a, b)<<endl;
cout<<Max<>(a, b)<<endl;
cout<<Max(3.0, 4.0)<<endl;
cout<<Max(5.0, 6.0, 7.0)<<endl;
cout<<Max('a', 100)<<endl;
system("pause");
return ;
}
类模板
- 类模板用于实现类所需数据的类型参数化
- 类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响
类模板与模板类
类模板由模板说明和类说明构成
template <类型形式参数表>
类声明
e.g.
template <typename Type>
class TClass
{
// TClass的成员函数
private:
Type DateMember;
//…
};
数组类模板例子:
#include <iostream>
using namespace std;
template <typename T>
class Array
{
public:
Array(int s);
virtual ~Array();
virtual const T& Entry(int index) const;
virtual void Enter(int index, const T & value);
protected:
int size;
T * element;//数据成员是T类型指针
};
//类模板的成员函数是函数模板
template <typename T>
Array<T>::Array(int s)
{
if (s > 1)
size = s;
else size = 1;
element = new T[size];
}
template <typename T>
Array <T>::~Array()
{
delete[] element;
}
template <typename T>
const T& Array<T>::Entry(int index) const
{
return element[index];
}
template <typename T>
void Array <T>::Enter(int index, const T& value)
{
element[index] = value;
}
void main()
{
Array <int> IntAry(5);
int i;
for (i = 0; i < 5; i++)
{
IntAry.Enter(i, i);
}
cout << "Integer Array :
";
for (i = 0; i < 5; i++)
{
cout << IntAry.Entry(i) << ' ';
}
cout << endl;
Array <double> DouAry(5);
for (i = 0; i < 5; i++)
{
DouAry.Enter(i, (i + 1)*0.35);
}
cout << "Double Array :
";
for (i = 0; i < 5; i++)
{
cout << DouAry.Entry(i) << ' ';
}
cout << endl;
system("pause");
}
输出结果:
Integer Array :
0 1 2 3 4
Double Array :
0.35 0.7 1.05 1.4 1.75
请按任意键继续. . .
类模板作函数参数
- 函数的形式参数类型可以是类模板或类模板的引用对应的实际参数是该类模板实例化的模板类对象
- 当一个函数拥有类模板参数时,这个函数必定是函数模板
一个用 Array 作参数的函数模板:
template <typename T>
void Tfun(const Array <T> & x, int index)
{
cout << x.Entry(index) << endl;
}
调用函数模板
Array <double> DouAry(5);
···
Tfun(DouAry, 3);
① 建立对象
class Array
{
public:
Array(int s);
virtual ~Array();
virtual const double & Entry(int index) const;
virtual void Enter(int index, const double & value);
private:
int size;
double * element;
};
调用构造函数,实例化模板类,建立对象
② 调用函数
virtual const double & Entry( int index ) const;
在类层次中的类模板
一个类模板在类层次结构中既可以是基类也可以是派生类:
- 类模板可以从模板类派生
- 类模板可以从非模板类派生
- 模板类可以从类模板派生
- 非模板类可以从类模板派生
e.g.(从类模板Array派生一个安全数组类模板BoundArray
)
template <typename T>
class Array
{
public:
Array(int s);
virtual ~Array();
virtual const T& Entry(int index) const;
virtual void Enter(int index, const T & value);
protected:
int size;
T * element;
};
template <typename T>
class BoundArray : public Array <T>
{
public:
BoundArray(int low = 0, int height = 1);
virtual const T& Entry(int index) const;
virtual void Enter(int index, const T& value);
private:
int min;
};
- 类模板派生普通类,在定义派生类时要对基类的抽象类参数实例化
- 从普通类派生模板类,意味着派生类添加了抽象类数据成员
从类模板A派生普通类B:
#include <iostream>
using namespace std;
template <typename T>//定义类模板
class A
{
public:
A(T x)
{
t = x;
}
void out()
{
cout << t << endl;
}
protected:
T t;
};
class B : public A<int>//派生一般类
{
public:
B(int a, double x) : A <int>(a)
{
y = x;
}
void out()
{
A <int> ::out();
cout << y << endl;
}
protected:
double y;
};
void main()
{
A <int> a(123);
a.out();
B b(789, 5.16);
b.out();
system("pause");
}
输出结果:
123
789
5.16
请按任意键继续. . .
类模板与友员
在类模板中可以声明各种友员关系
- 一个函数或函数模板可以类是或类模板的友员
- 一个类或类模板可以是类或类模板的友员类
- 声明这种模板之间的友员关系符号比较烦琐
template <typename T>
class X
{
friend void f1();
}
函数f1成为类模板X实例化的每个模板类的友员函数
template <typename T>
class X
{
friend void f2(X<T> &);
}
对特定类型(如double),使模板函数f2(X&)成为X的友员
template <typename T>
class X
{
friend void A::f3();
}
A类的成员函数f3成为类模板X实例化的每个模板类的友员函数
template <typename T>
class X
{
friend void B<T>::f4(X<T> &);
}
对特定类型(如double),使模板类B的成员函数f4(X&)成为模板类X的友员
template <typename T>
class X
{
friend class Y;
}
Y类的每个成员函数成为类模板X实例化的每个模板类的友员函数
template <typename T>
class X
{
friend class Z<T>;
}
对特定类型(如double),使模板类Z所有成员函数成为模板类X的友员
e.g.(为复数类模板用定义重载运算符友员函数)
#include <iostream>
using namespace std;
template <typename T>
class Complex;
template <typename T>
class Complex
{
public:
Complex(T r = 0, T i = 0);
Complex(T a)
{
Real = a;
Image = 0;
}
void print() const;
friend Complex operator+ <T>(const Complex<T> &c1, const Complex<T> &c2);
friend Complex operator- <T>(const Complex<T> &c1, const Complex<T> &c2);
friend Complex operator- <T>(const Complex<T> &c);
private:
T Real, Image;
};
template <typename T>
Complex<T>::Complex(T r, T i)
{
Real = r;
Image = i;
}
template <typename T>
Complex<T> operator+ (const Complex<T> &c1, const Complex<T> &c2)
{
T r = c1.Real + c2.Real;
T i = c1.Image + c2.Image;
return Complex<T>(r, i);
}
template <typename T>
Complex<T> operator- (const Complex<T> &c1, const Complex<T> &c2)
{
T r = c1.Real - c2.Real;
T i = c1.Image - c2.Image;
return Complex<T>(r, i);
}
template <typename T>
Complex<T> operator- (const Complex<T> &c)
{
return Complex<T>(-c.Real, -c.Image);
}
template <typename T>
void Complex<T>::print()const
{
cout << '(' << Real << " , " << Image << ')' << endl;
}
void main()
{
Complex<double> c1(2.5, 3.7), c2(4.2, 6.5);
Complex<double> c;
c = c1 - c2;
c.print();
c = c2 + c2;
c.print();
c = -c1;
c.print();
system("pause");
}
输出结果为:
(-1.7 , -2.8)
(8.4 , 13)
(-2.5 , -3.7)
请按任意键继续. . .
在这里过于模板的操作符重载,在开始的时候有一个问题,参考文章如下:
关于操作符的友元模版函数重载
类模板与static成员
- 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
- 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
- 每个模板类有自己的类模板的static数据成员副本
e.g.(为圆类模板定义静态成员)
#include <iostream>
using namespace std;
const double pi = 3.14159;
template<typename T>
class Circle
{
T radius;
static int total;//类模板的静态数据成员
public:
Circle(T r = 0)
{
radius = r;
total++;
}
void Set_Radius(T r)
{
radius = r;
}
double Get_Radius()
{
return radius;
}
double Get_Girth()
{
return 2 * pi * radius;
}
double Get_Area()
{
return pi * radius * radius;
}
static int ShowTotal();//类模板的静态成员函数
};
template<typename T>
int Circle<T>::total = 0;
template<typename T>
int Circle<T>::ShowTotal()
{
return total;
}
void main()
{
Circle<int> A, B;//建立了2个对象
A.Set_Radius(16);
cout << "A.Radius = " << A.Get_Radius() << endl;
cout << "A.Girth = " << A.Get_Girth() << endl;
cout << "A.Area = " << A.Get_Area() << endl;
B.Set_Radius(105);
cout << "B.radius = " << B.Get_Radius() << endl;
cout << "B.Girth=" << B.Get_Girth() << endl;
cout << "B.Area = " << B.Get_Area() << endl;
cout << "Total1=" << Circle<int>::ShowTotal() << endl;//显示建立的对象数
cout << endl;
Circle<double> X(6.23), Y(10.5), Z(25.6);//建立了3个对象
cout << "X.Radius = " << X.Get_Radius() << endl;
cout << "X.Girth = " << X.Get_Girth() << endl;
cout << "X.Area = " << X.Get_Area() << endl;
cout << "Y.radius = " << Y.Get_Radius() << endl;
cout << "Y.Girth=" << Y.Get_Girth() << endl;
cout << "Y.Area = " << Y.Get_Area() << endl;
cout << "Z.Girth=" << Z.Get_Girth() << endl;
cout << "Z.Area = " << Z.Get_Area() << endl;
cout << "Total2=" << Circle<double>::ShowTotal() << endl;//显示建立的对象数
system("pause");
}
输出结果:
A.Radius = 16
A.Girth = 100.531
A.Area = 804.247
B.radius = 105
B.Girth=659.734
B.Area = 34636
Total1=2X.Radius = 6.23
X.Girth = 39.1442
X.Area = 121.934
Y.radius = 10.5
Y.Girth=65.9734
Y.Area = 346.36
Z.Girth=160.849
Z.Area = 2058.87
Total2=3
请按任意键继续. . .
关于模板补充
模板调用的两种方式
template <typename T>
void swap(T &a, T &b)
{
T t = a;
a = b;
b = t;
}
int x = 1;
int y = 2;
swap(x, y);//自动类型推导
swap<int>(x, y);//具体类型调用
函数模板的深入理解
- 编译器并不是把函数模板处理成能够处理任意类型的函数
- 编译器从函数模板通过具体类型产生不同的函数
- 编译器会对函数模板进行两次编译
- 在声明的地方对模板代码本身进行编译
- 在调用的地方对参数替换后的代码进行编译
模板总结
- 模板是C++类型参数化的多态工具。C++提供函数模板和类模板
- 模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次
- 同一个类属参数可以用于多个模板
- 类属参数可用于函数的参数类型、返回类型和声明函数中的变量
- 模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。模板称为模板函数;实例化的类模板称为模板类
- 函数模板可以用多种方式重载
- 类模板可以在类层次中使用