所谓函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。
#include <iostream>
using namespace std;
template<typename T> void Swap(T *a, T *b){
T temp = *a;
*a = *b;
*b = temp;
}
int main(){
//交换 int 变量的值
int n1 = 100, n2 = 200;
Swap(&n1, &n2);
cout<<n1<<", "<<n2<<endl;
//交换 float 变量的值
float f1 = 12.5, f2 = 56.93;
Swap(&f1, &f2);
cout<<f1<<", "<<f2<<endl;
//交换 char 变量的值
char c1 = 'A', c2 = 'B';
Swap(&c1, &c2);
cout<<c1<<", "<<c2<<endl;
//交换 bool 变量的值
bool b1 = false, b2 = true;
Swap(&b1, &b2);
cout<<b1<<", "<<b2<<endl;
return 0;
}
运行结果:
200, 100
56.93, 12.5
B, A
1, 0
#include <iostream>
using namespace std;
template<typename T> void Swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
int main(){
//交换 int 变量的值
int n1 = 100, n2 = 200;
Swap(n1, n2);
cout<<n1<<", "<<n2<<endl;
//交换 float 变量的值
float f1 = 12.5, f2 = 56.93;
Swap(f1, f2);
cout<<f1<<", "<<f2<<endl;
//交换 char 变量的值
char c1 = 'A', c2 = 'B';
Swap(c1, c2);
cout<<c1<<", "<<c2<<endl;
//交换 bool 变量的值
bool b1 = false, b2 = true;
Swap(b1, b2);
cout<<b1<<", "<<b2<<endl;
return 0;
}
#include <iostream>
using namespace std;
//声明函数模板
template<typename T> T max(T a, T b, T c);
int main( ){
//求三个整数的最大值
int i1, i2, i3, i_max;
cin >> i1 >> i2 >> i3;
i_max = max(i1,i2,i3);
cout << "i_max=" << i_max << endl;
//求三个浮点数的最大值
double d1, d2, d3, d_max;
cin >> d1 >> d2 >> d3;
d_max = max(d1,d2,d3);
cout << "d_max=" << d_max << endl;
//求三个长整型数的最大值
long g1, g2, g3, g_max;
cin >> g1 >> g2 >> g3;
g_max = max(g1,g2,g3);
cout << "g_max=" << g_max << endl;
return 0;
}
//定义函数模板
template<typename T> //模板头,这里不能有分号
T max(T a, T b, T c){ //函数头
T max_num = a;
if(b > max_num) max_num = b;
if(c > max_num) max_num = c;
return max_num;
}
运行结果:
12 34 100↙
i_max=100
73.234 90.2 878.23↙
d_max=878.23
344 900 1000↙
g_max=1000
#include <iostream>
using namespace std;
template<class T1, class T2> //这里不能有分号
class Point{
public:
Point(T1 x, T2 y): m_x(x), m_y(y){ }
public:
T1 getX() const; //获取x坐标
void setX(T1 x); //设置x坐标
T2 getY() const; //获取y坐标
void setY(T2 y); //设置y坐标
private:
T1 m_x; //x坐标
T2 m_y; //y坐标
};
template<class T1, class T2> //模板头
T1 Point<T1, T2>::getX() const /*函数头*/ {
return m_x;
}
template<class T1, class T2>
void Point<T1, T2>::setX(T1 x){
m_x = x;
}
template<class T1, class T2>
T2 Point<T1, T2>::getY() const{
return m_y;
}
template<class T1, class T2>
void Point<T1, T2>::setY(T2 y){
m_y = y;
}
int main(){
Point<int, int> p1(10, 20);
cout<<"x="<<p1.getX()<<", y="<<p1.getY()<<endl;
Point<int, char*> p2(10, "东经180度");
cout<<"x="<<p2.getX()<<", y="<<p2.getY()<<endl;
Point<char*, char*> *p3 = new Point<char*, char*>("东经180度", "北纬210度");
cout<<"x="<<p3->getX()<<", y="<<p3->getY()<<endl;
return 0;
}
运行结果:
x=10, y=20
x=10, y=东经180度
x=东经180度, y=北纬210度
下面是一个重载函数模板的完整示例:
#include <iostream>
using namespace std;
template<class T> void Swap(T &a, T &b); //模板①:交换基本类型的值
template<typename T> void Swap(T a[], T b[], int len); //模板②:交换两个数组
void printArray(int arr[], int len); //打印数组元素
int main(){
//交换基本类型的值
int m = 10, n = 99;
Swap(m, n); //匹配模板①
cout<<m<<", "<<n<<endl;
//交换两个数组
int a[5] = { 1, 2, 3, 4, 5 };
int b[5] = { 10, 20, 30, 40, 50 };
int len = sizeof(a) / sizeof(int); //数组长度
Swap(a, b, len); //匹配模板②
printArray(a, len);
printArray(b, len);
return 0;
}
template<class T> void Swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
template<typename T> void Swap(T a[], T b[], int len){
T temp;
for(int i=0; i<len; i++){
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
void printArray(int arr[], int len){
for(int i=0; i<len; i++){
if(i == len-1){
cout<<arr[i]<<endl;
}else{
cout<<arr[i]<<", ";
}
}
}
运行结果:
99, 10
10, 20, 30, 40, 50
1, 2, 3, 4, 5
在 C++ 中,对于给定的函数名,可以有非模板函数、模板函数、显示具体化模板函数以及它们的重载版本,在调用函数时,显示具体化优先于常规模板,而非模板函数优先于显示具体化和常规模板。
#include <iostream>
using namespace std;
template<class T> void Swap(T &a, T &b); //模板①:交换基本类型的值
template<typename T, unsigned N> void Swap(T (&a)[N], T (&b)[N]); //模板②:交换两个数组
template<typename T, unsigned N> void printArray(T (&arr)[N]); //打印数组元素
int main(){
//交换基本类型的值
int m = 10, n = 99;
Swap(m, n); //匹配模板①
cout<<m<<", "<<n<<endl;
//交换两个数组
int a[5] = { 1, 2, 3, 4, 5 };
int b[5] = { 10, 20, 30, 40, 50 };
Swap(a, b); //匹配模板②
printArray(a);
printArray(b);
return 0;
}
template<class T> void Swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
template<typename T, unsigned N> void Swap(T (&a)[N], T (&b)[N]){
T temp;
for(int i=0; i<N; i++){
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
template<typename T, unsigned N> void printArray(T (&arr)[N]){
for(int i=0; i<N; i++){
if(i == N-1){
cout<<arr[i]<<endl;
}else{
cout<<arr[i]<<", ";
}
}
}
运行结果:
99, 10
10, 20, 30, 40, 50
1, 2, 3, 4, 5
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
template<typename T, int N>
class Array{
public:
Array();
~Array();
public:
T & operator[](int i); //重载下标运算符[]
int length() const { return m_length; } //获取数组长度
bool capacity(int n); //改变数组容量
private:
int m_length; //数组的当前长度
int m_capacity; //当前内存的容量(能容乃的元素的个数)
T *m_p; //指向数组内存的指针
};
template<typename T, int N>
Array<T, N>::Array(){
m_p = new T[N];
m_capacity = m_length = N;
}
template<typename T, int N>
Array<T, N>::~Array(){
delete[] m_p;
}
template<typename T, int N>
T & Array<T, N>::operator[](int i){
if(i<0 || i>=m_length){
cout<<"Exception: Array index out of bounds!"<<endl;
}
return m_p[i];
}
template<typename T, int N>
bool Array<T, N>::capacity(int n){
if(n > 0){ //增大数组
int len = m_length + n; //增大后的数组长度
if(len <= m_capacity){ //现有内存足以容纳增大后的数组
m_length = len;
return true;
}else{ //现有内存不能容纳增大后的数组
T *pTemp = new T[m_length + 2 * n * sizeof(T)]; //增加的内存足以容纳 2*n 个元素
if(pTemp == NULL){ //内存分配失败
cout<<"Exception: Failed to allocate memory!"<<endl;
return false;
}else{ //内存分配成功
memcpy( pTemp, m_p, m_length*sizeof(T) );
delete[] m_p;
m_p = pTemp;
m_capacity = m_length = len;
}
}
}else{ //收缩数组
int len = m_length - abs(n); //收缩后的数组长度
if(len < 0){
cout<<"Exception: Array length is too small!"<<endl;
return false;
}else{
m_length = len;
return true;
}
}
}
int main(){
Array<int, 5> arr;
//为数组元素赋值
for(int i=0, len=arr.length(); i<len; i++){
arr[i] = 2*i;
}
//第一次打印数组
for(int i=0, len=arr.length(); i<len; i++){
cout<<arr[i]<<" ";
}
cout<<endl;
//扩大容量并为增加的元素赋值
arr.capacity(8);
for(int i=5, len=arr.length(); i<len; i++){
arr[i] = 2*i;
}
//第二次打印数组
for(int i=0, len=arr.length(); i<len; i++){
cout<<arr[i]<<" ";
}
cout<<endl;
//收缩容量
arr.capacity(-4);
//第三次打印数组
for(int i=0, len=arr.length(); i<len; i++){
cout<<arr[i]<<" ";
}
cout<<endl;
return 0;
}
运行结果:
0 2 4 6 8
0 2 4 6 8 10 12 14 16 18 20 22 24
0 2 4 6 8 10 12 14 16
当非类型参数是一个指针(引用)时,绑定到该指针的实参必须具有静态的生存期;换句话说,实参必须存储在虚拟地址空间中的静态数据区。局部变量位于栈区,动态创建的对象位于堆区,它们都不能用作实参。
C++模板的实例化:
template<typename T> void Swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
int main(){
int n1 = 100, n2 = 200, n3 = 300, n4 = 400;
float f1 = 12.5, f2 = 56.93;
Swap(n1, n2); //T为int,实例化出 void Swap(int &a, int &b);
Swap(f1, f2); //T为float,实例化出 void Swap(float &a, float &b);
Swap(n3, n4); //T为int,调用刚才生成的 void Swap(int &a, int &b);
return 0;
}
#include <iostream>
using namespace std;
template<class T1, class T2>
class Point{
public:
Point(T1 x, T2 y): m_x(x), m_y(y){ }
public:
T1 getX() const{ return m_x; }
void setX(T1 x){ m_x = x; }
T2 getY() const{ return m_y; };
void setY(T2 y){ m_y = y; };
void display() const;
private:
T1 m_x;
T2 m_y;
};
template<class T1, class T2>
void Point<T1, T2>::display() const{
cout<<"x="<<m_x<<", y="<<m_y<<endl;
}
int main(){
Point<int, int> p1(10, 20);
p1.setX(40);
p1.setY(50);
cout<<"x="<<p1.getX()<<", y="<<p1.getY()<<endl;
Point<char*, char*> p2("东京180度", "北纬210度");
p2.display();
return 0;
}
总起来说,不管是函数还是类,声明和定义(实现)的分离其实是一回事,都是将函数定义放到其他文件中,最终要解决的问题也只有一个,就是把函数调用和函数定义对应起来(找到函数定义的地址,并填充到函数调用处),而保证完成这项工作的就是链接器。
通过上面的两个反面教材可以总结出,「不能将模板的声明和定义分散到多个文件中」的根本原因是:模板的实例化是由编译器完成的,而不是由链接器完成的,这可能会导致在链接期间找不到对应的实例。
函数模板和类模板的实例化语法是类似的,我们不妨对它们做一下总结:
extern template declaration; //实例化声明
template declaration; //实例化定义
对于函数模板来说,declaration 就是一个函数原型;对于类模板来说,declaration 就是一个类声明。