17. C++中的类模板

在学习类模板之前,建议先学习下函数模板的内容。

类是C++的核心,那是否能够将函数模板的思想应用于类呢?

类模板

一些类主要用于存储和组织数据元素。如:数组类,链表类,Stack类,Queue类等等。C++中可以将模板的思想应用于类,使得类可以不关注具体所操作的数据类型,而只关注类所需要实现的功能。

C++中的类模板,提供一种特殊的类以相同的行为处理不同的类型,在类声明前使用template进行标识,<typename T>用于说明类中使用的泛指类型T。

声明的泛指类型 T可用于声明成员变量和成员函数
       编译器对类模板的处理方式和函数模板相同
              1. 编译器从类模板通过具体类型产生不同的类;
              2. 编译器在声明的地方对类模板代码本身进行编译;
              3. 编译器在使用的地方对参数替换后的代码进行编译;

类模板的应用
               使用具体类型定义对象。注:类模板不支持自动类型推导定义对象。

#include <cstdlib>
#include <iostream>

using namespace std;

template<typename T>
class Operator
{
public:
    T add(T a, T b);
    T minus(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;
}

int main(int argc, char *argv[])
{
    Operator<int> op1;
    Operator<double> op2;
    
    cout<<op1.add(5, 4)<<endl;
    cout<<op1.minus(4, 5)<<endl;
    
    cout<<op2.add(1.3, 0.01)<<endl;
    cout<<op2.minus(0.01, 1.3)<<endl;
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}

编译运行结果如下:

注:在模板类外部定义成员函数的实现时,需要加上template<typename T>的声明。

 

类模板的工程应用
               由于类模板的编译机制不同,所以不能像普通类一样分开实现后(即cpp文件实现各个成员函数的功能,.h文件中定义),然后在使用时只包含头文件。在工程实践上,一般会把类模板的定义直接放到头文件中。只有被调用的类模板成员函数才会被编译器生成可执行代码!!!

通过类模板实现数组类。

//Array.cpp
#ifndef _ARRAY_DEF_H_
#define _ARRAY_DEF_H_

#include "Array.h"

template<typename T>
Array<T>::Array(int length)
{
    if( length < 0 )
    {
        length = 0;
    }
    
    mLength = length;
    mSpace = new T[mLength];
}

template<typename T>
Array<T>::Array(const Array& obj)
{
    mLength = obj.mLength;
    
    mSpace = new int[mLength];
    
    for(int i=0; i<mLength; i++)
    {
        mSpace[i] = obj.mSpace[i];
    }
}

template<typename T>
int Array<T>::length()
{
    return mLength;
}

template<typename T>
Array<T>::~Array()
{
    mLength = -1;
    
    delete[] mSpace;
}

template<typename T>
T& Array<T>::operator[](int i)
{
    return mSpace[i];
}

template<typename T>
Array<T>& Array<T>::operator= (const Array<T>& obj)
{
    delete[] mSpace;
    
    mLength = obj.mLength;
    
    mSpace = new int[mLength];
    
    for(int i=0; i<mLength; i++)
    {
        mSpace[i] = obj.mSpace[i];
    }
    
    return *this;
}

template<typename T>
bool Array<T>::operator== (const Array<T>& obj)
{
    bool ret = true;
    
    if( mLength == obj.mLength )
    {
        for(int i=0; i<mLength; i++)
        {
            if( mSpace[i] != obj.mSpace[i] )
            {
                ret = false;
                break;
            }
        }
    }
    else
    {
        ret = false;
    }
    
    return ret;
}

template<typename T>
bool Array<T>::operator!= (const Array& obj)
{
    return !(*this == obj);
}
#endif

//Array.h
#ifndef _ARRAY_H_
#define _ARRAY_H_

template<typename T>
class Array
{
private:
    int mLength;
    T* mSpace;

public:
    Array(int length);
    Array(const Array& obj);
    int length();
    ~Array();
    T& operator[](int i);
    Array& operator= (const Array& obj);
    bool operator== (const Array& obj);
    bool operator!= (const Array& obj);
};
#endif
#include <cstdlib>
#include <iostream>
#include "Array.h"

using namespace std;

int main(int argc, char *argv[])
{
    Array<int> ai(5);
    
    for(int i=0; i<ai.length(); i++)
    {
        ai[i] = i + 1;
    }
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}

我们正常的逻辑是这样写代码,头文件中写定义,cpp文件中实现好,然后主程序中只要包含头文件就好。但是如果是类模板这不能这样做,这样会导致编译不能通过。编译过程如下所示:

不难发现,编译器报了编译链接的错误,并且提示未定义。可我们明明在Array.cpp中定义 了,为什么会链接不到呢???这是因为编译器对模板和普通函数/普通类的的处理方式是不同的。

编译器对普通函数和普通类进行编译的时候,会将上述工程中的三个文件进行编译,因此会找到对应的定义,则会链接成功,不会编译报错。

编译器对类模板进行编译的时候,第一次编译器首先对这三个文件进行语法检查,当发现Array.cpp中都是模板的时候,只会进行类型申明、参数是否合法的检查,不会生成代码。当编译main.cpp的ai[i] = i + 1;这条语句的时候,会对Array.cpp的类模板进行进行第二次编译,main.cpp中并没有说明怎么生成ai[i]的代码,即main.cpp和Array.cpp没有任何的关系。因此就编译出错了。为了解决这个问题,做了已下改进。

//Array.hpp

#ifndef _ARRAY_DEF_H_
#define _ARRAY_DEF_H_

#include "Array.h"

template<typename T>
Array<T>::Array(int length)
{
    if( length < 0 )
    {
        length = 0;
    }
    
    mLength = length;
    mSpace = new T[mLength];
}

template<typename T>
Array<T>::Array(const Array& obj)
{
    mLength = obj.mLength;
    
    mSpace = new int[mLength];
    
    for(int i=0; i<mLength; i++)
    {
        mSpace[i] = obj.mSpace[i];
    }
}

template<typename T>
int Array<T>::length()
{
    return mLength;
}

template<typename T>
Array<T>::~Array()
{
    mLength = -1;
    
    delete[] mSpace;
}

template<typename T>
T& Array<T>::operator[](int i)
{
    return mSpace[i];
}

template<typename T>
Array<T>& Array<T>::operator= (const Array<T>& obj)
{
    delete[] mSpace;
    
    mLength = obj.mLength;
    
    mSpace = new int[mLength];
    
    for(int i=0; i<mLength; i++)
    {
        mSpace[i] = obj.mSpace[i];
    }
    
    return *this;
}

template<typename T>
bool Array<T>::operator== (const Array<T>& obj)
{
    bool ret = true;
    
    if( mLength == obj.mLength )
    {
        for(int i=0; i<mLength; i++)
        {
            if( mSpace[i] != obj.mSpace[i] )
            {
                ret = false;
                break;
            }
        }
    }
    else
    {
        ret = false;
    }
    
    return ret;
}

template<typename T>
bool Array<T>::operator!= (const Array& obj)
{
    return !(*this == obj);
}

#endif

//Array.h

#ifndef _ARRAY_H_
#define _ARRAY_H_

template<typename T>
class Array
{
private:
    int mLength;
    T* mSpace;

public:
    Array(int length);
    Array(const Array& obj);
    int length();
    ~Array();
    T& operator[](int i);
    Array& operator= (const Array& obj);
    bool operator== (const Array& obj);
    bool operator!= (const Array& obj);
};

#endif
//main.cpp
#include <cstdlib>
#include <iostream>
#include "Array.hpp"

using namespace std;

int main(int argc, char *argv[])
{
    Array<int> ai(5);
    
    for(int i=0; i<ai.length(); i++)
    {
        ai[i] = i + 1;
    }
    
    for(int i=0; i<ai.length(); i++)
    {
        cout<<ai[i]<<endl;
    }
    
    Array<double> ad(10);
    
    for(int i=0; i<ad.length(); i++)
    {
        ad[i] = (i + 1) / 100.0;
    }
    
    for(int i=0; i<ad.length(); i++)
    {
        cout<<ad[i]<<endl;
    }
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}

编译运行结果如下:

类模板的特化

