模板(Templates)是ANSI-C++ 标准中新引入的概念。如果你使用的 C++ 编译器不符合这个标准,则你很可能不能使用模板。

函数模板( Function templates)

模板(Templates)使得我们可以生成通用的函数,这些函数能够接受任意数据类型的参数,可返回任意类型的值,而不需要对所有可能的数据类型进行函数重载。这在一定程度上实现了宏(macro)的作用。它们的原型定义可以是下面两种中的任何一个:

template <class identifier> function_declaration;
template <typename identifier> function_declaration;

上面两种原型定义的不同之处在关键字class 或 typename的使用。它们实际是完全等价的,因为两种表达的意思和执行都一模一样。

例如,要生成一个模板,返回两个对象中较大的一个,我们可以这样写:

template <class GenericType>

GenericType GetMax (GenericType a, GenericType b) { return (a>b?a:b); }

在第一行声明中,我们已经生成了一个通用数据类型的模板,叫做GenericType。因此在其后面的函数中,GenericType 成为一个有效的数据类型,它被用来定义了两个参数a和 b ,并被用作了函数GetMax的返回值类型。

GenericType 仍没有代表任何具体的数据类型;当函数 GetMax 被调用的时候,我们可以使用任何有效的数据类型来调用它。这个数据类型将被作为pattern来代替函数中GenericType 出现的地方。用一个类型pattern来调用一个模板的方法如下:

function <type> (parameters);

例如,要调用GetMax 来比较两个int类型的整数可以这样写:

int x,y;
GetMax <int> (x,y);

因此,GetMax 的调用就好像所有的GenericType 出现的地方都用int 来代替一样。

这里是一个例子:

// function template

#include <iostream.h>

template <class T> T GetMax (T a, T b) {

T result;

result = (a>b)? a : b;

return (result);

}

int main () {

int i=5, j=6, k;

long l=10, m=5, n;

k=GetMax(i,j);

n=GetMax(l,m);

cout << k << endl;

cout << n << endl;

return 0;
}

6
10

(在这个例子中,我们将通用数据类型命名为T 而不是 GenericType ,因为T短一些,并且它是模板更为通用的标示之一,虽然使用任何有效的标示符都是可以的。)

在上面的例子中,我们对同样的函数GetMax()使用了两种参数类型:int 和 long,而只写了一种函数的实现,也就是说我们写了一个函数的模板,用了两种不同的pattern来调用它。

如你所见,在我们的模板函数 GetMax() 里,类型 T 可以被用来声明新的对象

T result;

result 是一个T类型的对象, 就像a 和 b一样,也就是说,它们都是同一类型的,这种类型就是当我们调用模板函数时写在尖括号<> 中的类型。

在这个具体的例子中,通用类型 T 被用作函数GetMax 的参数,不需要说明<int>或 <long>,编译器也可以自动检测到传入的数据类型,因此,我们也可以这样写这个例子:

int i,j;
GetMax (i,j);

因为i 和j 都是int 类型,编译器会自动假设我们想要函数按照int进行调用。这种暗示的方法更为有用,并产生同样的结果:

// function template II

#include <iostream.h>

template <class T> T GetMax (T a, T b) {

return (a>b?a:b);

}

int main () {

int i=5, j=6, k;

long l=10, m=5, n;

k=GetMax(i,j);

n=GetMax(l,m);

cout << k << endl;

cout << n << endl;

return 0;

}

6
10

注意在这个例子的main() 中我们如何调用模板函数GetMax() 而没有在括号<>中指明具体数据类型的。编译器自动决定每一个调用需要什么数据类型。

因为我们的模板函数只包括一种数据类型 (class T), 而且它的两个参数都是同一种类型,我们不能够用两个不同类型的参数来调用它:

int i;
long l;
k = GetMax (i,l);

上面的调用就是不对的,因为我们的函数等待的是两个同种类型的参数。

我们也可以使得模板函数接受两种或两种以上类型的数据,例如:

template <class T>

T GetMin (T a, U b) { return (a<b?a:b); }

在这个例子中,我们的模板函数 GetMin() 接受两个不同类型的参数,并返回一个与第一个参数同类型的对象。在这种定义下,我们可以这样调用该函数:

