从零开始学C++之模板(一):函数模板、函数模板特化、重载函数模板、非模板函数重载

一、引子

考虑求两数较大值函数max(a,b) 对于a,b的不同类型,都有相同的处理形式:

return a < b ? b : a;

用已有方法解决:

(1)宏替换 #define max(a,b) ((a)< (b) ? (b) : (a))

存在的问题:避开类型检查

(2)重载

存在的问题:需要许多重载版本

(3)使用函数模板

二、模板

模板是一种参数化的多态工具 所谓参数化的多态性,是指将程序所处理 的对象的类型参数化,使一段程序代码可以用于处理多不同类型的对象。 采用模板编程,可以为各种逻辑功能相同而数据类型不同的程序提供一种代码共享的机制。

模板包括函数模板(function template)、类模板(class template)。本文主要讨论函数模板

三、函数模板

(一)、函数模板的使用

函数模板的一般说明形式如下: 

template < 模板形参表>

返回值类型 函数名(模板函数形参表){

//函数定义体

}

1、函数模板的定义以关键字template开头 2、template之后<>中是函数模板的参数列表 3、函数模板的参数是类型参数,其类型为class或typename

template<class T> template<class T1, class T2>

4、模板形参在模板中作为一种类型使用,可以用于函数的形参、函数返回值和函数的局部变量 5、每个模板形参要在函数的形参列表中至少出现一次

6、模板参数名的作用域局限于函数模板的范围内

(二)、函数模板的使用

1、函数模板为所有的函数提供唯一的一段函数代码,增强了函数设计的通用性

2、使用函数模板的方法是先说明函数模板,然后实例化成相应的模板函数进行调用执行 函数模板不是函数,不能被执行 置换代码中的类型参数得到模板函数——实例化 实例化后的模板函数是真正的函数,可以被执行

3、模板被编译了两次

实例化之前,先检查模板代码本身,查看语法是否正确;在这里会发现语法错误,如果遗漏分号等。 实例化期间,检查模板代码,查看是否所有的调用都有效。在这里会发现无效的调用,如该实例化类型不支持某些函数调用或操作符等。

4、普通函数只需要声明,即可顺利编译,而模板的编译需要查看模板的定义(声明和定义需放在同个.h文件)

(三)、函数模板特化

假设现在我们有这样一个模板函数max:

template <typename T> const T& max(const T& a, const T& b) {

return a < b ? b : a;

}

然后现在我们要比较两个字符串的大小,如:

const char* str1 = "aaa"; const char* str2 = "zzz";

此时如果按一般的实例化,比较的将是str1 和 str2 的大小,即比较指针数值大小,而不是字符串大小,故我们需要实现一个模板函数的特化,如下:

template<> const char* const& max(const char* const& a, const char* const& b) {

return strcmp(a, b) < 0 ? b : a;

}

(四)、重载函数模板,非模板函数重载

C++语言可以重载一个函数模板 用户可以用非模板函数重载一个同名的函数模板

max.h:

#ifndef _MAX_H_
#define _MAX_H_

#include <iostream>
using namespace std;

template <typename T>
const T &max(const T &a, const T &b)
{
    cout << "template max(const T& a, const T& b)" << endl;
    return a < b ? b : a;
}

// 函数模板重载
template <typename T>
const T &max(const T &a, const T &b, const T &c)
{
    cout << "template max(const T& a, const T& b, const T& c)" << endl;
    return ::max(a, b) < c ? c : ::max(a, b);
    // ::max 会调用非模板函数
}

// 非模板函数重载
const int &max(const int &a, const int &b)
{
    cout << "max(const int& a, const int& b)" << endl;
    return a < b ? b : a;
}

// 函数模板特化
template <>
const char *const &max(const char *const &a, const char *const &b)
{
    cout << "template <> max(const char* const&a, const char* const& b)" << endl;
    return strcmp(a, b) < 0 ? b : a;
}

// 非模板函数重载
const char *const &max(const char *const &a, const char *const &b)
{
    cout << "max(const char* const&a, const char* const& b)" << endl;
    return strcmp(a, b) < 0 ? b : a;
}

#endif // _MAX_H_

main.cpp:

#include <iostream>
#include <string>
using namespace std;

#include "max.h"

class Test
{
public:

    friend bool operator<(const Test &t1, const Test &t2)
    {
        cout << "operator<(const Test& t1, const Test& t2)" << endl;
        return true;
    }
};

