模板函数
模板函数的声明形式:
template<class T>T func(T t){
int a = 10;
T b;
b.text();
b.te<>xt();//不允许
}
//template<class 类型形参> 函数返回值 函数名(调用形参类型 调用形参){};
模板函数的实例化:
int num = 10;
func<int>(num);//最完整的调用方式
func<>(num);//省略类型实参,将由编译器根据调用实参的类型来推断类型实参
func(num);//省略尖括号和类型实参,将由编译器根据调用实参的类型来推断类型实参
模板函数的二次编译:
第一次编译:发生在模板函数实例化之前
当编译器对模板函数进行编译的时候,先对模板函数本身的语法进行检查。
检查过程中,对于已知类型的变量的检查机制和普通函数相同;
但是对于类型未知的变量的检查,编译器尽可能的认为是没有问题的。
例如上述模板函数中的变量 a , b;
由于编译器在第一次编译的时候还不知道T是哪种类型,也不知道它有没有text(),这样的成员函数。所以干脆就不报错。
但是,不允许未知类型变量带有尖括号的成员变量。
第二次编译:发生在模板实例化时
实例化后的函数中,将不存在任何未知类型,这时的检查和普通函数一样。
所以上述的b.text()将会在实例化时报错。
模板函数的隐式推断:
在函数的实例化中,可以看到,即使省略尖括号和尖括号内的类型形参,也是可以正常的编译。
这是因为,编译在模板函数实例化的时候,可以根据模板函数的调用实参类型来推断出类型实参。
但是!这样的用法,仅仅只能用于类型形参和调用形参相关的情况下。
有三种情况下,编译器将不支持隐式推断:
1、类型形参和调用形参不完全相关
template<class T,class R>void func(T t){}
2、调用实参和调用形参之间存在隐式转换
template<class T>void func(T t1,T t2){}
func(10,10.5);
3、函数的返回值不支持隐式推断
template<class T,class R>R func(T t){}
func(10);
模板函数的重载:
普通函数与可以实例化为和普通函数一模一样的模板函数形成重载关系,如下:
void func(int a,int b){};
template<class T>void func(T t1, T t2){}
提到重载,必定要明确其调用的优先顺序。
当一次函数调用和普通函数一模一样的时候,那么其重载的模板函数肯定也有一样的实例化版本,这个时候,编译器时优先选择普通函数。例如:
func(10,20);
选择普通函数,对于编译器来说,可以省去隐式推断的过程。
其他情况,先看看模板函数能否达到最匹配的程度,然后再做选择。例如:
func(1.1,1.2);//选择模板函数实例化
func(1,1.1);//选择普通函数,模板函数隐式推断,并不支持隐式转换
当两个模板函数的实例化发生重载的时候,编译器会选择约束性更高的版本,例如:
template<class T>void func(T t1){}
template<class T>void func(T *t1){}
int num = 10;
func(&num);//选择调用形参为指针类型的版本
模板类:
模板类的声明形式:
//template<class 类型形参>class 类名{};
template<class T>class Student{
public:
Student(int one,T two):m_one(one),m_two(two){}
T printf(void);
static T func(void);
private:
static int m_one;
static T m_two;
};
模板类的实例化:
Student<int> student(10,10);
尖括号和类型实参无法省略。因为可以通过类模板实例化多个类,如果省略掉尖括号和类型实参,看起来各个实例化类之间就是重定义的关系。
模板类的成员函数:
模板类的成员函数,可以看作是模板函数。
首先,来看成员函数的定义方式,它可以放在类内定义,也可以放在类外定义,类外定义格式如下:
template<class T> T class<T>::printf(void){...}
其次,注意模板类的成员函数的实例化时间。
模板类的成员函数,并不是在类实例化时,进行实例化的。而是调用该成员函数时,才会初始化。虚函数除外。例如:
Student<int> student(10,10);//现在的Student<int>类中,只有成员变量和一个构造函数。
student.printf();//这个时候,Student<int>类中,才有了printf这个成员函数。
可以使用任意类型去实例化一个模板类,如果使用的类型是一个自定义类类型,而这个类类型并不满足这个模板类所需的全部功能(例如,重载所有的运算符)。这个时候只要类实例化的对象不去调用这些功能,那么就不会实例化这些函数。也就没有影响。
模板类的静态成员:
在普通的类中,静态成员是需要在全局区进行初始化的,而在模板类中,也是如此,形式如下:
template<T> T Stuent<T>::m_two;
template<T> int Student<T>::m_one = 0;
注意!模板类的静态成员,每个模板类的实例维护一份,模板类的实例的多个对象共同维护一份。
也就是说,静态成员,不属于模板类,也不属于对象,而属于模板类的实例。
模板类的递归实例化:
上面提到,可以通过任何类型来实例化一个模板类。
那么使用一个模板类的实例去实例化一个模板类,就称为模板类的递归实例化,如下:
template<T> class Arrary{
public:
T& operator[](size_t i){
return arr[i];
}
private:
T arr[10];
};
Arrary< Arrary<int> > a;
在最外层类维护的数组中,存放的是数组类型的元素。