C++模板

C++模板

模板是泛型编程的基础,泛型编程,即以一种独立于任何特定类型的方式编码。模板是创建泛型类或函数的蓝图或公式。C++中使用template关键字来定义模板。

在理解泛型之前,首先我们需要了解什么时泛型编程,简单点来说,因为C++是一门强类型语言(静态语言),所以无法做到像动态语言(python javascript等)那样子,编写一段通用的逻辑,可以把任意类型的变量传进去处理。泛型编程弥补了这个缺点,通过把通用逻辑设计为模板,摆脱了类型的限制,提供了继承机制以外的另一种抽象机制,极大地提升了代码的可重用性。

模板:用来定义函数。使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

函数模板

函数模板针对仅参数类型不同的函数,通俗点讲就是,一个函数模板可以处理相同操作逻辑的多种数据类型,具体是什么类型的数据,是在调用函数时传入的参数类型决定的。
函数的模板一般形式如下所示:

// implement strcmp-like generic compare function
// returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

模板定义以template关键字开始,后接模板形参表(template parameter list),模板形参表时用尖括号括住的一个或多个模板形参,形参之间以逗号分隔。

  1. 模板形参列表
     模板形参表很像函数形参表,函数形参表定义了特定类型的局部变量但并不初始化那些变量,在运行时再提供实参来初始化形参。
     同样,模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参(后面说明)。类型形参跟在关键字class和typename之后定义,例如,class T是名为T的类型形参,在这里class和typename没有区别。

  2. 使用函数模板
     使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。实际上,编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参。
    对于以下的调用:

int main(){
    //T is int
    //compiler instantiates int compare(const int&, const int&)
    cout << compare(1,0) << endl;

    //T is string
    //compiler instantiates int compare(const string &, const string &)
    string s1 = "hi", s2 = "world";
    cout << compare(s1, s2) << endl;
    return 0;
}
  1. inline函数模板
    函数模板可以用与非模板函数一样的方式声明为inline。说明符(inline)放在模板形参表(template < typename T>)之后、返回类型之前,不能放在关键字template之前。看下面的例子:
//ok:inline specifier follows template parameter list
template <typename T> inline T min(const T&, const T&);
//error: incorrect placement of inline specifier
inline template <typename T> T min(const T&, const T&);
  1. 成员函数模板
    也可以为类成员函数定义模板,如下所示:
#include<iostream>
#include<string>
using namespace std;
class Printer {
public:
    template<typename T>
    void print(const T& t) {
        cout << t <<endl;
    }
};
int main(){
    Printer p;
    p.print("abc");
    p.print(56);
    return 0;
}

类模板

就像定义模板函数,我们也可以定义类模板,类模板一般形式如下所示:

template <class type> class class-name {
//类主体
}
//使用方法
class-name<类型> c;

模板形参

像函数形参一样,程序员为模板形参选择的名字没有本质的含义。也就是说,模板形参惨的名称可以由程序员自己定义,如下所示都是合法的:

template<typename A>
template<typename B>

模板形参作用域
模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字:

typedef double T;
template <class T> T calc(const T &a, const T &b){
    //tmp has the type of the template parameter T
    //not that of the global typedef
    T tmp = a;
    //..
    return tmp;
}

使用模板形参名字的限制
用作模板形参的名字不能在模板内部重用:

template <class T> T calc(const T &a, const T &b){
    typedef double T; //error:redeclares template parameter T
    T tmp = a;
    // ...
    return tmp;
}

这个限制还意味着模板形参的名字只能在同一模板形参表中使用一次:

//error:illegal reuse of template parameter name v
template<class v, class v> v calc(const v&, const v&);

当然,正如可以重用函数形参名字一样,模板形参的名字也能在不同模板中重用:

template <class T> T calc(const T&, const T&);
template <class T> T compare(const T&, const T&);

模板声明
像其他任意函数或类一样,对于模板可以只声明而不定义。声明必须指出函数或类是一个模板:

//declares compare but does not define it
template <class T> int compare(const T&, const T&);

同一模板的声明和定义中,模板形参的名字不必相同:

//all three uses of calc refer to the same function template
//forward declarations of the template
template <class T> T calc(const T&, const T&);
template <class U> U calc(const U&, const U&);
//actual definition of the template
template <class Type>
Type calc(const Type& a, const Type& b){...}

