• 3.4 C++高级编程_函数模板_重载


    C++可以重载函数,类似的,也可以重载函数模板

    重载函数模板

    修改代码,分别定义两个函数模板,它们名字相同,但是传入的参数类型不同,一个是const,一个是非const。

    同时,他们的输出信息中的序号也不同。

    1. template<typename T>
    2. const T &my_max(const T &a, const T &b)
    3. {
    4. cout << "1: " << __PRETTY_FUNCTION__ << endl;
    5. return ((a < b) ? b : a);
    6. }
    7. template<typename T>
    8. const T &my_max(T &a, T &b)
    9. {
    10. cout << "2: " << __PRETTY_FUNCTION__ << endl;
    11. return ((a < b) ? b : a);
    12. }

    此时,在main函数中调用my_max函数,传参均为int型,发现调用的是第二个模板函数,也就是传参为非const类型的模板函数。

    1. int main(int argc, char **argv)
    2. {
    3. int ia = 1;
    4. int ib = 2;
    5. my_max(ia, ib);
    6. return 0;
    7. }

    增加一个函数my_max,传参的类型为int类型的引用。

    1. const int &my_max(int &a, int &b)
    2. {
    3. cout << "3: " << __PRETTY_FUNCTION__ << endl;
    4. return ((a < b) ? b : a);
    5. }

    此时,两个模板函数,一个普通函数,他们都可以匹配这个my_max,my_max会调用哪个函数呢?

    答:会调用my_max函数。

    函数的选择规则

    问:为什么此时调用的是普通函数 my_max,而不是其他的模板函数?函数选择规则是怎么样的?

    答:函数的选择规则如下:

    1. 先列出候选函数,包括普通函数,参数推导成功的模板函数;
    2. 在候选函数中,根据“类型转换”来排序(注意:模板函数只支持有限的类型转换);
    3. 如果某个候选函数的参数,跟调用时传入的参数更匹配,则选择此候选函数;
    4. 如果这些候选函数的参数匹配相同
      a.如果只有一个非模板函数,那么优先使用非模板函数
      b.如果都是模板函数,那么使用“更特化”的那个模板函数;

    根据上面说的函数选择过程,来推导一下本次测试代码的函数选择过程。

    列出候选函数

    首先,列出候选函数:

    1. 1 个,模板函数:const int &my_max(const int &a, const int &b);
    2. 2 个,模板函数:const int &my_max(int &a, int &b);
    3. 3 个,普通函数:const int &my_max(int &a, int &b);

    排序

    然后,根据参数的“类型转换”来排序。

    我们调用my_max函数时,传入了两个int型的参数。

    对于第1个函数,传入的 int 需要转换为 const int;

    对于第2个函数,传入的 int 直接就是 int;

    对于第3个函数,传入的 int 直接就是 int;

    那么,排序如下,最前面的数字代表选择的优先级:

    1. 2 —— 第 1 个,模板函数:const int &my_max(const int &a, const int &b);
    2. 1 —— 第 2 个,模板函数:const int &my_max(int &a, int &b);
    3. 1 —— 第 3 个,普通函数:const int &my_max(int &a, int &b);

    第二个模板函数和普通函数并列第一,第一个模板函数则为第二。

    选择“更匹配”的函数

    在第二步排序中,出现了两个备选项,此时编译器要怎么选择哪个函数?

    上面说过,匹配度相同时,会遵循如下的规则:

    1. 优先选择普通函数;
    2. 对于多个匹配的模板函数,选择“更特化”的那个;

    根据第一条,选择的是普通函数my_max

    需要注意,如果最终匹配完成后,还有多个函数匹配,则编译器会报二义性错误,因为此时编译器无法判断到底要调用哪个函数。

    二义性错误

    修改代码,增加一个新的模板函数,序号为4。

    1. template<typename T>
    2. const T my_max(T a, T b)
    3. {
    4. cout << "4: " << __PRETTY_FUNCTION__ << endl;
    5. return ((a < b) ? b : a);
    6. }

    同时,将前面的普通函数my_max屏蔽。

    可以看出,这个模板函数的排序也是1,它和序号2的模板函数排序一样,又都是模板函数,此时编译器就会报二义性错误。

    特化

    所谓特化,就是参数的匹配更加特殊/具体/细化

    修改代码,将普通函数my_max还原回来,修改序号4的函数模板如下:

    1. template<typename T>
    2. const T my_max(T *a, T *b)
    3. {
    4. cout << "4: " << __PRETTY_FUNCTION__ << endl;
    5. return ((*a < *b) ? *b : *a);
    6. }

    修改main函数,传入int *类型的参数,此时会调用哪个函数呢?

    测试发现,调用的是序号4的模板函数。

    注意:此时T被转换为了int,而不是int *

    推导一下选择过程。

    列出候选函数

    1. 1 个,模板函数:const int *&my_max(const int *&a, const int *&b);
    2. 2 个,模板函数:const int *&my_max(int *&a, int *&b);
    3. 3 个,普通函数:const int &my_max(int &a, int &b);
    4. 4 个,模板函数:const int *my_max(int *, int *b);

    排序

    对于第1个函数,传入的 int * 需要转换为 const int *;

    对于第2个函数,传入的 int * 直接就是 int *;

    对于第3个函数,不匹配;

    对于第4个函数,传入的 int * 直接就是 int *;

    那么,排序如下,最前面的数字代表选择的优先级:

    1. 2 —— 第 1 个,模板函数:const int *&my_max(const int *&a, const int *&b);
    2. 1 —— 第 2 个,模板函数:const int *&my_max(int *&a, int *&b);
    3. 不匹配 —— 第 3 个,普通函数:const int &my_max(int &a, int &b);
    4. 1 —— 第 4 个,模板函数:const int *my_max(int *, int *b);

    选择“更匹配”的函数

    此时,由于匹配的函数只有模板函数,所以会选择更“特化”的那个模板函数。

    分析一下序号2和4的模板函数:

    1. 序号2:my_max(T &, T &),可以是指针或者其他类型
    2. T 推导为 int 时, (int &, int &)
    3. T 推导为 int *, (int *&, int *&)
    4. ......
    5. 序号4:my_max(T *, T *),参数只能是指针
    6. T 推导为 int 时, (int *, int *)

    可以看出,序号2的函数模板,它的使用范围包括了序号4的函数模板。

    由于序号4的函数模板,参数的选择范围更小,参数的匹配更加具体,更特化(特殊化),所以选择的是序号4的模板函数。

    修改测试

    修改代码,将序号4的函数模板的传参修改为const。

    1. template<typename T>
    2. const T my_max(const T *a, const T *b)
    3. {
    4. cout << "4: " << __PRETTY_FUNCTION__ << endl;
    5. return ((*a < *b) ? *b : *a);
    6. }

    此时编译测试,发现调用了序号2的模板函数。

    1. template<typename T>
    2. const T &my_max(T &a, T &b)
    3. {
    4. cout << "2: " << __PRETTY_FUNCTION__ << endl;
    5. return ((a < b) ? b : a);
    6. }

    同样,推导一下函数选择。

    列出候选函数

    1. 1 个,模板函数:const int *&my_max(const int *&a, const int *&b);
    2. 2 个,模板函数:const int *&my_max(int *&a, int *&b);
    3. 3 个,普通函数:const int &my_max(int &a, int &b);
    4. 4 个,模板函数:const int *my_max(const int *, const int *b);

    排序

    对于第1个函数,传入的 int * 需要转换为 const int *;

    对于第2个函数,传入的 int * 直接就是 int *;

    对于第3个函数,不匹配;

    对于第4个函数,传入的 int * 需要转换为 const int *;

    那么,排序如下,最前面的数字代表选择的优先级:

    1. 2 —— 第 1 个,模板函数:const int *&my_max(const int *&a, const int *&b);
    2. 1 —— 第 2 个,模板函数:const int *&my_max(int *&a, int *&b);
    3. 不匹配 —— 第 3 个,普通函数:const int &my_max(int &a, int &b);
    4. 2 —— 第 4 个,模板函数:const int *my_max(const int *, const int *b);

    选择“更匹配”的函数

    显然,此时会选择序号2的模板函数。

    稍微再修改一下代码,如果此时将序号2的函数模板屏蔽掉,那么会选择哪个呢?

    答:会选择序号1的模板函数。

    1. template<typename T>
    2. const T &my_max(const T &a, const T &b)
    3. {
    4. cout << "1: " << __PRETTY_FUNCTION__ << endl;
    5. return ((a < b) ? b : a);
    6. }

    分析序号4的函数模板

    先分析一下序号4的函数模板:

    1. template<typename T>
    2. const T my_max(const T *a, const T *b)
    3. {
    4. cout << "4: " << __PRETTY_FUNCTION__ << endl;
    5. return ((*a < *b) ? *b : *a);
    6. }
    1. 序号4:my_max(const T *, const T *)
    2. 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的函数模板:

    序号2my_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普通函数用于比较字符串值。

    1. char *my_max(char *a, char *b)
    2. {
    3. cout << "3: " << __PRETTY_FUNCTION__ << endl;
    4. return strcmp(a, b) < 0 ? b : a;
    5. }

    修改main函数。

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

  • 相关阅读:
    Promise面试题
    typeinfo类型支持库学习
    MySQL JDBC驱动版本与数据库版本的对应关系及注意事项
    如何通过更好的文档管理减轻小型企业的压力
    第十二届钧瓷文化旅游节主题曲:让世界看见钧瓷的魅力
    使用Hadoop和Nutch构建音频爬虫:实现数据收集与分析
    java-初识Servlet,Tomcat,JDBC
    如何建立企业数字化营销体系?使用数字化营销系统助企业升级?
    LeetCode108. Convert Sorted Array to Binary Search Tree
    游戏录屏软件推荐,教你录制高清游戏视频
  • 原文地址:https://blog.csdn.net/qq_33141353/article/details/126326531