c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
总结:
下面我们来看这个代码:
实现多个函数用来返回两个数的最大值,要求能支持 char 类型、int 类型、double:
#include
using namespace std;
int Max(int a, int b)
{
return a>b ? a:b;
}
char Max(char a, char b)
{
return a>b ? a:b;
}
float Max(float a, float b)
{
return a>b ? a:b;
}
int main()
{
//char a = 'c';
int x = 1;
int y = 2;
cout<<"max(1, 2) = "<<Max(x, y)<<endl;
float a = 2.0;
float b = 3.0;
cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;
return 0;
}
由此代码我们可以看出,实际上我们不需要写那么多函数,只需要一个模板即可,那么接下来就是我们对这个代码进行的改良。
代码如下:
#include
using namespace std;
//int Max(int a, int b)
//{
// return a > b ? a : b;
//}
//char Max(char a, char b)
//{
// return a > b ? a : b;
//}
//float Max(float a, float b)
//{
// return a > b ? a : b;
//}
template <typename T>//template:模板 <>:泛型编程 T:类型的名字,可以替代任何类型
T Max(T a, T b)
{
return a > b ? a : b;
}
//如果T使用int 类型调用,就相当于调用这个函数
//int Max(int a, int b)
//{
// return a > b ? a : b;
//}
int main()
{
//char a = 'c';
int x = 1;
int y = 2;
cout << "max(1, 2) = " << Max(x, y) << endl;
cout << "max(1, 2) = " << Max<int>(x, y) << endl; //显示类型调用
float a = 2.0;
float b = 3.0;
cout << "max(2.0, 3.0) = " << Max(a, b) << endl;
return 0;
}
所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指
定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
函数模板定义形式:
由以下三部分组成: 模板说明 + 函数定义 + 函数模板调用
template < 类型形式参数表 >
类型 函数名 (形式参数表)
{
//语句序列
}
注意: 函数模板的参数可以有多个,但是如果定义多个一定要使用,不然会报错!typename写成class也是可以的,但是为了避免混淆,建议还是写成typename。
例如下面修改之后的代码:
#include
using namespace std;
//int Max(int a, int b)
//{
// return a > b ? a : b;
//}
//char Max(char a, char b)
//{
// return a > b ? a : b;
//}
//float Max(float a, float b)
//{
// return a > b ? a : b;
//}
template <typename T, typename T2>//template:模板 <>:泛型编程 T:类型的名字,可以替代任何类型
T Max(T a, T2 b)
{
return a > b ? a : b;
}
int main()
{
char c = 'c';
int x = 1;
int y = 2;
cout << "max(1, c) = " << Max(x, c) << endl;
cout << "max(1, c) = " << Max<int, char>(x, c) << endl;//显示类型调用
cout << "max(1, 2) = " << Max<int>(x, y) << endl; //写一个可以自动推导
float a = 2.0;
float b = 3.0;
cout << "max(2.0, 3.0) = " << Max<float>(a, b) << endl;
return 0;
}
那么讲到了函数模板,我们可能以后会听到模板函数这个概念,那么什么是模板函数呢?那么请看下面这张图,以这张图为例,当我们调用这个函数Max(1,2)的时候,实际上是用int Max(int a,int b)来编译的,而这个通过模板生成的函数,就叫做模板函数。
那么如果我们传一个类进去呢?实际上也会生成一个demo Max(demo a,demo b)的模板函数,但是要重载一下大于运算符,这样编译才不会报错。详见代码如下:
#include
using namespace std;
class demo
{
public:
demo(int _k = 0){k = _k;}
~demo(){}
int value(){return k;}
bool operator>(demo& dest)
{
if (this->k > dest.k) return true;
else return false;
}
private:
int k;
};
//int Max(int a, int b)
//{
// return a > b ? a : b;
//}
//char Max(char a, char b)
//{
// return a > b ? a : b;
//}
//float Max(float a, float b)
//{
// return a > b ? a : b;
//}
template <typename T>//template:模板 <>:泛型编程 T:类型的名字,可以替代任何类型
T Max(T a, T b)
{
return a > b ? a : b;
}
//如果T使用int 类型调用,就相当于调用这个函数
//int Max(int a, int b)
//{
// return a > b ? a : b;
//}
int main()
{
//char a = 'c';
int x = 1;
int y = 2;
cout << "max(1, 2) = " << Max(x, y) << endl; //实现参数类型的自动推导
cout << "max(1, 2) = " << Max<int>(x, y) << endl; //显示类型调用
float a = 2.0;
float b = 3.0;
cout << "max(2.0, 3.0) = " << Max(a, b) << endl;
demo d1(10);
demo d2(11);
cout << "max(d1,d2) = " << Max(d1,d2).value() << endl;
return 0;
}
首先呢,我们先看下面的这个例子:
#include
using namespace std;
template <typename T,typename T2>
T Swap(T2& a, T2& b)
{
T t;
t = a;
a = b;
b = t;
cout << "Swap模板函数被调用了" << endl;
}
//void Swap(char& a, int& b)
//{
// int t;
// t = a;
// a = b;
// b = t;
// cout << "普通函数被调用了" << endl;
//}
int main(void)
{
char cNum = 'c';
char iNum = 65;
//第一种情况,模板函数和普通函数并存,参数类型和普通重载函数更匹配
//调用普通函数
//Swap(cNum,iNum);
//第二种情况,不存在普通函数,函数模板会隐式数据类型转换嘛
//结论:不提供隐式的数据类型转换,必须是严格的匹配
//Swap(cNum, iNum);//如果一定要通过编译,一定要声明两种不同的数据类型
return 0;
}
根据代码可知,函数模板和普通函数区别结论:
1.两者允许并存
2.函数模板不允许自动类型转化
3.普通函数能够进行自动类型转换
接下来我们再举一个例子:
#include
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 T6;........................................................................................... 8>
T Max(T a, T b,T c)
{
cout << "调用T Max(T a,T b,T c)" << endl;
return Max(Max(a, b), c);
}
template <typename T1,typename T2>
T1 Max1(T1 a, T2 b)
{
cout << "调用T Max(T a,T b)" << endl;
return a > b ? a : b;
}
int main()
{
int a = 65;
int b = 66;
//当函数模板和普通函数都符合调用时,优先选择普通函数
cout << "Max(a,b) = " << Max(a, b) << endl;
//如果显示的使用函数模板,则使用<>类型列表,里面类型可加可不加
Max<>(a,b);
//如果函数模板会产生更好的匹配,会使用函数模板
Max(1.0, 2.0);
char c = 'a';
Max(c, a);
Max(3.0, 4.0, 5.0);
return 0;
}
所以由此我们得出结论,函数模板和普通函数在一起,调用规则:
1.函数模板可以像普通函数一样被重载
2.c++编译器优先考虑普通函数
3.如果函数模板可以产生一个更好的匹配,那么选择模板
4.可以通过空模板实参列表的语法限定编译器只通过模板匹配
下面看如下代码:
#include
using namespace std;
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
int x = 1;
int y = 2;
Max(x, y);
return 0;
}
该代码通过反汇编(g++ -S 1.cpp -o 1.S)观察之后,得出下图:
我们再举一个例子,代码如下:
#include
using namespace std;
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
int x = 1;
int y = 2;
Max(x, y);
float a = 2.0;
float b = 3.0;
Max(a,b);
return 0;
}
通过反汇编观察得到如下图:
由此我们可以得出以下结论:
1. 编译器并不是把函数模板处理成能够处理任意类型的函数
2.编译器从函数模板通过具体类型产生不同的函数
我们为什么需要使用类模板呢?类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,我们可以通过如下语句声明了一个类模板:
template < typename T>
class A
{
public:
A(T t)
{
this->t = t;
}
T &getT()
{
return t;
}
private:
T t;
};
类模板定义
类模板由模板说明和类说明构成:
模板说明同函数模板,如下:
template <类型形式参数>
类声明
例如如下的例子:
template < typename Type>
class ClassName
{
//ClassName的成员函数
private:
Type DataMember;
};
下面就是类模板的使用了,如下代码所示:
#include
using namespace std;
template <typename T>
class A
{
public:
//函数的参数列表使用虚拟类型
A(T t = 0)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T& getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
//以上三个地方任意一个使用虚拟类型编译都能通过,当然三个都使用也是没问题的
void printA(A<int> &a)
{
cout << a.getT() << endl;
}
int main()
{
//1、类模板定义对象必须显示指定类型
//2、模板中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(666);
cout << a.getT() << endl;
//模板类作为函数参数
printA(a);
return 0;
}
#include
using namespace std;
//继承中父子类和模板类的结合情况
//1.父类一般类,子类是模板类,和普通继承的玩法类似
//2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数
//3.父类和子类都是模板类时,子类的虚拟的类型可以传递到父类中
/*class B
{
public:
B(int b)
{
this->b = b;
}
private:
int b;
};
*/
template <typename T>
class A
{
public:
//函数的参数列表使用虚拟类型
A(T t)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T &getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
template <typename Tb>
class B: public A<int>
{
public:
B(Tb b):A<Tb>(b) //如果父类构造函数有默认参数,那么不显示指定也没问题,但是如果把父类构造函数的=0去掉,就一定要这么写了
{
this->b = b;
}
private:
Tb b;
};
void printA(A<int> &a)
{
cout << a.getT() << endl;
}
int main(void)
{
//1.模板类定义类对象,必须显示指定类型
//2.模板中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(666);
cout << a.getT() << endl;
B<int> b(888);
cout << "b(888): " << b.getT() << endl;
//模板类作为函数参数
printA(a);
return 0;
}
所以,通过上述代码可知,子类从模板类继承的时候,需要让编译器知道,父类的数据类型具体是什么
上面已讲解
#include
using namespace std;
template <typename T>
class A
{
public:
A(T t = 0);
T& getT();
A operator +(const A& other);
void print();
private:
T t;
};
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T& A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
A<T> tmp;//类的内部类型可以显示声明也可以不显示
tmp.t = this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print()
{
cout << this->t << endl;
}
int main(void)
{
A<int> a(666),b(888);
cout << a.getT() << endl;
A<int> tmp = a + b;
tmp.print();
return 0;
return 0;
}
在同一个cpp文件中把模板类的成员函数放到类的外部,需要注意以下几点:
demo.h:
#pragma once
template <typename T>
class A
{
public:
A(T t=0);
T &getT();
A operator +(const A &other);
void print();
private:
T t;
};
demo.c:
#include "demo.h"
#include
using namespace std;
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T& A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
A<T> tmp;//类的内部类型可以显示声明也可以不显示
tmp.t = this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print()
{
cout << this->t << endl;
}
int main(void)
{
A<int> a(666),b(888);
cout << a.getT() << endl;
A<int> tmp = a + b;
tmp.print();
return 0;
return 0;
}
注意: 当类模板的声明(.h文件)和实现(.cpp或hpp文件)完全分离,因为类模板的特殊实现,我们应该在使用类模板时使用#include包含实现部分.cpp或hpp文件。代码如下:
demo.h:
#pragma once
template <typename T>
class A
{
public:
A(T t=0);
T &getT();
A operator +(const A &other);
void print();
private:
T t;
};
demo.hpp:(一般在c++编译器中,对类模板的实现命名为.hpp)
#include "demo.h"
#include
using namespace std;
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T& A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
A<T> tmp;//类的内部类型可以显示声明也可以不显示
tmp.t = this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print()
{
cout << this->t << endl;
}
main.cpp:
#include "demo.hpp" //一定要包含这个,而不是“demo.h”
int main(void)
{
A<int> a(666),b(888);
cout << a.getT() << endl;
A<int> tmp = a + b;
tmp.print();
return 0;
return 0;
}
在类里面写一个友元函数,代码如下:
#include
using namespace std;
template <typename T>
class A
{
public:
A(T t = 0);
//声明一个友元函数,实现对两个A类对象进行加法操作
template <typename T>
friend A<T> addA(const A<T>& a, const A<T>& b);//在类里面要用友元函数一定要带声明,因为友元不属于类
T& getT();
A operator +(const A& other);
void print();
private:
T t;
};
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T& A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
A<T> tmp;//类的内部类型可以显示声明也可以不显示
tmp.t = this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print()
{
cout << this->t << endl;
}
//A 类的友元函数
template <typename T>
A<T> addA(const A<T>& a, const A<T>& b)
{
A<T> tmp;
cout << "call addA()..." << endl;
tmp.t = a.t + b.t;
return tmp;
}
int main(void)
{
A<int> a(666), b(888);
cout << a.getT() << endl;
A<int> tmp = a + b;
A<int> tmp1 = addA<int>(a, b);
tmp.print();
tmp1.print();
return 0;
}
所以结论如下:
1.类内部声明友元函数,必须要写一下形式
template < typename T >
friend A< T > addA(A< T > &a,A< T > &b);
2.友元函数实现,必须写成
template < typename T >
A< T > addA(A< T > &a,A< T > &b)
{
//…
}
3.友元函数调用必须写成
A< int > c4 = addA< int >(c1,c2);
代码如下:
#include
using namespace std;
template <typename T>
class A
{
public:
A(T t = 0);
//声明一个友元函数,实现对两个A类对象进行加法操作
template <typename T>
friend A<T> addA(const A<T>& a, const A<T>& b);//在类里面要用友元函数一定要带声明,因为友元不属于类
T& getT();
A operator +(const A& other);
void print();
public:
static int count;
private:
T t;
};
template <typename T> int A<T>::count = 666;
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T& A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T>& other)
{
A<T> tmp;//类的内部类型可以显示声明也可以不显示
tmp.t = this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print()
{
cout << this->t << endl;
}
//A 类的友元函数
template <typename T>
A<T> addA(const A<T>& a, const A<T>& b)
{
A<T> tmp;
cout << "call addA()..." << endl;
tmp.t = a.t + b.t;
return tmp;
}
int main(void)
{
A<int> a(666), b(888);
A<int> tmp = a + b;
A<float> c(777), d(999);
a.count = 888;
cout << b.count << endl;//共享一个静态成员
cout << "c.count:" << c.count << " " << "d.count:" << d.count << endl;//因为是两个不同的类
return 0;
}
总结:
1.从类模板实例化的每个模板类都有自己的类模板数据成员,该模板类的所有对象共享一个satic数据成员
2.和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
3.static数据成员也可以使用虚拟类型参数T
1) 先写出一个实际的类
2)将此类中准备改变的类型名(如int要改变成float或char)改成一个自己制定的虚拟类型名(如上例中的T)
3)在类声明前面加入一行,格式为
template < typename 虚拟类型参数 >
如:template < typename 虚拟类型参数>
class A
{
};//类体
4)用类模板定义对象时用以下参数:
类型板名<实际类形名>对象名;
或者,类模板名<实际类型名>对象名(实参表列);
如:
A< int >cmp;
A< int >cmp(3,7);
5) 如果在类模板外定义成员函数,应写成类模板形式:
template
函数类型 类模板名<虚拟类型参数>::成员函数名(函数参数表列){…}