每个模板类型形参前面必须带上关键字class或typename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的:

//error:must precede U by either typename of class
template <typename T, U> T calc(const T&, const U&);

typename与class的区别

类型形参由关键字class或者typename后接说明符构成。在函数模板形参表中,关键字typename和class具有相同的含义,可以互换使用,两个关键字都可以在同一模板形参表中使用。使用关键字typename代替关键字class指定模板类型形参也许更为直观,毕竟,可以使用内置类型(非类类型)作为实际的类型形参,而且,typename更清楚的指明后面的名字是一个类型名。但是,关键字typename是作为标准C++的组成部分加入到C++中,因此旧的程序更有可能只用class关键字。
除了定义数据成员或者函数成员外,类还可以定义类型成员。考虑如下函数:

template <class Parm, class U>
Parm fcn(Parm* array, U value){
    Parm::size_type *p; //If Parm::size_type is a type, then a declaration
    //If Parm::size_type is an object, then multiplication
}

我们知道size_type必定是绑定到Parm的那个类型的成员,但我们不知道size_type是一个类型成员的名字还是一个数据成员的名字,默认情况下,编译器假定这样的名字指定数据成员,而不是类型。

如果希望编译器将size_type当作类型,则必须显式的告诉编译器这样做:

template <class Parm, class U>
Parm fcn(Parm& array, U value){
    typename Parm::size_type *p; //ok:declares p to be a pointer
}

通过在成员名前加上关键字typename作为前缀,可以告诉编译器将成员当作类型。

如果拿不准是否需要以typename指明一个名字是一个类型,那么指定它是一个好主意。在类型之前指定typename没有害处,因此,即使typename是不必要的,也没关系。

非类型模板参数

模板形参不必都是类型。在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。例如,下面的函数模板声明了array_init是一个含有类型模板形参和一个非类型模板形参的函数模板。函数本身接受一个形参,该形参是数组的引用:

//initialize elements of an array to zero
template <class T, size_t N> void array_init(T (&parm)[N]){
    for(size_t i = 0; i != N; ++i){
        parm[i] = 0;
    }
}

模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参。
当调用array_init时,编译器从数组实参计算非类型形参的值:

int x[42];
double y[10];
array_init(x); //instantiates array_init(int(&)[42])
array_init(y); //instantiates array_init(double(&)[10])

对于上面的程序,编译器将实例化array_init的两个版本:第一个实例的形参绑定到int[42],另一个实例中的形参绑定到double[10]。对于模板的非类型形参而言,求值结果相同的表达式将认为是等价的。

int x[42];
const int sz = 40;
int y[sz+2];
array_init(x); //instantiates array_init(int(&)[42])
array_init(y); //equivalent instantiation

接下来我们了解一下类模板的非类型形参:

template<int hi, int wid>
class Screen{
public:
    //template nontype parameters used to initialize data members
    Screen():screen(hi * wid, '#'),cursor(0),height(hi),width(wid) {}
    //...
private:
    std::string   screen;
    std::string::size_type cursor;
    std::string::size_type height,width;
}

这个模板有两个形参,均为非类型形参。当用户定义Screen对象时,必须为每个形参提供常量表达式以供使用。类在默认构造函数中使用这些形参设置默认Screen的尺寸。
像任意类模板一样,使用Screen类型时必须显式声明形参值:

Screen<24, 80> hp2621; //screen 24 lines by 80 characters

参考资料

本文大部分内容摘自:《C++ Primer》
菜鸟教程–C++模板
菜鸟教程–C++模板详解
关于编译性语言、解释性语言和脚本语言,动态语言,静态语言的区别
C++ Template 基础篇(一):函数模板


   转载规则


《C++模板》 anhua 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
C++构造类型 C++构造类型
C++构造类型C++提供了多种基本的数据类型(int、char、double等)供用户使用,但是由于程序处理的问题往往比较复杂,而且呈现多样化,已有的基本数据类型很难满足使用要求。为了将不同类型的数据联合起来,作为一个整体进行处理,C++提
2019-12-20
下一篇 
C/C++ extern关键字 C/C++ extern关键字
C/C++ extern关键字本篇文章主要围绕一下几个问题对extern关键字进行说明。 声明和定义 变量和函数的声明 extern和static extern关键字与头文件的联系 extern “C” extern和const 声明和
2019-12-16
  目录