【C++】C++类模板和函数模板
模板
- C++另一种编程思想称为泛型编程,主要利用的技术就是模板。
- C++提供两种模板机制:函数模板和类模板。
- 编译器对类模板处理方式和函数模板相同,都是进行2次编译。
1.类模板
1.1 类模板语法
类模板的使用,定义对象时,必须显式指定类模板类型,因为编译器无法推导类型,使用具体类型
语法
template < typename T1,typename T2 >
class Operator //正常的类模板
{
public:
void add(T1 a, T2 b)
{
cout<<"add(T1 a, T2 b)"<<endl;
cout<<a+b<<endl;
}
};
/*使用main.cpp*/
int main()
{
Operator<int,float> Op; //使用必须显示使用
Op.add(1,1.5);
return 0;
}
1.2类模板的特例化
类模板和函数模板都可以指定特殊的类和函数。其中,函数模板没有部分特化。在编译时,会根据对象定义的类模板类型,首先去匹配完全特化,再来匹配部分特化,最后匹配正常的类模板。
Example
#include <iostream>
using namespace std;
template < typename T1,typename T2 >
class Operator //正常的类模板
{
public:
void add(T1 a, T2 b)
{
cout<<"add(T1 a, T2 b)"<<endl;
cout<<a+b<<endl;
}
};
template < typename T >
class Operator<T,T> //部分特化的类模板,当两个参数都一样,调用这个
{
public:
void add(T a, T b)
{
cout<<"add(T a, T b)"<<endl;
cout<<a+b<<endl;
}
};
template < typename T1,typename T2 >
class Operator<T1*,T2*> //部分特化的类模板,当两个参数都是指针,调用这个
{
public:
void add(T1* a, T2* b)
{
cout<<"add(T1* a, T2* b)"<<endl;
cout<<*a+*b<<endl;
}
};
template < >
class Operator<void*,void*> //完全特化的类模板,当两个参数都是void*,调用这个
{
public:
void add(void* a, void* b)
{
cout<<"add(void* a, void* b)"<<endl;
cout<<"add void* Error"<<endl; //void*无法进行加法
}
};
int main()
{
int *p1 = new int(1);
float *p2 = new float(1.25);
Operator<int,float> Op1; //匹配正常的类模板:class Operator
Op1.add(1,1.5);
Operator<int,int> Op2; //匹配部分特化的类模板:class Operator<T,T>
Op2.add(1,4);
Operator<int*,float*> Op3; //匹配部分特化的类模板:class Operator<T1*,T2*>
Op3.add(p1,p2);
Operator<void*,void*> Op4; //匹配完全特化的类模板:class Operator<void*,void*>
Op4.add(NULL,NULL);
delete p1;
delete p2;
return 0;
}
1.3模板类中数值型模板参数
在泛型编程中,数据的值和类型都被参数化。在第二次编译时,编译器会根据模板中的类型参数<实参.>去推导形参的值与类型;也就是说,模板不仅支持值的传递,还支持类型的传递,这就是模板与普通函数的最大区别了。
Example
template <typename T, int N>
void func()
{
T a[N]; // 使用模板参数定义局部数组;
}
func<double, 10>(); // 使用模板时,数值型参数必须是常量,不能是变量;
总之,合法的数值型模板参数必须满足,在编译阶段确保数值型模板参数是唯一确定的。例如如下几种就不可以:
- 在发生调用时,变量不能作为数值型模板参数;(在编译时,变量的值还没有唯一确定;如 int a = 10; func<double, a>(); 编译失败)
- 在模板声明时,浮点数不能作为数值型模板参数;(浮点数本身就不精确;如 template <typename T, double N> 编译失败 )
- 类对象不能作为数值型模板参数;
记录一下 使用数值型参数 最高效求1->n前n项和:
前n项和
template < int N >
class Sum
{
public:
// 编译器会将该const常量放入符号表中,此时可以用在数值型参数表中;
// static 修饰属于整个类,通过类可以直接使用;
// 递归使用
//该值在该模板类使用时就自动生成并随着程序运行加载到内存中。只有消耗读取内存的时间,所以最高效。
static const int VALUE = Sum<N-1>::VALUE + N;
};
template < >
class Sum < 1 >
{
public:
static const int VALUE = 1;
};
int main()
{
cout << "1 + 2 + 3 + ... + 10 = " << Sum<10>::VALUE << endl;
cout << "1 + 2 + 3 + ... + 100 = " << Sum<100>::VALUE << endl;
return 0;
}
2.函数模板
2.1 函数模板语法
语法
/*
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
*/
template<typename T>或template<class T>
2.2 函数模板的使用
- 隐式调用
- 显式调用
Example
//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
//利用模板实现交换
//1、自动类型推导
mySwap(a, b);
//2、显示指定类型
mySwap<int>(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
int main() {
test01();
system("pause");
return 0;
}
- 函数模板利用关键字 template
- 使用函数模板有两种方式:自动类型推导、显示指定类型
- 模板的目的是为了提高复用性,将类型参数化
2.3 多参数函数模板
如果返回值参数作为了模板类型,则必须需要指定返回值模板类型,工程中一般都将返回值参数作为第一个模板类型。因为编译器无法推导出返回值类型,可以从左向右部分指定类型参数。Example
#include <iostream>
using namespace std;
template<typename T1, typename T2, typename T3>
T1 Add(T2 a, T3 b)
{
return static_cast<T1>(a + b);
}
int main()
{
// int a = add(1,1.5); //该行编译出错,没有指定返回值类型
int a = Add<int>(1, 1.5);
cout << a << endl; //2
float b = Add<float, int, float>(1, 1.5);
cout << b << endl; //2.5
return 0;
}
2.4 函数模板的重载
- (1)函数模板可以像普通函数一样被重载
- (2)函数模板不接受隐式转换
- (3)当有函数模板,以及普通重载函数时,编译器会优先考虑普通函数
- (4)如果普通函数的参数无法匹配,编译器会尝试进行隐式转换,若转换成功,便调用普通函数
- (5)若转换失败,编译器便调用函数模板
- (6)可以通过空模板实参列表来限定编译器只匹配函数模板,即<>内为空
Example
#include <iostream>
using namespace std;
template <typename T>
T Max(T a,T b)
{
cout<<"T Max(T a,T 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;
}
int Max(int a,int b) //重载普通函数
{
cout<<"int Max(int a,int b)"<<endl;
return a > b ? a : b;
}
int main()
{
int a=0;
int b=1;
cout<<"a:b="<<Max(a,b) <<endl ; //调用普通函数 Max(int,int)
cout<<"a:b="<<Max<>(a,b)<<endl; //通过模板参数表 调用 函数模板 Max(int,int)
cout<<"1.5:2.0="<<Max(1.5,2.0)<<endl;
//由于两个参数默认都是double,所以无法隐式转换,则调用函数模板 Max(double,double)
int *p1 = new int(1);
int *p2 = new int(2);
cout<<"*p1:*p2="<<Max(p1,p2)<<endl; // 调用重载函数模板 Max(int* ,int* )
cout<<"'a',100="<< Max('a',100)<<endl;
//将char类型进行隐式转换,从而调用普通函数 Max(int,int)
delete p1;
delete p2;
return 0;
}
2.5 函数模板的特例化
函数模板的特例化:就是指特殊的实例化,将函数模板或类模板实例化为特殊的类型,通过模板特例化可以定制在特定模板参数下的函数模板/类模板实现,或者禁用特定模板的函数模板/类模板。
Example
#include <iostream>
using namespace std;
//普通模板
template<class T1, class T2>
bool Compare(T1 a, T2 b)
{
cout << "普通模板" << endl;
return a == b;
}
//函数模板特化
template<>
bool Compare(const char* a, const char* b)
{
cout << "函数模板特化" << endl;
return strcmp(a, b) == 0;
}
int main()
{
cout << Compare(1, 2) << endl;
cout << Compare("ab", "ab") << endl;
return 0;
}
2.6 函数模板的理解
- 编译器对函数模板进行了两次编译
- 第一次编译时,首先去检查函数模板本身有没有语法错误
- 第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数。
- 函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数。
Example
#include <iostream>
using namespace std;
template <typename T>
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
int main()
{
void(*FPii)(int&, int&);
FPii = Swap; //函数指针FPii
void(*FPff)(float&, float&);
FPff = Swap; //函数指针FPff
cout << static_cast<void *>(FPii) << endl; //003511AE
cout << static_cast<void *>(FPff) << endl; //00351055
//Swap是两个函数,分别在不同的地址
return 0;
}
函数模板必须要确定类型才可以使用
//利用模板提供通用的交换函数
template<class T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
mySwap(a, b); // 正确,可以推导出一致的T
//mySwap(a, c); // 错误,推导不出一致的T类型
}
// 2、模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{
cout << "func 调用" << endl;
}
void test02()
{
//func(); //错误,模板不能独立使用,必须确定出T的类型
func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
}
int main() {
test01();
test02();
system("pause");
return 0;
}