zoukankan      html  css  js  c++  java
  • C++学习之路: 模板函数

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 using namespace std;
     5 
     6 template <typename T>
     7 T max(T a, T b)
     8 {
     9     return a > b ? a : b;
    10 }
    11 
    12 
    13 
    14 int main(int argc, const char *argv[])
    15 {
    16     int i = 42;
    17     cout << ::max(7, i) << endl;
    18 
    19     double f1 = 3.4;
    20     double f2 = -6.7;
    21     cout << ::max(f1, f2) << endl;
    22 
    23     string s1 = "hello";
    24     string s2 = "world";
    25     cout << ::max(s1, s2) << endl;
    26 
    27 
    28     return 0;
    29 }
    30 //次模板函数,是值拷贝型, 输入和返回值都是值拷贝,开销巨大, 如果我们 输入和返回值都是类类型的话,    对资源的消耗是巨大的。
    31 所以一般用下面这种模板
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    template <typename T>
    const T &max(const T &a,const  T &b)
    {
        return a > b ? a : b;
    }
    
    
    
    int main(int argc, const char *argv[])
    {
        int i = 42;
        cout << ::max(7, i) << endl;
    
        double f1 = 3.4;
        double f2 = -6.7;
        cout << ::max(f1, f2) << endl;
    
        string s1 = "hello";
        string s2 = "world";
        cout << ::max(s1, s2) << endl;
    
    
        return 0;
    }

    上面这种模板,会节约很多。

    下面介绍一种有坑的情况

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 using namespace std;
     5 
     6 template <typename T>
     7 const T &max(const T &a, const T &b)
     8 {
     9     return a > b ? a : b;
    10 }
    11 
    12 
    13 
    14 int main(int argc, const char *argv[])
    15 {
    16     //编译错误
    17     cout << ::max(4, 4.7) << endl;
    18     return 0;
    19 }

    我们输入为 4-int  4.7-double  , 编译器无法找到一个模板去匹配, 编译错误。

    因为我们模板是两个参数都是T类型的,  而不是一个是T1,另一个是T2;

    好吧,我们尝试一下两个参数类型是不同的模板

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 using namespace std;
     5 
     6 template <typename T1, typename T2>
     7 const T1 &max(const T1 &a, const T2 &b)
     8 {
     9     return a > b ? a : b;
    10 }
    11 
    12 
    13 
    14 int main(int argc, const char *argv[])
    15 {
    16     cout << ::max(3, 4.5) << endl;  
    17     // int double
    18     // const int &max(const int &, const double &);
    19     // 因为T1与T2类型不同,所以进行强制类型转换
    20     // 产生了一个中间临时变量
    21     // 所以最后的返回值,引用了一个临时变量
    22 
    23     return 0;
    24 }

    上面是一个错误了例子, 编译不会通过, 错误十分隐晦, 我们看到 参数1是 T1, 参数2 是T2, 

    我们输入时 参数a是3 --int  参数b是4.5--double,那么我们告诉编译器去生成一个int &max(const int &, const double &)的函数,

    返回值和 参数一都是T1==int, 显然 4.5 是比3大的,  所以返回的是4.5,但是我们要求返回值是int型, 显然也不匹配, 所以编译器会进行,强制转换 生成一个tmp = (int)4.5;

    return  tmp的引用。  tmp是临时变量, 该函数调用结束后栈空间被释放, 返回临时变量的引用是没有意义的。

    所以导致编译错误    

    下面看一下模板的重载

    例子代码:

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 
     5 using namespace std;
     6 
     7 
     8 
     9 const int &max(const int &a, const int &b)
    10 {
    11     return a > b ? a : b ;
    12 }
    13 template <typename T>
    14 
    15 const T &max(const T &a, const T &b)
    16 {
    17     return a > b ? a: b ;
    18 }
    19 
    20 template <typename T>
    21 const T &max(const T &a, const T &b, const T &c)
    22 {
    23     return ::max(::max(a, b), c) ;
    24 }
    25 
    26 
    27 
    28 
    29 int main(int argc, const char *argv[])
    30 {
    31     ::max(7, 42, 68) ; //调用第三个
    32     ::max(7.0, 43.5) ; //调用第二个
    33     ::max('a', 'b')  ; //调用第二个
    34     ::max(7, 42) ;     //调用第一个,其实第二个也可以,但是取最匹配的
    35     ::max<>(7, 42) ; //指定从模板2 进行匹配, 根据模板生成一个 int &max(const int &, const int &)
    36     cout << ::max<double>(7,42) <<endl;  // 调用2 进行强制转换。
    37     cout << ::max('a', 42.7) << endl;   //调用1, 进行强制转换
    38     return 0;
    39 }

    总之不需要死记调用的规则,  掌握一个原则, 编译器总是选择调用最合适的那个函数。

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 using namespace std;
     5 
     6 
     7 
     8 template <typename T>
     9 const T &max(const T &a, const T &b)
    10 {
    11     return a > b ? a : b ;
    12 }
    13 
    14 template <typename T>
    15 const T *max(const T *a, const T *b)
    16 {
    17     return *a > *b ? a : b ;
    18 }
    19 
    20 const char *max(const char *a, const char *b)
    21 {
    22     return ::strcmp(a, b) > 0 ? a : b  ;
    23 }
    24 
    25 
    26 
    27 int main(int argc, const char *argv[])
    28 {
    29     int a = 7 ;
    30     int b = 42 ;
    31     ::max(a, b);  //调用第一个,上面3个模板 最匹配的显然是第一个。
    32 
    33     string s = "hey" ;
    34     strubg t = "world" ;
    35     ::max(s, t) ;  //调用第一个  最匹配的显然还是第一个
    36 
    37     int *p1 = &7 ;
    38     int *p2 = &8 ;
    39     ::max(p1, p2) ; //调用第二个,  当参数是指针时, 优先调用指针为参数的模板,最匹配为第二个
    40 
    41     const char *s1 = "fucker" ;
    42     const char *s2 = "asshole" ;
    43     ::max(s1, s2) ;       //调用第3个。  2和3都是 指针为参数的模板, 但是第三个更精确。所以调用第三个
    44     return 0;
    45 }

    根据上面几个例子, 我们可以总结出几个规律,这个规律是编译器 合成模板函数, 调用的规则, 这是C++最晦涩的部分之一, 其编译器实现是十分复杂的,集结的前人无数心血

    a)      当条件相同时,优先选择非模板函数。例如::max(7, 42);

    b)     在强制类型转化,与实例化模板可行之间,优先选择实例化模板。::max(7.0, 43.5); ::max('a', 'b');

    c)      实例化版本不可行,则去尝试普通函数的转化,例如::max('a', 42.7);

    d)     参数是指针时,优先选择指针版本。

    e)      总之,尽可能采用最匹配的版本。

    下面介绍一个陷阱

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 #include <string.h>
     5 using namespace std;
     6 
     7 
     8 template <typename T>
     9 const T &max(const T &a, const T &b)
    10 {
    11     return a > b ? a : b ;
    12 }
    13 const char *max(const char * a, const char *b)
    14 {
    15     return ::strcmp(a, b) > 0 ? a : b ;
    16 }
    17 
    18 template <typename T>
    19 const T &max(const T &a, const T &b, const T &c)  //三参数版本的模板函数, 要根据情况来调用 上面两个
    20 {
    21     return ::max(::max(a, b), c) ;  //调用1 或者 2
    22 }
    23 
    24 int main(int argc, const char *argv[])
    25 {
    26     ::max(7 ,42, 68) ; //ok   --> 当三调用 1 时, 1返回的也是引用,  3 再接收这个引用,在返回给主函数
    27 
    28     const char *p1 = "hello" ;
    29     const char *p2 = "world" ;
    30     const char *p3 = "fuck" ;
    31 
    32     ::max(p1, p2 , p3)  //--》这个3 要调用2, 而返回的是一个指针的拷贝(指针拷贝就是, 新创建一个指针, 指向旧指针的指向) 
    33                         //像这个   p1 --> a  <-- p2 ;  p1 是== p2的, 解引用后内容一样,但是指针是不同的。 
    34     return 0;       //当 3 再把 2 返回的一个临时指针(局部变量) 当引用 传出, 就出现了编译错误
    35 }

    上述得出结论:

    在模板函数重载中,不要混合使用传值和传引用。尽可能使用传引用。

    会导致产生临时变量, 以至于外层的函数当做引用 传出。 解决上面BUG的方法就是, 把函数2 的返回值和输入参数加上引用 。

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 #include <string.h>
     5 using namespace std;
     6 
     7 //传引用
     8 template <typename T>
     9 const T &max(const T &a, const T &b)
    10 {
    11     return a > b ? a : b;
    12 }
    13 
    14 const char *&max(const char *&a, const char *&b)
    15 {
    16     return ::strcmp(a, b) > 0 ? a : b;
    17 }
    18 
    19 //求3个任意类型的最大值
    20 //传引用
    21 template <typename T>
    22 const T &max(const T &a, const T &b, const T &c)
    23 {
    24     return ::max(::max(a, b), c);
    25     //::max(::max(s1, s2), s3); //返回的是一个临时的变量
    26     //这里将临时变量的引用返回出去,可能导致错误
    27     //const char *temp = ::max(::max(s1, s2), s3);
    28     //
    29 }
    30 
    31 
    32 int main(int argc, const char *argv[])
    33 {
    34     ::max(7, 42, 68);
    35 
    36     const char *s1 = "fegfwe";
    37     const char *s2 = "Fwtfgt";
    38     const char *s3 = "geryhgr5";
    39     ::max(s1, s2, s3);
    40 
    41 
    42     return 0;
    43 }

    OK , BUG 解决了。

    思考:  

    1.传值和传引用对于参数来说,本质区别在于是否产生了局部变量。

  • 相关阅读:
    允许Traceroute探测 (在防火墙中禁用Time Exceeded类型的ICMP包)
    fatal: not a git repository (or any of the parent directories): .git
    CentOS 7 配置白名单
    CentOS 7 找不到 iptables 文件(需要安装 iptables 服务)
    Grafana 匿名访问(免登录)
    How to install GCC/G++ 8 on CentOS
    【C++ Primer | 16】std::move、std::forward(完美转发)
    安利一个dll库缺失下载的网站
    google glog 源码编译踩坑 以及编译全流程
    VS工程不产生db后缀的文件
  • 原文地址:https://www.cnblogs.com/DLzhang/p/3998967.html
Copyright © 2011-2022 走看看