十九. 模板

● 模板的基本概念

模板(template)

函数模板:可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计。

语法: template <<模板的类型形参表>> <函数声明>

类模板:代表一簇类, 用户可以为类定义一种模式, 使得类中的某些数据成员, 某些成员函数的参数, 返回值或局部变量能取任意类型(包括系统预定义的和用户自定义的)

语法: template <<模板的类型形参表>> <类声明>

※ 泛型编程/泛化编程(generic programming):模板是泛型编程的基础. 泛型编程是独立于流行的面向对象编程的一种新的开发方式; 泛型编程的主要思想是将算法从特定的数据类型中抽象出来, 使算法成为通用的框架(frame), 它可以作用于各种不同的数据类型.

● 函数模板的格式

函数模板的格式是:

template <<模板形参表>>        //实际写代码时, 只用一个尖括号

返回类型 函数名(形参表)

{

函数体

}

注意:

1. 模板形参表有下面几种形式:

① class <数据类型标识符>    //尖括号不用写, 下同

② typename <数据类型标识符>

③ 类型说明符 <数据类型标识符>

※ ①②情况的模板形参称为模板类型参数, 类型参数(typename/class后面的标识符所代表的东西, 不同于传统函数中的数值形式的参数)可以用来指定函数模板本身的形参类型, 返回值类型, 以及函数体中的局部变量

③情况的形参称为模板非类型形参, 类型说明符是说明变量是什么类型的标识符, 如int等; 这一种模板的类型形参与普通的函数形参的形式相同, 这说明这个形参的类型是确定的, 不像类型参数还需要推导它到底是哪个类型

2. 函数返回值类型可以是普通类型(如void),也可以是模板形参表中指定的类型。

函数模板定义后,就可以用它生成各种具体的函数(称为模板函数)。

//求绝对值

#include <iostream>

using namespace std;

template <typename T>

T abs (T x)    //其实就是把原来的普通的数据类型换成了这里的"类型参数"T

{

return x<0? -x: x;

}

void main()

{

int n=-5;

double d=-5.5;

cout<<abs(n)<<endl;

cout<<abs(d)<<endl;

}

在上述主函数调用abs()时, 编译器可以从实参的类型推导出函数模板的类型参数T到底是什么类型. 但类型参数的类型确定后, 编译器将以函数为样板, 生产一个模板函数, 这一过程称为函数模板的实例化, 该模板函数称为函数模板abs的一个实例. 上例中的两个模板函数是:

int abs (int x)

{

return x<0? -x: x

}

double abs (double x)

{

return x<0? -x: x

}

注意: ① 函数模板本身在编译时不会生产目标代码, 只有模板生产的实例(模板函数)会生成目标代码;

② 被多个源文件引用的函数模板, 应当连同函数体一同放在头文件中, 而不能像普通函数那样只将声明放在头文件中;

③ 函数指针也只能指向模板的实例, 而不能执行模板本身

函数模板实例化分为显式实例化与隐式实例化:

(1) 显式实例化:

,具体类型名2,..., 常量表达式> (实参表)

说明:

① 根据< >中给出的具体类型,用类似于函数调用实参与形参结合的方式,将模板参数表中的参数化类型一一实例化成具体的类型, 函数中的参数化类型也

一一实例化。

② 如果模板参数表中有形式参数,还需要用常量表达式去初始化。

例如:

使用add<double>(8, 9)将T add(T x, T y) 实例化成:

double add(double, double)

使用sum<int, 100> 将T sum() 实例化成:

int sum(), size获得初值100;

(2) 隐式实例化:

隐式实例化的格式为函数调用式,实例化过程是在实参与形参结合时,用实参的类型实例化形参对应的参数化类型。

例如:

使用add( 'A','B') 将T add(T x, T y) 实例化成:

char add(char, char)

注意:

使用隐式实例化无法初始化模板参数表中的普通类型的形参,如果模板参数表中使用普通类型参数,必须使用显式初始化。

//上面的例子是隐式实例化, 下面是显式实例化的例子:

#include <iostream>

using namespace std;

template <class type,int len>    //定义一个模板类型, 模版参数有类型形参type, 也有非类型形参len

type Max(type array[len])    //定义函数模板, 形参是type类型的包含len个元素的数组

