C++之泛型编程笔记

来源:互联网 发布:日本心神战斗机 知乎 编辑:程序博客网 时间:2024/05/18 02:37

不考虑具体数据类型的编程模式就叫泛型编程。

1.    函数模板

提供一种特殊的函数可用不同类型进行调用

看起来和普通函数很相似似 ,区别是类型可被参数化。

定义为

template <typename T>

template告诉编译器即将开始泛型编程,typename T告诉后面的程序,T是一个泛指类型。

void Swap(T &a , T &b)

{

T t = a;

a = b;;

a=t;

}

函数模板的应用:泛型调用变量有两种方式,一种是编译器自动类型推导调用。

第二种是具体类型显示调用。

Int a =1;

Int b = 2;

Swap(a,b);//自动类型推导,自动推导为int 型

Float fa =3;

Float fb =4;

Swap<float>(fa,fb);//显示类型调用,用float替换参数类型T

 

例如:下面是用泛型实现的排序程序。

template<typename T>

void Swap(T& a, T& b)

{

    Tt = a;

    a= b;

    b= t;

}

 

template<typename T>

void SelectSort(T array[], int length)

{

   for(int i=0; i<length; i++)

    {

       T min = array[i];

       int index = i;

       for(int j=i+1; j<length; j++)

       {

           if( array[j] < min )

           {

                min = array[j];

                index = j;

           }

       }

       Swap(array[i], array[index]);

    }

}

调用时,

 intarray[] = {3, 2, 5, 3 , 4};

 SelectSort<int>(array, 5);

 charca[] = {'b', 'c', 'a', 'e', 'd', 'f'};

 SelectSort(ca,6);

函数模板的深入理解:编译器并不是把函数模板处理成能够处理任意类型的函数,编译器从函数模板通过具体类型产生不同的函数,编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

2,.函数模板与重载

函数模板可以像普通函数一样被重载,C++编译器优先考虑普通函数,如果函数模板可以产生一个更好的匹配,那么选择模板。可以通过空模板实参列表的语法限定编译器只通过模板匹配。

int Max(int a, int b)

{

   cout<<"int Max(int a, int b)"<<endl;

   return a > b ? a : b;

}

 

template<typename T>

T Max(T a, T b)

{

   cout<<"T Max(T a, T b)"<<endl;

    return a > b ? a : b;

}

 

template<typename T>

T Max(T a, T b, T c)

{

   cout<<"T Max(T a, T b, T c)"<<endl;

   return Max(Max(a, b), c);

}

使用时

  inta = 1;

  intb = 2;

 cout<<Max(a, b)<<endl;//优先选择原型

 cout<<Max<>(a, b)<<endl;//这个地方优先选择函数模板。

cout<<Max(3.0, 4.0)<<endl;//调用函数模板,因为是float型

函数模板不允许自动类型转化,普通函数可以,例如:

Max(‘a’,100);这个函数一定会调用普通函数,不会调用模板函数,因为模板函数不会进行自动类型转换。

函数模板可以定义任意多个不同的类型参数,

例如:template<typenameT1,typename T2,typename RT>

RT Add(T1 a , T2  b)

{

Return static_cast<RT>(a +b);

}

Add<char ,float,double>(’a’,100);///函数模板返回RT类型,

当声明的类型参数为返回值类型时,无法进行自动类型推导。

多参数函数模板不完美解决方案:将返回类型参数声明到第一个参数位置,调用时只需声明返回类型参数即可。

Add< double>(’a’,100);这样调用也可以,参数位置指定了返回值类型,从而c++编译器会进行推导。

3类模版

一些类主要用于存储和组织数据元素,如:数组类,链表类,stack类,queue类等

C++中可以将模板的思想应用于类,使得类可以不关注具体所操作的数据类型,而只关注类所需要实现的功能。

 

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

 

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

 

类模板的应用 : 使用具体类型定义对象 比如类模板Operator<int>  op1;

Operator<double>  op2;

这个定义完后编译器就会编译出两个类对象,,所以下面对于这两个类的使用就和以前的类一样了

op1.add(5,4);

op2.add(1.5,0.01);

以下面的array类为例

在Array.h中:

template<typename T>//声明一个泛型T

classArray

{

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);

};

