C++可以重载函数,类似的,也可以重载函数模板。
修改代码,分别定义两个函数模板,它们名字相同,但是传入的参数类型不同,一个是const,一个是非const。
同时,他们的输出信息中的序号也不同。
- template<typename T>
- const T &my_max(const T &a, const T &b)
- {
- cout << "1: " << __PRETTY_FUNCTION__ << endl;
- return ((a < b) ? b : a);
- }
-
- template<typename T>
- const T &my_max(T &a, T &b)
- {
- cout << "2: " << __PRETTY_FUNCTION__ << endl;
- return ((a < b) ? b : a);
- }
此时,在main函数中调用my_max函数,传参均为int型,发现调用的是第二个模板函数,也就是传参为非const类型的模板函数。
- int main(int argc, char **argv)
- {
- int ia = 1;
- int ib = 2;
-
- my_max(ia, ib);
-
- return 0;
- }

增加一个函数my_max,传参的类型为int类型的引用。
- const int &my_max(int &a, int &b)
- {
- cout << "3: " << __PRETTY_FUNCTION__ << endl;
- return ((a < b) ? b : a);
- }
此时,两个模板函数,一个普通函数,他们都可以匹配这个my_max,my_max会调用哪个函数呢?
答:会调用my_max函数。

问:为什么此时调用的是普通函数 my_max,而不是其他的模板函数?函数选择规则是怎么样的?
答:函数的选择规则如下:
根据上面说的函数选择过程,来推导一下本次测试代码的函数选择过程。
首先,列出候选函数:
- 第 1 个,模板函数:const int &my_max(const int &a, const int &b);
- 第 2 个,模板函数:const int &my_max(int &a, int &b);
- 第 3 个,普通函数:const int &my_max(int &a, int &b);
然后,根据参数的“类型转换”来排序。
我们调用my_max函数时,传入了两个int型的参数。
对于第1个函数,传入的 int 需要转换为 const int;
对于第2个函数,传入的 int 直接就是 int;
对于第3个函数,传入的 int 直接就是 int;
那么,排序如下,最前面的数字代表选择的优先级:
- 2 —— 第 1 个,模板函数:const int &my_max(const int &a, const int &b);
- 1 —— 第 2 个,模板函数:const int &my_max(int &a, int &b);
- 1 —— 第 3 个,普通函数:const int &my_max(int &a, int &b);
第二个模板函数和普通函数并列第一,第一个模板函数则为第二。
在第二步排序中,出现了两个备选项,此时编译器要怎么选择哪个函数?
上面说过,匹配度相同时,会遵循如下的规则:
根据第一条,选择的是普通函数my_max。
需要注意,如果最终匹配完成后,还有多个函数匹配,则编译器会报二义性错误,因为此时编译器无法判断到底要调用哪个函数。
修改代码,增加一个新的模板函数,序号为4。
- template<typename T>
- const T my_max(T a, T b)
- {
- cout << "4: " << __PRETTY_FUNCTION__ << endl;
- return ((a < b) ? b : a);
- }
同时,将前面的普通函数my_max屏蔽。
可以看出,这个模板函数的排序也是1,它和序号2的模板函数排序一样,又都是模板函数,此时编译器就会报二义性错误。

所谓特化,就是参数的匹配更加特殊/具体/细化。
修改代码,将普通函数my_max还原回来,修改序号4的函数模板如下:
- template<typename T>
- const T my_max(T *a, T *b)
- {
- cout << "4: " << __PRETTY_FUNCTION__ << endl;
- return ((*a < *b) ? *b : *a);
- }
修改main函数,传入int *类型的参数,此时会调用哪个函数呢?

测试发现,调用的是序号4的模板函数。
注意:此时T被转换为了int,而不是int *。

