函数模板是指这样的一类函数:可以用多种不同数据类型的参数进行调用,代表了一个函数家族。它的外表和普通的函数很相似,唯一的区别就是:函数中的有些元素是未确定的,这些元素将在使用的时候才被实例化。先来看一个简单的例子:

一、定义一个简单的函数模板

下面的这个例子就定义了一个模板函数,它会返回两个参数中最大的那一个:

// 文件:"max.hpp"
template<typename T>
inline const T& max(const T& x, const T& y)
{
return x < y ? y : x;
}

这个函数模板定义了一个“返回两个值中最大者”的函数家族,而参数的类型还没有确定,用类型模板参数T来确定。模板参数需要使用如下的方式来声明:

template< 模板参数列表 >

在这个例子中,模板参数列表为:typename T。关键字typename引入了T这个类型模板参数。当然了,可以使用任何标识符作为类型模板参数的名称。我们可以使用任何类型(基本数据类型、类类型)来实例化该函数模板,只要所使用的数据类型提供了函数模板中所需要的操作即可。例如,在这个例子中,类型T需要支持operator <,因为a和b就是通过这个操作符来比较大小的。

鉴于历史原因,也可以使用关键字class来取代typename来定义类型模板参数,然而应该尽可能地使用typename

二、使用函数模板

下面的程序使用了上面定义的这个函数模板:

#include <iostream>
#include <string>
#include "max.hpp" using namespace std; int main(int argc, char *argv[])
{
cout << max(4, 3) << endl; // 使用int类型实例化了函数模板,并调用了该函数实例。
cout << max(4.0, 3.0) << endl; // 使用double类型实例化了函数模板,并调用了该函数实例。
cout << max(string("hello"), string("world")) << endl; // 使用string类型实例化了函数模板,
// 并调用了该函数实例。
return 0;
}

通常而言,并不是把模板编译成一个可以处理任何类型的单一实体,而是针对于实例化函数模板参数的每种类型,都从函数模板中产生出一个独立的函数实体。因此,针对于每种类型,模板代码都被编译了一次。这种用具体类型代替模板参数的过程,叫做模板的实例化。它产生了一个新的函数实例(与面向对象程序设计中的实例化不同)。

如果试图基于一个不支持模板内部所使用的操作的类型实例化一个模板,那么将会引发一个编译期错误:

std::complex<double> c1, c2;
max(c1, c2); // 编译错误:std::complex并不支持运算符<

所以说:模板被编译了两次,分别发生于:

  • 模板实例化之前,查看语法是否正确,此时可能会发现遗漏的分号等。
  • 模板实例化期间,检查模板代码, 查看是否所有的调用都有效。此时可能会发现无效的调用,例如实例化类型不支持某些函数调用等。

所以这引发了一个重要的问题:当使用函数模板并且引发模板实例化时,编译器必须查看模板的定义。事实上,这就不同于普通的函数,因为对于普通的函数而言,只要有函数的声明(甚至不需要定义),就可以顺利地通过编译期。

三、函数模板实参推断

当我们为某些实参调用一个函数模板时,模板参数可以由我们所传递的实参来决定。

注意:函数模板在推断参数类型时,不允许自动类型转换,每个类型模板参数都必须正确的匹配。

template<typename T>
inline const T& max(const T& x, const T& y)
{
return x < y ? y : x;
} int main()
{
// 不能这样调用:
// max(10, 20.0); // 错误,因为函数模板中的类型推断拒绝隐式类型转换
// 这是因为,无法确定到底应该使用哪个参数类型来实例化这个模板函数。
// 所以,C++拒绝了这种做法。 可用的解决方案:
::max(static_cast<double>(10), 20.0); // OK,因为两个参数都为double。
::max<double>(10, 20.0); // OK, 显示指定参数,这样可以尝试对参数进行类型转换。
return 0;
}

注意:模板实参推断并不适合返回类型。因为返回类型并不会出现在函数调用参数的类型里面。

所以,必须要显示地指定返回类型:

template<typename T1, typename T2, typename RT>
inline RT func()
{
// ...
return RT();
} int main(int argc, char *argv[])
{
func<int>(); // 必须这样显示地指定返回类型才可以,无法进行自动类型推断。
return 0;
}

四、函数模板的重载