{

个元素的值

for(int i=1; i<len; i++) //循环次数, 即比较大小的次数为len-1, 遍历数组元素;

个元素的值)要和array数组的第1个元素的值进行比较

{

ret = (ret > array[i])? ret : array[i];    //ret用来存储经经比较后的值

}

return ret;    //返回经比较后得到的最大值

}

void main()

{

int iset[5] = {1,2,3,4,5};    //定义一个整型数组

int iret = Max<int,5>(iset);    //调用函数模板Max, 并使其实例化

cout<<iret<<endl;

double dset[3] = {10.5,11.2,9.8};    //定义一个实数数组

double dret = Max<double,3>(dset);    //调用函数模板Max

cout << dret << endl;

}

int iset[5] = {1,2,3,4,5};

int iret = Max<int,5>(iset);

跟2比, 然后2跟3比, 然后3跟4比, 然后4跟5比;

; i<len; i++), 那么将会进行5次比较--首先iret(值为1)个iset数组的第0个元素比较, 以此类推

类模板定义的语法为

类模板定义的语法为:

template <<模板形参表>>    //类模板形参表与函数模板的形式是一样的

class 类名    //不是模板名

{

类成员声明;

};

模板类的成员函数还可以在类外定义,其语法如下:

template <<模板形参表>>

<返回类型> <类名> <<模板参数名表>>∷<函数名> <(参数表)>

{

函数体;

}

类模板实例化的语法:

  1. 生成新类:

类名 <<类型实参表>>

类名 <<类型实参表>> 对象1, 对象2, …, 对象n (实参表);

注意:

① 类模板中的成员函数可以是函数模板,也可以是普通函数,

② 一个类模板的类型参数实例化后, 即这个类模板被其它代码引用时, 类模板会被实例化, 生成具体的模板类.

//例如,下面定义了一个模板类Student,为了增强类的适用性,将学号设计成参数化类型,它可以实例化成字符串、整型等;

//将成绩设计成参数化类型,它可以实例化成整型、浮点型、字符型(用来表示等级分)等;

template <class TNO, class TScore, int num> // TNO,TScore 为参数化类型

class Student

{

private:

TNO StudentID[num]; //参数化(parameterized)类型(可变类型)数组,存储姓名

TScore score[num]; //参数化类型数组,存储分数

public:

TNO TopStudent() //普通函数

{

return StudentID[0];

}

int BelowNum(TScore ascore) //成员函数模板

{

return 0;

}

void sort() //普通函数

{

}

};

//////////////////////////////////////////////////

//模板类的成员函数还可以在类外定义

template <class TNO, class TScore, int num>

class Student

{

private:

TNO StudentID[num];

TScore score[num];

public:

TNO TopStudent();

int BelowNum(TScore ascore);

void sort();

};

template <class TNO, class TScore, int num>

int Student<TNO, TScore, num>::BelowNum(TScore ascore)

{

return 0;

}

template <class TNO, class TScore, int num>

void Student<TNO, TScore, num>::sort()

{

}

template <class TNO, class TScore, int num>

TNO Student<TNO, TScore, num>::TopStudent()

{

return StudentID[0];

}

//类模板的具体案例

#include <iostream>

using namespace std;

template<class T1,class T2>    //定义类模板, T1和T2代表类成员的数据类型, 不是类的类型

class MyTemplate

{

T1 t1;

T2 t2;

public:

MyTemplate(T1 tt1,T2 tt2)    //构造函数

{

t1 =tt1;

t2=tt2;        //构造函数的功能是对数据成员赋值

}

void display()

{ cout << t1 << ' ' << t2 << endl;}

};

void main()

{

int a=123;

double b=3.1415;

MyTemplate<int ,double> mt(a,b);

mt.display();

}

//默认模板参数: 在类模板定义时, 为||类型形式参数中的一个或若干类型形参||赋予||默认值, 该默认值是一个普通的数据类型. 这样, 在生成新类时, 如果被赋予默认值的类型形参没有赋实参, 那么这个类型形参的值就是默认值

#include <iostream>

using namespace std;

template <class T1,class T2 = int> //int是模板形参的默认值

class MyTemplate

{

T1 t1;

T2 t2;

public:

MyTemplate(T1 tt1,T2 tt2)    //构造函数

{t1=tt1;t2=tt2;}

void display()

{

cout<< t1 << ' ' << t2 << endl;

}

};

