文章目录
1.函数模板
1.1 函数模板的定义
注意:
- c++中<>里面是类型形参列表,传入的是类型名称,不是变量
函数模板的定义形式:
//c++中<>里面是类型形参列表,传入的是类型名称,不是变量
//template、vector
//注意:可以使用任何标识符作为类型形参的名称,但是使用"T"已经称为一种惯例
//简单来说,T可以代替任何类型
//注意:class可以和typename相互替换
template<class 类型形参1, class 类型形参2, ...>
返回值类型 函数模板名(调用形参1, 调用形参2, ...){
...
}
//举例
template<class T>
T Max(T x,T y){
return x>y? x:y;
}
1.2 函数模板的使用(实例化)
注意:
- 使用函数模板,要先对函数模板进行实例化
形式
- 用具体数据类型替换函数模板类型形参
函数模板名<类型实参1, 类型实参2, ...>(调用实参1, 调用实参2,...);
//举例
Max<int>(123,456);
//注意:使用函数模板就包含了函数模板的实例化,因此不需要再去写额外的代码去实例化函数模板
//直接当做函数调用来使用就行了。
1.3 函数模板感性认知
编译器不是把函数模板编译成一个可以处理任何数据的单一实体,
而是,在实例化模板函数时,根据类型实参,从模板函数中产生一个真正的函数实体。
也就是说,编译器看见实例化函数模板的代码的时候,编译器就会依照我们定义的函数模板的格式,帮我们生成一个函数实体。
所以,最后编译完成以后,不会保存函数模板的任何信息,只会保存函数模板帮我们生成的函数。
函数模板本质就是一个编译器帮我们生成函数的_依据。**
编译器在实例化模板的时候会干两件事:
- 依据我们定义的模板,生成实体化函数。(函数重载)
- 将我们实例化模板的地方替换成生成的实体函数的调用。
(具体代码实现看1.4下面的代码)
1.4 实例化函数模板的条件
- 原则上,可以用任何类型来实例化函数模板,包括基本类型和类类型
- 前提:这个类型必须支持函数模板中所要执行的操作。
#ifndef FUNCTMPL_H_INCLUDED
#define FUNCTMPL_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
class Integer{
public:
Integer(int i):m_i(i){}
//注意,这个类中没有重载'>'操作符,所以不支持>运算。
//重载>以后,编译器依据函数模板生成的函数就能正确执行了
bool operator>(Integer const& that)const{
return m_i > that.m_i;
}
private:
int m_i;
};
//定义函数模板
template<class T>
T Max(T x, T y)
{
return x > y ? x : y;
}
void TemplateExample()
{
Integer ix=100, iy=200;
Max<Integer>(ix, iy);
//编译器生成的函数:
//Integer Max(Integer x, Integer y){return x > y? x : y;}
//如果Integer中没有重载>运算符编译器生成的第32行的代码就会运行错误,
//也就是说,这个类型不支持>操作。所以无法正常实例化。
int nx=10, ny=20;
cout << Max<int>(nx, ny) << endl;
//int Max(int x, int y){...}//step1:编译器帮我们生成的函数
// Max(nx, ny) //step2:将Max<int>(nx, ny)替换成Max(nx,ny)
double dx=12.3, dy=45.6;
cout << Max<double>(dx,dy) << endl;
//double Max(double x, double y){...}//编译器帮我们生成的函数
// Max(dx, dy)
string sx="world",sy="hello";
cout << Max<string>(sx, sy) << endl;
}
#endif // FUNCTMPL_H_INCLUDED
1.5 二次编译
- 编译器(g++)对函数模板都会进行两次编译
1.5.1 第一次编译:发生在实例化函数模板之前(产生真正函数实体之前)
- 只检查模板本身内部代码,查看基本语法词法是否书写正确
- 函数模板内部出现的所有标识符是否都有声明
- 对于已知类型的调用,查看是否合理有效
- 对于未知类型的调用,编译器认为都合理
- 因为编译器也不知道你要给这个未知类型传谁,这个位置类型到底能不能调用这个方法,编译器不知道,但是,编译器默认写代码的人是知道的,所以就不给报错
#ifndef TWOCOMPILE_H_INCLUDED
#define TWOCOMPILE_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
class A{
public:
A(){}
void func() {
cout << "A::func()" << endl;
}
};
template<class T>void foo()
{
//1、检查函数模板内部出现的所有标识符是否都有声明
//abcde;
//2、对于已知类型的调用,查看是否合理有效
A a;
a.func;//已知类型的调用
//3.对于未知类型的调用,编译器认为都合理
T t;//这里T就是未知类型,t是未知类型的对象
t.fdjioagoijagd();//对未知类型的调用
}
#endif // TWOCOMPILE_H_INCLUDED
1.5.2 第二次编译:发生在实例化函数模板之后(产生真正的函数体以后)
第二次编译发生在实例化函数模板以后(产生真正的函数体以后),这时候其实就不存在未知类型了,
所以,要结合所使用的类型实参,再次检查模板代码,查看所有调用是否真的均有效。
#ifndef TWOCOMPILE_H_INCLUDED
#define TWOCOMPILE_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
class A{
public:
A(){}
void func() {
cout << "A::func()" << endl;
}
};
template<class T>void foo()
{
//1、检查函数模板内部出现的所有标识符是否都有声明
//abcde;
//2、对于已知类型的调用,查看是否合理有效
A a;
a.func();//已知类型的调用
//3.对于未知类型的调用,编译器认为都合理
T t;//这里T就是未知类型,t是未知类型的对象
t.fdjioagoijagd();//对未知类型的调用
}
void test() {
foo<A>();
//编译器第二次检查:会进行报错,因为A类中没有fdjioagoijagd()函数
}
#endif // TWOCOMPILE_H_INCLUDED
第二次编译器检查报错结果:
1.6 隐式推断类型实参
**注意**
:
- 在c++中,所说的隐式,就是指这个功能是由编译器完成的
- 在c++中,所说的显示,就是指这个功能是由程序猿完成的
1.6.1 隐式推断类型实参应用和好处
如果函数模板的调用形参(的类型)和类型形参一样
template<class T>T Max(T x, T y){...}
那么,在实例化函数模板的时候,就算没有显示指明函数模板的类型实参,编译器也会根据调用实参类型推断出正确的类型实参。
Max(123, 456);//我们这么写
Max<int>(123,456);//编译器这么推断
这样写的好处(让编译器隐式推断的好处)
- 在调用函数模板的时候,可以获得和普通函数一样的书写形式(一样的语法表现形式)
#ifndef DEDUCTION_H_INCLUDED
#define DEDUCTION_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
//定义函数模板
template<class T>
T Max(T x, T y)
{
return x > y ? x : y;
}
void TemplateExample()
{
int nx=10, ny=20;
cout << Max(nx, ny) << endl;
// Max<>(nx, ny) --> Max<int>(nx, ny)//编译器会自己推断类型实参
double dx=12.3, dy=45.6;
cout << Max(dx,dy) << endl;
// Max<double>(dx, dy)
string sx="world",sy="hello";
cout << Max(sx, sy) << endl;
// Max<string>(sx, sy)
}
#endif // DEDUCTION_H_INCLUDED
1.6.2注意
:有三种情况,编译器无法进行隐式推断
- 调用参数和类型参数不完全相关
template<class T, class D>T Max(T x, T y){ }
- 隐式推断不支持隐式类型转换(隐式推断和类型转化不能同时进行)
template<class T> T Max(T x, T y){...}
Max(123, 45.6);
- 返回值类型也不支持隐式推断
#ifndef DEDUCTION_H_INCLUDED
#define DEDUCTION_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
//定义函数模板
template<class T>
T Max(T x, T y)
{
return x > y ? x : y;
}
template<class T, class D>void Func(D x){
}
template<class R, class T>R Hum(T x){
R r;
return r;
}
void TemplateExample()
{
int nx=10, ny=20;
cout << Max(nx, ny) << endl;
// Max<>(nx, ny) --> Max<int>(nx, ny)//编译器会自己推断类型实参
double dx=12.3, dy=45.6;
cout << Max(dx,dy) << endl;
// Max<double>(dx, dy)
string sx="world",sy="hello";
cout << Max(sx, sy) << endl;
// Max<string>(sx, sy)
Func<string>(nx);
//Func(nx);//1、类型实参列表不完全和调用实参列表相匹配,报错
//只要给了一个类型形参,另一个就能推断出来了
Max(nx, (int)dy);//强制类型转换,显示转换,不报错
//Max(nx, dy);//2、让编译器,做类型推断的同时隐式转换,报错
Hum<double>(nx);//给定R的类型,运行成功
//Hum(nx);//3、没有给定返回值R的类型,编译器无法推断,报错
}
#endif // DEDUCTION_H_INCLUDED
1.7 函数模板的重载
普通函数和可以实例化该函数的函数模板构成重载关系。
- 在数据类型匹配度一样,编译器选择普通函数,不选择函数模板。
- 若,函数模板可以产生具有更好数据类型匹配度的实例,那么选择函数模板。
- 函数模板的实例化不支持隐式类型转换,但是普通函数支持。(1.6.2中的知识)
- 可以在实例化的时候用
<>
强行通知编译器选择函数模板。
#ifndef OVERLOAD_H_INCLUDED
#define OVERLOAD_H_INCLUDED
#include <iostream>
#include <string>
using namespace std;
void Max(int x, int y){
cout << "调用普通函数:Max(int, int)" <<endl;
}
template<class T>void Max(T x, T y){
//这个函数模板和上面的函数就是重载关系
//因为,我用这个函数模板有可能能实例化出一个
//和上面一样的函数
cout << "调用函数模板:Max(T, T)" << endl;
}
void OverLoadTest(){
int nx=10, ny=20;
cout << "1.数据类型匹配度一样,选择普通函数" <<endl;
Max(nx, ny);
cout << endl;
double dx = 34.2, dy = 45.6;
cout << "2.函数模板产生的实例函数能更好匹配传入的数据类型,选函数模板" << endl;
Max(dx, dy);
cout << endl;
cout << "3.函数模板不支持隐式类型转换,使用普通函数" << endl;
Max(nx, dy);//有类型转换,选择普通函数,不选函数模板
cout << endl;
cout << "4.使用<>强制使用函数模板,就算数据匹配类型一样" << endl;
Max<>(nx, ny);//加上<>强制使用函数模板
cout << endl;
}
#endif // OVERLOAD_H_INCLUDED
代码运行结果: