模板的概念

在C++标准库中,几乎所有的代码都是模板代码。

模板是一种参数化的多态工具。

所谓参数化的多态性,是指将程序所处理 的对象的类型参数化,使一段程序代码可以用于处理多不同类型的对象。

采用模板编程,可以为各种逻辑功能相同而数据类型不同的程序提供一种代码共享的机制。

继承和组合提供了重用对象代码的方法,而模板提供了重用源代码的方法。


函数模板

所谓函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。

函数模板不是实际的函数,而是编译器用于生成一个或多个函数的 “模具”。在编写函数模板时,不必为形参、返回值或局部变量指定实际类型,而是使用类型形参来指定通用数据类型。当编译器遇到对函数的调用时,它将检查其实参的数据类型,并生成将与这些数据类型配合使用的函数代码。

一但定义了函数模板,就可以将类型参数用于函数定义和函数声明了。说得直白一点,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。

函数模板的定义

函数模板的一般说明形式如下:

template < 模板形参表>
返回值类型 函数名(模板函数形参表){
	//函数定义体
}

函数模板的定义以关键字template开头

template之后<>中是函数模板的参数列表

函数模板的参数是类型参数,其类型为classtypename

template<class T>
template<class T1, class T2>

模板的参数定义之后是函数模板的定义,是一个将类型参数作为某种类型使用的函数。

函数模板的参数名在模板中作为一种类型使用,可以用于函数的形参、函数返回值和函数的局部变量。

模板的每个形式参数要在函数的参数列表中至少出现一次。

形式参数名的作用域局限于函数模板的范围内。


函数模板的使用

函数模板规定了对数据的处理流程。

某些数据类型(模板的参数)要等到模板实例化时再确定具体的类型。

函数模板的实例化由编译器来完成
根据函数调用的实参类型确定模板形参的具体类型
用相应的类型替换函数模板中的模板参数完成函数模板的实例化


函数模板的示例

#include <iostream>
using namespace std;

#define SIZE 8
template<class ElementType>
void sortArray
(ElementType b[], int len)
{
    for(int pass=0;pass<len-1;
        pass++)
        for(int i = pass+1; i < len;i++)
            if ( b[ pass ] > b[ i ] )
            {
                ElementType hold;
                hold = b[ pass ];
                b[ pass ] = b[ i ];
                b[ i ] = hold;
            }
}

template<class ElementType >
void displayArray(ElementType b[],int len)
{
    for(int index=0;index<=len-1;
        index++)
        if ( index != len -1 )
            cout<<b[index]<<"\t";
        else
            cout<<b[index]<<endl;
}
int main()
{
    int ai[SIZE] = {18,35,36,61,9,112,77,12};
    double af[SIZE]={12.1, -23.8, 3.7, -16.0,9.1, 12.12, 7.7, 56.3};
    cout << "Before sorting:\n"<< "ai: \t";
    displayArray(ai, SIZE);
    sortArray(ai, SIZE);
    cout << "After sorting:\n"<< "ai: \t";
    displayArray(ai, SIZE);
    cout <<"Before sorting:\n"<< "af: \t";
    displayArray(af, SIZE);
    sortArray(af, SIZE);
    cout <<"After sorting:\n"<< "af: \t";
    displayArray(af, SIZE);

    return 0;
}

执行结果:

Before sorting:
ai:     18      35      36      61      9       112     77      12
After sorting:
ai:     9       12      18      35      36      61      77      112
Before sorting:
af:     12.1    -23.8   3.7     -16     9.1     12.12   7.7     56.3
After sorting:
af:     -23.8   -16     3.7     7.7     9.1     12.1    12.12   56.3
#include <iostream>
using namespace std;
template<typename T> 
void Swap(T &a, T &b){
    T temp = a;
    a = b;
    b = temp;
}
int main(){
    //交换 int 变量的值
    int a1 = 10, a2 = 20;
    Swap(a1, a2);
    cout<<"交换int变量的值:"<<a1<<", "<<a2<<endl;
   
    //交换 float 变量的值
    float b1 = 1.5, b2 = 2.5;
    Swap(b1, b2);
    cout<<"交换float变量的值:"<<b1<<", "<<b2<<endl;
   
    //交换 char 变量的值
    char c1 = 'A', c2 = 'B';
    Swap(c1, c2);
    cout<<"交换char变量的值:"<<c1<<", "<<c2<<endl;
   
    //交换 bool 变量的值
    bool d1 = false, d2 = true;
    Swap(d1, d2);
    cout<<"交换bool变量的值:"<<d1<<", "<<d2<<endl;
    return 0;
}

执行结果:

交换int变量的值:20, 10
交换float变量的值:2.5, 1.5
交换char变量的值:B, A
交换bool变量的值:1, 0

重载函数模板

C++语言允许一个函数模板可以使用多个模板参数或者重载一个函数模板。

用户可以用非模板函数重载一个同名的函数模板


类模板

类模板:将类定义中的数据类型参数化。

类模板实际上是函数模板的推广,可以用相同的类模板来组建任何类型的对象集合。

类模板的定义

