《C++ Templates》笔记——1.函数模板

函数模板是被参数化的函数,代表的是一个函数家族

1.初探函数模板

函数模板是被参数化的函数,代表的是一个函数家族。它们看起来与普通函数很相似,唯一的区别是有些函数元素是未确定的,这些函数将在使用时被参数化。模板主要有函数模板和类模板。

1.1定义模板

下面定义了一个返回两个值中最大值的函数模板:

1
2
3
4
5
template<typename T>
inline T const& max(T const& a, T const& b)
{
return a < b ? b : a;
}

其中,要比较的两个值是通过函数参数a和b传递给函数模板的,而参数的类型还没有确定,所以用模板参数T表示。实际上上面尖括号里面参数列表的关键字也可以用class取代typename,同时T也可以用别的标识符代替,T是惯例而已。

1.2使用模板

下面的程序展示了如何使用max()函数模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{
int a = max(1, 2);
std::cout << "a = max(1, 2)= " << a << std::endl;

std::string s1 = "chd";
std::string s2 = "chdayj";
::max(s1, s2);
std::cout << "::max(s1, s2)= " << ::max(s1, s2) << std::endl;
std::cout << typeid(a).name()<<std::endl;

system("pause");
return 0;
}

上面max()被调用了两次,实参每次都不同,一次两个int,一次两个std::string。程序结果如下:

1
2
3
a = max(1, 2)= 2
::max(s1, s2)= chdayj
int

::域限定符的使用时为了保证我们调用的是全局空间中的max(),因为标准库中也有一个std::max()模板。 typeid(a).name()是显示元素类型的函数。通常不是把模板编译成一个可以处理任何类型的单一实体,而是对不同的参数类型产生不同的实体。这种用具体类型代替模板参数的过程叫做实例化,它产生了一个模板的实例

一个模板一般会被编译两次,分别发生在:

  1. 实例化之前,检查代码本身是否语法正确。
  2. 实例化期间,检查模板代码中的调用是否有效。这里是引用

2.实参的演绎

上文中的函数模板当两个实参不一样时,比如max(4,4.7),就会出错,因为c++编译器不知道T的类型是int还是double。有如下三种方法去解决:

1
2
3
4
5
6
7
8
9
10
11
12
   //double a = max(1, 2.1); 
//不允许自动类型转换,每个T都必须正确匹配,解决方法如下:

//1.对实参进行强制类型转换。使它们可以互相匹配
double a1 = max(static_cast<double>(1), 2.1);
std::cout << "max(static_cast<double>(1), 2.1)= " << a1 << std::endl;

//2.显示指定(或者限定)T的类型
auto a2 = max<double>(1, 2.1);
std::cout << "max<double>(1, 2.1)= " << a2 << std::endl;

//3.指定两个参数可以具有不同的类型

3.模板参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
/*
函数模板有两种类型的参数:
1.模板参数: T1, T2
2.调用参数: a, b
*/
//缺点: 1.函数的值取决于调用实参的顺序 2.不能通过引用返回结果
template<typename T1, typename T2>
T1 const& max(T1 const& a, T2 const& b)
{
return a < b ? b : a;
}

template<typename T1, typename T2, typename RT>
RT const& RT_max(T1 const& a, T2 const& b)
{
return a < b ? b : a;
}

template<typename RT, typename T1, typename T2>
RT RTmax(T1 const& a, T2 const& b)
{
return a < b ? b : a;
}

int main()
{
max(3.2, 2);
std::cout << "max(3.2, 2)= " << max(3.2, 2) << std::endl;

max(2, 3.2);
std::cout << "max(2, 3.2)= " << max(2, 3.2) << std::endl;

//RT_max(2, 3.2);
RT_max<int, double, double>(2, 3.2);
std::cout << "RT_max<int, double, double>(2, 3.2) = "<< RT_max<int,double,double>(2,3.2)<< std::endl;

RTmax<double>(2, 3.2);
std::cout << "RTmax<double>(2, 3.2)= " << RTmax<double>(2, 3.2) << std::endl;

system("pause");
return 0;
}

很明显,前两个模板函数的缺点:

1.函数的值取决于调用实参的顺序,2和3.2的最大值可以是3,也可以是3.2;

2.不能通过引用返回结果,例子的返回类型必须是T1,而不能是T1 const&。

当模板参数和调用参数没有发生关联,或者不能用调用参数来决定模板参数的时候,就必须显式指定模板实参。所以在第三个模板函数中,需要引入第三个模板实参类型来定义函数模板的返回类型。然而,模板实参演绎并不适合返回类型,因为RT不会出现在函数调用参数类型里面,所以没法演绎。所以只能显式指定模板实参列表。

目前为止,上述例子都是显式指定所以模板实参,或者不显示指定任何模板实参。还有一种情况,就是只显式指定第一个实参,让演绎过程推导出其余实参。通常而言,必须指定最后一个不能被隐式演绎的模板实参之前的所以实参类型

4.重载函数模板

和普通函数一样,函数模板也可以被重载,即相同的函数名称可以具有不同的函数定义。下面的程序叙述了如何重载一个函数模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>

//1.求两个int值的最大值
int const& max(int const& a, int const& b)
{
return a < b ? b : a;
}

//2.求两个任意类型值中的最大值
template<typename T>
T const& max(T const& a, T const& b)
{
return a < b ? b : a;
}

//3.求三个任意类型值中的最大值
template<typename T>
T const& max(T const& a, T const& b, T const& c)
{
return ::max(::max(a,b),c);
}

int main()
{
::max(17, 22,96);//调用具有3个参数的模板
std::cout << "::max(17, 22,96)= " << ::max(17, 22, 96) << std::endl;

::max(7.0, 9.0);//(如果模板可以产生更好匹配的函数,则选用模板)调用max<double>(通过实参演绎)
std::cout << "::max(7.0, 9.0)= " << ::max(7.0, 9.0) << std::endl;

::max('a', 'b');//调用max<char>(通过实参演绎)
std::cout << "::max('a', 'b')= " << ::max('a', 'b') << std::endl;

::max(17, 22);//调用int重载的非模板函数
std::cout << "::max(17, 22)= " << ::max(17, 22) << std::endl;

::max<>(17, 22);//调用max<int>(通过实参演绎)
std::cout << "::max<>(17, 22)= " << ::max<>(17, 22) << std::endl;

::max<double>(17, 22);//调用max<double>(没有实参演绎)
std::cout << "::max<double>(17, 22)= " << ::max<double>(17, 22) << std::endl;

::max('a', 42.7);//(对于不同类型的参数,只允许调用非模板函数)调用int重载的非模板函数
std::cout << "::max('a', 42.7)= " << ::max('a', 42.7) << std::endl;

system("pause");
return 0;
}

更多请参考:
《C++ Templates》

-------------本文结束感谢您的阅读-------------