int i,j;
long l;
i = GetMin <int, long> (j,l);

或者,简单的用

i = GetMin (j,l);

虽然 j 和 l 是不同的类型。

类模板(Class templates)

我们也可以定义类模板(class templates),使得一个类可以有基于通用类型的成员,而不需要在类生成的时候定义具体的数据类型,例如:

template <class T>

class pair {

T values [2];

public:

pair (T first, T second) {

values[0]=first;

values[1]=second;

}

};

上面我们定义的类可以用来存储两个任意类型的元素。例如,如果我们想要定义该类的一个对象,用来存储两个整型数据115 和 36 ,我们可以这样写:

pair<int> myobject (115, 36);

我们同时可以用这个类来生成另一个对象用来存储任何其他类型数据,例如:

pair<float> myfloats (3.0, 2.18);

在上面的例子中,类的唯一一个成员函数已经被inline 定义。如果我们要在类之外定义它的一个成员函数,我们必须在每一函数前面加template <... >。

// class templates

#include <iostream.h>

template <class T> class pair {
     T value1, value2;
public:
    pair (T first, T second) {

value1=first;

value2=second;

}

T getmax ();

};

template <class T>

T pair::getmax (){

T retval;

retval = value1>value2? value1 : value2;

return retval;

}

int main () {

pair myobject (100, 75);

cout << myobject.getmax();

return 0;

}

100

注意成员函数getmax 是怎样开始定义的:

template <class T>
T pair::getmax ()

所有写 T 的地方都是必需的,每次你定义模板类的成员函数的时候都需要遵循类似的格式(这里第二个T表示函数返回值的类型,这个根据需要可能会有变化)。

模板特殊化(Template specialization)

模板的特殊化是当模板中的pattern有确定的类型时,模板有一个具体的实现。例如假设我们的类模板pair 包含一个取模计算(module operation)的函数,而我们希望这个函数只有当对象中存储的数据为整型(int)的时候才能工作,其他时候,我们需要这个函数总是返回0。这可以通过下面的代码来实现:

// Template specialization

#include <iostream.h>

template <class T> class pair {

T value1, value2;

public:

pair (T first, T second){

value1=first;

value2=second;

}

T module () {return 0;}

};

template <>

class pair <int> {

int value1, value2;

public:

pair (int first, int second){

value1=first;

value2=second;

}

int module ();

};

template <>

int pair<int>::module() {

return value1%value2;

}

int main () {

pair <int> myints (100,75);

pair <float> myfloats (100.0,75.0);

cout << myints.module() << '\n';

cout << myfloats.module() << '\n';

return 0;

}

25
0

由上面的代码可以看到,模板特殊化由以下格式定义:

template <> class class_name <type>

这个特殊化本身也是模板定义的一部分,因此,我们必须在该定义开头写template <>。而且因为它确实为一个具体类型的特殊定义,通用数据类型在这里不能够使用,所以第一对尖括号<> 内必须为空。在类名称后面,我们必须将这个特殊化中使用的具体数据类型写在尖括号<>中。

当我们特殊化模板的一个数据类型的时候,同时还必须重新定义类的所有成员的特殊化实现(如果你仔细看上面的例子,会发现我们不得不在特殊化的定义中包含它自己的构造函数 constructor,虽然它与通用模板中的构造函数是一样的)。这样做的原因就是特殊化不会继承通用模板的任何一个成员。

模板的参数值(Parameter values for templates)

除了模板参数前面跟关键字class 或 typename 表示一个通用类型外,函数模板和类模板还可以包含其它不是代表一个类型的参数,例如代表一个常数,这些通常是基本数据类型的。例如,下面的例子定义了一个用来存储数组的类模板:

// array template

#include <iostream.h>

template <class T, int N>

class array {

T memblock [N];

public:

void setmember (int x, T value);

T getmember (int x);

};

template <class T, int N>

void array<T,N>::setmember (int x, T value) {

memblock[x]=value;

}

template <class T, int N>

T array<T,N>::getmember (int x) {

return memblock[x];

}

int main () {

array <int,5> myints;

array <float,5> myfloats;

myints.setmember (0,100);

myfloats.setmember (3,3.1416);

cout << myints.getmember(0) << '\n';

cout << myfloats.getmember(3) << '\n';

return 0;

}

