精华内容
参与话题
问答
  • C++ 普通函数和模板函数调用规则

    万次阅读 2020-07-29 22:30:45
    1.如果函数模板和普通函数都可以实现,优先调用普通函数 2.可以通过空模板参数列表来强制调用函数模板 3.函数模板也可以发生重载 4.如果函数模板可以产生更好的匹配, 优先调用函数模板 */ #include<iostream>...

    /**
    调用规则如下:
    1.如果函数模板和普通函数都可以实现,优先调用普通函数
    2.可以通过空模板参数列表来强制调用函数模板
    3.函数模板也可以发生重载
    4.如果函数模板可以产生更好的匹配, 优先调用函数模板
    */ 

    总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性

    #include<iostream>;
    using namespace std;
    
    
    /**
    调用规则如下:
    1.如果函数模板和普通函数都可以实现,优先调用普通函数
    2.可以通过空模板参数列表来强制调用函数模板
    3.函数模板也可以发生重载
    4.如果函数模板可以产生更好的匹配, 优先调用函数模板
    */
    
    //普通函数与函数模板调用规则
    void myPrint(int a,int b) {
    	cout << "调用的普通函数" << endl;
    }
    template<typename T>
    void myPrint(T a, T b) {
    	cout << "调用的模板" << endl;
    }
    template<typename T>
    void myPrint(T a, T b, T C) {
    	cout << "调用重载的模板" << endl;
    }
    void test01() {
    	//1、如果函数模板和普通函数都可以实现,优先调用普通函数
    	//注意如果告诉编译器普通 函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不
    	int a = 10;
    	int b = 20;
    
    
    	myPrint(a, b); //调用普通函数
    	// 2、可以通过空模板参数列表来强制调用函数模板
    	myPrint<>(a, b); //调用函数模板
    	//3、函数模板也可以发生重载
    	int C = 30;
    	myPrint(a, b, C); //调用重载的函数模板
    	//4、如果函数模板可以产生 更好的匹配,优先调用函数模板
    	char C1 = 'a';
    	char C2 = 'b';
    	myPrint(C1, C2); //调用函数模板
    }
    
    
    int main() {
    	test01();
    	system("pause");
    	return 0;
    }

     

    展开全文
  • 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++知识目录

    展开全文
  • 模板函数

    2014-10-17 14:31:59
    1、 2、 3、 4、 5、 6、

    1、新建一个Win32控制台项目,添加代码如下:

    // STL_Test1.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    
    
    
    // 定义一个Max模板函数
    template <class T>	// 注:class可以用typename替代:template <typename T>
    T Max(T a, T b)
    {
    	return a > b ? a : b;
    };
    
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	int x1 = 1;
    	int y1 = 2;
    	int z1 = Max(x1, y1);
    
    	double z2 = Max(1.2, 3.4);
    
    	return 0;
    }
    


    2、通过断点调试可以发现,模板函数一开始并未被编译,只有当MAIN函数中具体调用该函数时,才会被编译成相应的函数。


    3、函数模板的格式:


       template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)
       {
          函数体
       }

      其中template和class是关见字,class可以用typename 关见字代替,在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。

    模板函数可以有多个类型参数,但是每个类型参数都必须用关键字class或typename限定。此外,模板参数中还可以出现确定类型参数,称为非类型参数。例如:

       template <class T1,class T2,int T3,......> 返回类型 函数名(参数列表)
       {
          函数体
       }

    注:在传递实参时,非类型参数T3只能使用常量


    4、定义模板函数时,不允许template语句与函数模板定义之间有其他的语句。例如上例中:

    template <class T>
    int a;	// 错误,不允许在此位置有任何语句
    T Max(T a, T b)
    {
    	return a > b ? a : b;
    };


    5、当然上述模板函数也可以用一个宏函数来实现:#define Max(a, b) (((a) > (b)) ? (a) : (b))


    6、那么,Max(1.3, 4);当这样调用上述模板函数呢?

    会发现编译器报错,“T Max(T,T)”: 模板 参数“T”不明确

    可以这样做:

    Max((int)1.3, 4);
    Max(1.3, (double)4);

    Max<int>(1.3, 4);
    Max<double>(1.3, 4);


    7、特化:

    // STL_Test1.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include "string.h"
    
    // 定义一个Max模板函数
    template <class T>	// 注:class可以用typename替代:template <typename T>
    T Max(T a, T b)
    {
    	return a > b ? a : b;
    };
    
    template<>
    const char* Max<const char*>(const char* a, const char* b)
    {
    	return (strcmp(a, b) > 0 ? a : b);
    }
    
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	const char *p = Max("aaa", "bbb");
    
    	return 0;
    }
    

    8、

    展开全文
  • c++模板函数

    千次阅读 2020-08-28 22:11:17
    编写模板函数,交换变量a和b的值(可能是int,float,double) template<typename T> //template<typename T> //只需加这一段代码 const T* my_find(T *a,T *b) { T tmp; tmp = x; x = y; y = ...

    编写模板函数,交换变量a和b的值(可能是int,float,double)

    template<typename T>  //template<typename T>  //只需加这一段代码
       const T* my_find(T *a,T *b)
       {   
         T tmp;
         tmp = x;
         x = y;
         y = tmp;
       }

     

    展开全文
  • 函数模板与模板函数

    2019-05-31 21:20:21
    函数模板与模板函数 函数模板是一组函数的抽象描述,它不是一个实实在在的函数,函数模板不会编译成任何目标代码。函数模板必须先实例化成模板函数,这些模板函数再程序运行时会进行编译和链接,然后产生相应的...
  • 指针函数:本质是一个函数函数的返回值是某一类型的指针。形式一般如下: 类型标识符 * 函数名(参数列表) 如 int * f(x,y). 函数指针:本质上是指针,它指向的是一个函数。...函数模板:对一批模样相同的函数
  • 这期间有涉及到函数模板与模板函数,类模板与模板类的概念 (类似于类与类对象的区别) 注意:模板类的函数声明和实现必须都在头文件中完成,不能像普通类那样声明在.h文件中,实现在.cpp文件中。 1、函数模板和...
  • 模板类与类模板、函数模板与模板函数等的区别 函数指针 = 指向函数的指针 指针函数=返回指针的函数 数组指针=指向数组的指针 指针数组=内容是指针的数组 类模板=用来产生类的模板 模板类=使用类模板产生的类...
  • C++模板函数和非模板函数可以构成函数的重载么?模板参数可以作为函数重载的依据么?
  • int fun(int a); float fun(float a); double fun(double a);...一个函数模板能实例化出无数个模板函数 // 模板函数 T fun&lt;int&gt; (T a); T fun&lt;float&gt; (T a); T fun&lt;...
  • 函数模板和模板函数

    千次阅读 2017-07-18 09:38:26
    1.函数模板的声明和模板函数的生成 1.1函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。 函数模板的...
  • 问题来源:定义相同功能,但是参数类型不同的函数需要为每种类型都定义函数,代码量大大增加。 如定义两个整数之和:int fun1(int a,int b);...定义一个通用的函数模板,而不是为每个类型都定义一

空空如也

1 2 3 4 5 ... 20
收藏数 46,754
精华内容 18,701
关键字:

模板函数