C++是一种强类型语言,它要求程序中每一个对象的类型在编译阶段就能确定。一方面,这可以在很大程度上保证程序不会出现因类型问题而导致的错误。但另一方面,这种对类型的强力约束也限制了编码的灵活性,并且有可能导致编码效率的低下。泛型编程技术的引入使得两方面得到 了很好的平衡。
C++语言中有函数重载(overload)的功用,可以让相同功能(相同函数名)的函数处理不同的数据类型:
#include <iostream>
using namespace std;
int adder(int a, int b)
{
return a+b;
}
double adder(double a, double b) //重载adder()
{
return a+b;
}
void main()
{
cout<<adder(3,4)<<endl;
cout<<adder(3.3,4.4)<<endl;
system("pause");
}
/*
7
7.7
*/
不过这样还是会造成程序代码重复(函数体重复)的问题,这时就可以使用模板。所谓模板就是以相同程序代码处理不同数据类型的函数或类,在模板中以参数来代替数据类型(类型泛化),如此一来,就能在使用模板时,再设置数据类型来取代参数,产生一个新的函数或类。
模板根据不同的应用,分为函数模板(function template)和类模板(class tmeplate):
1 函数模板
函数模板就是函数中的某个参数或返回值的类型是不确定的,是可变的,这些不确定的类型称为模板参数。如果给函数模板的模板参数指定了一个具体的类型,就得到了一个可以执行的函数,这个函数称为模板函数。函数模板可以节省程序员的工作量,若干个被处理的数据类型不同,但处理流程完全一样的函数可以写成一个函数模板。
函数模板是指在函数声明之前加上模板的声明,如此一来,就能以相同的函数,使用不同的数据类型。
函数模板的原型为:
template <数据类型参数表> 返回值类型 函数名(参数表){}
template <class T, class U> U func(T a, int b, U c)
{;}
//class也可以用typename代替
一般的写法:
template <class 模板参数行>
返回数据类型 函数名称(参数行)
{
定义函数模板
return 返回值;
}
template <class T>
void swap(T* a, T* b) //可以是T
{
...
}
实例1:
#include <cstdio>
#include <cstdlib>
template<class T>
T add(T m, T n)
{
return m + n;
}
int main()
{
float i;
i = add<float>(4.1, 7);
printf("%f\n",i);
system("pause");
return 0;
}
//11.100000
实例2
#include <stdio.h>
#include <stdlib.h>
template<typename T>
T max(const T arr[], int size) {
T maxElem=arr[0];
for (int i=0;i<size;i++)
if(arr[i]>maxElem) maxElem=arr[i];
return maxElem;
}
int main(){
int a[] = {1,2,4,8,7};
int b = max(a,sizeof(a)/sizeof(int));
printf("最大数组元素:%d",b);
system("pause");
return 0;
}
//最大数组元素:8
//函数模板也可显示实例化:template int max<int>(const int[],int);
实例3:函数模板重载:
#include <iostream>
using namespace std;
template<class T> T add(T a, T b)//模板加法函数
{
return a+b;
}
template<class T> T add(T a, T b, T c)//重载模板加法函数
{
return a+b+c;
}
void main()
{
cout<<add(1,2)<<endl;//整型加法
cout<<add(1.2,3.4,4.5)<<endl;//浮点加法
}
2 类模板
类模板是先声明模板,将数据类型以模板参数取代,就可以在使用时才指定数据类型。
template <class 模板参数行>
class 类名称
{
定义类模板
}
类模板可以实例化成一个类,一个类才可以实例化成一个具体的对象。
类模板的产生对象的方式:
类名称<数据类型> 对象名称; //声明一般对象
或
类名称<数据类型> 对象名称(); //此对象名称为对象和构造函数的合并声明
在C++14之前的版本中,模板可以是函数模板或类模板。C++14还也可以创建变量模板。模板的一般规则,包括特化都适用于变量模板的声明和定义。
实例:
#include <iostream>
using namespace std;
template <class T>
class ptr
{
private:
T *ip;//指针
public:
ptr(T *p);
~ptr();
};
template <class T>
ptr<T>::ptr(T *p)
{
ip=p;//初始化
cout<<"调用指针管理类构造函数"<<endl;
}
template <class T>
ptr<T>::~ptr()
{
ip=NULL;//释放
cout<<"指针管理类析构函数被调用"<<endl;
}
void main()
{
int a=10;//自定义变量a
int b;
{
ptr<int> p(&b);//自定义指针类对象
}//超出作用域自动析构,释放指针
system("pause");
}
/*
调用指针管理类构造函数
指针管理类析构函数被调用
*/
实例2:
template <typename T1=int, typename T2=double>
class HoldsPair
{
private:
T1 value1;
T2 value2;
public:
HoldsPair(const T1& val1, const T2& val2) // constructor
: value1(val1), value2(val2) {}
// Accessor functions
const T1 & GetFirstValue () const
{
return value1;
}
const T2& GetSecondValue () const
{
return value2;
}
};
#include <iostream>
using namespace std;
int main ()
{
HoldsPair <> pairIntDbl (300, 10.09);
HoldsPair <short, const char*> pairShortStr(25, "Learn templates, love C++");
cout << "The first object contains -" << endl;
cout << "Value 1: " << pairIntDbl.GetFirstValue () << endl;
cout << "Value 2: " << pairIntDbl.GetSecondValue () << endl;
cout << "The second object contains -" << endl;
cout << "Value 1: " << pairShortStr.GetFirstValue () << endl;
cout << "Value 2: " << pairShortStr.GetSecondValue () << endl;
system("pause");
return 0;
}
/*
The first object contains -
Value 1: 300
Value 2: 10.09
The second object contains -
Value 1: 25
Value 2: Learn templates, love C++
*/
3 相关问题
3.1 什么是模板的实例化?
类模板只是个设计图纸,不是一个真正的类。要使得类模板变成一个真正的类,必须用真正的类型名或常量替换类模板的形式参数。这个过程称为类模板的实例化。实例化后,类模板成为了一个真正的类,可以定义这个类的对象了。
3.2 为什么要定义模板?定义类模板有什么好处?
有了类模板,可以将一组功能类似、存储方式也类似的类定义成一个类模板,可以进一步减少程序员的工作量。
3.3 同样是模板,为什么函数模板的使用与普通的函数完全一样,而类模板在使用时还必须被实例化?
在函数模板中,如果模板的形式参数出现在函数形式参数表中,那么当函数调用时,编译器可以根据函数的实际参数的类型确定模板的实际参数,然后对函数模板进行实例化。在定义类模板的对象时,无法确定模板形式参数对应的实际参数值,因此只能在程序中显式地指出模板实际参数的值。
函数模板在调用时中,会有赋值操作,而类模板对象时,没有涉及到这样的赋值操作,所以要显示指定类型,就像基本数据类型的定义或类实例化一样,前面要有类型信息。
函数模板的类型可以通过函数调用进行推断。
3.4 什么时候需要用到类模板的声明?为什么?
一般来说,当定义一个类模板是另一个类模板的友元时必须要用到类模板的声明。当类模板A声明类模板B是它的友元时,编译器必须知道有这样的一个类模板B存在。如果类模板B的定义出现在类模板A的定义前面,则没有问题。但如果类模板B定义在类模板A后面时,编译器就不知道B是什么,也无法确定类模板A中对类模板B的名是否合法,这时可以通过类模板的声明来告诉编译器B是一个类模板。
3.5 类模板继承时的语法与普通的类继承有什么不同?
类模板继承时,凡是涉及到基类的地方,都必须在基类名后面跟上模板的形式参数名。即:基类名<形式参数1,形式参数2,……>。
3.6 定义了一个类模板,在编译通过后为什么还不能确保类模板的语法是正确的?
由于类模板包含有模板参数,这些模板参数对应的实际参数在编译时尚未确定,所以编译器无法确定那些类型为模板参数的数据或函数的用法是否正确,只好暂时不检查。
-End-