C++中的模板


一、为什么使用模板

1、C/C++是一种静态类型语言(预处理->汇编->编译->链接),好处是速度快,缺点是实现通用代码麻烦。例如:实现支持所有类型的快速排序。
2、借助函数重载实现通用代码,好处是实现简单,但代码段会增加。
3、借助宏函数实现通用代码,类型检查不严格。
4、借助回调函数实现通用代码,使用麻烦。
5、由于以上原因C++之父在C++实现了模板技术,让C++能够支持泛型编程。

二、函数模板

1、函数模板的定义

  template <typename 类型参数1,typename 类型参数2,...>
  类型参数1 函数名(参数类型2 参数名)
  {
      return 返回值;
  }

  template <typename T>
  T find(T* arr,size_t len)
  {
     return val;
  }

可以用任何标识符作为类型参数名,但使用‘T’ 是约定俗成的,它表示调用这个函数时所指定的任意类型。

2、函数模板的使用

    C++编译的编译器并不是把模板编译成一个可以处理任何类型的单一实体,而是根据模板的使用者的参数,产生不同的函数的实体。
    根据具体类型代表模板参数生成函数实体过程叫实例化。

模板是在使用时才实例化,可以自动实例化,也可以手动实例化(在函数调用时函数名与小括号之间加<类型参数>)。

每个函数模板都会进行二次编译,第一次编译在实例化之前,检查模板代码本身是否正确,第二次是实例化过程中,结合所使用类型参数,再次检查模板代码,是否所有的代码都有效。  
注意:第二次编译才会生成二进制指令,第一次编译仅仅是在编译器内部生成一个用于描述模板的数据结构。
#include <iostream>
using namespace std;

template <typename T,typename T1>
T Max(T val1,T1 val2)
{
    return val1 > val2 ? val1 : val2;
}

class A
{
    int val;
public:
    A(int val=0):val(val) {}
    bool operator>(A& that)
    {
        return val > that.val;
    }
};

int main()
{
    cout << Max('a',100) << endl;
    cout << Max(100,99) << endl;
    cout << Max(3.14,2.18) << endl;

    A a1,a2;
    Max(a1,a2);
}

3、函数模板的隐式推断

    函数模板虽然可以手动实例化,但使用麻烦,因此一般都根据参数类型进行隐式推断模板的参数。
    注意:不能隐式推断的三种情况
    1、函数参数与模板参数类型没有关系
    2、不允许隐式类型转换
    3、返回值类型不能隐式推断
#include <iostream>
using namespace std;

char arr[10];

template <typename T>
T Min(T v1,T v2)
{
    cout << "T" << endl;
    return v1 <v2 ? v1 : v2;
}

char Min(char ch1,char ch2)
{
    cout << "char" << endl;
    return ch1 < ch2 ? ch1 : ch2;
}

template <typename T,typename R>
R func(T a)
{
    return 10;
}

int main()
{
    //cout << Min(3.14,12) << endl;
    cout << Min('a','h') << endl;
    cout << Min<int>(3.14,12) << endl;
    long temp = func<int,long>(100);
}

4、函数模板与默认形参之间有冲突。

5、普通函数与同名的模板函数构成重载,编译器会优先调用普通函数,如果实现一个与模板函数功能一致的普通函数,那么这叫做模板函数的特化。

注意:一般char*类型都需要特化。

三、类模板

1、类模板的定义

 template <typename M,typename R,typename A,typename O...>
 class Test
 {
 public:
     M val;
     Test(A a)
     {
         O var;
     }
     R func(void)
     {

     }
 };

2、类模板的使用

    类模板的参数不支持隐式推断,必须显示指定类型参数。
    类名<类型...> 对象;

类模板分为两步进行实例化:
    编译期:编译器将类模板实例化类,并生成类对象创建指令。
    运行期:处理器执行类对象创建指令,将类实例化为对象。
    类模板也是一种静态多态。

类模板中,只有那些被调用的成员函数才实例化出代码,即产生二进制指令(调用谁实例化谁)。

3、类模板中的静态成员

    静态成员需要在类外定义,这一点不改变,但与普通类的定义不同。
    template <typename ...> 类型 类名<...>::成员名;