100
3.1416

我们也可以为模板参数设置默认值,就像为函数参数设置默认值一样。

下面是一些模板定义的例子:

template <class T> // 最常用的:一个class 参数。

template <class T, class U> // 两个class 参数。

template <class T, int N> // 一个class 和一个整数。

template <class T = char> // 有一个默认值。

template <int Tfunc (int)> // 参数为一个函数。

模板与多文件工程 (Templates and multiple-file projects)

从编译器的角度来看,模板不同于一般的函数或类。它们在需要时才被编译(compiled on demand),也就是说一个模板的代码直到需要生成一个对象的时候(instantiation)才被编译。当需要instantiation的时候,编译器根据模板为特定的调用数据类型生成一个特殊的函数。

当工程变得越来越大的时候,程序代码通常会被分割为多个源程序文件。在这种情况下,通常接口(interface)和实现(implementation)是分开的。用一个函数库做例子,接口通常包括所有能被调用的函数的原型定义。它们通常被定义在以.h 为扩展名的头文件 (header file) 中;而实现 (函数的定义) 则在独立的C++代码文件中。

模板这种类似宏(macro-like) 的功能,对多文件工程有一定的限制:函数或类模板的实现 (定义) 必须与原型声明在同一个文件中。也就是说我们不能再 将接口(interface)存储在单独的头文件中,而必须将接口和实现放在使用模板的同一个文件中。

回到函数库的例子,如果我们想要建立一个函数模板的库,我们不能再使用头文件(.h) ,取而代之,我们应该生成一个模板文件(template file),将函数模板的接口和实现 都放在这个文件中 (这种文件没有惯用扩展名,除了不要使用.h扩展名或不要不加任何扩展名)。在一个工程中多次包含同时具有声明和实现的模板文件并不会产生链接错误 (linkage errors),因为它们只有在需要时才被编译,而兼容模板的编译器应该已经考虑到这种情况,不会生成重复的代码。

模板(Templates)是ANSI-C++ 标准中新引入的概念。如果你使用的 C++ 编译器不符合这个标准,则你很可能不能使用模板。

函数模板( Function templates)

模板(Templates)使得我们可以生成通用的函数,这些函数能够接受任意数据类型的参数,可返回任意类型的值,而不需要对所有可能的数据类型进行函数重载。这在一定程度上实现了宏(macro)的作用。它们的原型定义可以是下面两种中的任何一个:

template <class identifier> function_declaration;
template <typename identifier> function_declaration;

上面两种原型定义的不同之处在关键字class 或 typename的使用。它们实际是完全等价的,因为两种表达的意思和执行都一模一样。

例如,要生成一个模板,返回两个对象中较大的一个,我们可以这样写:

template <class GenericType>

GenericType GetMax (GenericType a, GenericType b) { return (a>b?a:b); }

在第一行声明中,我们已经生成了一个通用数据类型的模板,叫做GenericType。因此在其后面的函数中,GenericType 成为一个有效的数据类型,它被用来定义了两个参数a和 b ,并被用作了函数GetMax的返回值类型。

GenericType 仍没有代表任何具体的数据类型;当函数 GetMax 被调用的时候,我们可以使用任何有效的数据类型来调用它。这个数据类型将被作为pattern来代替函数中GenericType 出现的地方。用一个类型pattern来调用一个模板的方法如下:

function <type> (parameters);

例如,要调用GetMax 来比较两个int类型的整数可以这样写:

int x,y;
GetMax <int> (x,y);

因此,GetMax 的调用就好像所有的GenericType 出现的地方都用int 来代替一样。

这里是一个例子:

// function template

#include <iostream.h>

template <class T> T GetMax (T a, T b) {

T result;

result = (a>b)? a : b;

return (result);

}

int main () {

int i=5, j=6, k;

long l=10, m=5, n;

k=GetMax(i,j);

n=GetMax(l,m);

cout << k << endl;

cout << n << endl;

return 0;
}

6
10

(在这个例子中,我们将通用数据类型命名为T 而不是 GenericType ,因为T短一些,并且它是模板更为通用的标示之一,虽然使用任何有效的标示符都是可以的。)