        类模板可以被特化,用template<>声明一个类时,表示这是一个特化类。

特化类模板的意义,当类模板在处理某种特定类型有缺陷时,可以通过类模板的特化来克服处理这种特定类型带来的不足。注:编译器优先选择特化类生成对象。

#include <cstdlib>
#include <iostream>

using namespace std;

template<typename T>
class Test
{
public:
    T test(T v)
    {
        cout<<"T test(T v)"<<endl;
        cout<<"sizeof(T) = "<<sizeof(T)<<endl;
        
        return v;
    }
};

template<>
class Test<int>
{
public:
    int test(int v)
    {
        cout<<"int test(int v)"<<endl;
        cout<<"sizeof(int) = "<<sizeof(int)<<endl;
        
        return v;
    }
};

int main(int argc, char *argv[])
{
    Test<int> t1;
    
    cout<<t1.test(1)<<endl;
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}

编译运行结果如下:

 

类模板可以定义多个类型参数:

#include <cstdlib>
#include <iostream>

using namespace std;

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

int main(int argc, char *argv[])
{
    Test<double, int> t;
    
    t.add(10.0001, 8);
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}

编译运行结果如下:

 

类模板可以被局部特化,可以指定类模板的特定实现,并要求某些类型参数仍然必须得模板的用户指定。注:编译器优先选择特化类。

#include <cstdlib>
#include <iostream>

using namespace std;

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


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

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

int main(int argc, char *argv[])
{
    int i = 0;
    int j = 0;
    
    Test<double, int> t; // <T, int>
    Test<long, long> ti; // <T1, T2>
    Test<float, int> tt; // <T, int>
    Test<int*, int*> tp; // <T*, T*>
    
    t.add(10.0001, 8);
    ti.add(2, 3);
    tt.add(4, 5);
    tp.add(&i, &j);
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}

编译运行结果如下:

为什么需要特化,而不重新定义新类?
               特化和重新定义新类看上去没有本质区别,但是如果定义新类,那么将变成一个类模板和一个新类,使用的时候需要考虑究竟是用类模板还是用新类。而特化可以统一的方式使用类模板和特化类,编译器自动优先选择特化类。

 

函数模板和类模板的模板参数可以是普通数值。

#include <cstdlib>
#include <iostream>

using namespace std;

template<typename T, int N>
void func()
{
    T array[N] = {0};
    
    for(int i = 0; i < N; i++)
    {
        array[i] = i + 1;
        
        cout<<array[i]<<" ";
    }
    
    cout<<endl;
}

int main(int argc, char *argv[])
{
    func<int, 5>();
    func<float, 10>();
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}

编译运行结果如下:

 

非类型模板参数与特化

#include <cstdlib>
#include <iostream>

using namespace std;

template<int N>
class Sum
{
public:
    static const int VALUE = Sum<N - 1>::VALUE + N;
};

template<>
class Sum<1>
{
public:
    static const int VALUE = 1;
};

int main(int argc, char *argv[])
{
    cout<<Sum<10>::VALUE<<endl;
    cout<<Sum<100>::VALUE<<endl;
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}

编译运行结果如下:

 

非类型模板参数的限制