和普通的函数一样,函数模板也可以被重载。在下面的例子中,一个非模板函数可以和一个同名的函数模板同时存在,这称为函数模板的特化。而且该函数模板还被实例化为这个非模板函数。

// #1
inline const int& max(const int& a, const int& b)
{
return a < b ? b : a;
} // #2
template<typename T>
inline const T& max(const T& a, const T& b)
{
return a < b ? b : a;
} // #3
template<typename T>
inline const T& max(const T& a, const T& b, const T& c)
{
return max(max(a, b), c);
} int main(int argc, char *argv[])
{
/*01*/max(7, 42, 68); // 调用#3
/*02*/max(7.0, 6.0); // 调用#2
/*03*/max('a', 'b'); // 调用#2
/*04*/max(7, 42); // 调用#1
/*05*/max<>(7, 42); // 调用#2
/*06*/max<double>(7, 42); // 调用#2但是没有推断参数
/*07*/max('a', 42.7); // 调用#1
return 0;
}

总结如下:

  • 对于非模板函数和同名的函数模板,如果其它条件都是相同的话,那么在调用的时候,重载解析过程中会优先调用非模板函数,而不会实例化模板(04)。
  • 如果模板可以产生一个具有更好匹配的函数,那么将选择模板(02, 03)。
  • 还可以显示地指定一个空的模板参数列表,告诉编译器:必须使用模板来匹配(05)。
  • 由于函数模板拒绝隐式类型转换,所以当所有的模板都无法匹配,但是发现可以通过强制类型转换来匹配一个非模板函数时,将调用那个函数(07)。

五、函数模板重载的注意事项

在重载函数模板时,请谨记:将对函数声明的改变限制在以下两种情况中:

  • 改变参数的数目
  • 显示指定模板的参数(即函数模板特化)

否则,很可能会导致非预期的结果,例如在下面的例子中,模板函数是使用引用进行传参的,然而在其中的一个重载中(实际上是针对char*进行的特化),却使用了值传递的方式,这将会导致不可预期的结果:

template<typename T>
inline const T& max(const T& a, const T& b)
{
return a < b ? b : a;
} // #2: 存在隐患,因为其它的重载都是以引用传递参数,而这个重载版本
// 却使用了值传递,不符合上面介绍的需要遵守的两个可变条件。
inline const char* max(const char* a, const char* b)
{
return std::strcmp(a, b) < 0 ? b : a;
} template<typename T>
inline const T& max(const T& a, const T& b, const T& c)
{
// 这里对max(a, b)的调用,如果调用了函数#2,
// 那么将会返回一个局部的值,如果恰好这个局部的值
// 又比c大,那么将会返回一个指向局部变量的指针,
// 这是很危险的(非预期的行为)。
return max( max(a, b), c );
} int main()
{
char str1[] = "frederic";
char str2[] = "anica";
char str3[] = "lucas"; char* p1 = str1;
char* p2 = str2;
char* p3 = str3; // 这种用法是错的,这是因为:
// max(a, b)返回的是一个指针,这个指针是一个局部的对象,
// 并且这个局部的对象很有可能会被返回。
auto result = max(p1, p2, p3);
return 0;
}

