C++—模板(1)模板与函数模板
1、引入
如何编写一个通用加法函数?
第一个方法是使用函数重载, 针对每个所需相同行为的不同类型重新实现这个函数。C++的这种编程机制给编程者极大的方便,不需要为功能相似、参数不同的函数选用不同的函数名,也增强了程序的可读性。简单示例:
int Add(const int &_iLeft, const int &_iRight)
{
return (_iLeft + _iRight) ;
}f
loat Add(const float &_fLeft, const float &_fRight)
{
return (_fLeft + _fRight) ;
}
【 缺点】
1、 只要有新类型出现, 就要重新添加对应函数。
2、 除类型外, 所有函数的函数体都相同, 代码的复用率不高
3、 如果函数只 是返回值类型不同, 函数重载不能解决
4、 一个方法有问题, 所有的方法都有问题, 不好维护。
还有一个方法是使用公共基类, 将通用的代码放在公共的基础类里面,让需要这部分功能的类去继承它。但是这也有【 缺点】:
1、 借助公共基类来编写通用代码, 将失去类型检查的优点;
2、 对于以后实现的许多类, 都必须继承自 某个特定的基类, 代码维护更加困难。
此外还可以使用特殊的预处理程序
#define ADD(a, b) ((a) + (b) )
【 缺点】不是函数, 不进行参数类型检测, 安全性不高
还有什么 办法吗?
-----------泛型编程
泛型编程: 编写与类型无关的逻辑代码, 是代码复用的一种手段。 模板是泛型编程的基础。
泛型编程最初诞生于C++中,由Alexander Stepanov[2]和David Musser[3]创立。目的是为了实现C++的STL(标准模版库)。其语言支持机制就是模板(Templates)。模板的精神其实很简单:参数化类型。换句话说,把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来做成模板参数T。
模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
模板是一种对类型进行参数化的工具;通常有两种形式:函数模板和类模板;函数模板针对仅参数类型不同的函数;类模板针对仅数据成员和成员函数类型不同的类。使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。下面分别介绍。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
2、函数模板
函数模板: 代表了 一个函数家族, 该函数与类型无关, 在使用时被参数化, 根据实参类型产生函数的特定类型版本。
模板函数的一般格式
template<typename Param1, typename Param2, . . . , class Paramn>
返回值类型 函数名 (参数列表)
{(函数体) . . . }
这里的template和typename都是关键字,typename是用来定义模板参数关键字, 也可以使用class,在这里typename 和class没区别。 但是建议尽量使用typename。注意: 不能使用 struct代替typename。<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。模板是一个蓝图, 它本身不是类或者函数, 编译器用模板产生指定的类或者函数的特定类型版本, 产生模板特定类型的过程称为函数模板实例化。
template <class T>
T Add(T left, T right) {
return left + right;
}
int main() {
cout << Add(, ) << endl;
cout << Add(20.12, 30.54) << endl;
cout << Add(, (int)30.54) << endl;
cout << Add<int>(, '') << endl;
getchar();
return ;
}


注意: 模板被编译了两次:
实例化之前,检查模板代码本身,查看是否出现语法错误, 如:遗漏分号;
在实例化期间,检查模板代码,查看是否所有的调用都有效, 如:实例化类型不支持某些函数调用。
【 实参推演】从函数实参确定模板形参类型和值的过程称为模板实参推断多个类型形参的实参必须完全匹配。
【 类型形参转换】一般不会转换实参以 匹配已有的实例化, 相反会产生新的实例。 编译器只 会执行两种转换: 1、 const转换: 接收const引 用或者const指针的函数可以分别用非const对象的引 用或者指针来调用 2、 数组或函数到指针的转换: 如果模板形参不是引 用类型, 则对数组或函数类型的实参应用常规指 针转换。 数组实参将当做指向其第一个元素的指针, 函数实参当做指向函数类型的指针。
2、1 模板参数
函数模板有两种类型参数: 模板参数和调用参数
模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用 , 遵循名字屏蔽规则
typedef int T;
template<class T>
void FunTest(T t)
{
cout << "t Type = " << typeid(t).name() << endl; }
T gloab;
int main() {
FunTest();
cout << "gloab Type = " << typeid(gloab).name() << endl;
return ;
}

模板形参的名字在同一模板形参列表中只能使用 一次
所有模板形参前面必须加上class或者typename关键字修饰
注意: 在函 数模板的内 部不能指定缺省的模板实参。
下面模板函数声明有问题吗?
template<class T, U, typename V>
void f1(T, U, V) ;
template<class T>
T f2(int &T) ;
template<class T>
T f3 (T, T) ;
typedef int TYPENAME;
template<typename TYPENAME>
TYPENAME f4(TYPENAME) ;
2、2非模板类型参数
非模板类型形参是模板内 部定义的常量, 在需要常量表达式的时候, 可以使用非模板类型参数。例如数组长度:
template<typename T,int N>
void FunTest(T(&_array)[N])
{
for (int indx = ; indx < N; ++indx) {
_array[indx] = ;
}
}
int main() {
int a[];
float b[];
FunTest(a);
FunTest(b);
getchar();
return ;
}