推导一下选择过程。
- 第 1 个,模板函数:const int *&my_max(const int *&a, const int *&b);
- 第 2 个,模板函数:const int *&my_max(int *&a, int *&b);
- 第 3 个,普通函数:const int &my_max(int &a, int &b);
- 第 4 个,模板函数:const int *my_max(int *, int *b);
对于第1个函数,传入的 int * 需要转换为 const int *;
对于第2个函数,传入的 int * 直接就是 int *;
对于第3个函数,不匹配;
对于第4个函数,传入的 int * 直接就是 int *;
那么,排序如下,最前面的数字代表选择的优先级:
- 2 —— 第 1 个,模板函数:const int *&my_max(const int *&a, const int *&b);
- 1 —— 第 2 个,模板函数:const int *&my_max(int *&a, int *&b);
- 不匹配 —— 第 3 个,普通函数:const int &my_max(int &a, int &b);
- 1 —— 第 4 个,模板函数:const int *my_max(int *, int *b);
此时,由于匹配的函数只有模板函数,所以会选择更“特化”的那个模板函数。
分析一下序号2和4的模板函数:
- 序号2:my_max(T &, T &),可以是指针或者其他类型
- T 推导为 int 时, (int &, int &)
- T 推导为 int *时, (int *&, int *&)
- ......
-
- 序号4:my_max(T *, T *),参数只能是指针
- T 推导为 int 时, (int *, int *)
可以看出,序号2的函数模板,它的使用范围包括了序号4的函数模板。
由于序号4的函数模板,参数的选择范围更小,参数的匹配更加具体,更特化(特殊化),所以选择的是序号4的模板函数。
修改代码,将序号4的函数模板的传参修改为const。
- template<typename T>
- const T my_max(const T *a, const T *b)
- {
- cout << "4: " << __PRETTY_FUNCTION__ << endl;
- return ((*a < *b) ? *b : *a);
- }
此时编译测试,发现调用了序号2的模板函数。

- template<typename T>
- const T &my_max(T &a, T &b)
- {
- cout << "2: " << __PRETTY_FUNCTION__ << endl;
- return ((a < b) ? b : a);
- }
同样,推导一下函数选择。
- 第 1 个,模板函数:const int *&my_max(const int *&a, const int *&b);
- 第 2 个,模板函数:const int *&my_max(int *&a, int *&b);
- 第 3 个,普通函数:const int &my_max(int &a, int &b);
- 第 4 个,模板函数:const int *my_max(const int *, const int *b);
对于第1个函数,传入的 int * 需要转换为 const int *;
对于第2个函数,传入的 int * 直接就是 int *;
对于第3个函数,不匹配;
对于第4个函数,传入的 int * 需要转换为 const int *;
那么,排序如下,最前面的数字代表选择的优先级:
- 2 —— 第 1 个,模板函数:const int *&my_max(const int *&a, const int *&b);
- 1 —— 第 2 个,模板函数:const int *&my_max(int *&a, int *&b);
- 不匹配 —— 第 3 个,普通函数:const int &my_max(int &a, int &b);
- 2 —— 第 4 个,模板函数:const int *my_max(const int *, const int *b);
显然,此时会选择序号2的模板函数。
稍微再修改一下代码,如果此时将序号2的函数模板屏蔽掉,那么会选择哪个呢?
答:会选择序号1的模板函数。

- template<typename T>
- const T &my_max(const T &a, const T &b)
- {
- cout << "1: " << __PRETTY_FUNCTION__ << endl;
- return ((a < b) ? b : a);
- }
先分析一下序号4的函数模板:
- template<typename T>
- const T my_max(const T *a, const T *b)
- {
- cout << "4: " << __PRETTY_FUNCTION__ << endl;
- return ((*a < *b) ? *b : *a);
- }
- 序号4:my_max(const T *, const T *)
- T 推导为 int 时, (const int *, const int *)
首先说一下,对于指针,有一种简单的记忆方法:
从右往左读,遇到 “p” 就替换成 “p is a”,遇到 “*” 就替换成 “point to”。
然后,来看一下序号4中的const int *是什么意思?
假设参数名叫p,那么根据上面的方法,可以读作:p is a point to const int,即P是一个指向只读int型变量的指针。
再分析一下序号2的函数模板:
序号2:my_max(const T &, const T &)
这里的 const 表示常量引用,是用来修饰 T 的。
如果有 int a = 1;const int &b = a;那么通过b无法修改a。
如果 T = int *,那么const (int *) &中,const修饰的是int *,即如果有:
int *q = &a;
const (int *)p = q;(此语句有问题,此处只是为了表明const是用于修改T的,理解即可)
那么,无法通过 p 修改 q(p是常量引用,无法修改),但是 p 所指向的内容可以修改,也就是可以修改a。
综上,我们传入的int *是可读可写的,如果使用的是序号4的模板函数,那么就变成了只读的,使用范围就被限制了。
但如果使用序号1,那么指向的内容还是可读可写的,所以使用的是序号1的模板。
此时,如果将序号1的函数模板也屏蔽掉,则会使用序号4的函数模板。

当传入的参数为int型时,我们的本意是比较这两个int型变量的大小;当传入的参数为int *型时,我们的本意是比较这两个指针指向的变量的大小。

那么,当传入的参数为字符串时,我们的本意是比较这两个字符串字符值的大小。
修改代码,增加一个my_max普通函数用于比较字符串值。
- char *my_max(char *a, char *b)
- {
- cout << "3: " << __PRETTY_FUNCTION__ << endl;
- return strcmp(a, b) < 0 ? b : a;
- }
修改main函数。

此时编译测试,结果如下:
