类模板(四十八)

        我们上节博客介绍了泛型编程思想,那么在 C++ 中是否可以将泛型的思想应用于类呢?答案肯定是显而易见的,在 C++ 中的标准库中,就是一些通用的类模板。我们先来看看类模板有哪些特性,它主要是用于存储和组织数据元素,类中数据组织的方式和数据元素的具体类型无关,如:数组类、链表类、Stack 类等。C++ 中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。

        在 C++ 中的类模板是以相同的方式处理不同的类型,并且在类声明前使用 template 进行标识< typename T > 用于说明类中使用的泛指类型 T。类模板只能显示指定具体类型,无法自动推导;使用具体类型(Type)定义对象。声明的泛指类型 T 可以出现在类模板的任意地方;编译器对类模板的处理方式和函数模板相同:即 a> 从类模板通过具体类型产生不同的类;b> 在声明的地方对类模板代码本身进行编译;c> 在使用的地方对参数替换后的代码进行编译。

        下来我们还是以代码为例来进行分析

#include <iostream>
#include <string>

using namespace std;

template < typename T >
class Operator
{
public:
    T add(T a, T b)
    {
        return a + b;
    }
    
    T minus(T a, T b)
    {
        return a - b;
    }
    T multiply(T a, T b)
    {
        return a * b;
    }
    T divide(T a, T b)
    {
        return a / b;
    }
};

int main()
{
    Operator<int> op1;
    
    cout << "op1.add(1, 2) = " << op1.add(1, 2) << endl;
    
    Operator<string> op2;
    
    cout << op2.add("D.T.", "Software") << endl;
    //cout << op2.minus("D.T.", "Software") << endl;
    
    return 0;
}

        我们来试试看看能不能实现整数和字符串的相加操作呢?

图片.png

        我们看到已经实现了,那么我们再来试试字符串的减法呢?

图片.png

        我们看到字符串是不支持减法的,因为编译器不知道该怎么进行操作。下来我们写一个减法操作符的重载函数,看看还会出错嘛,如下

string operator- (string& l, string& r)
{
    return "Minus";
}

        编译结果如下

图片.png

        我们看到编译没报错,也就是说,编译器在编译到字符串的减法操作时,它发现不支持,但是为了显示它的强大没先报错,先去查找有没有实现好的重载函数。结果找到了,便去调用这个函数了。

        类模板在工程中的应用遵从以下几条规则:1、类模板必须在头文件中定义;2、类模板不能分开实现在不同的文件中;3、类模板外部定义的成员函数需要加上模板<>声明。下来我们就以工程中的标准再来实现下上面的类模板


Operator.h 源码

#ifndef _OPERATOR_H_
#define _OPERATOR_H_

template < typename T >
class Operator
{
public:
    T add(T a, T b);
    T minus(T a, T b);
    T multiply(T a, T b);
    T divide(T a, T b);
};

template < typename T >
T Operator<T>::add(T a, T b)
{
    return a + b;
}

template < typename T >
T Operator<T>::minus(T a, T b)
{
    return a - b;
}

template < typename T >
T Operator<T>::multiply(T a, T b)
{
    return a * b;
}

template < typename T >
T Operator<T>::divide(T a, T b)
{
    return a / b;
}

#endif


test.cpp 源码

#include <iostream>
#include <string>
#include "Operator.h"

using namespace std;

int main()
{
    Operator<int> op1;
    
    cout << op1.add(1, 2) << endl;
    cout << op1.multiply(4, 5) << endl;
    cout << op1.minus(5, 6) << endl;
    cout << op1.divide(10, 5) << endl;
    
    return 0;
}

        我们编译后结果如下

图片.png

        我们看到运行已经得到正确的结果了。那么我们接下来想想类模板能否支持泛型编程中的那种定义任意多个不同类型的参数吗?显然是肯定支持了。如下

图片.png

        类模板可以是可以被特化的:a> 指定类模板的特定实现;b> 部分类型参数必须显示指定;c> 根据类型参数分开实现类模板。如下所示

图片.png

        类模板的特化类型分为两种:部分特化和完全特化。部分特化是指用特定规则来约束类型参数,而完全特化则是指完全显示指定类型参数。如下

图片.png

        下来我们还是以代码为例来进行分析说明

#include <iostream>
#include <string>

using namespace std;

template
< typename T1, typename T2 >
class Test
{
public:
    void add(T1 a, T2 b)
    {
        cout << "void add(T1 a, T2 b)" << endl;
        cout << a + b << endl;
    }
};