在上面的例子中,我们对同样的函数GetMax()使用了两种参数类型:int 和 long,而只写了一种函数的实现,也就是说我们写了一个函数的模板,用了两种不同的pattern来调用它。

如你所见,在我们的模板函数 GetMax() 里,类型 T 可以被用来声明新的对象

T result;

result 是一个T类型的对象, 就像a 和 b一样,也就是说,它们都是同一类型的,这种类型就是当我们调用模板函数时写在尖括号<> 中的类型。

在这个具体的例子中,通用类型 T 被用作函数GetMax 的参数,不需要说明<int>或 <long>,编译器也可以自动检测到传入的数据类型,因此,我们也可以这样写这个例子:

int i,j;
GetMax (i,j);

因为i 和j 都是int 类型,编译器会自动假设我们想要函数按照int进行调用。这种暗示的方法更为有用,并产生同样的结果:

// function template II

#include <iostream.h>

template <class T> T GetMax (T a, T b) {

return (a>b?a:b);

}

int main () {

int i=5, j=6, k;

long l=10, m=5, n;

k=GetMax(i,j);

n=GetMax(l,m);

cout << k << endl;

cout << n << endl;

return 0;

}

6
10

注意在这个例子的main() 中我们如何调用模板函数GetMax() 而没有在括号<>中指明具体数据类型的。编译器自动决定每一个调用需要什么数据类型。

因为我们的模板函数只包括一种数据类型 (class T), 而且它的两个参数都是同一种类型,我们不能够用两个不同类型的参数来调用它:

int i;
long l;
k = GetMax (i,l);

上面的调用就是不对的,因为我们的函数等待的是两个同种类型的参数。

我们也可以使得模板函数接受两种或两种以上类型的数据,例如:

template <class T>

T GetMin (T a, U b) { return (a<b?a:b); }

在这个例子中,我们的模板函数 GetMin() 接受两个不同类型的参数,并返回一个与第一个参数同类型的对象。在这种定义下,我们可以这样调用该函数:

int i,j;
long l;
i = GetMin <int, long> (j,l);

或者,简单的用

i = GetMin (j,l);

虽然 j 和 l 是不同的类型。

类模板(Class templates)

我们也可以定义类模板(class templates),使得一个类可以有基于通用类型的成员,而不需要在类生成的时候定义具体的数据类型,例如:

template <class T>

class pair {

T values [2];

public:

pair (T first, T second) {

values[0]=first;

values[1]=second;

}

};

上面我们定义的类可以用来存储两个任意类型的元素。例如,如果我们想要定义该类的一个对象,用来存储两个整型数据115 和 36 ,我们可以这样写:

pair<int> myobject (115, 36);

我们同时可以用这个类来生成另一个对象用来存储任何其他类型数据,例如:

pair<float> myfloats (3.0, 2.18);

在上面的例子中,类的唯一一个成员函数已经被inline 定义。如果我们要在类之外定义它的一个成员函数,我们必须在每一函数前面加template <... >。

// class templates

#include <iostream.h>

template <class T> class pair {
     T value1, value2;
public:
    pair (T first, T second) {

value1=first;

value2=second;

}

T getmax ();

};

template <class T>

T pair::getmax (){

T retval;

retval = value1>value2? value1 : value2;

return retval;

}

int main () {

pair myobject (100, 75);

cout << myobject.getmax();

return 0;

}

100

注意成员函数getmax 是怎样开始定义的:

template <class T>
T pair::getmax ()

所有写 T 的地方都是必需的,每次你定义模板类的成员函数的时候都需要遵循类似的格式(这里第二个T表示函数返回值的类型,这个根据需要可能会有变化)。

模板特殊化(Template specialization)

模板的特殊化是当模板中的pattern有确定的类型时,模板有一个具体的实现。例如假设我们的类模板pair 包含一个取模计算(module operation)的函数,而我们希望这个函数只有当对象中存储的数据为整型(int)的时候才能工作,其他时候,我们需要这个函数总是返回0。这可以通过下面的代码来实现:

// Template specialization

#include <iostream.h>

template <class T> class pair {

T value1, value2;

public:

pair (T first, T second){

value1=first;

value2=second;

}

T module () {return 0;}

};

template <>