#include <iostream>

using namespace std;

template <typename A>
class Test
{
public:
    static A a;
    static int b;
    Test(A arg)
    {
        cout << arg << endl;
    }
};

template<typename A> A Test<A>::a;
template<typename A> int Test<A>::b;

int main()
{
    //Test* t = new Test(100);
    Test<int> t(100);
}

4、递归实例化

    类模板的参数可以是任何类型,只有该类型提供类模板所需要的功能。
    类模板的实例化已经是一个有效的类型了,因此它也可以当作类模板的参数,这种叫作递归实例化。
    Vectors<Vectors<int>> //二维数组
    Test<Test<int>>

5、类的局部特化

    当类的某个成员函数不能通用,需要对特殊类型(char*)实现一个特殊版本,这叫类的局部特化。
    template<> 类型 返回值类型 类名<类型>::函数名(参数)
    {

    }
    注意:在类外实现

6、全类特化

    当需要针对某种类型对类全部实现一个特殊版本,这种叫类的全类特化。


template <> 类名<类型>
{
    ...
};
#include <iostream>
#include <string.h>
using namespace std;

template <typename T>
class Compare
{
    T a,b;
public:
    Compare(T a,T b):a(a),b(b) { }
    const T& max(void)
    {
        return a > b ? a : b;
    }
    const T& min(void)
    {
        return a < b ? a : b;
    }
};
/*
template<> const char* const& Compare<const char*>::max(void)
{
    cout << "------" << endl;
    if(1 == strcmp(a,b))
        return a;
    else
        return b;
}
*/
template<> class Compare<const char*>
{
    const char* str1;
    const char* str2;
public:
    Compare(const char* str1,const char* str2)
    {
        this->str1 = str1;
        this->str2 = str2;
    }

    const char* max(void)
    {
        if(1 == strcmp(str1,str2))
            return str1;
        else
            return str2;
    }
    const char* min(void)
    {
        if(1 == strcmp(str1,str2))
            return str2;
        else
            return str1;
    }
};

int main()
{
    Compare<const char*> com("ad","adf");;
    cout << com.max() << endl;

}

7、类模板的缺省值

    类模板的类型参数可以设置默认值类型,规则与函数的默认形参基本一致(设置缺省值类型靠右)。
    后面的类型参数可以使用前面的类型,但前面不能使用后面的。

8、普通数据也可以作为模板参数

template <typename T,类型 B>
{
   int arr[B];
}

给类模板一个数据,在类中就可以像使用宏明一样使用参数。
注意:实例化类中提供的数据必须是常量。
#include <iostream>
#include <typeinfo>
using namespace std;

template <typename A=int,typename B=A>
class Test
{
public:
    Test(void)
    {
        cout << typeid(A).name() << " " << typeid(B).name() << endl;
    }
};

int main()
{
    Test<> t;
}

模板的技巧

1、typename可以用class代替
2、不能直接使用模板父类的成员

#include <iostream>
using namespace std;

template <class T>
class Base
{
public: 
    void func(void)
    {
        cout << "bbbb" << endl;
    }
};

template <typename T>
class Test:public Base<T>
{
public:
    Test(void)
    {
        Base<T>::func();
    }
};

int main()
{
    Test<int> t;
}
3、在类模板中可以定义虚函数(多态),但虚函数不能是模板函数。

Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Love丶伊卡洛斯 !
评论
 Previous
C++中STL相关知识 C++中STL相关知识
STL介绍 STL标准模板库,由惠普实验室提供,里面集成了常用的数据结构类模板和算法函数模板等。 容器:用来存储各种类型数据的数据结构。 迭代器:类似于专门用来指向容器成员的指针,用来遍历、操作、管理容器中的成员,可以大大
2019-08-31
Next 
C++中类的继承(二) C++中类的继承(二)
一、子类的构造、析构、拷贝1、子类的构造在执行它的构造函数前会根据继承表的顺序执行父类的构造函数。 默认执行父类的无参构造 显示调用有参构造,在子类的构造函数后,初始化列表中显示调用父类的有参构造函数。 2、子类在它的析构
2019-08-31
  TOC