int main(void)
{
    //与 std::max 区别开来
    cout <<::max(5.5, 6.6) << endl;     // 自动推导 max(const double&, const double&);
    cout <<::max('a', 'c') << endl;     // 自动推导 max(const char&, const char&);

    Test t1;
    Test t2;
    ::max(t1, t2); // Test::operator<(const Test& t1, const Test& t2)

    const char *str1 = "aaa";
    const char *str2 = "zzz";
    cout <<::max(str1, str2) << endl; //优先选择非模板函数

    cout <<::max<>(str1, str2) << endl; //指定使用模板,进而找到模板特化
    //  cout<<::max<const char*>(str1, str2); // 显式指定模板特化函数max(const char* const&a, const char* const& b)
    cout <<::max(1, 5, 3) << endl; // 模板匹配,进而自动推导

    cout <<::max('a', 50) << endl; // 'a'即97;选择非模板函数(char可以隐式转换成int)

    cout <<::max(97, 100) << endl;          // 优先选择非模板函数

    cout <<::max<>(97, 100) << endl;        // 指定使用模板,进而自动推导
    //  cout<<::max<>('a', 50)<<endl;       // Error,指定使用模板,但编译器不知道怎样推导
    cout <<::max<int>(97, 100) << endl; // 显式指定模板函数max(const int&, const int&)
    cout <<::max<int>('a', 50) << endl; // 显式指定模板函数max(const int&, const int&)


    return 0;
}

函数模板可以通过传递的参数类型自动推导,查看是否有合适的函数实例可用,而类模板则必须显式说明模板的类型参数,这样才能实例化模板类实例。

四、模板的偏特化

模板的偏特化是指需要根据模板的某些但不是全部的参数进行特化

(1) 类模板的偏特化 例如c++标准库中的类vector的定义 template <class T, class Allocator> class vector { // … // }; template <class Allocator> class vector<bool, Allocator> { //…//}; 这个偏特化的例子中,一个参数被绑定到bool类型,而另一个参数仍未绑定需要由用户指定。

(2) 函数模板的偏特化 严格的来说,函数模板并不支持偏特化,但由于可以对函数进行重载,所以可以达到类似于类模板偏特化的效果。 template <class T> void f(T);  (a)根据重载规则,对(a)进行重载template < class T> void f(T*);  (b)如果将(a)称为基模板,那么(b)称为对基模板(a)的重载,而非对(a)的偏特化。C++的标准委员会仍在对下一个版本中是否允许函数模板的偏特化进行讨论。

参考:

C++ primer 第四版 Effective C++ 3rd C++编程规范

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏PhpZendo

PHP 多任务协程处理

上周 有幸和同事一起在 SilverStripe 分享最近的工作事宜。今天我计划分享 PHP 异步编程,不过由于上周我聊过 ReactPHP;我决定讨论一些不一...

371
来自专栏C/C++基础

C++异常处理的开销

C++异常是C++有别于C的一大特性 ,异常处理机制给开发人员处理程序中可能出现的意外错误带来了极大的方便,但为了实现异常,编译器会引入额外的数据结构与处理机制...

642
来自专栏编程

Redis字符串数据结构的使用场景

各位晚上好,这几天复习一下Redis中的数据结构。 首先,字符串类型是Redis最基础的数据结构。首先,键(key)都是字符串类型,而且在Redis中的其他数据...

19510
来自专栏carven

js原生函数之call和apply,bind

call 和 apply 和 bind 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。

570
来自专栏前端架构

说说Angular $http service中的缓存

在Angular中,我们可以非常方便的进行AJAX请求。我们只需要注入$http,进行一个AJAX请求 – 然后我们就能得到一个promise对象,并可以很方便...

742
来自专栏互联网杂技

前端异步代码解决方案实践(二)

早前有针对 Promise 的语法写过博文,不过仅限入门级别,浅尝辄止食而无味。后面一直想写 Promise 实现,碍于理解程度有限,多次下笔未能满意。一拖再拖...

1046
来自专栏java架构学习交流

通过Struts了解MVC框架,兼说如何在面试中利用Struts证明自己

    虽然目前Struts MVC框架不怎么用了,但它确是个能帮助大家很好地入门Web MVC框架,而且,一些历史项目可能还用Struts,反正技多不压身,大...

1767
来自专栏高性能服务器开发

这些年一直记不住的 Java I/O

阅读目录 参考资料 前言 从对立到统一,字节流和字符流 从抽象到具体,数据的来源和目的 从简单到丰富,使用 Decorator 模式扩展功能 Java 7 中引...

3115
来自专栏Android知识点总结

再见kotlin--02函数

542
来自专栏前端儿

JS 数组去重(数组元素是对象的情况)

但当数组元素是对象时,就不能简单地比较了,需要以某种方式遍历各值再判断是否已出现。

520

扫码关注云+社区