class pair <int> {

int value1, value2;

public:

pair (int first, int second){

value1=first;

value2=second;

}

int module ();

};

template <>

int pair<int>::module() {

return value1%value2;

}

int main () {

pair <int> myints (100,75);

pair <float> myfloats (100.0,75.0);

cout << myints.module() << '\n';

cout << myfloats.module() << '\n';

return 0;

}

25
0

由上面的代码可以看到,模板特殊化由以下格式定义:

template <> class class_name <type>

这个特殊化本身也是模板定义的一部分,因此,我们必须在该定义开头写template <>。而且因为它确实为一个具体类型的特殊定义,通用数据类型在这里不能够使用,所以第一对尖括号<> 内必须为空。在类名称后面,我们必须将这个特殊化中使用的具体数据类型写在尖括号<>中。

当我们特殊化模板的一个数据类型的时候,同时还必须重新定义类的所有成员的特殊化实现(如果你仔细看上面的例子,会发现我们不得不在特殊化的定义中包含它自己的构造函数 constructor,虽然它与通用模板中的构造函数是一样的)。这样做的原因就是特殊化不会继承通用模板的任何一个成员。

模板的参数值(Parameter values for templates)

除了模板参数前面跟关键字class 或 typename 表示一个通用类型外,函数模板和类模板还可以包含其它不是代表一个类型的参数,例如代表一个常数,这些通常是基本数据类型的。例如,下面的例子定义了一个用来存储数组的类模板:

// array template

#include <iostream.h>

template <class T, int N>

class array {

T memblock [N];

public:

void setmember (int x, T value);

T getmember (int x);

};

template <class T, int N>

void array<T,N>::setmember (int x, T value) {

memblock[x]=value;

}

template <class T, int N>

T array<T,N>::getmember (int x) {

return memblock[x];

}

int main () {

array <int,5> myints;

array <float,5> myfloats;

myints.setmember (0,100);

myfloats.setmember (3,3.1416);

cout << myints.getmember(0) << '\n';

cout << myfloats.getmember(3) << '\n';

return 0;

}

100
3.1416

我们也可以为模板参数设置默认值,就像为函数参数设置默认值一样。

下面是一些模板定义的例子:

template <class T> // 最常用的:一个class 参数。

template <class T, class U> // 两个class 参数。

template <class T, int N> // 一个class 和一个整数。

template <class T = char> // 有一个默认值。

template <int Tfunc (int)> // 参数为一个函数。

模板与多文件工程 (Templates and multiple-file projects)

从编译器的角度来看,模板不同于一般的函数或类。它们在需要时才被编译(compiled on demand),也就是说一个模板的代码直到需要生成一个对象的时候(instantiation)才被编译。当需要instantiation的时候,编译器根据模板为特定的调用数据类型生成一个特殊的函数。

当工程变得越来越大的时候,程序代码通常会被分割为多个源程序文件。在这种情况下,通常接口(interface)和实现(implementation)是分开的。用一个函数库做例子,接口通常包括所有能被调用的函数的原型定义。它们通常被定义在以.h 为扩展名的头文件 (header file) 中;而实现 (函数的定义) 则在独立的C++代码文件中。

模板这种类似宏(macro-like) 的功能,对多文件工程有一定的限制:函数或类模板的实现 (定义) 必须与原型声明在同一个文件中。也就是说我们不能再 将接口(interface)存储在单独的头文件中,而必须将接口和实现放在使用模板的同一个文件中。

回到函数库的例子,如果我们想要建立一个函数模板的库,我们不能再使用头文件(.h) ,取而代之,我们应该生成一个模板文件(template file),将函数模板的接口和实现 都放在这个文件中 (这种文件没有惯用扩展名,除了不要使用.h扩展名或不要不加任何扩展名)。在一个工程中多次包含同时具有声明和实现的模板文件并不会产生链接错误 (linkage errors),因为它们只有在需要时才被编译,而兼容模板的编译器应该已经考虑到这种情况,不会生成重复的代码。