template  <类型形参表>
class  <类名>
{     //类说明体  };

template  <类型形参表>
<返回类型> <类名> <类型名表>::<成员函数1>(形参表)
{     //成员函数定义体  };

template  <类型形参表>
<返回类型> <类名> <类型名表>::<成员函数2>(形参表)
{     //成员函数定义体  };
···
template  <类型形参表>
<返回类型> <类名> <类型名表>::<成员函数n>(形参表)
{     //成员函数定义体  };

类模板的示例

#include <iostream>
using namespace std;

template<class ElementType>
class Stack {
public:
    Stack( int = 8 );   // 省缺栈元素的树数目为8
    ~Stack(){ delete [] data; }
    int pop(ElementType &num);
    int push(ElementType num);
private:
    ElementType *data;      //栈数据存储
    int memNum;      //栈元素个数
    int size;      //栈大小
};

template<class ElementType>
Stack<ElementType>::Stack(int s) {
    size = s > 0 ? s : 8;
    data = new ElementType[size];
    memNum = 0;
}

template<class ElementType>
int Stack<ElementType>::pop
(ElementType &num) {
    if (memNum==0)
        return 0;
    num = data[ -- memNum ];
    return 1;
}

template<class ElementType>
int Stack<ElementType>::push
(ElementType mem){
    if (memNum == size)
        return 0;
    data[ memNum ++ ] = mem;
    return 1;
}

int main()
{
    Stack<double> doubleStack(6);
    double f = 3.14;
    cout<<"Pushing elements into doubleStack:\n";
    while (doubleStack.push(f))
    {
        cout << f << ' ';
        f += f;
    }
    cout << "\nStack is full. Cannot push " << f << " onto the doubleStack.";
    cout<<"\nPopping elements from doubleStack:\n";
    while (doubleStack.pop(f))
        cout<<f<<' ';
}

执行结果:

Pushing elements into doubleStack:
3.14 6.28 12.56 25.12 50.24 100.48
Stack is full. Cannot push 200.96 onto the doubleStack.
Popping elements from doubleStack:
100.48 50.24 25.12 12.56 6.28 3.14

派生类模板

通过继承可以产生派生类。

通过继承同样可产生派生的类模板。

几种不同的派生:①从一般类派生出类模板;②从类模板中派生出类模板

从一般类派生出类模版

一般类(其中不使用类型参数的类)作基类,派生出类模板(其中要使用类型参数)。

基本语法:

class CB 
{ 	
	//CB 为一般类
	... 	
};
template <class T> 
class CA:public CB { 
	//被派生出的CA 为类模板,使用了类型参数T
	T t; //私有数据为T 类型的 public:
	... 
}; 

从类模版派生出类模版

类模板作基类,派生出新的类模板。但仅基类中用到类型参数T(而派生的类模板中不使用T)。

基本语法:

template <class T> 
class CB { //CB 为类模板
	T t; //私有数据为T 类型的 
public:
	T gett(){ //用到类型参数T return t; } 
	... 
}; 

template <class T> 
class CA:public CB<T> {//CA 为类模板,其基类CB 为类模板
//将被“传递”给基类CB,本派生类中并不使用该类型参数T 
	double t1; //私有数据成员 
	... 
};

类模板作基类,派生出新的类模板,且基类与派生类中均使用同一个类型参数T。

template <class T> 
class CB { 
	//CB 为类模板(其中使用了类型参数T),它将作为类模板CA 的基类T t; //数据成员为T 类型的 
	public:
		T gett(){ //用到类型参数T
			return t;
		} 
		... 
	}; 
template <class T> 
class CA:public CB<T> { //CA 为类模板,其基类CB 也为类模板。注意,类型参数T 将被“传递”给基类CB;本派生类中也将使用这同一个类型参数T 
	T t1; //数据为T 类型的 
public: ...};

类模板作基类,派生出新的类模板,但基类中使用类型参数T2,而派生类中使用另一个类型参数T1(而不使用T2)。

template <class T2> class CB { 
  	//CB 为类模板(其中使用了类型参数T2),它将作为类模板CA 的基类
		T2 t2; //数据为T2 类型的 public: 
		... 
}; 
template <class T1, class T2>
 class CA:public CB<T2> { //CA 为类模板,其基类CB 也为类模板。注意,类型参数T2 将被“传递”给基类CB;本派生类中还将使用另一个类型参数T1 
	T1 t1; //数据为T1 类型的 public: 
	... 
}; 

类模板继承示例

#include <iostream>
using namespace std;

class CMyString:public basic_string<char>
{
public:
    void Trim();
    void LTrim();
    void RTrim();
    CMyString();
    CMyString(const char *s);
};

void CMyString::LTrim(){
    string::size_type len;
    if ((len=this->size())==0){
        return;
    }
    this->erase(0,this->find_first_not_of(" \t",0));
}

void CMyString::RTrim(){
    string::size_type len;
    if ((len=size())==0){
        return;
    }
    this->erase(this->find_last_not_of(" \t")+1);
}

CMyString::CMyString():basic_string<char>()
{
}
CMyString::CMyString(const char *s):basic_string<char>(s)
{
}
void CMyString::Trim(){
    LTrim();
    RTrim();
}
int main(){
    CMyString str1("\t   Hello World    ");
    str1.Trim();
    cout<<str1<<endl;

    return 0;
}

执行结果:

Hello World

派生类和模板

为了运行的效率,类模板是相互独立的,即独立设计,没有使用继承的思想。对类模板的扩展是采用适配子(adapter)来完成的。通用性是模板库的设计出发点之一,这是由泛型算法和函数对象等手段达到的。

派生类的目标之一也是代码的复用和程序的通用性,最典型的就是MFC,派生类的优点是可以由简到繁,逐步深入,程序编制过程中可以充分利用前面的工作,一步步完成一个复杂的任务。

模板追求的是运行效率,而派生追求的是编程的效率。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