.h文件中至少类模版的声明,,类模版的定义还需要实现,,在Array.hpp中,

#include"Array.h"

template<typename T>//在每为一个类模板的成员函数做定义时,上面都得声明,下面的函数是泛型函数。

Array<T>::Array(intlength)

{

    if( length < 0 )

    {

        length = 0;

    }

    mLength = length;

    mSpace = new T[mLength];

}

template<typename T>

Array<T>::Array(constArray& obj)

{

    mLength = obj.mLength;

    mSpace = new int[mLength];

    for(int i=0; i<mLength; i++)

    {

        mSpace[i] = obj.mSpace[i];

    }

}

 

template<typename T>

intArray<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>

boolArray<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>

boolArray<T>::operator!= (const Array& obj)

{

    return !(*this == obj);

}

 

上面是一个泛型类模板的实现,确实非常高级,感觉很高大上。

 

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

template<typenameT>

classTest

{

public:

    Ttest(T v)

    {

        cout<<"T test(Tv)"<<endl;

        cout<<"sizeof(T) ="<<sizeof(T)<<endl;

       

        return v;

    }

};

特化类

template<>

class Test<int>

{

public:

    int test(int v)

    {

        cout<<"inttest(int v)"<<endl;

        cout<<"sizeof(int)= "<<sizeof(int)<<endl;

       

        return v;

    }

};

特化类模板的意义:当类模板在处理某种特定类型有缺陷时,可以通过类模板的特化来克服处理这种特定类型带来的不足。

 

类模版可以定义多个类型参数

4.类模板的局部特化

类模版可以定义多个类型参数

template<typenameT1, typename T2>

classTest

{

public:

    void add(T1 a, T2 b)

    {

        cout<<(a + b)<<endl;

    }

};

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

Template<typename T1,typenameT2>

ClassTest

{

Public :

     Void add(T1 a ,T2 b)

     {

     Cout<<(a+b)<<endl;

}

}

类模板局部特化

template<typename T>

classTest<T, T>

{

public:

    void add(T a, T b)

    {

        cout<<"add(T a, Tb)"<<endl;

        cout<<static_cast<T>(a +b)<<endl;

    }

};

 

template<typename T>

classTest<T, int>

{

public:

    void add(T a, int b)

    {

        cout<<"add(T a, intb)"<<endl;

        cout<<a + b<<endl;

    }

};

template<typename T1, typename T2>

classTest<T1*, T2*>

{

public:

    void add(T1* a, T2* b)

    {

        cout<<"add(T1* a, T2*b)"<<endl;

    }

};

调用时:

Test<double,int> t; // <T, int>

Test<long,long> ti; // <T1, T2>

Test<float,int> tt; // <T, int>

Test<int*,int*> tp; // <T*, T*>

 

为什么要特化模版类而不重新定义类呢?

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

5.非类型模板参数与特化

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

 

template<int N>

class Sum

{

public:

    static const int VALUE = Sum<N -1>::VALUE + N;

};

 

template<>

classSum<1>

{

public:

    static const int VALUE = 1;

};

使用这个模板类后可以快速算出1~n自然数的和。

cout<<Sum<10>::VALUE<<endl;

cout<<Sum<100>::VALUE<<endl;

速度很快,,是直接输出的,因为编译器已经算好了。

非类型模板参数的限制:变量不能作为模板参数,浮点数和类对象不能作为模板参数,全局指针不能作为模板参数。编译器的推导过程是在编译阶段完成的因此,编译器的推导必须依赖于特化类,否则推导过程无法结束。

 

在实际工程中内存操作的BUG主要有:未及时释放内存,产生内存泄漏,重复操作同一段内存,导致行为未知,使用越界,操作了不属于自己的内存。

内存越界的问题常发生于数组的使用中,所以在一般情况下,开发者应该预先编写数组类对象代替c语言中的原生数组。内存泄漏和内存多次释放常发生于指针的使用过程中,开发者可以预先编写智能指针类对象代替c语言中的原生指针。工程中的智能指针是一个类模板,主要实现了:通过构造函数接管申请的堆内存,通过析构函数确保堆内存被及时释放,通过重载指针运算符* 和->  模拟指针的行为,通过重载比较运算符==和!=模拟指针的比较。
0 0
原创粉丝点击