C++ 高级篇(一)—— 模板(Templates)相关推荐

  1. 谷粒商城--订单服务--高级篇笔记十一

    1.页面环境搭建 1.1 静态资源导入nginx 等待付款 --------->detail 订单页 --------->list 结算页 --------->confirm 收银页 ...

  2. 【谷粒商城高级篇】商城业务:商品检索

    谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...

  3. 谷粒商城-分布式高级篇【业务编写】

    谷粒商城-分布式基础篇[环境准备] 谷粒商城-分布式基础[业务编写] 谷粒商城-分布式高级篇[业务编写]持续更新 谷粒商城-分布式高级篇-ElasticSearch 谷粒商城-分布式高级篇-分布式锁与 ...

  4. 谷粒商城之高级篇(3)

    2 商城业务 2.7 订单服务 2.7.22 创建业务交换机&队列 这里承接 知识补充篇 6 RabbitMQ 订单分布式主体逻辑 订单超时未支付触发订单过期状态修改与库存解锁 创建订单时消息 ...

  5. 谷粒商城--认证中心--高级篇笔记八

    谷粒商城–认证中心–高级篇笔记八 1. 环境搭建 1.1 新建模块gulimall-auth-server 1.2 pom文件 上面没选好直接复制下面的pom文件,记得排除gulimall-commo ...

  6. 谷粒商城--秒杀服务--高级篇笔记十二

    谷粒商城–秒杀服务–高级篇笔记十二 1.后台添加秒杀商品 未配置秒杀服务相关网关 1.1 配置网关 - id: coupon_routeuri: lb://gulimall-couponpredica ...

  7. 谷粒商城-分布式高级篇[商城业务-检索服务]

    谷粒商城-分布式基础篇[环境准备] 谷粒商城-分布式基础[业务编写] 谷粒商城-分布式高级篇[业务编写]持续更新 谷粒商城-分布式高级篇-ElasticSearch 谷粒商城-分布式高级篇-分布式锁与 ...

  8. 谷粒商城高级篇上(未完待续)

    谷粒商城高级篇(上)保姆级整理 之前整理了基础篇,Typora提示将近20000词,谷粒商城基础篇保姆级整理 在学高级篇的时候,不知不觉又整理了两万多词,做了一阶段,先发出来,剩余部分整理好了再发.自 ...

  9. Docker入门(基础篇+高级篇)

    Docker 参考手册基础篇+高级篇 一.Docker常用命令 docker run docker stop docker start 进入一个运行中的容器 docker ps docker imag ...

最新文章

  1. super.getClass()方法调用返回结果的原因
  2. 强烈推荐|我做系统架构的一些原则
  3. linux 用户与工作组
  4. java 拼音的模糊匹配 算法_c#拼音模糊匹配算法
  5. Sprinboot支付宝h5支付、退款(java版)
  6. struts2通配符的问题的解决
  7. 高通芯片(Android)恢复\编辑\备份 imei(nv信息)工具及步骤
  8. 中兴新支点操作系统_【中兴新支点操作系统】中兴新支点操作系统下载 v3.3.1 官方版-趣致软件园...
  9. Python初学者(零基础学习Python入门)书籍、视频、资料分享
  10. Android桌面插件的开发
  11. 用谷歌浏览器检查功能实现精灵图
  12. [Android Studio] 手机安装apk报错解决方法
  13. 基于Docker的Python开发
  14. github配置ssh key密钥的方法
  15. python应用程序无法正常启动0xc000007b_应用程序0xc000007b无法正常启动怎么办,图文详解如何解决...
  16. 深入应用C++11:代码优化与工程级应用
  17. Android 中获取机顶盒的 mac 地址
  18. 网关(gateway)与路由器(router)的区别与联系
  19. linux系统网关配置,Linux双网关配置(电信网通)
  20. tp5 读取/下载 excel文件内容

热门文章

  1. LNMP里常见的502问题
  2. Ubuntu10.04制作官方源镜像以及搭建本地源
  3. 测试nignx php请求并发数,nginx 优化(突破十万并发)
  4. leetcode 839. 相似字符串组(并查集)
  5. leetcode1424. 对角线遍历 II(排序)
  6. leetcode面试题 10.03. 搜索旋转数组(二分法)
  7. 使用Chatkit构建Node.js命令行聊天应用程序
  8. FreeCodeCamp纳什维尔聚会的回顾
  9. 机器学习 来源框架_机器学习的秘密来源:策展
  10. 【覆盖安装】通用测试点