精华内容
下载资源
问答
  • 这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,终导致链接错误。  上面这句话有点抽象。要理解...
  • 本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解。具体内容如下: 泛型编程(Generic Programming)是一种编程范式,通过将类型参数化来实现在同一份代码上...
  • 练习 C++中函数模板、类模板的创建和使用方法。 (1) 理解模板的作用。 (2) 学习函数模板及其声明方法,掌握模板函数及其生成方法。 (3) 学习函数模板的两种不同的实例化方法。 (4) 学习类模板的声明与使用方法。
  • 所谓函数模板实际上是建立一个通用函数,其涵涵素类型额形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板。 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需要在模板...
  • C++函数模板 我们知道,数据或数值可以通过函数参数传递,在函数定义时它们是未知的,只有在发生函数调用时才能确定其值。这就是数据的参数化。 其实,数据类型也可以通过参数来传递,在函数定义是可以不指明具体的...
  • c++函数模板

    2016-02-16 14:54:19
    其中,template为关键字,表示定义一个模板(可以是函数模板或类模板),尖括号表示模板参数,模板类型参数使用关键字class或typename开始,其后是一个用户定义的合法的标识符。 如果用户在调用函数模板时显式标识...
  • C++函数模板(模板函数)详解

    万次阅读 多人点赞 2019-07-04 16:03:01
    C++函数模板(模板函数)详解定义用法:函数模板的原理延申用法2.1为什么需要类模板2.2单个类模板语法2.3继承中的类模板语法案例1:案例2:2.4类模板的基础语法2.5类模板语法知识体系梳理1.所有的类模板函数写在类的...

    定义

    函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。(好吧,咱也听不懂,直接上用法吧?)

    用法:

    面向对象的继承和多态机制有效提高了程序的可重用性和可扩充性。在程序的可重用性方面,程序员还希望得到更多支持。举一个最简单的例子,为了交换两个整型变量的值,需要写下面的 Swap 函数:

    void Swap(int & x, int & y)
    {
        int tmp = x;
        x = y;
        y = tmp;
    }
    

    为了交换两个 double 型变量的值,还需要编写下面的 Swap 函数:

    void Swap (double & xr double & y)
    {
        double tmp = x;
        x = y;
        y = tmp;
    }
    

    如果还要交换两个 char 型变量的值,交换两个 CStudent 类对象的值……都需要再编写 Swap 函数。而这些 Swap 函数除了处理的数据类型不同外,形式上都是一样的。能否只写一遍 Swap 函数,就能用来交换各种类型的变量的值呢?继承和多态显然无法解决这个问题。因此,“模板”的概念就应运而生了。

    众所周知,有了“模子”后,用“模子”来批量制造陶瓷、塑料、金属制品等就变得容易了。程序设计语言中的模板就是用来批量生成功能和形式都几乎相同的代码的。有了模板,编译器就能在需要的时候,根据模板自动生成程序的代码。从同一个模板自动生成的代码,形式几乎是一样的。

    函数模板的原理

    C++ 语言支持模板。有了模板,可以只写一个 Swap 模板,编译器会根据 Swap 模板自动生成多个 Sawp 函数,用以交换不同类型变量的值。

    在 C++ 中,模板分为函数模板和类模板两种。

    • 函数模板是用于生成函数;
    • 类模板则是用于生成类的。

    函数模板的写法如下:

    template <class 类型参数1, class类型参数2, ...>
    返回值类型  模板名(形参表)
    {
        函数体
    }
    

    其中的 class 关键字也可以用 typename 关键字替换,例如:

    template <typename 类型参数1, typename 类型参数2, ...>
    

    函数模板看上去就像一个函数。前面提到的 Swap 模板的写法如下:

    template <class T>
    void Swap(T & x, T & y)
    {
        T tmp = x;
        x = y;
        y = tmp;
    }
    

    T 是类型参数,代表类型。编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动地保留。同一个类型参数只能替换为同一种类型。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。

    例如下面的程序:

    #include <iostream>
    using namespace std;
    template<class T>
    void Swap(T & x, T & y)
    {
        T tmp = x;
        x = y;
        y = tmp;
    }
    int main()
    {
        int n = 1, m = 2;
        Swap(n, m);  //编译器自动生成 void Swap (int &, int &)函数
        double f = 1.2, g = 2.3;
        Swap(f, g);  //编译器自动生成 void Swap (double &, double &)函数
        return 0;
    }
    

    编译器在编译到Swap(n, m);时找不到函数 Swap 的定义,但是发现实参 n、m 都是 int 类型的,用 int 类型替换 Swap 模板中的 T 能得到下面的函数:

    void Swap (int & x, int & y)
    {
        int tmp = x;
        x = y;
        y = tmp;
    }
    

    该函数可以匹配Swap(n, m);这条语句。于是编译器就自动用 int 替换 Swap 模板中的 T,生成上面的 Swap 函数,将该 Swap 函数的源代码加入程序中一起编译,并且将Swap(n, m);编译成对自动生成的 Swap 函数的调用。

    同理,编译器在编译到Swap(f, g);时会用 double 替换 Swap 模板中的 T,自动生成以下 Swap 函数:

    void Swap(double & x, double & y)
    {
        double tmp = x;
        x = y;
        y = tmp;
    }
    

    然后再将Swap(f, g);编译成对该 Swap 函数的调用。

    编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数称为模板函数。在某些编译器中,模板只有在被实例化时,编译器才会检查其语法正确性。如果程序中写了一个模板却没有用到,那么编译器不会报告这个模板中的语法错误。

    编译器对模板进行实例化时,并非只能通过模板调用语句的实参来实例化模板中的类型参数,模板调用语句可以明确指明要把类型参数实例化为哪种类型。可以用:

    模板名<实际类型参数1, 实际类型参数2, ...>
    

    的方式告诉编译器应该如何实例化模板函数。例如下面的程序:

    #include <iostream>
    using namespace std;
    template <class T>
    T Inc(int n)
    {
        return 1 + n;
    }
    int main()
    {
        cout << Inc<double>(4) / 2;
        return 0;
    }
    

    Inc(4)指明了此处实例化的模板函数原型应为:

    double Inc(double);
    

    编译器不会因为实参 4 是 int 类型,就生成原型为 int Inc(int) 的函数。因此,上面程序输出的结果是 2.5 而非 2。

    函数模板中可以有不止一个类型参数。例如,下面这个函数模板的写法是合法的:

    template <class Tl, class T2>
    T2 print(T1 argl, T2 arg2)
    {
        cout << arg1 << " " << arg2 << endl;
        return arg2;
    }
    

    【实例】一个求数组中最大元素的函数模板
    例题:设计一个分数类 CFraction,再设计一个名为 MaxElement 的函数模板,能够求数组中最大的元素,并用该模板求一个 CFmction 数组中的最大元素。

    示例程序如下:

    #include <iostream>
    using namespace std;
    template <class T>
    T MaxElement(T a[], int size) //size是数组元素个数
    {
        T tmpMax = a[0];
        for (int i = 1; i < size; ++i)
            if (tmpMax < a[i])
                tmpMax = a[i];
        return tmpMax;
    }
    class CFraction //分数类
    {
        int numerator;   //分子
        int denominator; //分母
    public:
        CFraction(int n, int d) :numerator(n), denominator(d) { };
        bool operator <(const CFraction & f) const
        {//为避免除法产生的浮点误差,用乘法判断两个分数的大小关系
            if (denominator * f.denominator > 0)
                return numerator * f.denominator < denominator * f.numerator;
            else
                return numerator * f.denominator > denominator * f.numerator;
        }
        bool operator == (const CFraction & f) const
        {//为避免除法产生的浮点误差,用乘法判断两个分数是否相等
            return numerator * f.denominator == denominator * f.numerator;
        }
        friend ostream & operator <<(ostream & o, const CFraction & f);
    };
    ostream & operator <<(ostream & o, const CFraction & f)
    {//重载 << 使得分数对象可以通过cout输出
        o << f.numerator << "/" << f.denominator; //输出"分子/分母" 形式
        return o;
    }
    int main()
    {
        int a[5] = { 1,5,2,3,4 };
        CFraction f[4] = { CFraction(8,6),CFraction(-8,4),
            CFraction(3,2), CFraction(5,6) };
        cout << MaxElement(a, 5) << endl;
        cout << MaxElement(f, 4) << endl;
        return 0;
    }
    

    编译到第 41 行时,根据实参 a 的类型,编译器通过 MaxElement 模板自动生成了一个 MaxElement 函数,原型为:

    int MaxElement(int a[], int size);
    

    编译到第 42 行时,根据 f 的类型,编译器又生成一个 MaxElement 函数,原型为:

    CFraction MaxElement(CFraction a[], int size);
    

    在该函数中,用到了<比较两个 CFraction 对象的大小。如果没有对<进行适当的重载,编译时就会出错。

    从 MaxElement 模板的写法可以看出,在函数模板中,类型参数不但可以用来定义参数的类型,还能用于定义局部变量和函数模板的返回值。

    延申用法

    2.1为什么需要类模板

    • 类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:

      在这里插入图片描述

    • 类模板用于实现类所需数据的类型参数化

    • 类模板在表示如数组、表、图等数据结构显得特别重要,

    • 这些数据结构的表示和算法不受所包含的元素类型的影响

    2.2单个类模板语法

     1 //类的类型参数化 抽象的类
     2 //单个类模板
     3 template<typename T>
     4 class A 
     5 {
     6 public:
     7     A(T t)
     8     {
     9         this->t = t;
    10     }
    11 
    12     T &getT()
    13     {
    14         return t;
    15     }
    16 protected:
    17 public:
    18     T t;
    19 };
    20 void main()
    21 {
    22    //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
    23     A<int>  a(100); 
    24     a.getT();
    25     printAA(a);
    26     return ;
    27 }
    

    2.3继承中的类模板语法

    在这里插入图片描述

    案例1:

     1 //结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int> 
     2 //
     3 class B : public A<int>
     4 {
     5 public:
     6     B(int i) : A<int>(i)
     7     {
     8 
     9     }
    10     void printB()
    11     {
    12         cout<<"A:"<<t<<endl;
    13     }
    14 protected:
    15 private:
    16 };
    17 
    18 //模板与上继承
    19 //怎么样从基类继承  
    20 //若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数
    21 void pintBB(B &b)
    22 {
    23     b.printB();
    24 }
    25 void printAA(A<int> &a)  //类模板做函数参数 
    26 {
    27      //
    28     a.getT();
    29 }
    30 
    31 void main()
    32 {
    33     A<int>  a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 
    34     a.getT();
    35     printAA(a);
    36 
    37     B b(10);
    38     b.printB();
    39 
    40 
    41     cout<<"hello..."<<endl;
    42     system("pause");
    43     return ;
    44 }
    

    案例2:

     1 #include<iostream>
     2 using namespace std;
     3 //A编程模板类--类型参数化
     4 /*
     5 类模板的定义 类模板的使用 类模板做函数参数
     6 */
     7 template <typename T>
     8 class A
     9 {
    10 public:
    11     A(T a = 0)
    12     {
    13         this->a = a;
    14     }
    15 public:
    16     void printA()
    17     {
    18         cout << "a:" << a << endl;
    19     }
    20 protected:
    21     T a;
    22 private:
    23     
    24 };
    25 //从模板类派生时,需要具体化模板类,C++编译器需要知道父类的数据类型是什么样子的
    26 //要知道父类所占的内存多少
    27 class B :public A<int>
    28 {
    29 public:
    30     B(int a =10, int b =20):A<int>(a)
    31     {
    32         this->b = b;
    33     }
    34     void printB()
    35     {
    36         cout << "a:" << a << "b:" << b << endl;
    37     }
    38 protected:
    39 private:
    40     int b;
    41     
    42 };
    43 //从模板类派生模板类
    44 template <typename T>
    45 class C :public A<T>
    46 {
    47 
    48 public:
    49     C(T c,T a) : A<T>(a)
    50     {
    51         this->c = c;
    52     }
    53     void printC()
    54     {
    55         cout << "c:" << c << endl;
    56     }
    57 protected:
    58     T c;
    59 private:
    60     
    61 };
    62 
    63 void main()
    64 {
    65     //B b1(1, 2);
    66     //b1.printB();
    67     C<int> c1(1,2);
    68     c1.printC();
    69 }
    

    2.4类模板的基础语法

     1 #include<iostream>
     2 using namespace std;
     3 //A编程模板类--类型参数化
     4 /*
     5     类模板的定义 类模板的使用 类模板做函数参数
     6 */
     7 template <typename T>
     8 class A
     9 {
    10 public:
    11     A(T a = 0)
    12     {
    13         this->a = a;
    14     }
    15 public:
    16     void printA()
    17     {
    18         cout << "a:" << a << endl;
    19     }
    20 protected:
    21 private:
    22     T a;
    23 };
    24 //参数 C++编译器具体的类
    25 void UseA(A<int> &a)
    26 {
    27     a.printA();
    28 }
    29 void main()
    30 {
    31     //模板类本身就是抽象的,具体的类,具体的变量
    32     A<int> a1(11),a2(22),a3(33);//模板类是抽象的, 需要类型具体化
    33     //a1.printA();
    34 
    35     UseA(a1);
    36     UseA(a2);
    37     UseA(a3);
    38 }
    

    2.5类模板语法知识体系梳理

    1.所有的类模板函数写在类的内部

    代码:

    复数类:

     1 #include<iostream>
     2 using namespace std;
     3 template <typename T>
     4 class Complex
     5 {
     6 public:
     7     friend Complex MySub(Complex &c1, Complex &c2)
     8     {
     9         Complex tmp(c1.a-c2.a, c1.b-c2.b);
    10         return tmp;
    11     }
    12 
    13     friend ostream & operator<< (ostream &out, Complex &c3)
    14     {
    15         out << c3.a << "+" << c3.b <<"i"<< endl;
    16         return out;
    17     }
    18     Complex(T a, T b)
    19     {
    20         this->a = a;
    21         this->b = b;
    22     }
    23     Complex operator+(Complex &c2)
    24     {
    25         Complex tmp(a + c2.a, b + c2.b);
    26         return tmp;
    27     }
    28     void printCom()
    29     {
    30         cout << "a:" << a << " b:" << b << endl;
    31     }
    32 protected:
    33 private:
    34     T a;
    35     T b;
    36 };
    37 
    38 /*
    39     重载运算符的正规写法:
    40     重载左移<<  右移>> 只能用友元函数,其他的运算符重载都要用成员函数,不要滥用友元函数
    41 */
    42 //ostream & operator<< (ostream &out, Complex &c3)
    43 //{
    44 //    out<< "a:" << c3.a << " b:" << c3.b << endl;
    45 //    return out;
    46 //}
    47 void main()
    48 {
    49     Complex<int>     c1(1,2);
    50     Complex<int>     c2(3, 4);
    51 
    52     Complex<int> c3 = c1 + c2;//重载加号运算符
    53     
    54     c3.printCom();
    55 
    56     //重载左移运算符
    57     cout << c3 << endl;
    58     
    59     {
    60         Complex<int> c4 = MySub(c1 , c2);
    61         
    62         cout << c4 << endl;
    63     }
    64     system("pause");
    65 }
    

    2.所有的类模板函数写在类的外部,在一个cpp中

    注意:

    复制代码
    //构造函数 没有问题

    • 普通函数 没有问题

    • 友元函数:用友元函数重载 << >>

      friend ostream& operator<< (ostream &out, Complex &c3) ;

    • 友元函数:友元函数不是实现函数重载(非 << >>)

    1 需要在类前增加 类的前置声明 函数的前置声明

    template<typename T>
    
    class Complex;  
    
    template<typename T>
    
    Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
    

    2 类的内部声明 必须写成:

    friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2);
    

    3 友元函数实现 必须写成:

    template<typename T>
    
      Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
    
    {
    
    Complex<T> tmp(c1.a - c2.a, c1.b-c2.b);
    
    return tmp;
    
    }
    

    4 友元函数调用 必须写成

    Complex<int> c4 = mySub<int>(c1, c2);
    
    cout<<c4;
    

    结论:友元函数只用来进行 左移 友移操作符重载。

    复数类:

    代码:

     1 #include<iostream>
     2 using namespace std;
     3 
     4 template<typename T>
     5 class Complex;
     6 template<typename T>
     7 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
     8 
     9 template <typename T>
    10 class Complex
    11 {
    12 public:
    13     friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
    14 
    15     friend ostream & operator<< <T>(ostream &out, Complex &c3);
    16     Complex(T a, T b);
    17     void printCom();
    18     Complex operator+(Complex &c2);
    19     Complex operator-(Complex &c2);
    20     
    21 protected:
    22 private:
    23     T a;
    24     T b;
    25 };
    26 
    27 //构造函数的实现,写在了外部
    28 template <typename T>
    29 Complex<T>::Complex(T a, T b)
    30 {
    31     this->a = a;
    32     this->b = b;
    33 }
    34 
    35 template <typename T>
    36 void Complex<T>::printCom()
    37 {
    38     cout << "a:" << a << " b:" << b << endl;
    39 }
    40 //成员函数实现加号运算符重载
    41 template <typename T>
    42 Complex<T> Complex<T>::operator+(Complex<T> &c2)
    43 {
    44     Complex tmp(a + c2.a, b + c2.b);
    45     return tmp;
    46 }
    47 template <typename T>
    48 Complex<T> Complex<T>::operator-(Complex<T> &c2)
    49 {
    50     Complex(a-c2.a,a-c2.b);
    51     return tmp;
    52 }
    53 //友元函数实现<<左移运算符重载
    54 
    55 /*
    56 严重性    代码    说明    项目    文件    行    禁止显示状态
    57 错误    C2768    “operator <<”: 非法使用显式模板参数    泛型编程课堂操练    
    58 
    59 错误的本质:两次编译的函数头,第一次编译的函数头,和第二次编译的函数有不一样
    60 */
    61 template <typename T>
    62 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T
    63 {
    64     out << c3.a << "+" << c3.b << "i" << endl;
    65     return out;
    66 }
    67 
    68 
    69 //
    70 template <typename T>
    71 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
    72 {
    73     Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
    74     return tmp;
    75 }
    76 
    77 void main()
    78 {
    79     Complex<int>     c1(1, 2);
    80     Complex<int>     c2(3, 4);
    81 
    82     Complex<int> c3 = c1 + c2;//重载加号运算符
    83 
    84     c3.printCom();
    85 
    86     //重载左移运算符
    87     cout << c3 << endl;
    88 
    89     {
    90         Complex<int> c4 = mySub<int>(c1, c2);
    91 
    92         cout << c4 << endl;
    93     }
    94     system("pause");
    95 }
    

    所有的类模板函数写在类的外部,在不同的.h和.cpp中
    也就是类模板函数说明和类模板实现分开

    //类模板函数

    构造函数

    普通成员函数

    友元函数

    用友元函数重载<<>>;

    用友元函数重载非<< >>

    demo_complex.cpp

     1 #include"demo_09complex.h"
     2 #include<iostream>
     3 using namespace std;
     4 
     5 template <typename T>
     6 Complex<T>::Complex(T a, T b)
     7 {
     8     this->a = a;
     9     this->b = b;
    10 }
    11 
    12 template <typename T>
    13 void Complex<T>::printCom()
    14 {
    15     cout << "a:" << a << " b:" << b << endl;
    16 }
    17 //成员函数实现加号运算符重载
    18 template <typename T>
    19 Complex<T> Complex<T>::operator+(Complex<T> &c2)
    20 {
    21     Complex tmp(a + c2.a, b + c2.b);
    22     return tmp;
    23 }
    24 //template <typename T>
    25 //Complex<T> Complex<T>::operator-(Complex<T> &c2)
    26 //{
    27 //    Complex(a - c2.a, a - c2.b);
    28 //    return tmp;
    29 //}
    30 template <typename T>
    31 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T
    32 {
    33     out << c3.a << "+" << c3.b << "i" << endl;
    34     return out;
    35 }
    36 
    37 
    38 //
    39 //template <typename T>
    40 //Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
    41 //{
    42 //    Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
    43 //    return tmp;
    44 //}
    

    demo_09complex.h

     1 #pragma once
     2 #include<iostream>
     3 using namespace std;
     4 template <typename T>
     5 class Complex
     6 {
     7 public:
     8     //friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
     9 
    10     friend ostream & operator<< <T>(ostream &out, Complex &c3);
    11     Complex(T a, T b);
    12     void printCom();
    13     Complex operator+(Complex &c2);
    14     //Complex operator-(Complex &c2);
    15 
    16 protected:
    17 private:
    18     T a;
    19     T b;
    20 };
    

    demo_09complex_text.cpp

     1 #include"demo_09complex.h"
     2 #include"demo_09complex.cpp"
     3 
     4 #include<iostream>
     5 using namespace std;
     6 
     7 void main()
     8 {
     9     Complex<int>     c1(1, 2);
    10     Complex<int>     c2(3, 4);
    11 
    12     Complex<int> c3 = c1 + c2;//重载加号运算符
    13 
    14     c3.printCom();
    15 
    16     //重载左移运算符
    17     cout << c3 << endl;
    18 
    19     /*{
    20         Complex<int> c4 = mySub<int>(c1, c2);
    21 
    22         cout << c4 << endl;
    23     }*/
    24     system("pause");
    25 }
    

    2.5总结

    归纳以上的介绍,可以这样声明和使用类模板:

    1. 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
    2. 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
    3. 在类声明前面加入一行,格式为:

      template <class 虚拟类型参数>
    
      如:
    
          template <class numtype> //注意本行末尾无分号
    
          class Compare
    
            {…}; //类体
    
    1. 用类模板定义对象时用以下形式:

     类模板名<实际类型名> 对象名;
    
          类模板名<实际类型名> 对象名(实参表列);
    
      如:
    
          Compare<int> cmp;
    
          Compare<int> cmp(3,7);
    
    1. 如果在类模板外定义成员函数,应写成类模板形式:

     template <class 虚拟类型参数>
    
         函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
    

    关于类模板的几点说明:

    1. 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:

      template <class T1,class T2>
      
       class someclass
      
       {…};
      

      在定义对象时分别代入实际的类型名,如:

      someclass<int,double> obj;

    2. 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。

    3. 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

    2.6类模板中的static关键字

    从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
    每个模板类有自己的类模板的static数据成员副本

     1 #include<iostream>
     2 using namespace std;
     3 template <typename T>
     4 class AA
     5 {
     6 public:
     7     static T m_a;
     8 protected:
     9 private:
    10 };
    11 template <typename T>
    12 T AA<T>::m_a =0;
    13 void main()
    14 {
    15     AA<int> a1, a2, a3;
    16     a1.m_a = 10;
    17     a2.m_a++;
    18     a3.m_a++;
    19     cout << AA<int>::m_a << endl;
    20 
    21     AA<char> b1, b2, b3;
    22     b1.m_a = 'a';
    23     b2.m_a++;
    24     b3.m_a++;
    25     cout << AA<char>::m_a << endl;
    26 
    27     //m_a是每个类型的类,去使用,手工写两个类 int  char
    28     system("pause");
    29 }
    

    案例2:以下来自:C++类模板遇上static关键字

     1 #include <iostream>
     2 using namespace std;
     3 
     4 template<typename T>
     5 class Obj{
     6 public:
     7     static T m_t;
     8 };
     9 
    10 template<typename T>
    11 T Obj<T>::m_t = 0;
    12 
    13 int main04(){
    14     Obj<int> i1,i2,i3;
    15     i1.m_t = 10;
    16     i2.m_t++;
    17     i3.m_t++;
    18     cout << Obj<int>::m_t<<endl;
    19 
    20     Obj<float> f1,f2,f3;
    21     f1.m_t = 10;
    22     f2.m_t++;
    23     f3.m_t++;
    24     cout << Obj<float>::m_t<<endl;
    25 
    26     Obj<char> c1,c2,c3;
    27     c1.m_t = 'a';
    28     c2.m_t++;
    29     c3.m_t++;
    30     cout << Obj<char>::m_t<<endl;
    31 }
    

    当类模板中出现static修饰的静态类成员的时候,我们只要按照正常理解就可以了。static的作用是将类的成员修饰成静态的,所谓的静态类成员就是指类的成员为类级别的,不需要实例化对象就可以使用,而且类的所有对象都共享同一个静态类成员,因为类静态成员是属于类而不是对象。那么,类模板的实现机制是通过二次编译原理实现的。c++编译器并不是在第一个编译类模板的时候就把所有可能出现的类型都分别编译出对应的类(太多组合了),而是在第一个编译的时候编译一部分,遇到泛型不会替换成具体的类型(这个时候编译器还不知道具体的类型),而是在第二次编译的时候再将泛型替换成具体的类型(这个时候编译器知道了具体的类型了)。由于类模板的二次编译原理再加上static关键字修饰的成员,当它们在一起的时候实际上一个类模板会被编译成多个具体类型的类,所以,不同类型的类模板对应的static成员也是不同的(不同的类),但相同类型的类模板的static成员是共享的(同一个类)。

    2.7类模板在项目开发中的应用

    小结

    • 模板是C++类型参数化的多态工具。C++提供函数模板和类模板。

    • 模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。

    • 同一个类属参数可以用于多个模板。

    • 类属参数可用于函数的参数类型、返回类型和声明函数中的变量。

    • 模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。

    • 模板称为模板函数;实例化的类模板称为模板类。

    • 函数模板可以用多种方式重载。

    • 类模板可以在类层次中使用 。

    训练题

    1) 请设计一个数组模板类( MyVector ),完成对int、char、Teacher类型元素的管理。

    设计:

    • 类模板 构造函数 拷贝构造函数 << [] 重载=操作符

    • a2=a1

    • 实现

    2) 请仔细思考:

    a) 如果数组模板类中的元素是Teacher元素时,需要Teacher类做什么工作

    b) 如果数组模板类中的元素是Teacher元素时,Teacher类含有指针属性哪?

     1 class Teacher
     2 {
     3     friend ostream & operator<<(ostream &out, const Teacher &obj);
     4 public:
     5     Teacher(char *name, int age)
     6     {
     7         this->age = age;
     8         strcpy(this->name, name);
     9     }
    10 
    11     Teacher()
    12     {
    13         this->age = 0;
    14         strcpy(this->name, "");
    15     }
    16     
    17 private:
    18     int age;
    19     char name[32];
    20 };
    21 
    22 
    23 class Teacher
    24 {
    25     friend ostream & operator<<(ostream &out, const Teacher &obj);
    26 public:
    27     Teacher(char *name, int age)
    28     {
    29         this->age = age;
    30         strcpy(this->name, name);
    31     }
    32 
    33     Teacher()
    34     {
    35         this->age = 0;
    36         strcpy(this->name, "");
    37     }
    38     
    39 private:
    40     int age;
    41     char *pname;
    42 };
    

    结论1: 如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。

    结论2:需要Teacher封装的函数有:

    1. 重写拷贝构造函数

    2. 重载等号操作符

    3. 重载左移操作符。

    理论提高:

    所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。

    3) 请从数组模板中进行派生

     1 //演示从模板类 派生 一般类
     2 #include "MyVector.cpp"
     3 
     4 class MyArray01 : public MyVector<double>
     5 {
     6 public:
     7     MyArray01(int len) : MyVector<double>(len)
     8     {
     9         ;
    10     }
    11 protected:
    12 private:
    13 };
    14 
    15 
    16 //演示从模板类 派生 模板类 //BoundArray 
    17 template <typename T>
    18 class MyArray02 : public MyVector<T>
    19 {
    20 public:
    21     MyArray02(int len) : MyVector<double>(len)
    22     {
    23         ;
    24     }
    25 protected:
    26 private:
    27 };
    28 测试案例:
    29 
    30 //演示 从模板类 继承 模板类
    31 void main()
    32 {
    33     MyArray02<double> dArray2(10);
    34     dArray2[1] = 3.15;
    35 
    36 }
    37 
    38 
    39 //演示 从模板类 继承 一般类
    40 void main11()
    41 {
    42     MyArray01 d_array(10);
    43 
    44     for (int i=0; i<d_array.getLen(); i++)
    45     {
    46         d_array[i] = 3.15;
    47     }
    48 
    49     for (int i=0; i<d_array.getLen(); i++)
    50     {
    51         cout << d_array[i] << " ";
    52     }
    53 
    54     cout<<"hello..."<<endl;
    55     system("pause");
    56     return ;
    57 }
    

    作业:

    封装你自己的数组类;设计被存储的元素为类对象;

    思考:类对象的类,应该实现的功能。

    1. 优化Teacher类, 属性变成 char *panme, 构造函数里面 分配内存

    2. 优化Teacher类,析构函数 释放panme指向的内存空间

    3. 优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数

    4. 优化Teacher类,在Teacher增加 <<

    5. 在模板数组类中,存int char Teacher Teacher*(指针类型)

    zuoye.h

     1 #pragma once
     2 
     3 template <typename T>
     4 class MyVector
     5 {
     6 
     7     //friend ostream & operator<< <T>(ostream &out, const MyVector &obj);
     8 public:
     9     MyVector(int size = 0);//构造函数
    10     MyVector(const MyVector &obj);//copy构造函数
    11     ~MyVector();
    12 public:
    13     T& operator [](int index);
    14     MyVector &operator=(const MyVector &obj);
    15 
    16 
    17     int getLen()
    18     {
    19         return m_len;
    20     }
    21 protected:
    22 private:
    23     T *m_space;
    24     int m_len;
    25 
    26 };
    

    zuoye_test12.cpp

      1 #include"zuoye.h"
      2 #include"zuoye12.cpp"
      3 #include<iostream>
      4 using namespace std;
      5 class Teacher
      6 {
      7 public:
      8     Teacher()
      9     {
     10         age = 33;
     11         m_p = new char[1];
     12         strcpy(m_p, " ");
     13     }
     14 
     15     Teacher(char *name, int age)
     16     {
     17         this->age = age;
     18         m_p = new char[strlen(name)+1];
     19         strcpy(this->m_p, name);
     20 
     21     }
     22     Teacher(const Teacher &obj)
     23     {
     24         m_p = new char[strlen(obj.m_p) + 1];
     25         strcpy(this->m_p, obj.m_p);
     26         age = obj.age;
     27     }
     28     ~Teacher()
     29     {
     30         if (m_p!=NULL)
     31         {
     32             delete[] m_p;
     33             m_p = NULL;
     34         }
     35     }
     36     void printT()
     37     {
     38         cout << m_p << ", " << age;
     39     }
     40 public:
     41     //重载<< ==
     42     friend ostream & operator<<(ostream &out,Teacher &t);
     43     Teacher & operator=(const Teacher &obj) 
     44     {
     45         //1
     46         if (m_p!=NULL)
     47         {
     48             delete[] m_p;
     49             m_p = NULL;
     50             age = 33;
     51         }
     52         //2
     53         m_p = new char[strlen(obj.m_p) + 1];
     54         age = obj.age;
     55         //3
     56         strcpy(this->m_p, obj.m_p);
     57         return *this;
     58     }
     59 protected:
     60 private:
     61     int age;
     62     //char name[32];
     63     char *m_p;
     64 };
     65 ostream & operator<<(ostream &out, Teacher &t)
     66 {
     67     out << t.m_p << ", " << t.age << endl;
     68     return out;
     69 }
     70 
     71 void main()
     72 {
     73     Teacher t1("t1", 31), t2("t2", 32);
     74 
     75     MyVector<Teacher *> Tarray(2);
     76 
     77     Tarray[0] = &t1;
     78     Tarray[1] = &t2;
     79     for (int i = 0; i < 2; i++)
     80     {
     81         Teacher *tmp = Tarray[i];
     82         tmp->printT();
     83     }
     84     system("pause");
     85 }
     86 void main123()
     87 {
     88     Teacher t1("t1", 31), t2("t2", 32);
     89     MyVector<Teacher> Tarray(2);
     90     Tarray[0] = t1;
     91     Tarray[1] = t2;
     92     for (int i = 0; i < 2; i++)
     93     {
     94         Teacher tmp = Tarray[i];
     95         tmp.printT();
     96     }
     97     system("pause");
     98 }
     99 void main112()
    100 {
    101     MyVector<int>  myv1(10);
    102     myv1[0] = 'a';
    103     myv1[1] = 'b';
    104     myv1[2] = 'c';
    105     myv1[3] = 'd';
    106     myv1[4] = 'e';
    107     //cout << myv1;
    108     MyVector<int>  myv2 = myv1;
    109 }
    110 
    111 
    112 void main111()
    113 {
    114     MyVector<int>  myv1(10);
    115     for (int i = 0; i < myv1.getLen(); i++)
    116     {
    117         myv1[i] = i + 1;
    118         cout << myv1[i] << " ";
    119     }
    120 
    121     MyVector<int>  myv2 = myv1;
    122     for (int i = 0; i < myv2.getLen(); i++)
    123     {
    124         myv2[i] = i + 1;
    125         cout << myv2[i] << " ";
    126     }
    127 
    128     //cout << myv2 << endl;//重载左移运算符
    129 
    130     system("pause");
    131 }
    

    zuoye12.cpp

     1 #include"zuoye.h"
     2 #include<iostream>
     3 using namespace std;
     4 
     5 template <typename T>
     6 ostream & operator<<(ostream &out, const MyVector<T> &obj)
     7 {
     8     for (int i = 0; i<obj.m_len; i++)
     9     {
    10         out << obj.m_space[i] << " ";
    11     }
    12     out << endl;
    13     return out;
    14 }
    15 //构造函数
    16 template <typename T>
    17 MyVector<T>::MyVector(int size = 0)
    18 {
    19     m_space = new T[size];
    20     m_len = size;
    21 }
    22 //MyVector<int>  myv2 = myv1;
    23 template <typename T>
    24 MyVector<T>::MyVector(const MyVector &obj)
    25 {
    26     //根据大小分配内存
    27     m_len = obj.m_len;
    28     m_space = new T[m_len];
    29     //copy数据
    30     for (int i = 0; i<m_len; i++)
    31     {
    32         m_space[i] = obj.m_space[i];
    33     }
    34 }
    35 template <typename T>
    36 MyVector<T>::~MyVector()
    37 {
    38     if (m_space != NULL)
    39     {
    40         delete[] m_space;
    41         m_space = NULL;
    42         m_len = 0;
    43 
    44     }
    45 }
    46 template <typename T>
    47 T& MyVector<T>::operator [](int index)
    48 {
    49     return m_space[index];
    50 }
    51 template <typename T>
    52 MyVector<T> & MyVector<T>::operator=(const MyVector<T> &obj)
    53 {
    54     //先把a2的内存释放掉
    55     if (m_space != NULL)
    56     {
    57         delete[] m_space;
    58         m_space = NULL;
    59         m_len = 0;
    60 
    61     }
    62 
    63     //根据a1分配内存
    64     m_len = obj.m_len;
    65     m_space = new T[m_len];
    66 
    67     //copy数据
    68     for (i = 0; i<m_len; i += )
    69     {
    70         m_space[i] = obj.m_space[i];
    71     }
    72     return *this;//a2= a1 返回a2的自身
    73 }
    

    参考一些资料,加上一些见解,如果有雷同,纯属巧合。
    更多C++相关知识体系,请移步C++知识目录

    展开全文
  • 函数模板和普通函数区别结论:  函数模板不允许自动类型转化  普通函数能够进行自动类型转换  函数模板和普通函数在一起,调用规则:  1 函数模板可以像普通函数一样被重载  2 C++编译器优先考虑普通函数 ...
  • C++函数模板

    2016-01-14 08:54:10
    利用C++中的函数模板实现输出一列数的最大、最小值。
  • 函数模板与宏定义

    2012-04-16 10:54:15
    函数模板与宏定义。代码复用的不同方式的定义,包含了用函数模板实现代码复用,用宏定义展开为函数的定义与使用。
  • 函数模板参数

    千次阅读 2019-03-24 00:22:11
    1)从模板函数实参表获得的信息有矛盾之处。 template<typename T> void fun(T const& a,T const& b);但是你调用时却是fun(250,250.4);那你就必须写成fun<int>(250,250.4); 2)需要获得特定...

    C++模板实参的省略

    下面列举的几种情况不能省略模板实参:
    1)从模板函数实参表获得的信息有矛盾之处。

    template<typename T> void fun(T const& a,T const& b);但是你调用时却是fun(250,250.4);那你就必须写成fun<int>(250,250.4);
    2)需要获得特定类型的返回值,而不管参数的类型如何。

    template<typename T,typename RTRT fun(T const& a,T const& b);此时没有办法进行演绎,所以你可以重写template<typename RT,typename T> RT fun(T const& a,T const& b);调用时写成fun<double>(12,13);
    3)虚拟类型参数没有出现在模板函数的形参表中。

    用typename和class类型声明的参数称为虚拟类型参数,而用《类型修饰》声明的参数称为常规参数
    4)函数模板含有常规形参。

    template<typename T> void fun();调用时直接是fun()!!

     

    具体template使用网上可以看到说明,在这里强调一点关于template模版实参为空的特例。 转载网络关于c++考级的要点. 知识点4:

     

        1.1  函数模板

      1.1.1  考点1:函数模板的概念和声明

        函数模板是一系列相关函数的模型或样板,这些相关函数的源代码形式相同,只是所针对的数据类型不同。对于函数模板,数据类型本身成了它的参数,因而是一种参数化类型的函数。类的成员函数也可以声明为函数模板。
        声明一个函数模板的格式如下:
        template <模板形参表声明>
        返回类型 函数名(函数形参表)
        {
           …… //函数体
        }

        其中,<模板形参表声明>是由一个或多个“模板形参”组成的,如果是多个,则要用逗号隔开。“模板形参”具有下面3种形式:

          typename 参数名
          class 参数名
         类型修饰 参数名

        这里的参数名可以是任意合法的C++标识符。前两种形式是等价的,也就是说,在声明模板形参时,关键字typename和class可以互换。用typename或class声明的参数称为虚拟类型参数;而用“类型修饰”声明的参数则称为常规参数。这里的“类型修饰”是指具体的数据类型(如int、double、char等)。函数模板的<模板形参表声明>中,一定要包含虚拟类型参数,而常规参数则可以根据实际需要选择。

        <模板形参表声明>中声明的虚拟类型参数可以用做:

         函数的返回值类型
         函数的形参的类型
         函数体内变量的类型

           例1.1.1  下列是模板声明的开始部分,其中正确的是(   )。
        A.template <T>            B.template <class T1,T2>
        C.template <class T1,class T2>     D.template <class T1;class T2>
        答案:C

           例1.1.2  下面的函数模板定义中错误的是(   )。
        A.template<class Q>       B.template<class Q>
           Q F(Q x){return Q+x;}         Q F(Q x){return x+x;}
        C.template<class T>       D.template<class T>
           T F(T x){return x*x;}         bool F(T x){return x>1;}
        解析:各个选项中声明的Q和T都是虚拟类型参数。函数模板形参表中声明的虚拟类型参数可以用做:
         函数的返回值类型
         函数的形参的类型
         函数体内变量的类型
        选项B、C和D符合以上三种用法。而选项A让虚拟类型参数Q直接参与运算是错误的。虚拟类型是具体类型(如int型,double型等)的抽象。我们知道,int本身并不能直接参与数学运算,但是由它定义的int型变量或int型参数可以直接参与运算。虚拟类型Q和T本身也不能直接参与运算,但是由它们定义的“T类型”或“Q类型”的参数x可以参与运算。所以,选项A中“Q+x”的表达是错误的。
        答案:A
    1.1.2  考点2:模板函数

        函数模板中声明的函数称为模板函数。
        调用一个模板函数的格式如下:
        函数名 <模板实参表> (函数实参表);
        或
        函数名(函数实参表);
        第二种格式中省略了所有的模板实参,使用这种格式是需要一定条件的(详见考点4)。
        类模板的成员函数都是模板函数。

           例1.1.3  以下函数模板max的功能是:返回数组a中最大元素的值。请将横线处缺失部分补充完整。
        template <typename T>
        T max (T a[], int n)
        {
         T m = a[0];
         for (int i = 1; i < n; i++)
            if (a[i]>m)          ;
            return m;
        }
        解析:本题考查模板函数的定义。在编写函数体时,模板函数与普通函数是一样的。根据题意,返回的m值应该是a中最大元素的值,所以当a[i]>m时,应该将较大的a[i]值赋给m。在遍历数组a[]后,最终m就保存了数组a[]中最大元素的值。
    1.1.3  考点3:函数模板的实例化

        在调用模板函数时,编译系统依据实际所使用的数据类型生成某个具体函数定义的过程称为函数模板的实例化。在实例化过程中,是用实际类型(如int、long等)替代虚拟类型的。
        实例化的过程或结果通常是看不见的,编译系统会根据函数调用的具体情况自动传递相应的模板实参,生成相应的函数实例。每一个实例就是一个函数定义。
        实例化过程中,除了可以用各种具体的C++固有数据类型取代虚拟类型,还可以用某些用户自定义类型来取代虚拟类型,这里的自定义类型包括结构体以及某些用户定义的类。

           例1.1.4  下列程序的输出结果是        。
        #include <iostream>
        using namespace std;
        template <typename T>
        T fun (T a, T b) {return (a<=b)?a:b;}
        int main()
        {
           cout<<fun(3,6)<<','<<fun(3.14F,6.28F)<<endl;
           return 0;
        }
        解析:本题定义了一个模板函数fun,其形参a、b及函数返回类型均为T类型。函数fun的功能是返回a、b中数值较小的数。在main函数中,第一次调用fun时,实际上是调用了“int fun(int a,int b)”这个函数,故返回整型数据“3”。第二次调用时,实际上是调用了函数“float fun (float a, float b)”,返回浮点型数据“3.14”。
        答案:3,3.14

        1.1.4  考点4:模板实参的省略

        在调用模板函数时,编译系统需要足够的信息来判别每个虚拟类型参数所对应的实际类型,可以从两个不同的渠道获得信息:从“模板实参表”(在“<”和“>”之间)或从模板“函数实参表”(在“(”和“)”之间),“模板实参表”的信息优先于“函数实参表”的信息。如果从后者得到的信息已经能够判断其中部分或全部虚拟类型参数所对应的实际类型,而且它们又正好是“模板形参表声明”中最后的若干参数,则在“模板实参表”中可以省略这几个参数。如果所有的模板实参都被省略了,则空表“<>”也可以省略。

        但在以下情况中,模板实参是不能省略的。

        ① 从模板“函数实参表”中获得的信息有矛盾。例如,当编译系统从某一个函数实参中获得的信息是虚拟类型参数T(假设为T)对应的实际类型为int,可是从另一个函数实参中获得的信息却是虚拟类型参数T对应的实际类型为double时,就产生了矛盾。T不可能同时为
    int和double型,这将导致编译器无法找到匹配的函数模板的定义,编译时报错。解决这一问题的方法之一就是显示给出虚拟类型参数T对应的模板实参,强制T对应于int或是double。
        ② 虚拟类型参数用做函数的返回值类型,而且函数需要返回特定类型的值,而不管函数实参的类型是什么。在这种情况下,需要用模板实参强制虚拟类型参数对应于特定类型。
        ③ 虚拟类型参数没有出现在模板的“函数形参表”中。此时无法从模板的“函数实参表”中获取对应的信息,因而不能省略模板实参。
        ④ 函数模板含有常规形参。常规参数是用具体的类型修饰符(如int、double、char*等)定义的,对应的实参必须是常量表达式。因此,常规参数的信息无法从模板的“函数实参表”中获得,调用模板函数时必须显示给出对应于常规参数的模板实参。

           例1.1.5  有如下函数模板定义:
        template <class T>
        T func (T x, T y){ return x*x+y*y;}
        在下列对func的调用中,错误的是(   )。
        A.func(3,5);    B.func(3.0,5.5);  C.func(3,5.5);  D.func<int>(3,5.5);
        解析:对于本题中定义的模板函数,如果用选项C中“func(3,5.5);”的形式来调用,在编译时会出现“template parameter 'T' is ambiguous,could be 'double' or 'int' ”的错误,也就是说T对应的实际类型是不明确的。编译系统从第一个参数“3”获得的信息是“T对应于int”,而从第二个参数“5.5”处获得的信息是“T对应于double”,二者相互矛盾,因而编译时产生错误。
        选项D中提供了模板实参<int>,因为模板实参优先于函数实参,所以T对应的实际类型是明确的,在这里就是int型。在调用过程中,double型的参数“5.5”将被自动转换成int型。
        选型A和B所提供的两个函数实参的类型是一致的,不存在矛盾。
        答案:C

           例1.1.6  有如下函数模板声明:
        template <typename T>
        T Max(T a,T b) {return (a>=b)?a:b;}
        下列对函数模板Max的调用中错误的是(   )。
        A.Max(3.5,4.5)            B.Max(3.5,4)
        C.Max<double>(3.5,4.5)      D.Max<double>(3.5,4)
        答案:B

    展开全文
  • c语言函数模板

    2013-01-15 22:24:39
    适合入门的新手!
  • C++函数模板与类模板的区别

    万次阅读 多人点赞 2018-07-18 16:54:55
    C++ 除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。 声明...

    类模板:

    C++ 除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。

    声明类模板的语法为:

    template<typename 类型参数1 , typename 类型参数2 , …> class 类名{
        //TODO:
    };

    类模板和函数模板都是以 template 开头(当然也可以使用 class,目前来讲它们没有任何区别),后跟类型参数;类型参数不能为空,多个类型参数用逗号隔开。

    一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。

    假如我们现在要定义一个类来表示坐标,要求坐标的数据类型可以是整数、小数和字符串,例如:

    • x = 10、y = 10
    • x = 12.88、y = 129.65
    • x = "东京180度"、y = "北纬210度"


    这个时候就可以使用类模板,请看下面的代码:

     
    1. template<typename T1, typename T2> //这里不能有分号
    2. class Point{
    3. public:
    4. Point(T1 x, T2 y): m_x(x), m_y(y){ }
    5. public:
    6. T1 getX() const; //获取x坐标
    7. void setX(T1 x); //设置x坐标
    8. T2 getY() const; //获取y坐标
    9. void setY(T2 y); //设置y坐标
    10. private:
    11. T1 m_x; //x坐标
    12. T2 m_y; //y坐标
    13. };

    x 坐标和 y 坐标的数据类型不确定,借助类模板可以将数据类型参数化,这样就不必定义多个类了。

    注意:模板头和类头是一个整体,可以换行,但是中间不能有分号。

    上面的代码仅仅是类的声明,我们还需要在类外定义成员函数。在类外定义成员函数时仍然需要带上模板头,格式为:

    template<typename 类型参数1 , typename 类型参数2 , …>
    返回值类型 类名<类型参数1 , 类型参数2, ...>::函数名(形参列表){
        //TODO:
    }

    第一行是模板头,第二行是函数头,它们可以合并到一行,不过为了让代码格式更加清晰,一般是将它们分成两行。

    下面就对 Point 类的成员函数进行定义:

     
    1. template<typename T1, typename T2> //模板头
    2. T1 Point<T1, T2>::getX() const /*函数头*/ {
    3. return m_x;
    4. }
    5.  
    6. template<typename T1, typename T2>
    7. void Point<T1, T2>::setX(T1 x){
    8. m_x = x;
    9. }
    10.  
    11. template<typename T1, typename T2>
    12. T2 Point<T1, T2>::getY() const{
    13. return m_y;
    14. }
    15.  
    16. template<typename T1, typename T2>
    17. void Point<T1, T2>::setY(T2 y){
    18. m_y = y;
    19. }

    请读者仔细观察代码,除了 template 关键字后面要指明类型参数,类名 Point 后面也要带上类型参数,只是不加 typename 关键字了。另外需要注意的是,在类外定义成员函数时,template 后面的类型参数要和类声明时的一致。

    使用类模板创建对象

    上面的两段代码完成了类的定义,接下来就可以使用该类创建对象了。使用类模板创建对象时,需要指明具体的数据类型。请看下面的代码:

     
    1. Point<int, int> p1(10, 20);
    2. Point<int, float> p2(10, 15.5);
    3. Point<float, char*> p3(12.4, "东京180度");

    与函数模板不同的是,类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型。

    除了对象变量,我们也可以使用对象指针的方式来实例化:

     
    1. Point<float, float> *p1 = new Point<float, float>(10.6, 109.3);
    2. Point<char*, char*> *p = new Point<char*, char*>("东京180度", "北纬210度");

    需要注意的是,赋值号两边都要指明具体的数据类型,且要保持一致。下面的写法是错误的:

     
    1. //赋值号两边的数据类型不一致
    2. Point<float, float> *p = new Point<float, int>(10.6, 109);
    3. //赋值号右边没有指明数据类型
    4. Point<float, float> *p = new Point(10.6, 109);

    综合示例

    将上面的类定义和类实例化的代码整合起来,构成一个完整的示例,如下所示:

     
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. template<class T1, class T2> //这里不能有分号
    5. class Point{
    6. public:
    7. Point(T1 x, T2 y): m_x(x), m_y(y){ }
    8. public:
    9. T1 getX() const; //获取x坐标
    10. void setX(T1 x); //设置x坐标
    11. T2 getY() const; //获取y坐标
    12. void setY(T2 y); //设置y坐标
    13. private:
    14. T1 m_x; //x坐标
    15. T2 m_y; //y坐标
    16. };
    17.  
    18. template<class T1, class T2> //模板头
    19. T1 Point<T1, T2>::getX() const /*函数头*/ {
    20. return m_x;
    21. }
    22.  
    23. template<class T1, class T2>
    24. void Point<T1, T2>::setX(T1 x){
    25. m_x = x;
    26. }
    27.  
    28. template<class T1, class T2>
    29. T2 Point<T1, T2>::getY() const{
    30. return m_y;
    31. }
    32.  
    33. template<class T1, class T2>
    34. void Point<T1, T2>::setY(T2 y){
    35. m_y = y;
    36. }
    37.  
    38. int main(){
    39. Point<int, int> p1(10, 20);
    40. cout<<"x="<<p1.getX()<<", y="<<p1.getY()<<endl;
    41.  
    42. Point<int, char*> p2(10, "东京180度");
    43. cout<<"x="<<p2.getX()<<", y="<<p2.getY()<<endl;
    44.  
    45. Point<char*, char*> *p3 = new Point<char*, char*>("东京180度", "北纬210度");
    46. cout<<"x="<<p3->getX()<<", y="<<p3->getY()<<endl;
    47.  
    48. return 0;
    49. }

    运行结果:
    x=10, y=20
    x=10, y=东京180度
    x=东京180度, y=北纬210度

    在定义类型参数时我们使用了 class,而不是 typename,这样做的目的是让读者对两种写法都熟悉。

    函数模板:

    在《C++函数重载》一节中,为了交换不同类型的变量的值,我们通过函数重载定义了四个名字相同、参数列表不同的函数,如下所示:

     
    1. //交换 int 变量的值
    2. void Swap(int *a, int *b){
    3. int temp = *a;
    4. *a = *b;
    5. *b = temp;
    6. }
    7.  
    8. //交换 float 变量的值
    9. void Swap(float *a, float *b){
    10. float temp = *a;
    11. *a = *b;
    12. *b = temp;
    13. }
    14.  
    15. //交换 char 变量的值
    16. void Swap(char *a, char *b){
    17. char temp = *a;
    18. *a = *b;
    19. *b = temp;
    20. }
    21.  
    22. //交换 bool 变量的值
    23. void Swap(bool *a, bool *b){
    24. char temp = *a;
    25. *a = *b;
    26. *b = temp;
    27. }

    这些函数虽然在调用时方便了一些,但从本质上说还是定义了三个功能相同、函数体相同的函数,只是数据的类型不同而已,这看起来有点浪费代码,能不能把它们压缩成一个函数呢?

    能!可以借助本节讲的函数模板。

    我们知道,数据的值可以通过函数参数传递,在函数定义时数据的值是未知的,只有等到函数调用时接收了实参才能确定其值。这就是值的参数化。

    在C++中,数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当发生函数调用时,编译器可以根据传入的实参自动推断数据类型。这就是类型的参数化。

    值(Value)和类型(Type)是数据的两个主要特征,它们在C++中都可以被参数化。

    所谓函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。

    在函数模板中,数据的值和类型都被参数化了,发生函数调用时编译器会根据传入的实参来推演形参的值和类型。换个角度说,函数模板除了支持值的参数化,还支持类型的参数化。

    一但定义了函数模板,就可以将类型参数用于函数定义和函数声明了。说得直白一点,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。

    下面我们就来实践一下,将上面的四个Swap() 函数压缩为一个函数模板:

     
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. template<typename T> void Swap(T *a, T *b){
    5. T temp = *a;
    6. *a = *b;
    7. *b = temp;
    8. }
    9.  
    10. int main(){
    11. //交换 int 变量的值
    12. int n1 = 100, n2 = 200;
    13. Swap(&n1, &n2);
    14. cout<<n1<<", "<<n2<<endl;
    15.  
    16. //交换 float 变量的值
    17. float f1 = 12.5, f2 = 56.93;
    18. Swap(&f1, &f2);
    19. cout<<f1<<", "<<f2<<endl;
    20.  
    21. //交换 char 变量的值
    22. char c1 = 'A', c2 = 'B';
    23. Swap(&c1, &c2);
    24. cout<<c1<<", "<<c2<<endl;
    25.  
    26. //交换 bool 变量的值
    27. bool b1 = false, b2 = true;
    28. Swap(&b1, &b2);
    29. cout<<b1<<", "<<b2<<endl;
    30.  
    31. return 0;
    32. }

    运行结果:
    200, 100
    56.93, 12.5
    B, A
    1, 0

    请读者重点关注第 4 行代码。template是定义函数模板的关键字,它后面紧跟尖括号<>,尖括号包围的是类型参数(也可以说是虚拟的类型,或者说是类型占位符)。typename是另外一个关键字,用来声明具体的类型参数,这里的类型参数就是T。从整体上看,template<typename T>被称为模板头。

    模板头中包含的类型参数可以用在函数定义的各个位置,包括返回值、形参列表和函数体;本例我们在形参列表和函数体中使用了类型参数T

    类型参数的命名规则跟其他标识符的命名规则一样,不过使用 T、T1、T2、Type 等已经成为了一种惯例。

    定义了函数模板后,就可以像调用普通函数一样来调用它们了。

    在讲解C++函数重载时我们还没有学到引用(Reference),为了达到交换两个变量的值的目的只能使用指针,而现在我们已经对引用进行了深入讲解,不妨趁此机会来实践一把,使用引用重新实现 Swap() 这个函数模板:

     
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. template<typename T> void Swap(T &a, T &b){
    5. T temp = a;
    6. a = b;
    7. b = temp;
    8. }
    9.  
    10. int main(){
    11. //交换 int 变量的值
    12. int n1 = 100, n2 = 200;
    13. Swap(n1, n2);
    14. cout<<n1<<", "<<n2<<endl;
    15.  
    16. //交换 float 变量的值
    17. float f1 = 12.5, f2 = 56.93;
    18. Swap(f1, f2);
    19. cout<<f1<<", "<<f2<<endl;
    20.  
    21. //交换 char 变量的值
    22. char c1 = 'A', c2 = 'B';
    23. Swap(c1, c2);
    24. cout<<c1<<", "<<c2<<endl;
    25.  
    26. //交换 bool 变量的值
    27. bool b1 = false, b2 = true;
    28. Swap(b1, b2);
    29. cout<<b1<<", "<<b2<<endl;
    30.  
    31. return 0;
    32. }

    引用不但使得函数定义简洁明了,也使得调用函数方便了很多。整体来看,引用让编码更加漂亮。

    下面我们来总结一下定义模板函数的语法:

    template <typename 类型参数1 , typename 类型参数2 , ...> 返回值类型  函数名(形参列表){
        //在函数体中可以使用类型参数
    }

    类型参数可以有多个,它们之间以逗号,分隔。类型参数列表以< >包围,形式参数列表以( )包围。

    typename关键字也可以使用class关键字替代,它们没有任何区别。C++ 早期对模板的支持并不严谨,没有引入新的关键字,而是用 class 来指明类型参数,但是 class 关键字本来已经用在类的定义中了,这样做显得不太友好,所以后来 C++ 又引入了一个新的关键字 typename,专门用来定义类型参数。不过至今仍然有很多代码在使用 class 关键字,包括 C++ 标准库、一些开源程序等。

    本教程会交替使用 typename 和 class,旨在让读者在别的地方遇到它们时不会感觉陌生。更改上面的 Swap() 函数,使用 class 来指明类型参数:

     
    1. template<class T> void Swap(T &a, T &b){
    2. T temp = a;
    3. a = b;
    4. b = temp;
    5. }

    除了将 typename 替换为 class,其他都是一样的。

    为了加深对函数模板的理解,我们再来看一个求三个数的最大值的例子:

     
    1. #include <iostream>
    2. using namespace std;
    3.  
    4. //声明函数模板
    5. template<typename T> T max(T a, T b, T c);
    6.  
    7. int main( ){
    8. //求三个整数的最大值
    9. int i1, i2, i3, i_max;
    10. cin >> i1 >> i2 >> i3;
    11. i_max = max(i1,i2,i3);
    12. cout << "i_max=" << i_max << endl;
    13.  
    14. //求三个浮点数的最大值
    15. double d1, d2, d3, d_max;
    16. cin >> d1 >> d2 >> d3;
    17. d_max = max(d1,d2,d3);
    18. cout << "d_max=" << d_max << endl;
    19.  
    20. //求三个长整型数的最大值
    21. long g1, g2, g3, g_max;
    22. cin >> g1 >> g2 >> g3;
    23. g_max = max(g1,g2,g3);
    24. cout << "g_max=" << g_max << endl;
    25.  
    26. return 0;
    27. }
    28.  
    29. //定义函数模板
    30. template<typename T> //模板头,这里不能有分号
    31. T max(T a, T b, T c){ //函数头
    32. T max_num = a;
    33. if(b > max_num) max_num = b;
    34. if(c > max_num) max_num = c;
    35. return max_num;
    36. }

    运行结果:
    12  34  100↙
    i_max=100
    73.234  90.2  878.23↙
    d_max=878.23
    344  900  1000↙
    g_max=1000

    函数模板也可以提前声明,不过声明时需要带上模板头,并且模板头和函数定义(声明)是一个不可分割的整体,它们可以换行,但中间不能有分号。

    两者的区别:

    函数模板与类模板有什么区别?答:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化 必须由程序员在程序中显式地指定。

     

    即函数模板允许隐式调用和显式调用而类模板只能显示调用这期间有涉及到函数模板与模板函数,类模板与模板类的概念(类似于类与类对象的区别)请看下面例子

    注意:模板类的函数声明和实现必须都在头文件中完成,不能像普通类那样声明在.h文件中实现在.cpp文件中,原因可以看链接http://hi.baidu.com/cn_rigel/blog/item/6cf6fc083723e2286a60fb53.html

     

    #include "stdafx.h"
    #include <iostream>
    using namespace std;

    //使用模板创建一个返回最大值的函数
    //这是一个函数模板
    template <class Type>
    Type MaxValue(Type a,Type b)
    {
        if ( a > b)
        {
            return a;
        }
        else
            return b;
    }

    //创建一个堆栈模板类
    //这是一个类模板
    template <class T>
    class Stack
    {
    public:
        Stack(){ m_nPos = 0;}
        ~Stack(){}

        void Push(T value);
        T Pop();

        bool IsEmpty()
        {
            return m_nPos == 0;
        }
        bool HasElement()
        {
            return !IsEmpty();
        }
        bool IsFull()
        {
            return m_nPos == STATCK_SIZE;
        }

    private:
        int m_nPos;
        //使用常量表示堆栈的大小

        const static int STATCK_SIZE = 100;
        T m_Data[STATCK_SIZE];
    };
    //模板类的成员函数实现

    template <class T>
    void Stack<T> ::Push(T value)
    {
        //使用后置递增操作符

        m_Data[m_nPos++] = value;
    }
    template <class T>
    T Stack<T>::Pop()
    {
        //使用前置递减操作符

        return m_Data[--m_nPos];
    }

    void TestMaxValue()
    {
        //隐式调用
     
    //函数模板的实例化在程序调用时自动完成
       cout << MaxValue(100, 204)<< endl;//MaxValue(100, 204)这是一个模板函数
        cout << MaxValue(2.5002,30.003) << endl;//MaxValue(2.5002,30.003)这也是一个模板函数
    //当然由程序员自己指定也可以
        //显示调用

        cout << MaxValue<int>(10,20) << endl;
        cout << MaxValue<double>(2.5002,30.003) << endl;
    }

    void TestStack()
    {
        //测试模板类(整数)

        Stack <int> intStack;//类模板的实例化由程序员显示的指定
        intStack.Push(10);
        intStack.Push(20);
        intStack.Push(30);

        while (intStack.HasElement())
        {
            cout << intStack.Pop() << endl;
        }

        //测试模板类(浮点)

        Stack <float> floatStack;//类模板的实例化由程序员显示的指定
        floatStack.Push(1.001);
        floatStack.Push(2.002);
        floatStack.Push(3.003);

        while (floatStack.HasElement())
        {
            cout << floatStack.Pop() << endl;
        }

        //测试动态创建对象

        //Stack创建的指针必须指明类型

        Stack<int>* pInt = new Stack<int>();类模板的实例化由程序员显示的指定
        pInt->Push(10);


        pInt->Push(20);
        pInt->Push(30);

        while (pInt->HasElement())
        {
            cout << pInt->Pop() << endl;
        }
        if ( pInt != NULL)
        {
            delete pInt;
            pInt = NULL;
        }
    }

    区别2:

    在C++中有好几个这样的术语,但是我们很多时候用的并不正确,几乎是互相替换混淆使用。下面我想彻底辨清几个术语,这样就可以避免很多概念上的混淆和使用上的错误。

    这几个词是:

    函数指针——指针函数

    数组指针——指针数组

    类模板——模板类

    函数模板——模板函数

    最终在使用中,我们就可以让它们实至名归,名正言顺。

    1.函数指针——指针函数

    函数指针的重点是指针。表示的是一个指针,它指向的是一个函数,例子:

    int   (*pf)();

    指针函数的重点是函数。表示的是一个函数,它的返回值是指针。例子:

    int*   fun();

     
    2.数组指针——指针数组

    数组指针的重点是指针。表示的是一个指针,它指向的是一个数组,例子:

    int   (*pa)[8];

    指针数组的重点是数组。表示的是一个数组,它包含的元素是指针。例子;

    int*   ap[8];

     

    3.类模板——模板类(class   template——template   class)

    类模板的重点是模板。表示的是一个模板,专门用于产生类的模子。例子:

    template   <typename   T>

    class   Vector

    {

                …

    };

    使用这个Vector模板就可以产生很多的class(类),Vector <int> 、Vector <char> 、Vector <   Vector <int>   > 、Vector <Shape*> ……。

    模板类的重点是类。表示的是由一个模板生成而来的类。例子:

    上面的Vector <int> 、Vector <char> 、……全是模板类。

    这两个词很容易混淆,我看到很多文章都将其用错,甚至一些英文文章也是这样。将他们区分开是很重要的,你也就可以理解为什么在定义模板的头文件.h时,模板的成员函数实现也必须写在头文件.h中,而不能像普通的类(class)那样,class的声明(declaration)写在.h文件中,class的定义(definition)写在.cpp文件中。请参照Marshall   Cline的《C++   FAQ   Lite》中的[34]   Container   classes   and   templates中的[34.12]   Why   can 't   I   separate   the   definition   of   my   templates   class   from   it 's   declaration   and   put   it   inside   a   .cpp   file?   URL地址是http://www.parashift.com/c++-faq-lite/containers-and-templates.html#faq-34.12

    我将几句关键的段落摘录如下,英文很好理解:

    In   order   for   the   compiler   to   generate   the   code,   it   must   see   both   the   template   definition   (not   just   declaration)   and   the   specific   types/whatever   used   to   "fill   in "   the   template.   For   example,   if   you 're   trying   to   use   a   Foo <int> ,   the   compiler   must   see   both   the   Foo   template   and   the   fact   that   you 're   trying   to   make   a   specific   Foo <int> .  

    Suppose   you   have   a   template   Foo   defined   like   this:  

      template <class   T>
      class   Foo   {
      public:
          Foo();
          void   someMethod(T   x);
      private:
          T   x;
      };  

    Along   with   similar   definitions   for   the   member   functions:  

      template <class   T>
      Foo <T> ::Foo()
      {
          ...
      }
     
      template <class   T>
      void   Foo <T> ::someMethod(T   x)
      {
          ...
      }  

    Now   suppose   you   have   some   code   in   file   Bar.cpp   that   uses   Foo <int> :  

      //   Bar.cpp
     
      void   blah_blah_blah()
      {
          ...
          Foo <int>   f;
          f.someMethod(5);
          ...
      }  

    Clearly   somebody   somewhere   is   going   to   have   to   use   the   "pattern "   for   the   constructor   definition   and   for   the   someMethod()   definition   and   instantiate   those   when   T   is   actually   int.   But   if   you   had   put   the   definition   of   the   constructor   and   someMethod()   into   file   Foo.cpp,   the   compiler   would   see   the   template   code   when   it   compiled   Foo.cpp   and   it   would   see   Foo <int>   when   it   compiled   Bar.cpp,   but   there   would   never   be   a   time   when   it   saw   both   the   template   code   and   Foo <int> .   So   by   rule   above,   it   could   never   generate   the   code   for   Foo <int> ::someMethod().  

    关于一个缺省模板参数的例子:

    template   <typename   T   =   int>

    class   Array

    {

                …

    };

    第一次我定义这个模板并使用它的时候,是这样用的:

    Array   books;//我认为有缺省模板参数,这就相当于Array <int>   books

    上面的用法是错误的,编译不会通过,原因是Array不是一个类。正确的用法是Array <>   books;

    这里Array <> 就是一个用于缺省模板参数的类模板所生成的一个具体类。

     

    4.函数模板——模板函数(function   template——template   function)

    函数模板的重点是模板。表示的是一个模板,专门用来生产函数。例子:

    template   <typename   T>

    void   fun(T   a)

    {

                …

    }

    在运用的时候,可以显式(explicitly)生产模板函数,fun <int> 、fun <double> 、fun <Shape*> ……。

    也可以在使用的过程中由编译器进行模板参数推导,帮你隐式(implicitly)生成。

    fun(6);//隐式生成fun <int>

    fun(8.9);//隐式生成fun <double>

    fun(‘a’);//   隐式生成fun <char>

    Shape*   ps   =   new   Cirlcle;

    fun(ps);//隐式生成fun <Shape*>

     

    模板函数的重点是函数。表示的是由一个模板生成而来的函数。例子:

    上面显式(explicitly)或者隐式(implicitly)生成的fun <int> 、fun <Shape*> ……都是模板函数。

    关于模板本身,是一个非常庞大的主题,要把它讲清楚,需要的不是一篇文章,而是一本书,幸运的是,这本书已经有了:David   Vandevoorde,   Nicolai   M.   Josuttis写的《C++   Templates:   The   Complete   Guide》。可惜在大陆买不到纸版,不过有一个电子版在网上流传。

     

    模板本身的使用是很受限制的,一般来说,它们就只是一个产生类和函数的模子。除此之外,运用的领域非常少了,所以不可能有什么模板指针存在的,即指向模板的指针,这是因为在C++中,模板就是一个代码的代码生产工具,在最终的代码中,根本就没有模板本身存在,只有模板具现出来的具体类和具体函数的代码存在。

    但是类模板(class   template)还可以作为模板的模板参数(template   template   parameter)使用,在Andrei   Alexandrescu的《Modern   C++   Design》中的基于策略的设计(Policy   based   Design)中大量的用到。

    template <   typename   T,   template <typename   U>   class   Y>

    class   Foo

    {

                …

    };

     

    从文章的讨论中,可以看到,名字是非常重要的,如果对名字的使用不恰当的话,会引起很多的麻烦和误解。我们在实际的程序中各种标识符的命名也是一门学问,为了清晰易懂,有时候还是需要付出一定的代价。
     

    展开全文
  • 1.函数模板的声明和模板函数的生成 1.1函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。 函数模板的声明...
  • C++ 函数模板&类模板详解

    千次阅读 2018-12-02 16:32:37
    在 C++ 中,模板分为函数模板和类模板两种。函数模板是用于生成函数的,类模板则是用于生成类的。 函数模板&amp;模板函数 类模板&amp;模板类 必须区分概念 函数模板是模板,模板函数时具体的函数 类模板...

    在 C++ 中,模板分为函数模板和类模板两种。函数模板是用于生成函数的,类模板则是用于生成类的。

    函数模板&模板函数     类模板&模板类  必须区分概念

    函数模板是模板,模板函数时具体的函数
    类模板是模板,模板类时具体的类
    函数模板实例化而得到的函数称为模板函数
    类模板实例化得到的类叫模板类

     

    一.函数模板

    函数模板的基本写法为:
    template <typename 类型参数1, typename 类型参数2, ...> 
    返回值类型  函数模板名(形参表)
    {
        函数体
    }

    其中的 typename 关键字也可以用 class 关键字替换,例如:
    template <class 类型参数1, class 类型参数2, ...>

    举例,求两个值的最大值Max:

    #include<iostream>
    using namespace std;
    
    template <typename T>                   
    T const& Max(T const& a, T const& b)
    {
    	return a < b ? b : a;
    }
    
    int main()
    {
    	int i = 20;
    	int j = 30;
    	int result1 = Max(i, j); //编译到这一句时,编译器自动生成int Max(int, int)函数
    
    	double f1 = 12.5;
    	double f2 = 20.88;
    	double result2 = Max(f1, f2); //编译到这句时,编译器自动生成double Max(double, double)函数
    
    	string s1 = "Hello";
    	string s2 = "world";
    	string result3 = Max(s1, s2); //编译到这句时,编译器自动生成string Max(string, string)函数
    	return 0;
    }

    模板在定义时,编译器不会对它编译,因为没有一个实体可用。只有在编译到某句代码时,如int result1 = Max(i, j);时,根据模板再将Max(i, j)编译成int Max(int, int)这样具体的函数。
    编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数称为模板函数。在大部分编译器中,模板只有在被实例化时,编译器才会检查其语法正确性。如果程序中写了一个模板却没有用到,那么编译器不会报告这个模板中的语法错误

     

    编译器对模板进行实例化时,并非只能通过模板调用语句的实参来实例化模板中的类型参数,模板调用语句可以明确指明要把类型参数实例化为哪种类型。可以用:

    函数名<实际类型参数1, 实际类型参数2, ...>

    的方式告诉编译器应该如何实例化模板函数。例如下面的程序:

    #include<iostream>
    using namespace std;
    
    template <typename T>
    T add(T n)
    {
    	return n+1;
    }
    
    int main()
    {
    	cout << add(4) / 2 << endl; //正常写法,输出2
    	cout << add<double>(4) / 2 << endl; //指明要用double来实例化add函数,输出2.5
    	return 0;
    }

    第一句正常调用输出2,第二句为编译器指定类型,则输出2.5

     

     

    二.类模板

    人们需要编写多个形式和功能都相似的函数,因此有了函数模板来减少重复劳动;人们也需要编写多个形式和功能都相似的类,于是C++引入了类模板的概念,编译器从类模板可以自动生成多个类,避免了程序员的重复劳动。

    类模板的写法如下:
    template <class 类形参数1, class 类型参数2, ...>
    class 类模板名称
    {
        成员函数和成员变量
    };

    具体如下:

    template<class T1, class T2> 
    class A
    {
    public:
    	T1 value1;
    	T2 value2;
    	T1 add(T1 a, T2 b);
    };

     

    类模板中的成员函数放在类外定义时的语法如下:
    template <class 类形参数1, class 类型参数2, ...>
    返回值类型  类模板名<class 类塑参数1, class 类型参数2, ...>::成员函数名(形参表)
    {
        。。。
    }

    具体如下:

    template<class T1,class T2>
    T1 A<T1, T2>::add(T1 a, T2 b)
    {
    	return a + b;
    }

     

    用类模板定义对象的写法如下:
    类模板名<真实类型参数表> 对象名(构造函数实际参数表);
    若构造函数无参的话则为:
    类模板名 <真实类型参数表> 对象名;

     

    下面实现一个简单的key,value的键值对----类模板mDictionary

    实践中常常会碰到,某项数据记录由两部分组成,一部分是关键字,另一部分是值。关键字用来对记录进行排序和检索,根据关键字能查到值。例如,学生记录由两部分组成,一部分是姓名,另一部分是年纪。要能根据姓名对学生进行查询检索,key就是姓名,value就是年龄。

    template<class T1, class T2> 
    class mDictionary
    {
    public:
    	T1 key;      //关键字
    	T2 value;    //值
    	mDictionary(T1 k, T2 v) :key(k), value(v)
    	{
    	};
    	T2 getvalue(T1 k);
    };
    
    template<class T1,class T2>
    T2 mDictionary<T1, T2>::getvalue(T1 k)
    {
    	if (k == this->key)    //如果姓名传进来相同,则返回年龄
    	{
    		return this->value;
    	}
    	else
    	{
    		return 0;
    	}
    }
    
    int main()
    {
    	mDictionary<string, int> dic("Bob",22);
    	std::cout << dic.getvalue("Bob")<<std::endl;    //返回22
    	std::cout << dic.getvalue("Tom") << std::endl;  //返回0
    	return 0;
    }

    实例化一个类模板时,如第main函数第一行-------mDictionary<string, int>  dic("Bob",22);
    真实类型参数表中的参数是具体的类型名,如 string、int 等,它们用来一一对应地替换类模板定义中“类型参数表”中的类型参数。类模板名 <真实类型参数表>就成为一个具体的类的名字,就是mDictionary<string, int>就可以看成一个类型
    如有一个类A, 初始化类A的时候,一般都是A  a;  mDictionary<string, int>就等价于A,可以看成一个A类型

    编译器编译到这一行时,就会用 string 替换 mDictionary模板中的 T1,用 int 替换 T2,其余部分原样保留,这样就自动生成了一个新的类。这个类的名字编译器是如何处理的不需要知道,可以认为它的名字就是mDictionary<string, int>。也可以说,dic 对象的类型就是 Pair<string, int>。
    mDictionary<string, int> 类的成员函数自然也是通过替换 mDictionary模板的成员函数中的 T1、T2 得到的。
    编译器由类模板生成类的过程叫类模板的实例化。由类模板实例化得到的类叫模板类

     

    函数模板作为类模板成员
    类模板中的成员函数还可以是一个函数模板。成员函数模板只有在被调用时才会被实例化。例如下面的程序:

    #include <iostream>
    using namespace std;
    template <class T>
    class A
    {
    public:
        template <class T2>             //定义新类型T2
        void Func(T2 t) { cout << t; }  //成员函数模板
    };
    int main()
    {
        A<int> a;
        a.Func('K');  //成员函数模板Func被实例化
        a.Func("hello");
        return 0;
    }

    程序的输出结果是:Khello

    这个例子与上面例子不同的是,mDictionary中的getvalue是一个类模板中的成员函数,没有定义新的模板,而Func函数是一个新的函数模板,在Func函数上面一句,定义了新类型T2,姑且先这样理解。其实只要了解有这种写法即可,既类模板中还可以定义函数模板

     

     

     

     

    展开全文
  • C++中的函数模板

    万次阅读 多人点赞 2018-08-23 19:57:54
    之前我们知道的交换两个变量的方法有宏定义、函数,这两种方式都能实现两个变量的交换,但是各有各的优缺点 宏定义: - 优点:代码复用,适合所有的类型 - 缺点:缺少类型检查,宏在预处理阶段就被替换掉,编译器...
  • 在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多...
  • (1)输人并运行所给的参考程1...(4)使用重载函数模板重新实现上小题中的函数Maxl。 (5)使用系统函数pow(x,y)计算xy的值,注意包含头文件math.h。 (6)用递归的方法编写函数求Fibonacci级数,观察递归调用的过程。
  • C++模板、类模板、函数模板详解都在这里了

    万次阅读 多人点赞 2019-07-13 21:45:39
    C++模板、类模板、函数模板详解一、引言二、函数模板1、定义2、调用3、多个虚类型三、类模板1、定义2、调用四、类的函数模板五、类作为数据类型传入六、类模板与继承七、类模板与友元 一、引言 在写排序算法时,如果...
  • 6、 函数模板和类模板

    千次阅读 2018-07-15 15:30:38
    函数模板和类模板前言C++提供了函数模板(functiontemplate)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的...
  • 模板类与类模板、函数模板与模板函数等的区别 函数指针 = 指向函数的指针 指针函数=返回指针的函数 数组指针=指向数组的指针 指针数组=内容是指针的数组 类模板=用来产生类的模板 模板类=使用类模板产生的类...
  • 1.在编写C++程序时,很多情况下会同时使用函数模板函数模板的显示具体化: void Swap(T &amp;a, T &amp;b) { T temp; temp = a; a = b; b = temp; } template &lt;&gt; void Swap&lt;...
  • 函数模板与模板函数

    千次阅读 2019-05-31 21:20:21
    函数模板与模板函数 函数模板是一组函数的抽象描述,它不是一个实实在在的函数,函数模板不会编译成任何目标代码。函数模板必须先实例化成模板函数,这些模板函数再程序运行时会进行编译和链接,然后产生相应的...
  • 函数模板和模板函数

    千次阅读 2017-07-18 09:38:26
    1.函数模板的声明和模板函数的生成 1.1函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。 函数模板的...
  • 这篇开始学习类模板相关知识,本篇主要学习什么是类模板,还有类模板和函数模板的区别 1.类模板语法 template <class T> 类 模板声明下面是函数就是函数模板,如果是类就叫类模板 2.一个类模板例子 ...
  • C++之函数重载和函数模板

    千次阅读 2019-04-17 21:03:26
    因为函数重载比较容易理解,并且非常有助于我们理解函数模板的意义,所以这里我们先来用一个经典的例子展示为什么要使用函数重载,这比读文字定义有效的多。 现在我们编写一个交换两个int变量值得函数,可以这样写:...
  • C++ 函数模板和类模板--泛型编程

    千次阅读 2018-08-06 22:06:11
    所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中...
  • C++ 函数模板

    万次阅读 2018-08-13 17:42:51
    函数模板是通用的函数描述,它们使用泛型来定义函数,其中的泛型可用具体的类型替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时候...
  • C++函数模板和模板函数的区别

    千次阅读 2020-09-02 09:44:52
    函数模板:函数的重载能够实现一个函数名多用,将实现相同或者相似功能的函数用同一个函数名来定义,但是在程序中仍然要分别定义每一个函数。为进一步简化,C++提供了函数模板,实际上是建立一个通用函数,其函数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 599,596
精华内容 239,838
关键字:

函数模板