template
< typename T1, typename T2 >
class Test < T1*, T2* >
{
public:
    void add(T1* a, T2* b)
    {
        cout << "void add(T1*, T2*)" << endl;
        cout << *a + *b << endl;
    }
};

template
< typename T >
class Test < T, T >
{
public:
    void add(T a, T b)
    {
        cout << "void add(T a, T b)" << endl;
        cout << a + b << endl;
    }
    void print()
    {
        cout << "class Test < T, T >" << endl;
    }
};

template
<  >
class Test < void*, void* >
{
public:
    void add(void* a, void* b)
    {
        cout << "void add(void* a, void* b)" << endl;
        cout << "Error to add void* param..." << endl;
    }
};

int main()
{
    Test<int, float> t1;
    Test<long, long> t2;
    Test<void*, void*> t3;
    
    t1.add(1, 2.5);
    
    cout << endl;
    
    t2.add(5, 5);
    t2.print();
    
    cout << endl;
    
    t3.add(NULL, NULL);
    
    cout << endl;
    
    Test<int*, double*> t4;
    int a = 1;
    double b = 0.1;
    
    t4.add(&a, &b);
    
    return 0;
}

        我们先是定义了一个模板类,接着定义了它的部分特化(参数类型为 void*以及参数类型是相同的),最后一个是完全特化。我们来看看编译结果

图片.png

        我们看到编译是通过的,说明编译器是支持这样的写法的,也正常运行结束了。那么类模板特化还要注意几个事项:1、特化只是模板的分开实现,其本质上还是用一个类模板;2、特化类模板的使用方式是统一的,必须显示指定每一个类型参数。那么类模板特化与重定义有区别吗?函数模板可以特化吗?重定义和特化是不同的,进行重定义时是:一个类模板和一个新类(或者两个类模板),使用的时候需要考虑如何选择的问题函数模板是可以特化的,以统一的方式使用类模板和特化类,编译器自动优先选择特化类。函数模板只支持类型参数完全特化,如下

图片.png

        下来我们还是以代码为例来进行分析

#include <iostream>
#include <string>

using namespace std;

template
< typename T1, typename T2 >
class Test
{
public:
    void add(T1 a, T2 b)
    {
        cout << "void add(T1 a, T2 b)" << endl;
        cout << a + b << endl;
    }
};

class Test_void
{
public:
    void add(void* a, void* b)
    {
        cout << "void add(void* a, void* b)" << endl;
        cout << "Error to add void* param..." << endl;
    }
};

template
< typename T >
bool Equal(T a, T b)
{
    cout << "bool Equal(T a, T b)" << endl;
    
    return a == b;
}

template
< >
bool Equal<double>(double a, double b)
{
    const double delta = 0.00000000000001;
    double r = a - b;
    
    cout << "bool Equal<double>(double a, double b)" << endl;
    
    return (-delta < r) && (r < delta);
}
/*
bool Equal(double a, double b)
{
    const double delta = 0.0000000000001;
    double r = a - b;
    
    cout << "bool Equal(double a, double b)" << endl;
    
    return (-delta < r) && (r < delta);
}*/

int main()
{
    cout << Equal( 1, 1 ) << endl;
    cout << Equal( 0.001, 0.001 ) << endl;
    
    return 0;
}

        我们看到重定义则是类 Test_void 那样的,我们在底下定义了 Equal 函数的特化模板,下来编译看看结果

图片.png

        我们看到编译器确实支持函数特化的写法,并且完美运行。下来我们将全局函数 Equal 的注释去掉,再来看看编译结果

图片.png

        我们看到调用的是全局函数,如果我们非要在这调用我们的函数特化模板该怎样做呢?我们只需在 main 函数中的最后一个 Equal 函数加上 <>,改为 cout << Equal<>( 0.001, 0.001 ) << endl;再来看看编译结果

图片.png

        我们看到已经已经成功的调用函数模板了。在工程中的建议是:当需要重载函数模板时,优先考虑使用模板特化;当模板特化无法满足需求时,再来使用函数重载!通过对类模板的学习,总结如下:1、泛型编程的思想是可以应用于类的;2、类模板以相同的方式处理不同类型的数据;3、类模板非常适用于编写数据结构相关的代码,它在使用时只能显示指定类型;4、类模板可以定义任意多个不同的类型参数;5、类模板可以被部分特化和完全特化,其特化的本质是模板的分开实现;6、函数模板只支持完全特化,工程中使用模板特化代替类(函数)重定义。


        欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083

猜你喜欢

转载自blog.51cto.com/12810168/2123318