1、什么是模板?模板有什么作用?
模板分为函数模板和类模板。函数模板是对函数功能框架的描述,具体功能由实际传递的参数决定。
有了函数模板,编译器就会根据模板自动生成多个函数名相同,参数列表不同的函数,不需要手动写;
例:求一个矩形面积
当传入的长、宽均是正整数时,我们需要这样写函数:
void Rectangle(int a,int b)
{
int s;
s=a*b;
cout<<"s="<<s<<endl;
}
但如果传入的长、宽都是带小数的时候,我们就需要这样写:
void Rectangle(double a,double b)
{
double s;
s=a*b;
cout<<"s="<<s<<endl;
}
可以发现,这样写实在繁琐,形式都是一样的,还需要写两边。为了解决这个问题,就有了函数模板。
2、函数模板该怎样使用?
函数模板的写法如下:
template<typename 类型参数1,typename 类型参数2>
返回值类型 模板名(形参名)
{
函数体;
}
对于上边的程序,我们可以定义这样一个函数模板:
template<typename T>
void Rectangle(T&x,T&y)
{
T s;
s=x*y
cout<<"s"<<s<<endl;
}
T是类型参数,代表传入参数的类型。函数在由模板生成函数时,会自动根据实参的类型对模板中的类型参数进行替换。
3、函数模板和模板函数有什么区别?
有模板实例化的到的具体函数叫做模板函数(实例化:编译器由模板自动替换生成具体函数的过程叫做实例化);
函数模板会在编译期根据使用情况生成对应的函数
模板不编译;
但是模板生成的函数指令会编译;
模板中的语法错误,会在生成对应的指令时候被编译出错误;
函数模板有类型自推的能力,使用函数模板可以不用传模板类型参数;
对于上边的程序:
template<typename T>
void Rectangle(T&x,T&y)
{
T s;
s=x*y
cout<<"s"<<s<<endl;
}
这是函数模板
void Rectangle(int a,int b)
{
int s;
s=a*b;
cout<<"s="<<s<<endl;
}
void Rectangle(double a,double b)
{
double s;
s=a*b;
cout<<"s="<<s<<endl;
}
这是模板函数
下边我们来看一段小代码:
template<typename T>
bool compare(T a, T b)
{
cout << "template bool compare(T a, T b)" << endl;
cout << typeid(T).name() << endl;
return a > b;
}
int main()
{
compare(10, 20);
compare(10.2, 20.3);
//compare(10, 20);
//compare(10.2, 20.3);
}
在编译器对模板进行实例化时,模板的类型参数并非只能通过传入实参来判断,还可以直接在传参的时候就指明类型参数该实例化为哪种类型。(上边程序屏蔽的部分)。
此处实例化的模板函数是:
bool Compare(double a,double b);
还需要说明的是:在指定类型的时候,可以指定多个类型,如:
compare<double,float>(10.2, 20)
即使不指定,编译器也会替换为相应的类型,即:
compare<double>(10.2, 20)//20的类型为int
同样,如果不指定,还是根据实参进行识别的话,也会识别出不同的类型,即:
compare(10.2, 20);//10.2 为double,20为int
示例:写一个冒泡排序的模板
template<typename T>
void swap1(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
template<typename T>
void sort(T* arr, int len)
{
cout << "void sort(T *arr,int len)" << endl;
cout << typeid(T).name() << endl;
if (NULL == arr)
{
return;
}
for (int i = 0; i < len-1; i++)
{
for (int j = 0; j < len - i - 1; j++)
{
if (!(arr[j] > arr[j + 1]))
{
swap1(arr[j], arr[j + 1]);
}
}
}
}
int main()
{
int arr[] = { 23,45,34,11,67,89,4,3,90,21 };
double arr[] = { 23,45,34,11,67,89,4,3,90,21 };
int len = sizeof(arr) / sizeof(arr[0]);
sort(arr, len);
return 0;
}
冒泡排序函数模板在编译期会自动生成以下两种函数:
void sort(int *arr,int len);
void sort(double *arr,int len);
4、函数模板的特例化
问题:特例化是什么?为什么要特例化?
答:特例化顾名思义就是函数模板的特殊实例化,它是为了完成一半模板函数不能完成的特殊任务。例:
还是刚才那个代码:
template<typename T>
bool compare(T a, T b)
{
cout << "template bool compare(T a, T b)" << endl;
cout << typeid(T).name() << endl;
return a > b;
}
int main()
{
if (compare("aaa", "sss"))//会直接比较地址,比较结果不可信
{
cout << "aaa > sss" << endl;
}
else
{
cout << "sss > aaa" << endl;
}
进行一般的数值比较是,上边的模板函数就可以了,但如果两个实参都是字符串的话,它们的实参类型就位`const char*`类型。
众所周知,进行字符串比较时,是按照字符一个一个进行比较的,这样的结果才可信,但是如果此时我们还是调用上边的模板函数来比较的话,它就不是比较的字符串了,它比较的是整个字符串的地址。这样造成的结果就是,左边的永远比右边的大
}
上述代码一眼就能看出来sss大,正确答案应该是sss > aaa
,打印结果如下:
我们发现,这个结果明显不正确。
通过观察地址可以发现,aaa的地址比sss大,这就说明了对于字符串这种特殊的实参,我们不能用普通的模板函数来比较,必须另写一个模板函数的特例化来解决这些问题,即:
template<>
bool compare(const char* a, const char* b)
{
cout << "bool compare(const char* a, const char* b)" << endl;
return strcmp(a, b) > 0;
}
解释:特例化并不是对函数模板进行重载,相反,它也只是函数模板实例化的特殊模板函数。这种特例化的模板函数在写时,需要直接在形参参数列表中写明参数类型,不能再使用类型参数。
特例化分为部分特例化和全部特例化。全部特例化就是上边这种,将两个参数都写明参数类型。部分特例化就是只将一部分的参数写明参数类型,如:
template<T>
bool compare(T a,int b);
5、类模板
还是先明确一个定义,类模板的实例化就是模板类。
类模板,实际上是建立一个通用类,内部的数据成员,成员函数的返回值类型和参数类型不具体说明,用类型参数来代表。当使用类模板定义对象时,系统会根据实参的类型去替换模板中的类型参数,从而实例化出一个模板类,用以实现具体的功能。
其格式如下:
template<typename 类型参数>
class 类名
{
类成员说明
};
当需要实现具体功能时,就需要建立具体的类模板,如:求矩形面积
template<typename T>
class Rectangle
{
public:
Rectangle(T a,T b)
{
x=a;
y=b;
}
T Area()
{
return a*b;
}
private:
int x,y;
}
int main()
{
Rectangle<int>a(10, 20);
}
注意:类模板
类模板不编译
会在编译期根据使用方式,生成对应的类代码
类模板中没有使用到的成员方法不会在编译器生成对应的指令
类模板使用时必须加上模板类型参数,类模板无法自己推导类型参数
为什么类模板的成员方法不能再其它cpp文件中实现?
类模板需要在编译时期将使用到的成员方法生成对应的指令
编译器是指针对单文件的
如果将类模板的成员方法实现在其他文件,编译期使用到该方法的文件不可见,就无法生成对应的指令----就会报无法解析的外部符号
再看一个代码:
template <typename T>
class Arr
{
public:
Arr(T x,T y)
{
a=x;
b=y;
cout << "Arr()" << endl;
}
T Area();
private:
T a,b;
};
//类模板的成员方法类外实现
template<typename T>
void Arr<T>::Area()
{
return x*y;
cout << "void Arr::Area()" << endl;
}
int main()
{
Arr<int>A(1,2);
Arr<float>B(2,3);
Arr<double>C(3,4);
return 0;
}
上述代码中,类模板进过实例化之后产生了三个模板类,并产生了A、B、C三个对象。
注意:类的成员函数在类外实现时需要在函数定义之前进行模板声明,在成员函数名前写上“类名::”;