C++中的函数新特性(二)
- 浅谈函数模板和泛型编程
- 函数模板的实现原理
- 扩展:模板重载简介
- 局限性:模板的具体化
- 局限性:模板实例的选择
- 函数的选择
- 局限性:类型的判定
函数模板
浅谈函数模板与泛型编程
泛型编程
通过一种语言机制
实现一个通用的方法(标准容器库)
通用是指:每一种数据类型都能用
函数模板怎么实现泛型编程呢?
上次的例子:
如果我要打一个交换两个int变量的函数
我可以这样
void swap(int & a, int & b)
{
int temp;
temp = a;
a = b;
b = temp;
}
如果通过函数模板来实现泛型编程
则可以这样
template <typename T >
void swap(T & a, T & b)
{
T temp;
temp = a;
a = b;
b = temp;
}
关键字:template(模板)
关键字:typename
第一行,指出这是一个函数模板
一个可替代的变量类型,我们暂且命名为T
然后后面函数的定义
就用T来代替具体的类型
之后我们的函数调用
就可以用这一个swap函数,去交换int变量
去交换double变量
实际上函数模板就是在编译的时候
将T换成所需要的类型
就像 typedef T int ;一样
所以我们在调用这个函数的时候
可以这样调用
double a,b;
swap(a,b);
int c,d;
swap(c,d);
然后就达到了泛型编程的目的
对,无论什么类型,都可以调用
吗?
。
。
。
当然,事情并没有那么简单。
数据类型,远远不止这几种基本类型
还有结构体,还有类
还有……………………
这个在后面的局限性里拓展一下
函数模板的实现原理
template <typename T >
void swap(T & a, T & b)
{
T temp;
temp = a;
a = b;
b = temp;
}
函数模板的语法
template是模板的关键字
typename是在模板中说明类型的关键字
template < typename T >
在第一行中,先声明了一个类型参数T
然后在函数的具体定义中,
把需要用到该类型的全部用T
原理:
在编译过程
编译器会自动根据函数的调用情况
知道需要使用哪种类型的函数
然后就会
直接把T替换成对应的类型
生成一个新函数
所以,在编译过程,
模板就会变成真正的函数
(这个过程叫做模板的实例化)
在编译过后,程序就没有模板了
只有对应的函数
模板的重载
同函数一样,模板也是可以重载的
(还记得什么叫函数的重载吗?)
(C++中允许同名函数的存在)
(虽然同名,不过会根据函数的特征标选择)
(所以不会产生冲突)
模板重载
同名函数模板
在C++中是允许存在的
前提是模板的参数不同
先放个例子
template <typename T >
T add(T a, T b)
{
return a+b;
}
对这个模板,
我们可以使两个相同类型的变量相加
然后返回他们的和
template <typename T1, typename T2 >
T1 add(T1 a, T2 b)
{
return a+b;
}
对这个模板
我们可以使两个具有不同类型的变量相加
并且返回第一个变量所具有的类型
当然,我们还可以这样
template <typename T,int t >
T add(T a, T b)
{
return a+b+t;
}
而这个模板,我们可以在模板参数里
放入一个int类型的整数
然后相加的时候加多一个数
模板局限性1:
如果模板对某一种类型不合适
可以自己另外定义一个函数代替模板吗
假设我现在有这样一个模板
template <typename T >
void swap(T & a, T & b)
{
T temp;
temp = a;
a = b;
b = temp;
}
然后我有这样一种
用结构体定义的数据类型
struct person{
char name[40];
int age;
};
我能否这样?
person p1,p2;
// after initialization
swap( p1, p2 );
虽然C++允许结构体的赋值
可是,如果我只想交换age这一个变量呢
只想交换年龄而不改名可以吗?
模板的显式具体化技术
我们想做的事情就是
想让一个模板,对其余的所有类型都适用
唯独用person类型的时候
我想另外定义一个而不使用模板的定义
这里,我们使用模板的显式具体化技术
具体化:
就是对某一种类型给出具体的定义
这里我先给出person类型的显式具体化例子
//函数原型
template <>
void swap<person>(person & a, person & b);
//函数定义
template <>
void swap<person>(person & a, person & b)
{
int temp;
temp = a.age;
a.age = b.age;
b.age = temp;
}
对比普通模板
//函数原型
template <typename T >
void swap(T & a, T & b);
//函数定义
template <typename T >
void swap(T & a, T & b)
{
T temp;
temp = a;
a = b;
b = temp;
}
区别1:template还在
但是后面的<>中可自定义的类型参数没有了
也就是说没有了可自定义的类型参数
区别2:函数名
函数名后面加上了< person >
表明这是该模板下该类型的定义
区别3:形参列表及函数定义
没有了自定义的类型参数
后面的一切操作
都是由具体的类型完成
函数的具体化
使得在调用函数的时候
能够根据类型,选择使用模板函数
还是用模板函数下的具体化
模板局限性2:
假设有这样一种模板函数
template <typename T >
T add(T a, T b)
{
return a+b;
}
问题描述
使这个模板函数这样调用
int a = 2; double d = 3.0; add(a,d);
模板函数该如何选择类型?
这里我们需要用到模板的另一种语法
模板的实例化技术
什么是实例化?
就是从模板变成真正的函数
实例化分为
隐式实例化和显式实例化
隐式实例化
在前面的原理中我们说到
模板在编译过程就会变成真正的函数
在编译后
代码是没有模板这一种东东的
模板,已经变成了一个一个的函数
如果以上面的swap函数为例
我一旦调用swap函数
int a = 2;
int b = 3;
add(a,b);
编译器便会知道我需要int类型的add函数
然后就会帮我通过模板
生成int类型函数
int add(int a, int b)
{
return a+b;
}
这样,就是函数模板的一个实例化
由于我并没有告诉编译器要实例化
只是编译器比较聪明知道应该要这么做
就叫隐式实例化
显式实例化
如果要明确的告诉编译器
需要实例化某一个类型的函数
我可以这样做
template void add<int>(int , int );
通过这一条语句
编译器便会知道需要生成int类型的swap函数
(无论我后面有没有调用)
还可以通过这样的方法
在函数调用的时候调用显式实例化的一个函数
int x = 3;
double y = 3.2;
double sum = add<double>(x,y);
从而解决模板函数调用模糊不清
不知道该调用谁的问题
提出几个问题
- 所有类型都可以直接相加吗?
如:我用前面的add函数模板,去相加字符串可以吗?
其实是不可以的
但是如果可以自己为字符串重载运算符+
(也就是自己定义一个适用于字符串的+运算符)
也许就可以了
- 如果我在模板中不知道该返回什么类型的参数
template <typename T1, typename T2 >
???? add(T1 a, T2 b)
{
return a+b;
}
上面的程序,我可不能确定a+b的类型呀
可能是T1,也可能是T2
这可不能随便定下来呀
能不能在运行的时候根据我的参数的输入决定呢?
这个又能扯到很多东西……下次再说说
我还是希望能够快点讲到类……
这样又有一堆东西可以写了……