目录
为了代码重用,代码就必须是通用的;通用的代码就必须不受数据类型的限制。那么我们可以把数据类型改为一个设计参数。这种类型的程序设计称为参数化程序设计。
1、模板的概念:
C++除有一种泛型编程的思想,泛型编程的具体例子就是模板。所谓模板,就是一个通用的类型,我们可以向模板传递不同的数据类型,从而得到不同的具体类型。C++中提供的模板有函数模板和类模板;
函数模板的作用就是将函数的数据类型参数化,比如说我们平常写的函数有具体的形参类型,返回类型。类型参数化可以在我们写函数时将这些数据类型变成未知的变量,从而得到一个函数模板,我们可以根据传入不同的数据类型得到不同的结果和实现。
函数模板的语法:
template<模板参数表>
返回类型 函数名(形式参数表)
{
...;//函数体
}
<模板参数表>尖括号中不能为空,参数可以有多个,用逗号分开。例如:
// 一个接受单个类型参数的函数模板
template<typename T>
void print(T value) {
std::cout << value << std::endl;
}
// 一个接受两个类型参数的函数模板
template<typename T1, typename T2>
void funa(T1 a, T2 b) {}
// 一个接受一个类型参数和一个非类型参数的函数模板
template<typename T, int N>
void printArray(const T* array, int size) {
for (int i = 0; i < (size < N ? size : N); ++i) {
std::cout << array[i] << " ";
}
std::cout << std::endl;
}
模板参数主要是模板类型参数。 模板类型参数代表一种类型 ,由关键字class 或 typename (建议用typename) 后加一个标识符构成,在这里两个关键字的意义相同,它们表示后面的参数名代表一个潜在的内置或用户设计的类型。
template就是语法关键字,表示要声明一个模板;typename语法关键字表示后面紧跟一个虚拟数据类型;
我们可以写一个模板函数 :
template<class T>
T my_max(T a, T b)
{
return a > b? a:b;
}
这个函数的两个形参是一个通用数据类型T,我们可以根据传进去的数据不同得到不同的实现。
我们如果将T定义为char类型,那么这个函数就变成比较两个char的大小;如果我们将T定义为int类型,那么这个函数的实现就变成比较两个int的大小。
2、使用方法
或者说怎么利用这个模板得到一个具体的函数呢?
下面有两种具体定义一个函数的方法:
2.1让编译器自动类型推导:
int main()
{
my_max(12, 23);
my_max('a', 'b');
my_max(12.23, 34.45);
return 0;
}
上面表示让编译器根据我们传进去的数据自己推导T应该成为什么数据类型。
函数模板根据一组实际类型或(和)值构造出独立的函数的过程通常是隐式发生的,称为模板实参推演。
2.2显式指定数据类型:
int main()
{
my_max<int>(12, 23);
my_max<char>('a', 'b');
my_max<double>(12.23, 34.45);
return 0;
}
上面表示显式指定数据类型,我们可以在实参列表前加上声明指定T的数据类型。
3、函数模板的注意事项
3.1函数模板中的通用数据类型要唯一
即我们不能模板传递不同的数据类型,这样编译器会不知道选择哪一种数据类型。
template<typename T>
void func(T& a, T& b)
{}
int main()
{
func(12,12.23);
return 0;
}
3.2函数模板的隐式转换问题
在函数模板中,如果两个不同类型的实参被传递给接受相同类型参数的模板函数,编译器可能会尝试通过隐式类型转换来匹配模板参数。这可能会导致意外的行为或性能下降。例如:
template<typename T>
void func(const T& a,const T& b)
{}
int main()
{
func<int>(12, 12.23);
return 0;
}
//上述的例子因为使用的不是显式数据指定的方式创建函数模板,那么编译器会不知道T的具体数据类型,所以不会发生隐式的数据转换
//如果我们使用的是显式地函数模板,那么编译器也可能会自动帮我们进行隐式数据转换。
总结:
-
如果显式指定了模板参数类型,那么传递给函数的实参类型必须与指定的模板参数类型兼容。
-
如果让编译器自动推导模板参数类型,那么所有实参的类型必须能够匹配到一个单一的模板参数类型上。
3.3函数模板和普通函数的调用规则
规则1:函数模板和普通函数都可以调用,优先调用普通函数
当函数模板和普通函数(非模板函数)都可以匹配一个函数调用时,编译器会优先调用普通函数。这是因为普通函数提供了更具体的类型信息,而函数模板则是更一般的解决方案。编译器总是倾向于使用更具体、更明确的函数定义。
规则2:我们可以加上空的模板参数列表强制调用函数模板
如果希望强制调用函数模板,即使存在可以匹配的普通函数,可以在函数调用时使用空的模板参数列表(<>)。这样做会告诉编译器希望使用模板版本,而不是任何可能存在的普通函数。
template<typename T>
void func(T x) {
// 模板函数实现
}
void func(int x) {
// 普通函数实现
}
int main() {
func<>(42); // 强制调用模板函数
return 0;
}
在这个例子中,func<>(42) 会调用模板函数,而不是普通函数。
规则3:函数模板可以发生重载
函数模板可以像普通函数一样被重载。这意味着可以有多个具有相同名称但模板参数不同的函数模板。编译器会根据提供的实参类型来选择最合适的模板进行实例化。
template<typename T>
void func(T x) {
// 第一个模板函数实现
}
template<typename T, typename U>
void func(T x, U y) {
// 第二个模板函数实现
}
在这个例子中,func 被重载了两次,一次接受一个参数,另一次接受两个参数。
1092

被折叠的 条评论
为什么被折叠?



