模板重用源代码(编译之前的代码);而继承和组合重用对象代码(编译之后产生的目标代码)。
- 泛型编程概述
一、为什么要需要模板?
对于如求解整数、双精度浮点数、字符的最大值的问题,在C语言中,要用三个不同函数名的函数实现;在C++语言中,要用三个相同函数名的重载函数实现;而对于函数名、函数体相同,仅仅是参数列表类型、返回值类型不同,则可通过模板编写一个函数实现三种数据类型的处理。
C++中,模板包括函数模板、类模板。
二、泛型Generic Type
泛型编程指,独立于任何特定类型的方式编写代码,到使用泛型代码时,再由程序员指定代码实例所操作的具体类型;
泛型优势:节省键入代码量、节省空间、方便维护;
- 函数模板
一、函数模板的定义
template <模板形参表> // 模板前缀
返回值类型 函数名 (形参列表)
{
函数体语句
}
其中,模板形参表可包含一个或多个模板形参,多个模板形参之间以逗号分隔,由保留字typename或class引导(推荐使用typename!);
模板形参列表中每一个模板形参在函数形参列表中必须至少出现一次;
模板形参列表中每一个模板形参可作为返回值类型、函数参数类型、函数体内的局部变量或局部对象的类型;
用于表示模板形参类型的名字应该使用一个大写字母;
注意:对函数模板,模板形参表中给出的每一个模板形参都必须在函数形参表中出现;
二、函数模板的实例化
使用函数模板时,由编译器根据函数调用中所给出的实参类型,确定相应的模板实参,即确定使用什么类型代替模板类型形参,这一过程称为模板实参推断;
模板实参确定后,编译器使用模板实参代替相应的模板形参产生并编译函数模板的一个特定版本,这一过程称为函数模板的实例化;
产生的函数称为函数模板的一个实例;
1 只有一个模板形参
#include <iostream>
using namespace std;
template <typename T>
T maxValue(T a, T b) // 必须保证形参a和b的类型相同
{
return a > b ? a : b;
}
int main()
{
cout<<maxValue(1,3)<<endl;
cout<<maxValue(1.0,3.2)<<endl;
cout<<maxValue('A','B')<<endl; // 比较两个字符的ASCII码
cout<<maxValue("TWC","YQ")<<endl; // 比较两个字符串首地址char*,因而T==char*
system("pause");
return 0;
}
执行结果:
3
3.2
B
TWC
2 多个模板形参
#include <iostream>
using namespace std;
template <typename T, typename S>
T maxValue(T a, T b)
{
T temp=static_cast<T>(b);
return a > temp ? a : temp;
}
int main()
{
cout<<maxValue(1.0,3)<<endl;
cout<<maxValue(1,1.1)<<endl;
cout<<maxValue('A',90)<<endl;
// cout<<maxValue('H',"YQ")<<endl; 编译出错(T=char, S=const char*),因为static_cast无法完成char*转换char
system("pause");
return 0;
}
执行结果:
1.0
1
Z
其中,static_cast<类型>(变量名);语句用于将变量转换为特定类型;
三、函数模板与重载
若要实现不同的函数行为,则对函数模板进行重载,即定义函数名相同但函数形参列表不同的函数模板,或定义与函数模板同名的非模板函数,在其函数体中完成不同的行为。
模板函数与函数重载具有不同的适用范围:模板函数适用于函数体中处理行为相同的情形;函数重载的函数体可以不同。
举例:
#include <iostream>
using namespace std;
template <typename T> // 定义函数模板demoFunc
void demoFunc(const T a, const T b)
{
cout<<"The first generic version of demoFunc()"<<endl;
cout<<"The arguments: "<<a<<" "<<b<<endl;
}
template <typename T> // 定义函数模板demoFunc的重载版本
void demoFunc(const T a)
{
cout<<"The second generic version of demoFunc()"<<endl;
cout<<"The argument: "<<a<<endl;
}
void demoFunc(const int a, const int b) // 定义重载函数模板demoFunc的非模板函数
{
cout<<"The ordinary version of demoFunc()"<<endl;
cout<<"The arguments: "<<a<<" "<<b<<endl;
}
int main()
{
char ch1='A', ch2='B';
int iv1=3, iv2=5;
double dv1=2.8, dv2=8.5;
demoFunc(dv1, dv2); // 调用第一个函数模板的实例
demoFunc(iv1); // 调用第二个函数模板的实例
demoFunc(iv1, iv2); // 调用非模板函数
demoFunc(ch1, iv2); // 调用非模板函数,ch1进行隐式类型转换
system("pause");
return 0;
}
执行结果:
The first generic version of demoFunc()
The arguments: 2.8 8.5
The second generic version of demoFunc()
The argument: 3
The ordinary version of demoFunc()
The arguments:3 5
The ordinary version of demoFunc()
The arguments: 65 5
注意:
函数模板实例化过程中,不进行常规隐式类型转换,因此该例中若去掉非模板函数的定义,则函数调用demoFunc(ch1,iv2)将导致编译失败。
- 类模板
一、类模板的定义
类模板的主要用途是定义容器数据类型;要支持对任意类型的容器元素进行操作,定义一个类模板即可。
1 定义类模板的一般语法形式:
template <模板形参表>
class 类名
{
类成员定义
};
其中,模板类型形参可用作类中数据成员的类型、成员函数的形参类型、成员函数的返回类型、成员函数中局部对象的类型等。
2 举例:
堆栈是一种广泛应用的容器数据类型,特点是先进后出。下面给出链式堆栈类模板的定义及实现:
#ifndef GSTACK_H
#define GSTACK_H
#include <new> // 使用其中的bad_alloc类
#include <stdexcept> // 使用其中的logic_error类
template <typename ElementType>
class Stack{
public:
Stack(); // 构造函数,将栈顶置为空指针
~Stack(); // 析构函数,释放堆栈元素所占用的内存
void push(ElementType obj) throw(std::bad_alloc);
// 将元素obj压入堆栈
void pop() throw(std::logic_error);
// 将当前栈顶元素弹出堆栈
ElementType getTop() const throw(std::logic_error);
// 返回当前栈顶的元素值
bool isEmpty() const;
// 判断堆栈是否为空
private:
struct Node{ // 堆栈节点类型
ElementType element; // 节点中存放的元素
Node* next; // 指向下一节点的指针
}
Node* top; // 堆栈的栈顶
};
template <typename ElementType>
Stack<ElementType>::Stack()
{
top=NULL;
}
template <typename ElementType>
Stack<ElementType>::~Stack()
{
while(top!=NULL)
pop();
}
template <typename ElementType>
void Stack<ElementType>::push(ElementType obj)
throw(std::bad_alloc)
{
Node* temp;
try{
temp=new Node; // 创建一个新节点
temp->element=obj; // 新节点的元素赋值
temp->next=top; // 原来的栈顶节点作为新节点的下一节点
top=temp; // 修改栈顶指针:将新节点置为栈顶节点
}
catch(std::bad_alloc e) // 内存分配失败时进行异常处理
{
throw; // 重新抛出异常
}
}
template <typename ElementType>
void Stack<ElementType>::pop()
throw(std::logic_error)
{
Node* temp;
if(top!=NULL) // 对战不为空时进行弹出处理
{
temp=top;
top=top->next; // 将原栈顶的下一节点设为栈顶
delete temp; // 释放被弹出节点占用的存储空间
}
else
{
throw std::logic_error("pop from empty Stack");
}
}
template <typename ElementType>
ElementType Stack<ElementType>::egtTop() const
throw (std::logic_error)
{
if(top==NULL)
{
throw std::logic_error("get top from empty Stack");
}
return top->element;
}
template <typename ElementType>
bool Stack<ElementType>::isEmpty() const
{
return (top==NULL);
}
#endif
实现类模板时需要注意两点:
a 对于在类定义体外实现的成员函数,需要在函数定义之前带上由template引导的模板形参表,在类模板的名字与作用域分辨符::之间加上由各个模板形参名字构成的列表;
b 类模板的定义与实现通常放在同一个文件中;
二、类模板的实例化
类模板仅描述适用于任意类型的通用模型,其中处理对象的数据类型尚未确定。因此不能使用类模板直接创建对象,即一个类模板不是一个普通意义上的类类型,类模板必须经过实例化才能获得真正的类类型,才能用于创建对象。
类模板实例化的一般语法形式如下:
类模板名 <模板实参表>
如上例定义的堆栈类模板,可声明存放int型元素的堆栈对象:Stack<int> intStack;
也可为类模板的类型参数指定一个默认类型:
template <typename ElementType=int>
class Stack{
// ......
};
Stack<> stack;
注意:函数模板的实例化是由编译器根据函数调用中给出的实参类型自动完成;类模板的实例化则必须由程序员指定模板实参,编译器才能自动生成相应的类实例。
#include <iostream>
#include <cstdlib> // 使用其中的exit()
#include "genericStack.h"
using namespace std;
int main()
{
Stack<int> stack; // 使用一个保存int型元素的堆栈
for(int i=0;i<0;i++)
{
try{
stack.push(i);
}
catch(bad_alloc e)
{
cout<<"Exception occurred: "<<e.what()<<endl;
exit(1); // 终止程序
}
}
while(!Stack.isEmpty())
{
cout<<stack.getTop()<<" ";
stack.pop();
}
return 0;
}
注意:
对于泛型编程,声明和实现放在一起;对于非泛型编程,声明和实现分离;