C++之模板编程
当我们越来越多的使用C++的特性, 将越来越多的问题和事物抽象成对象时, 我们不难发现:很多对象都具有共性。 比如 数值可以增加、减少;字符串也可以增加减少。 它们的动作是相似的, 只是对象的类型不同而已。
C++ 提供了“模板”这一特性, 可以将“类型” 参数化, 使得编写的代码更具有通用性。 因此大家都称模板编程为 “通用编程”或 “泛型编程”。
一般而言, 模板分为 函数模板 和 类模板,下面就让我们分别来了解一下它们。
一、 函数模板
1、 函数模板的定义和使用
定义一个模板函数的格式并不复杂, 如下:
template <模板参数列表>
返回类型 函数名(函数参数列表)
{
// code ...
}
下面, 让我们来举一个例子来说明 模板函数的作用和用法(具体代码见 Exp01)。
// 定义一个函数模板, 用来实现任意类型数据的相互交换。
template <typename T> // 声明一个 T 数据类型, 此类型随 调用方 类型的变化而变化
void swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
上面的代码, 说明了模板函数的用法。下面再给出调用的代码, 我们看看如何使用这个函数模板:
int main(int argc, char* argv[])
{
int nNum1 = 50;
int nNum2 = 30;
double dfNum1 = 2.1;
double dfNum2 = 3.0;
char *pszFirst = "Hello ";
char *pszSec = "world!";
swap <int> (nNum1, nNum2); // 将swap函数模板实例化为 int类型的模板函数再调用
printf("nNum1 = %d, nNum2 = %d\r\n", nNum1, nNum2);
swap<double> (dfNum1, dfNum2);
printf("dfNum1 = %f, pszSec = %f\r\n", dfNum1, dfNum2);
swap<char *> (pszFirst, pszSec);
printf("pszFirst = %s, pszSec = %s\r\n", pszFirst, pszSec);
return 0;
}
具体的执行结果如下:
我相信,如果你是第一次见到模板的代码,那你一定也会像我一样好奇,这个功能是怎么实现的,它是怎么做到让一段代码来兼容各种类型的呢?
当我要反汇编该EXE得时候,无意间查看了下编程生成的map文件,让我看到了如下的内容:
Address |
Publics by Value |
Rva+Base |
Lib:Object |
0001:00000140 |
?swap@@YAXAAH0@Z |
00401140 f i |
Exp01.obj |
0001:00000190 |
?swap@@YAXAAN0@Z |
00401190 f i |
Exp01.obj |
0001:000001f0 |
?swap@@YAXAAPAD0@Z |
004011f0 f i |
Exp01.obj |
由此可见, 我们编写的void swap(T& a, T& b), 只是一个“函数模板”, 要使用它需要先将它实例化为一个“模板函数”(如:swap <int>)。编译器在编译此程序的时候,为每个调用此模板的代码生成了一个函数。而且在后期的使用过程中,更加让我认识到:
a、 函数模板 并不是函数,它仅在编译时根据调用此模板函数时传递的实参类型来生成具体的函数体!若 函数模板没有被调用责不生成任何代码也不对模板代码作任何语法检查。
b、 在模板类型参数中, 实参与形参要求严格匹配,它不会进行任何的类型转换。
c、 对于函数模板,我们在调用时不一定必须先进行将它实例化为一个函数也是可以的,编译器会自动的识别模板的类型。(换句话说:Exp01中的代码可以直接使用swap, 而不需要<>)
2、 函数模板的重载
当编写的一个模板无法满足所有需要的情况时,就需要对模板进行重载(或叫 特例化),例如:我们编写了一个较大值的模板Max:
template <typename T>
T const& Max(T const& a, T const& b)
{
return a < b ? b : a;
}
A、 当我们需要传入两个指针类型的实参时,该模板就失效了,需要重载该模板:
template <typename T>
T const& Max(T* const& a, T* const& b)
{
return *a < *b ? *b : *a;
}
B、 倘若我们再需要比较两个字符串大小时,上面两个模板都失效了。因为char* 并没有提供 operator < 运行,我们只能通过调用strcmp库函数自己实现一个Max模板的特例(见Exp02):
const char* Max(const char*& a, const char*& b)
{
return strcmp(a, b) < 0 ? b : a;
}
说明:
C++模板机制规定,如果一个调用,即匹配普通函数,又能匹配模板函数的话,则优先匹配普通函数。因此,当我们模板特例化的时候,会先匹配特例化的函数。
二、 类模板
1、 基本概念
类模板一般应用于容器类中,使得容器能够处理各种类型的对象,如(详见Exp03):
struct Node
{
Node( int nData = 0 )
{
m_nData = nData;
m_pPrev = m_pNext = NULL;
}
int m_nData; // 数据元素
Node* m_pPrev; // 指向上一个元素的指针
Node* m_pNext; // 指向下一个元素的指针
};
class CDList
{
private:
int m_nCount;
Node* m_pHead;
Node* m_pTail;
int m_nMessage;
public:
CDList();
virtual ~CDList();
public:
int GetLen() const
{
m_nCount;
}
Node* GetHead() const
{
return m_pHead;
}
Node* GetTail() const
{
return m_pTail;
}
public:
bool Change(int nIndex1,int nIndex2);
void Release();
//增加
Node* AddTail( int nData );
Node* AddHead( int nData );
Node* operator[](int nIndex);
//删除
bool DeleteNode( int nIndex );
void PrintAll();
//查找
Node* FindNode( int nIndex );
};
对于这样的链表,其节点的元素只能存放整型数据。如果要想让此双向链表能够存放任何一种类型的元素,那此时我们需要的问题与函数模板就一样了,将此类修改成类模板,现在先不管类模板的写法,让我们按照函数模板的方法将类修改一下:
template <typename T>
struct Node
{
Node( T Data )
{
m_Data = Data;
m_pPrev = m_pNext = NULL;
}
T m_Data; // 通用类型的数据元素
Node<T>* m_pPrev; // 指向上一个元素的指针
Node<T>* m_pNext; // 指向下一个元素的指针
};
这样,我们每个节点都可以使用通用的类型了,当然,对节点的处理也一样得处理。按照这个样子将类型参数化(为节省篇幅,具体的实现部分请参考Exp04):
template <typename T>
class CDList
{
private:
int m_nMessage; // 消息号
int m_nCount; // 链表中 元素的数量
Node<T>* m_pHead; // 链表头指针
Node<T>* m_pTail; // 链表尾指针
public:
CDList();
virtual ~CDList();
public:
int GetLen() const
{
m_nCount;
}
Node<T>* GetHead() const
{
return m_pHead;
}
Node<T>* GetTail() const
{
return m_pTail;
}
public:
bool Change(int nIndex1,int nIndex2);
void Release();
//增加
Node<T>* AddTail( T Data );
Node<T>* AddHead( T Data );
Node<T>* operator[](int nIndex);
//删除
bool DeleteNode( int nIndex );
void PrintAll();
//查找
Node<T>* FindNode( int nIndex );
};
这样就修改好了,很简单吧,貌似类模板没有什么太多的新语法规范。完整的模板代码,大家可以参考Exp04,下面我们总结一下类模板的一些语法小细节。
2、 类模板的定义
通过上面的一番修改,我相信你一定对类模板有了一定的了解,下面我们大致的总结一下类模板的定义格式:
Template <typename T>
Class 类名
{
// code,可以使用模板参数T来指定通用的数据类型。
}
正如上面的Exp04中一样,我们的模板写好了,但是它不能直接使用,就像函数模板一样,我们需要先将模板实例化成一个模板类才可以使用。在函数模板中,编译器会针对我们传递的实参类型等信息自动的给我们实例化函数模板为模板函数,但是类模板就没有这么智能了,必须手工实例化:
int main(int argc, char* argv[])
{
CDList<int> MyList; // 将CDList实例化为一个int类型,也就是说链表中数据元素为整型
//(20) (80) 100 200 50 60
MyList.AddTail(20);
MyList.AddTail(80);
MyList.AddTail(100);
MyList.AddTail(200);
MyList.AddTail(50);
MyList.AddTail(60);
MyList.PrintAll();
MyList.Change(0,1);
MyList.PrintAll();
return 0;
}
程序执行结果:
总结:
a、 类模板 同样也不是类,它仅在编译时根据实例化本模板时传递的实参来生成具体的类代码!若 类模板没有被实例化也没有被调用,那编译器不会为本模板生成任何代码也不对模板代码作任何语法检查。
3、 类模板的特化
类模板的特化又被叫做类模板的定做,首先让我们来了解下什么叫作定做。
通过上面几个小节的学习,我相信,大家都知道模板不能直接被使用:必须先给模板传递一个实参,将它实例化为一个模板类,然后才可以用它来定义具体的对象。这样就隐含了一个问题:
我们通过给模板传递一个实参来实例化的模板类中的代码都是在模板中定义好的,如果我们不能用与定义好的模板代码来生成模板类,这时就需要使用模板的特化,也就是“定做”。
比如,我们刚才写好的双向链表模板中,对于某一个类(比如CStudent)来说,不允许添加重复的节点,但是对于像普通的int,double等数据类型以及其它一些类时,又不需要有这类的限制。这时我们就需要将此双向链表模板针对这个不允许有重复节点的类(如:CStudent)进行特化,大致代码如下:
template <>
class CDList<CStudent>
{
private:
int m_nMessage; // 消息号
int m_nCount; // 链表中 元素的数量
Node<CStudent>* m_pHead; // 链表头指针
Node<CStudent>* m_pTail; // 链表尾指针
public:
bool Change(int nIndex1,int nIndex2);
void Release();
//增加
Node<CStudent>* AddTail( CStudent Data );
Node<CStudent>* AddHead( CStudent Data );
Node<CStudent>* operator[](int nIndex);
//删除
bool DeleteNode( int nIndex );
void PrintAll();
//查找
Node<CStudent>* FindNode( int nIndex );
};
Node<CStudent>* CDList<CStudent>::AddTail( CStudent Data )
{
Node<CStudent>* pNewNode = new Node<CStudent>(Data);
if ( m_pTail )
m_pTail->m_pNext = pNewNode;
pNewNode->m_pPrev = m_pTail;
if ( m_pTail == NULL )
m_pHead = pNewNode;
m_pTail = pNewNode;
m_nCount++;
return pNewNode;
}
…
由此可知,为CStudent类定做的CDList模板类,就是以CDList<CStudent>为类名重写一份CDList<CStudent>实现而抛弃编译器为我们生成的CDList<CStudent>类。
当一个模板拥有多个模板参数时,如果我们只对其部分参数定做则称为“局部定做”,这样定做出来的“物件”仍然是一个模板,因为我们只特化了一部分模板参数….
说明:
刚才,我们为CStudent类定做的CDList模板类,其实我们没有必要将整个CDList<CStudent>类都写一遍,只需要重写Add函数即可,例如:
Template<>
CDList<CStudent>::Add(CStudent& n)
{
……
}
当然,这样的代码,需要写在Cpp文件中,并在.h文件中声明。
三、 模板程序的组织
当然,如果你足够细心,你一定会好奇,为什么我给的例子中,模板的代码都写在头文件中(.h文件),这里我们就讨论模板代码的组织方式。C++支持两种模板代码的组织方式,分别是包含方式(我们使用的就是包含方式)和分离方式。这两种组织方式没有太根本的区别,就是一个将代码全写在头文件中,分离方式是像写类一样声明和定义分别写在头文件(.h文件)和实现文件(cpp文件)中。
下面我们分别讨论下这两种代码组织方式。
1、 包含方式
本专题中,所有的实例代码中的模板代码都是以包含方式组织的。因为好多的编译器(如VC6的cl)并不支持分离方式组织代码,将代码写在头文件也只是方便编译器的预处理工作能方便的将.h文件中的代码根据模板实参的类型生成相应的模板类。
当然,将代码都写在头文件中还有一点点小要求:
A、 如果模板的成员函数写在类外,则需要写成如下样式(见Exp04):
template <typename T> // 每个类外成员函数前都要有这句
Node<T>* CDList<T>::AddTail( T Data )
{
Node<T>* pNewNode = new Node<T>(Data);
if ( m_pTail )
m_pTail->m_pNext = pNewNode;
pNewNode->m_pPrev = m_pTail;
if ( m_pTail == NULL )
m_pHead = pNewNode;
m_pTail = pNewNode;
m_nCount++;
return pNewNode;
}
B、 对于特化的代码则需要在.h文件中声明并在.cpp文件中定义,如果都写在.h文件中编译会报重定义错误。
2、 分离方式
上面已经提到过,所谓的分离方式组织代码,就是将模板的声明和定义分别写在头文件(.h文件)和实现文件(cpp文件)中,需要注意的是,并不是所有的编译器都支持这种写法,目前我只知道GCC支持这种写法。
当然,分离方式组织代码也有个小要求,就是在模板的声明和定义的template关键字前都加上export关键字。比如:
// .h 头文件中
export template <typename T>
class CDList
{
public:
CDList();
virtual ~CDList();
public:
bool Change(int nIndex1,int nIndex2);
void Release();
//增加
Node<T>* AddTail( T Data );
Node<T>* AddHead( T Data );
Node<T>* operator[](int nIndex);
//删除
bool DeleteNode( int nIndex );
void PrintAll();
//查找
Node<T>* FindNode( int nIndex );
};
在实现文件(cpp文件)中。
export template <typename T> // 每个类外成员函数前都要有这句
Node<T>* CDList<T>::AddTail( T Data )
{
Node<T>* pNewNode = new Node<T>(Data);
if ( m_pTail )
m_pTail->m_pNext = pNewNode;
pNewNode->m_pPrev = m_pTail;
if ( m_pTail == NULL )
m_pHead = pNewNode;
m_pTail = pNewNode;
m_nCount++;
return pNewNode;
}
….
四、 学习小结
经过上面各个小节的学习,我相信大家一定像我一样,对模板有了一点认识。大家也一定都知道,模板只是在编译期间编写,所有的代码都只有效与编译期。
因此,模板的重载、特化等多态性也都是在编译期间体现出来的,如果我们对编译生成的可执行文件进行反汇编时,我们不会找到任何与模板有关的代码,因为模板只是编译期间的产物。
关于模板的作用,我相信大家也一定都体会到了,它可以大大的减轻我们的编码负担,提高编程效率。关于模板的用法和技巧还有很多,单单模板特性足可以出一本书的篇幅来描述其特性及用法。
因此本专题也只是带领大家了解模板的基础用法,关于模板的更多更深入知识,请参考 “模板元编程”相关内容。
原文来自:http://www.cppblog.com/besterChen/archive/2010/07/22/121000.html
C++之模板编程的更多相关文章
- c++模板编程-typename与class关键字的区别
最近一直在研究c++模板编程,虽然有些困难,但希望能够坚持下去.今天,在书上看见一个讨论模板编程typename与class两个关键字的区别,觉得挺有意义的,就把它们给总结一下. 先看一个例子: te ...
- C++ 11可变参数接口设计在模板编程中应用的一点点总结
概述 本人对模板编程的应用并非很深,若要用一句话总结我个人对模板编程的理解,我想说的是:模板编程是对类定义的弱化. 如何理解“类定义的弱化”? 一个完整的类有如下几部分组成: 类的名称: 类的成员变量 ...
- C++模板编程中只特化模板类的一个成员函数
模板编程中如果要特化或偏特化(局部特化)一个类模板,需要特化该类模板的所有成员函数.类模板中大多数成员函数的功能可能是一模一样的,特化时我们可能只需要重新实现1.2个成员函数即可.在这种情况下,如果全 ...
- c++ 基于Policy 的 模板编程
在没真正接触c++ 模板编程之前.真的没有想到c++ 还能够这么用.最大的感触是:太灵活了,太强大了. 最初接触模板威力还是在Delta3d中,感觉里面的模板使用实在是灵活与方便,特别是dtAI中使 ...
- C++模板编程中只特化模板类的一个成员函数(花样特化一个成员函数)
转自:https://www.cnblogs.com/zhoug2020/p/6581477.html 模板编程中如果要特化或偏特化(局部特化)一个类模板,需要特化该类模板的所有成员函数.类模板中大多 ...
- C++模板编程与宏编程经验谈
C++模板编程与宏编程经验谈 有人说C 与C++的不同主要是因为C++支持模板,不要说区别是面向对象化编程,因为C同样能很好的实现对象化编程,面向对象化其实只是思想,在很多语言中都能实现,区别在于实现 ...
- C#模板编程(2): 编写C#预处理器,让模板来的再自然一点
在<C#模板编程(1):有了泛型,为什么还需要模板?>文中,指出了C#泛型的局限性,为了突破这个局限性,我们需要模板编程.但是,C#语法以及IDE均不支持C#模板编程,怎么办呢?自己动手, ...
- 深入C++04:模板编程
模板编程 函数模板 模板意义:对类型也进行参数化: 函数模板:是不编译的,因为类型不知道 模板的实例化:函数调用点进行实例化,生成模板函数 模板函数:这才是要被编译器所编译的 函数模板.模板的特例化. ...
- C++模板编程里的主版本模板类、全特化、偏特化(C++ Type Traits)
1. 主版本模板类 首先我们来看一段初学者都能看懂,应用了模板的程序: 1 #include <iostream> 2 using namespace std; 3 4 template ...
随机推荐
- Hadoop基于Protocol Buffer的RPC实现代码分析-Server端--转载
原文地址:http://yanbohappy.sinaapp.com/?p=110 最新版本的Hadoop代码中已经默认了Protocol buffer(以下简称PB,http://code.goog ...
- P2774 方格取数问题
题目背景 none! 题目描述 在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数.现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大.试设计一个满足要求的取数算法.对于 ...
- Mybatis笔记五:Mybatis的全局配置文件Configuration.xml讲解
从 XML 中构建 SqlSessionFactory 每个基于Mybatis应用都是以一个SqlSessionFactory实例为中心.SqlSessionFactory实例可以由SqlSessio ...
- 【Revit API】Revit读取当前rvt的所有视图与其名称
1)读取所有视图: public static ViewSet GetAllViews(Document doc) { ViewSet views = new ViewSet(); FilteredE ...
- 【BZOJ4011】【HNOI2015】落忆枫音(动态规划)
[BZOJ4011][HNOI2015]落忆枫音(动态规划) 题面 BZOJ 洛谷 Description 「恒逸,你相信灵魂的存在吗?」 郭恒逸和姚枫茜漫步在枫音乡的街道上.望着漫天飞舞的红枫,枫茜 ...
- Howto run google-chrome as root
Just want to add a permanent solution to the problem: 1. Open google-chrome located in /usr/bin with ...
- Hdu5181 numbers
numbers Time Limit: 12000/6000 MS (Java/Others) Memory Limit: 196608/196608 K (Java/Others)Total ...
- rar 解压
三.rar命令语法 将/etc 目录压缩为etc.rar 命令为: rar a etc.rar /etc 1 将etc.rar 解压 命令为: rar x etc.rar unrar -e etc.t ...
- 「PLC」PLC基本编程
PLC中无非就是三大量:开关量(数字量).模拟量.脉冲量.只在搞清楚三者之间的关系,你就能熟练的掌握PLC了. PLC编程算法(一) 1. 开关量也称逻辑量,指仅有两个取值,0或1.ON或OFF.它是 ...
- C#获取用户基本信息一(关注了公众号的用户)
一.获取Code 假设我们需要网页授权的页面的地址为redirect_uri 需要获取Code的话我们第一步是跳转到授权地址,我们第一步便是获取拼接授权地址 --采用snsapi_base方式 pu ...