              1. 变量不能作为模板参数;
              2. 浮点数和类对象不能作为模板参数;
              3. 全局指针不能作为模板参数;

        注:编译器的推导过程是在编译阶段完成的。因此,编译器的推导必须依赖于特化类,否则推导过程无法结束。

 

在实际工程中内存操作是bug的重要来源。C++将堆内存交由程序员自由使用,因此:

       1. 未及时释放,将产生内存泄漏;

        2. 重复释放同一段内存,行为未知;

        3. 使用越界,操作了不属于自己的内存;

 

怎样最大限度的避开上述的使用问题?

内存越界的问题常发生于数组的使用中
        解决方案:数组类
        工程中,在非特殊情况下,要求开发者使用预先编写的数组类对象代替C语言中的原生数组。

内存泄漏和内存多次释放常发生于指针的使用过程中
        解决方案:智能指针
       工程中,要求开发者使用预先编写的智能指针类对象代替C语言中的原生指针

 

什么是智能指针?

工程中的智能指针是一个类模板
               1. 通过构造函数接管申请的堆内存;
               2. 通过析构函数确保堆内存被及时释放;
               3. 通过重载指针运算符 * 和 -> 模拟指针的行为;
               4. 通过重载比较运算符 == 和 != 模拟指针的比较;

//main
#include <cstdlib>
#include <iostream>
#include "SmartPointer.hpp"

using namespace std;

class Test
{
public:
    int i;
    void print()
    {
        cout<<i<<endl;
    }
};

int main(int argc, char *argv[])
{
    SmartPointer<int> pi = new int(5);
    SmartPointer<Test> pt = new Test();
    
    cout<<*pi<<endl;
    
    *pi = 10;
    
    cout<<*pi<<endl;
    
    pt->i = 20;
    pt->print();
    
    cout << "Press the enter key to continue ...";
    cin.get();
    return EXIT_SUCCESS;
}
//SmartPointer.hcpp

#ifndef _SMARTPOINTER_DEF_H_
#define _SMARTPOINTER_DEF_H_

#include "SmartPointer.h"

template<typename T>
SmartPointer<T>::SmartPointer()
{
    m_pointer = NULL;
}

template<typename T>
SmartPointer<T>::SmartPointer(const T* pointer)
{
    m_pointer = const_cast<T*>(pointer);
}

template<typename T>
SmartPointer<T>::~SmartPointer()
{
    delete m_pointer;
}

template<typename T>
T* SmartPointer<T>::operator->()
{
    return m_pointer;
}

template<typename T>
T& SmartPointer<T>::operator*()
{
    return *m_pointer;    
}    

#endif
//SmartPointer.h
#ifndef _SMARTPOINTER_H_
#define _SMARTPOINTER_H_

template<typename T>
class SmartPointer
{
protected:
    T* m_pointer;
public:
    SmartPointer();
    SmartPointer(const T* pointer);
    ~SmartPointer();
    T* operator->();
    T& operator*();
};

#endif

编译运行结果如下:

 

小结:

      1. 类模板中可以有一个或多个未指定的泛指类型
              2. 可以在需要的特化类模板
              3. 特化可以统一的方式使用类模板和新定义的类
              4. 特化类总是被编译器优先选择使用
              5. 模板的参数可以是普通数值
              6. 数组类和智能指针可以最大限度的避免内存相关的bug
 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值