void main()

{

int a=123;

double b=3.1415;

MyTemplate<int ,double> mt1(a,b);

MyTemplate<int> mt2(a,b); //第二个模板形参的没有被赋予实参, 因此该模板形参的值就是默认的int

mt1.display();

mt2.display();

}

//模板形参表中, 可以有一个非模板类型形参, 并且该形参已经被赋值:

#include <iostream>

using namespace std;

template<class T1,class T2,int num= 10 >

class MyTemplate

{

T1 t1;

T2 t2;

public:

MyTemplate(T1 tt1,T2 tt2) //构造函数

{t1 =tt1+num; t2=tt2+num;}

void display()

{ cout << t1 << ' ' << t2 <<endl;}

};

void main()

{

int a=123;

double b=3.1415;

MyTemplate<int ,double> mt1(a,b);

MyTemplate<int ,double ,100> mt2(a,b); //类模板生成模板类以后, 用模板类创建对象, 并赋值

mt1.display();

mt2.display();

}

● 模板的特殊化/模板的定制(template specialization)

使用场景: 定义完函数模板或类模板以后, 我们发现有的数据类型不适用于已经定义好的模板(如下面例子中的char* 类型不适用于Type函数模板); 或者我们需要运用一个新的自定义的类类型(如下面例子中类模板的T应该对应的是一个普通的数据类型, 如int; 但如果我们要T对应一个自定义的Date类类型, 就需要特殊化/定制模板)

定义的目的: 补充模板, 从而扩展模板的功能

//函数模板的定制

#include <iostream >

#include <string >

using namespace std;

template<class Type>

Type min(Type a,Type b)//定义函数模板

{

if(a < b)

return a;

else

return b;

}

template <> //定义一个针对字符串的补充模板, 即函数模板的定制

char * min(char * a,char * b) // 上面的空模板形参表值允许在补充模板中使用, 可以把这个补充模板删去; 由于普通函数优先于模板函数, 因此能达到同样的效果, 但还是提倡用空的补充模板, 便于管理, 同时在没调用的情况下不会生产无用的目标代码.

{

if(strcmp(a,b)>=0)

return b;

else

return a;

}

void main ()

{

cout << "最小值:" << min(10,1) << endl;

cout << "最小值:" << min('a','b') << endl;

cout << "最小值:" << min("ah","ai") << endl;

}

//如果传送给上面这个模板的是指向字符串的字符指针, 即要求返回两个字符串中的较大者时, 模板是一句字符串的第一个字符来确定其大小的, 如果第一个字符相同, 则输出第一个字符串, 因而不能得出正确结果(就算正确结果本身就是第一个字符串, 那也不算真正的正确).

//类模板的定制

#include <iostream>

using namespace std;

class Date

{

int iMonth,iDay,iYear;

char Format[128];

public:

Date(int m=0,int d=0,int y=0)

{

iMonth=m;

iDay=d;

iYear=y;

}

friend ostream& operator<<(ostream& os,const Date t)

{

cout << "Month: " << t.iMonth << ' ' ;

cout << "Day: " << t.iDay<< ' ';

cout << "Year: " << t.iYear<< ' ' ;

return os;

}

void Display()

{

cout << "Month: " << iMonth;

cout << "Day: " << iDay;

cout << "Year: " << iYear;

cout << endl;

}

};

template <class T>

class Set

{

T t;

public:

Set(T st) : t(st) {}

void Display()

{

cout << t << endl;

}

};

class Set<Date>

{

Date t;

public:

Set(Date st): t(st){}

void Display()

{

cout << "Date :" << t << endl;

}

};

void main()

{

Set<int> intset(123);

Set<Date> dt =Date(1,2,3);

intset.Display();

dt.Display();

}

● 类模板的派生与继承

(略)