//类型等价性
const int iByteCnt = ;
int b[iByteCnt+] ;
int a[] ;
FunTest(a) ; // FunTest<int, 10> 两个数组等价
FunTest(b) ; // FunTest<int, 10> 编译器不会合成新的函数
模板形参说明:
1、 模板形参表使用<>括起来;
2、 和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同;
3、 定义模板函数时模板形参表不能为空;
4、 模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后;
5、 模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换;
6、 模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
2、3模板函数重载
template<typename T>
1 int Max(const int& left, const int & right)
{
return left>right? left: right;
}
template<typename T>
T Max(const T& left, const T& right)
{
return left>right? left: right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{
return Max(Max(a, b) , c) ;
} ;
int main()
{
Max(, , ) ;
Max<>(, ) ;
Max(, ) ;
Max(, . ) ;
Max<int>(. , . ) ;
Max(. , . ) ;
return ;
}
注意: 函数的所有重载版本的声明都应该位于该函数被调用位置之前。
【 说明】
1、 一个非模板函数可以和一个同名 的函数模板同时存在, 而且该函数模板还可以被实例化为这个非模板函数。
2、 对于非模板函数和同名 函数模板, 如果其他条件都相同, 在调动时会优先调动非模板函数而不会从该模板产生出一个实例。 如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
3、 显式指定一个空的模板实参列表, 该语法告诉编译器只 有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
4、 模板函数不允许自 动类型转换, 但普通函数可以进行自 动类型转换。
2、4模板函数特化
有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下, 通用模板定义对于某个类型可能是完全错误的, 或者不能编译, 或者做一些错误的事情。
template<typename T>
int compare(T t1, T t2) {
if (t1 < t2)
return -;
if (t1 > t2)
return ;
return ;
}
int main() {
char *pStr1 = "abcd";
char *pStr2 = "";
cout << compare(pStr1, pStr2) << endl;
getchar();
return ;
}
输出结果是-1;但是当我们定义如下:
char *pStr2 = "";
char *pStr1 = "abcd";
其他全部不变时,输出结果依然是-1。说明这之中存在问题。分析如下:
因为直接将两个指针变量的地址传递给函数模板,在比较时直接比较的是两个地址的大小,而没有比较指针内容,所以返回值一直是-1。显然这个函数满足不了我们的需求了,它支持常见int, float等类型的数据的比较,但是不支持char*(string)类型。所以我们必须对其进行特化,以让它支持两个字符串的比较。所谓特化,就是将泛型的东东搞得具体化一些,从字面上来解释,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,或受到特定的修饰(例如const或者摇身一变成为了指针之类的东东,甚至是经过别的模板类包装之后的模板类型)或完全被指定了下来。
我们可以下面这样来定义
template<>
int compare<const char*>(const char* const p1, const char* const p2)
{
return strcmp(p1, p2);
}
也可以这样定义:
template < >
int compare(const char* left, const char* right)
{
return strcmp(p1, p2);
}
模板函数特化形式如下:
1、 关键字template后面接一对空的尖括号<>;
2、 函数名 后接模板名 和一对尖括号, 尖括号中指定这个特化定义的模板形参;
3、 函数形参表;
4、 函数体;
template<>
返回值 函数名 <Type>(参数列表)
{
// 函数体
}
特化的声明必须与特定的模板相匹配, 否则
注意: 在模板特化版本的调用中 , 实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配, 编译器将为实参模板定义中实例化一个实例。函数模版的特化,当函数调用发现有特化后的匹配函数时,会优先调用特化的函数,而不再通过函数模版来进行实例化。
template<typename T>
int compare(T t1, T t2) {
cout << "in template<class T>..." << endl;
if (t1 < t2)
return -;
if (t1 > t2)
return ;
return ;
}
// 这个是一个特化的函数模版
template<>
int compare<const char*>(const char* const p1, const char* const p2)
{
cout << "in special template< >..." << endl;
return strcmp(p1, p2);
}
// 特化的函数模版, 两个特化的模版本质相同, 因此编译器会报错
// error: redefinition of 'int compare(T, T) [with T = const char*]'|
// 这个其实本质是函数重载
/*template < >
int compare(const char* left, const char* right)
{
std::cout << "in special template< >..." << std::endl; return strcmp(left, right);
}*/
int main() {
const char *pStr1 = "abcd";
const char *pStr2 = "";
char *pStr3 = "abcd";
char *pStr4 = "";
cout << compare(pStr1, pStr2) << endl;
cout << compare(pStr3, pStr4) << endl;
getchar();
return ;
}
输出结果:
分析如下:

注意: 特化不能出 现在模板实例的调用 之后, 应该在头文件中 包含模板特化的声明 , 然后使用 该特化版本的每个源文件包含该头文件。
C++—模板(1)模板与函数模板的更多相关文章
- C++ 模板常见特性(函数模板、类模板)
背景 C++ 是很强大,有各种特性来提高代码的可重用性,有助于减少开发的代码量和工作量. C++ 提高代码的可重用性主要有两方面: 继承 模板 继承的特性我已在前面篇章写过了,本篇主要是说明「模板」的 ...
- luogu P5410 模板 扩展 KMP Z函数 模板
LINK:P5410 模板 扩展 KMP Z 函数 画了10min学习了一下. 不算很难 思想就是利用前面的最长匹配来更新后面的东西. 复杂度是线性的 如果不要求线性可能直接上SA更舒服一点? 不管了 ...
- C++函数模板
函数模板提供了一种函数行为,该函数行为可以用多种不同的类型进行调用,也就是说,函数模板代表一个函数家族,这些函数的元素是未定的,在使用的时候被参数化. 本文地址:http://www.cnblogs. ...
- [Reprint] C++函数模板与类模板实例解析
这篇文章主要介绍了C++函数模板与类模板,需要的朋友可以参考下 本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程( ...
- C++-函数模板特化如何避免重复定义
我正在用一个基于模板的库源代码,该库包含一些针对特定类型的模板函数特化.类模板,函数模板和模板函数特化都在头文件中.我在我的.cpp文件中 #include 头文件并编译链接工程.但是为了在整个工程 ...
- 类型推导:函数模板与auto
1.从函数模板谈起 函数模板的类型推导机制是在c++98时代就有的,auto的类型推导机制与其基本一致,所以先理解函数模板类型推导. 函数模板可以用如下代码框架表示: #template<typ ...
- C++使用函数模板
函数模板: 函数模板是蓝图或处方功能,编译器使用其发电功能系列中的新成员. 第一次使用时,新的功能是创建.从功能模板生成的函数的实例称为模板或模板的实例.函数模板的开始是keywordtemplate ...
- 25.C++- 泛型编程之函数模板(详解)
本章学习: 1)初探函数模板 2)深入理解函数模板 3)多参函数模板 4)重载函数和函数模板 当我们想写个Swap()交换函数时,通常这样写: void Swap(int& a, int&am ...
- C++_进阶之函数模板_类模板
C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...
随机推荐
- Hadoop系列(三):hadoop基本测试
下面是对hadoop的一些基本测试示例 Hadoop自带测试类简单使用 这个测试类名叫做 hadoop-mapreduce-client-jobclient.jar,位置在 hadoop/share/ ...
- List根据对象的两个字段进行排序,并且有一个倒序
用java8 的lambda 表达式 list.sort(Comparator.comparing(Live::getId) .thenComparing(Live::getAppId, Compar ...
- linux环境下安装lnmp出现php安装失败
找到lnmp1.5/include/version.sh文件打开修改 #Freetype_Ver='freetype-2.7' 为 #Freetype_Ver='freetype-2.9'即可
- Angular记录(6)
文档资料 箭头函数--MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_fun ...
- 如何运用jieba库分词
使用jieba库分词 一.什么是jieba库 1.jieba库概述 jieba是优秀的中文分词第三方库,中文文本需要通过分词获得单个词语. 2.jieba库的使用:(jieba库支持3种分词模式) 通 ...
- 五十九、linux 编程—— I/O 多路复用 fcntl
59.1 介绍 前面介绍的函数如,recv.send.read 和 write 等函数都是阻塞性函数,若资源没有准备好,则调用该函数的进程将进入阻塞状态.我们可以使用 I/O 多路复用来解决此问题(即 ...
- react native 中时间选择插件
npm install react-native-datepicker --save import DatePicker from 'react-native-datepicker'; <Vie ...
- 1、jQuery的使用入门
一.创建一个WEB项目,在WebRoot下新建一个Jscript文件夹,并将jQuery中的jquery-3.1.1.min.js文件复制过来. 二.用<script>标签引入jQuery ...
- ThinkPHP—URL的访问以及各种方法的操作
1.URL访问 ThinkPHP采用单一入口模式访问应用,对应用的所有请求都定向到应用的入口文件,系统会从URL参数中解析当前请求的模块.控制器和操作,下面是一个标准的URL访问格式: 第一种访问方式 ...
- struts2 s2-032漏洞分析
0x01Brief Description 最近面试几家公司,很多都问到了s2漏洞的原理,之前调试分析过java反序列化的漏洞,觉得s2漏洞应该不会太难,今天就分析了一下,然后发现其实漏洞的原理不难, ...