C++函数模板详解(一):概念和特性的更多相关文章

  1. 25.C++- 泛型编程之函数模板(详解)

    本章学习: 1)初探函数模板 2)深入理解函数模板 3)多参函数模板 4)重载函数和函数模板 当我们想写个Swap()交换函数时,通常这样写: void Swap(int& a, int&am ...

  2. 26.C++- 泛型编程之类模板(详解)

    在上章25.C++- 泛型编程之函数模板(详解) 学习了后,本章继续来学习类模板   类模板介绍 和函数模板一样,将泛型思想应用于类. 编译器对类模板处理方式和函数模板相同,都是进行2次编译 类模板通 ...

  3. C++ 类模板详解(一):概念和基本使用方式

    与函数模板类似地(C++函数模板详解(一):概念和特性) ,类也可以被一种或多种类型参数化.例如,容器类就是一个具有这种特性的典型例子,它通常被用于管理某种特定类型的元素.只要使用类模板,我们就可以实 ...

  4. C++模板详解(三):参数化声明详解

    在前两节中(C++模板详解(一).C++模板详解(二)),我们了解了函数模板和类模板的基本概念和使用方法.在这篇博文里,我们主要来详细地阐述一下"模板的参数声明"这个话题,并且也谈 ...

  5. C++模板详解

    参考:C++ 模板详解(一) 模板:对类型进行参数化的工具:通常有两种形式: 函数模板:仅参数类型不同: 类模板:   仅数据成员和成员函数类型不同. 目的:让程序员编写与类型无关的代码. 注意:模板 ...

  6. Oracle函数sys_connect_by_path 详解

    Oracle函数sys_connect_by_path 详解 语法:Oracle函数:sys_connect_by_path 主要用于树查询(层次查询) 以及 多列转行.其语法一般为:       s ...

  7. C语言对文件的操作函数用法详解1

    在ANSIC中,对文件的操作分为两种方式,即: 流式文件操作 I/O文件操作 一.流式文件操作 这种方式的文件操作有一个重要的结构FILE,FILE在stdio.h中定义如下: typedef str ...

  8. 自写函数VB6 STUFF函数 和 VB.net 2010 STUFF函数 详解

    '*************************************************************************'**模 块 名:自写函数VB6 STUFF函数 和 ...

  9. SQL Server数据库ROW_NUMBER()函数使用详解

    SQL Server数据库ROW_NUMBER()函数使用详解 摘自:http://database.51cto.com/art/201108/283399.htm SQL Server数据库ROW_ ...

随机推荐

  1. JavaScript 构造树形结构的一种高效算法

    引言 我们经常会碰到树形数据结构,比如组织层级.省市县或者动植物分类等等数据.下面是一个树形结构的例子: 在实际应用中,比较常见的做法是将这些信息存储为下面的结构,特别是当存在1对多的父/子节点关系时 ...

  2. 洛谷$P1345\ [USACO5.4]$ 奶牛的电信$Telecowmunication$ 网络流

    正解:最小割 解题报告: 传送门$QwQ$ $QwQ$好久没做网络流了来复健下. 这个一看就很最小割趴?考虑咋建图?就把点拆成边权为$1$的边,然后原有的边因为不能割所以边权为$inf$. 然后跑个最 ...

  3. $bzoj4722$ 由乃 搜索

    正解:搜索 解题报告: 传送门$QwQ$ 首先发现长度为$len$的子集的值域为$[0,v\cdot len+len]$,数量为$2^{len}$.所以当$2^{len}\geq v\cdot len ...

  4. 洛谷$P4755\ Beautiful\ Pair$ 最大值分治

    正解:最大值分治 解题报告: 传送门$QwQ$ 昂考虑如果已经钦定了点$x$是这个$max$了,然后现在要求有多少对$[l,r]$满足$a_x=max\left\{a_i\right\},i\in[l ...

  5. HDU5179 beautiful number 题解 数位DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5179 题目大意: 给你一个数 \(A = a_1a_2 \cdots a_n\) ,我们称 \(A\) ...

  6. Python基础复习面向对象篇

    目录 类与对象的概念 实例方法 实例变量 初始化方法 析构方法 常用内置方法 继承 类方法与静态方法 动态扩展类与实例 @property装饰器 概述 面向对象是当前流行的程序设计方法,其以人类习惯的 ...

  7. Vuex入门实践(上)

    一.前言 vuex被称为是专为vue应用程序开发的的状态管理模式.它的作用使用一句话描述就是:让组件之间可以共享数据 话不多少,先抛开概念,我们写一个简单的示例感受一波. 二.项目开发环境 项目开发环 ...

  8. 18.Python模块包(pycharm右键创建文件夹和python package的区别)中__init__.py文件的作用

    原来在python模块的每一个包中,都有一个__init__.py文件(这个文件定义了包的属性和方法)然后是一些模块文件和子目录,假如子目录中也有 __init__.py 那么它就是这个包的子包了.当 ...

  9. schedule of 2016-09-19~2016-09-25(Monday~Sunday)——1st semester of 2nd Grade

    2016/9/19 Monday 1.make ppt for today's group meeting 2.recite 100 words 3.review <图解机器学习>ch1~ ...

  10. vnpy源码阅读学习(1):准备工作

    vnpy源码阅读学习 目标 通过阅读vnpy,学习量化交易系统的一些设计思路和理念. 通过阅读vnpy学习python项目开发的一些技巧和范式 通过vnpy的设计,可以用python复现一个小型简单的 ...