(C/C++学习笔记) 十九. 模板的更多相关文章

  1. python3.4学习笔记(十九) 同一台机器同时安装 python2.7 和 python3.4的解决方法

    python3.4学习笔记(十九) 同一台机器同时安装 python2.7 和 python3.4的解决方法 同一台机器同时安装 python2.7 和 python3.4不会冲突.安装在不同目录,然 ...

  2. Java基础学习笔记十九 IO

    File IO概述 回想之前写过的程序,数据都是在内存中,一旦程序运行结束,这些数据都没有了,等下次再想使用这些数据,可是已经没有了.那怎么办呢?能不能把运算完的数据都保存下来,下次程序启动的时候,再 ...

  3. Java基础学习笔记十九 File

    IO概述 回想之前写过的程序,数据都是在内存中,一旦程序运行结束,这些数据都没有了,等下次再想使用这些数据,可是已经没有了.那怎么办呢?能不能把运算完的数据都保存下来,下次程序启动的时候,再把这些数据 ...

  4. JavaScript权威设计--跨域,XMLHttpRequest(简要学习笔记十九)

    1.跨域指的是什么? URL 说明 是否允许通信 http://www.a.com/a.jshttp://www.a.com/b.js 同一域名下 允许 http://www.a.com/lab/a. ...

  5. python 学习笔记十九 django深入学习四 cookie,session

    缓存 一个动态网站的基本权衡点就是,它是动态的. 每次用户请求一个页面,Web服务器将进行所有涵盖数据库查询到模版渲染到业务逻辑的请求,用来创建浏览者需要的页面.当程序访问量大时,耗时必然会更加明显, ...

  6. SharpGL学习笔记(十九) 摄像机漫游

    所谓的摄像机漫游,就是可以在场景中来回走动. 现实中,我们通过眼睛观察东西,身体移动带动眼睛移动观察身边的事物,这也是在漫游. 在OpenGL中我们使用函数LookAt()来操作摄像机在三维场景中进行 ...

  7. yii2源码学习笔记(十九)

    view剩余代码 /** * @return string|boolean the view file currently being rendered. False if no view file ...

  8. PHP学习笔记十九【析构函数】

    <?php class Person{ public $name; public $age; public function __construct($iname,$iage) { $this- ...

  9. JSTL 标签库 使用(web基础学习笔记十九)

    标签库概要: 一.C标签库介绍 1.1.<c:> 核心标签库  JSTL 核心标签库(C标签)标签共有13个,功能上分为4类:1.表达式控制标签:out.set.remove.catch2 ...

随机推荐

  1. Getting Started withProcessing 第八章总结

    运动 在这一章中,作者讲述了如何对图元中的对象进行实现动画的效果. 实现运动的几种方式 在书中,作者通过讲解一些对应的知识,让图元能够产生移动的效果.这几种方式包括: 速度和方向 在全局变量中定义两个 ...

  2. php安装soap等扩展的方式: 已经安装了php却发现少安装了一下扩展

    php安装soap等扩展的方式: 已经安装了php却发现少安装了一下扩展 1.首先确认下php.ini的安装位置 我的安装目录是: /usr/local/php 一般位置: /usr/local/ph ...

  3. tchart...

    using System;using System.Collections;using System.ComponentModel;using System.Drawing; using System ...

  4. 新C# 操作Excel属性

    C# 操作Excel属性 数字(Range.NumberFormatlocal 属性) 常规:Range.NumberFormatlocal = "G/通用格式" 数值:Range ...

  5. 比较Class.getResource与Class.getClassLoader().getResource两种方式读取资源文件

    /** * @author zhangboqing * @date 2018/7/10 */ public class FileDemo { public static void main(Strin ...

  6. LeetCode--255--用队列实现栈(java版)

    使用队列实现栈的下列操作: push(x) -- 元素 x 入栈 pop() -- 移除栈顶元素 top() -- 获取栈顶元素 empty() -- 返回栈是否为空 注意: 你只能使用队列的基本操作 ...

  7. js将字符串转json

    Json格式字符串 "{"rows":[{"date":"2018-11-19","money":" ...

  8. 6 Django REST framework JWT 和登录功能实现

    JWT 在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证. 我们不再使用Session认证机制,而使用Json Web Token认证机制. Json web token ( ...

  9. PHP const关键字

    常量是一个简单的标识符.在脚本执行期间该值不能改变(除了所谓的魔术常量,他们其实不是常量).常量默认大小写敏感.通常常量标识符总是大写的. 可以用define()函数来定义常量.在php5.3.0以后 ...

  10. thinkphp5的生命周期

    1.入口文件 虚拟主机目录/public/index.php 2.引入启动文件 虚拟主机目录/thinkphp/start.php 3.运行App下面的run方法 虚拟主机目录/thinkphp/li ...