• 模板
  • 模板语义
  • 函数模板
    • 重载泛化
    • 函数模板
      • 语法
      • 模板泛化
      • 特性小结
      • 编译原理
  • 函数模板应用
    • 算法抽象
    • 快速排序算法实现模板化
    • 函数模板默认参数
    • 函数模板的特化
  • 函数模板适用场景

模板

模板语义

模板是一门语言,支持泛型的语法基础。

泛型(Generic Programming),是指在多种数据类型上皆可操作的含意。泛型编程的代表作品 STL 是一种高效、泛型、可交互操作的软件组件。

泛型编程最初诞生于 C++中,目的是为了实现 C++的 STL(标准模板库)。其语言支持机制就是模板(Templates)。

模板的精神其实很简单:类型参数化(type parameterized),即类型也是一种参数,也是一种静多态。

换句话说,把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽象出来做成模板参数 T。

函数模板

重载泛化

重载函数,虽然实现了泛化的一些特性,但是不彻底,且有二义性(ambiguous)的存在。

代码演示:

#include <iostream>
using namespace std;void myswap(int & a, int & b)
{int t = a;a = b;b = t;
}void myswap(double & a, double & b)
{double t = a;a = b;b = t;
}
int main()
{double a = 2; double b = 3;myswap(a, b);cout << a << "   " << b << endl;int aa = 2; int bb = 3;myswap(aa, bb);cout << aa<<"   " << bb << endl;return 0;
}

运行结果为:

如下,示例中 long 类型可以隐式的转化为 int 或是 double,编译会出现二义性,导致编译失败。
代码演示:

#include <iostream>
using namespace std;void myswap(int & a, int & b) //函数重载
{int t = a;a = b;b = t;
}void myswap(double & a, double & b) //函数重载
{double t = a;a = b;b = t;
}int main()
{long a = 2; long b = 3;myswap(a, b);//ambiguousreturn 0;
}

编译器报错:

可以增加函数重载类型来解决上面的问题,也是一种最好的解决方案。

例如增加:

void myswap(long & a, long & b) //函数重载
{long t = a;a = b;b = t;
}

也可以使用模板来解决函数重载的泛化不彻底的问题。
使用模板实现彻底泛化。

函数模板

语法

//在一个函数的参数表,返回类型和函数体中使用参数化的类型。
template<typename 类型参数 T1,typename/class 类型参数 T2,...>
返回类型 函数模板名(函数参数列表)
{函数模板定义体
}

模板泛化

template,既可以与函数同行,也可以将函数另起一行来书写。T 即为范化的类型。

其过程,相当于经历了两次编译,先依据实参类型,实例化函数,然后再将实例化的函数,参与编译。

代码演示:

#include <iostream>
using namespace std;template<typename T>
void myswap(T & a, T & b)
{T t = a;a = b;b = t;
}int main()
{long a = 2; long b = 3;myswap(a, b);cout << a << "   " << b << endl;return 0;
}

运行结果:

我们说重载对于泛化不彻底,模板彻底实现泛化,我们接下来进行说明:
代码演示:

#include <iostream>
using namespace std;template<typename T>
void myswap(T & a, T & b)
{T t = a;a = b;b = t;
}int main()
{long a = 2; long b = 3;myswap(a, b);cout << a << "   " << b << endl;string s1 = "hello";string s2 = "world";myswap(s1,s2);cout << s1 << "   " << s2 << endl;double d1 = 3.1;double d2 = 4.1;myswap(d1, d2);cout << d1 << "   " << d2 << endl;return 0;
}

运行结果:

那么就会引发我们思考:
模板是不是悄悄的把我们学习过的所有类型全部重载了一遍呢?

我们通过代码进行验证:自实现类型使用模板。
代码演示:

#include <iostream>
using namespace std;class MyType
{public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y;
};template<typename T>
void myswap(T & a, T & b)
{T t = a;a = b;b = t;
}int main()
{long a = 2; long b = 3;myswap(a, b);cout << a << "   " << b << endl;string s1 = "hello";string s2 = "world";myswap(s1,s2);cout << s1 << "   " << s2 << endl;double d1 = 3.1;double d2 = 4.1;myswap(d1, d2);cout << d1 << "   " << d2 << endl;MyType mt1(1, 2);MyType mt2(3, 4);myswap(mt1, mt2);cout << mt1._x <<"   "<<mt1._y << endl;return 0;
}

运行结果:

我们可以看到,不只是系统提供的数据类型可以实现模板泛化,自定义的类也可以实现模板泛化。

所以模板解决了所有类型的操作,不管是基础类型还是自定义类型。

模板的原理:

其实模板的使用本来应该是如下的使用方式:
代码演示:

#include <iostream>
using namespace std;class MyType
{public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y;
};template<typename T>
void myswap(T & a, T & b)
{T t = a;a = b;b = t;
}int main()
{long a = 2; long b = 3;myswap<long>(a, b);cout << a << "   " << b << endl;string s1 = "hello";string s2 = "world";myswap<string>(s1,s2);cout << s1 << "   " << s2 << endl;double d1 = 3.1;double d2 = 4.1;myswap<double>(d1, d2);cout << d1 << "   " << d2 << endl;MyType mt1(1, 2);MyType mt2(3, 4);myswap<MyType>(mt1, mt2);cout << mt1._x <<"   "<<mt1._y << endl;return 0;
}

运行结果:

只不过现在类型推导足够强大了,所以

myswap<long>(a, b);
myswap<string>(s1,s2);
myswap<double>(d1, d2);
myswap<MyType>(mt1, mt2);

就省略成了:

myswap(a, b);
myswap(s1,s2);
myswap(d1, d2);
myswap(mt1, mt2);

上面代码的过程,相当于经历了两次编译,先依据实参类型,实例化函数。然后再将实例化的函数,参与编译。

函数模板myswap
–>实例化为
模板函数myswap < int >
–>模板函数的调用myswap < int >( 1 , 2 )

一般来说模板经历两个过程:
编译器编译检查模板的语法是否正确,语法通过时。当发生某一具体调用时,根据特定类型(类型由我们明确的告诉编译器或者进行自动类型推导)然后产生特定类型的模板函数版本用来调用。

什么叫做类型参数化呢?
在模板中可以设置默认类型:
代码演示:

template<typename T = int>
void myswap(T & a, T & b)
{T t = a;a = b;b = t;
}
template<typename T = int>

一般情况下不给默认值。

myswap<long>(a, b);

也不写类型。

因为类型推导已经强大到不需要写。

那么也就是把类型当作参数传递给函数模板,然后函数模板实例化为模板函数进行调用。

特性小结

先实例化,再调用。
严格匹配,不存在隐式转化。
尺有所短,寸有所长。

编译原理

编译器遇到模板方法定义时,会进行语法检查,但是并不编译模板。编译器无法编译模板定义,因为它不知道使用什么类型。比如 T a,b; 在不知晓 T 具体类型时,是无法分配内存,更无从谈编译 a = b;

T 获取类型的过程,称为模板的实例化,函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译(类型检查),在调用的地方对参数替换后的代码进行编译(代码生成)。

那么模板既然这么好,模板是不是就没有缺陷了呢?
编译器并不是把函数模板处理成能够处理任意类的函数。比如,自定义类型,如何处理呢?

代码演示:

#include <iostream>
using namespace std;class MyType
{public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y;
};template<typename T = int>
void myswap(T & a, T & b)
{T t = a;a = b;b = t;
}int main()
{int i = 3;double j = 1.2;myswap(i, j);return 0;
}

编译器报错:

那么我们可以不可以传递两个类型来实现模板呢?
代码演示:

#include <iostream>
using namespace std;class MyType
{public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y;
};template<typename T1 , typename T2>
void myswap(T1 & a, T2 & b)
{T t = a;  //类型应该使用 T1 呢?还是使用T2呢?a = b;b = t;
}//实现比较的函数
T Max(T1 & a, T2 & b)   //返回类型应该使用 T1 呢?还是使用T2呢?
{}int main()
{int i = 3;double j = 1.2;myswap(i, j);return 0;
}

结论是不可以,详细原因在代码中已经给出。

模板参数做返回类型需要编译器支持类型推导,需要确定返回类型来解决。

函数模板应用

算法抽象

数据类型的泛化,使的模板特别适合于作算法的抽象。
STL 中的 algorithm 就是典型的代表。
比如,算法 sort 就是支持类型泛化的代表排序算法。

例如:我把东西从A地搬到B地我只考虑怎么搬更省力而不考虑搬的是什么东西。

快速排序算法实现模板化

将快速排序算法实现模板化。此时,无论转入的是 int 的还是 float 的类型,均是可以处理。相同逻辑的函数没有必要写两遍。

代码演示:

#include <iostream>
#include <typeinfo>
using namespace std;
template<typename T>
void quickSort(T* array, int left, int right)
{if (left < right){int low = left; int high = right;T pivot = array[low];while (low < high){while (array[high] >= pivot && high > low)high--;array[low] = array[high];while (array[low] <= pivot && high > low)low++;array[high] = array[low];}array[low] = pivot;quickSort(array, left, low - 1);quickSort(array, low + 1, right);}
}int main()
{int array[10] = { 1,3,5,7,2,4,6,8,0,9 };quickSort(array, 0, 9);for (auto i : array){cout << i << endl;}
}

运行结果:

那么我们对于数据类型进行修改:
代码演示:

#include <iostream>
#include <typeinfo>
using namespace std;
template<typename T>
void quickSort(T* array, int left, int right)
{if (left < right){int low = left; int high = right;T pivot = array[low];while (low < high){while (array[high] >= pivot && high > low)high--;array[low] = array[high];while (array[low] <= pivot && high > low)low++;array[high] = array[low];}array[low] = pivot;quickSort(array, left, low - 1);quickSort(array, low + 1, right);}
}
int main()
{char array[10] = { 'j','g','f','r' ,'d' ,'e' ,'w' ,'h' ,'j' ,'j' };quickSort(array, 0, 9);for (auto i : array){cout << i << endl;}
}

运行结果为:

也可以对于字符串进行排序:
对于main函数进行修改:

int main()
{string array[10] = { "sdf" ,"gfsd" ,"rewt" ,"yter" ,"ndfg" ,"asdf" ,"hgjf" ,"sdfg" ,"jrrt" ,"hike" };quickSort(array, 0, 9);for (auto i : array){cout << i << endl;}
}

运行结果:

写模板时,先用基本类型实现,然后套用模板,然后进行测试。

函数模板默认参数

重在理解,类型参数化,此时的默认参数,不再是一数值,而是类型。

函数模板,在调用时,先实例化为模板函数,然后再调用。当然也可以设置默认类型的默认值。由于系统强大的自动推导能力,有时默认也没有太大的意义。

template<typename T = int>
void quickSort(T * array,int left, int right)

函数模板的特化

Template Specialization,函数模板的特化,即函数模板的特殊情况,个例行为。

就是在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的实例版本。

template<typename T> int compare( T &a,T &b)
template<> int compare <const char*>( const char*&a, const char*&b)

当以特化定义时的形参使用模板时,将调用特化版本,模板特化分为全特化和偏特化,函数模板的特化,只能全特化;

比如,我们在比较两个数的大小时:
代码演示:

#include <iostream>
#include <string.h>
using namespace std;template<typename T> int compare(T& a,T& b)
{if (a > b) return 1;else if (a < b)return -1;else return 0;
}int main()
{int a = 10;int b = 20;cout << compare(a, b) << endl;string sa = "abcd";string sb = "abc";cout << compare(sa, sb) << endl;return 0;
}

运行结果:

上面的代码是模板的正常使用,没有问题。

我们对于代码进行修改:

#include <iostream>
#include <string.h>
using namespace std;template<typename T> int compare(T& a,T& b)
{if (a > b) return 1;else if (a < b)return -1;else return 0;
}int main()
{string sa = "abcd";string sb = "abc";cout << compare(sa, sb) << endl;const char * ca = "abcd";const char * cb = "abc";cout << compare(ca, cb) << endl;return 0;
}

运行结果:

我们可以看到上面的测试运行出来了不同的结果。

原因分析:
实参为两个 char * 时,比较的是指针地址的大小,而不是指针指向内容的大小。

此时就需要为该函数模板定义一个特化版本,即特殊处理的版本:

代码演示:

#include <iostream>
#include <string.h>
using namespace std;template<typename T> int compare(T& a, T& b)
{if (a > b) return 1;else if (a < b)return -1;else return 0;
}//模板特化版本
template<> int compare < const char* >(const char*& a, const char*& b)
{return strcmp(a, b);
}int main()
{const char * ca = "abcd";const char * cb = "abc";cout << compare(ca, cb) << endl;return 0;
}

运行结果:

函数模板适用场景

函数模板,只适用于函数的参数个数相同而类型不同,且函数体相同的情况。

C++模板:模板简述,函数模板详细说明【C++模板】(56)相关推荐

  1. C++模板学习02(类模板)(类模板语法、类模板与函数模板的区别、类模板中的成员函数创建时机、类模板对象做函数参数、类模板与继承、类模板成员函数类外实现、类模板分文件编写、类模板与友元)

    C++引用详情(引用的基本语法,注意事项,做函数的参数以及引用的本质,常量引用) 函数高级C++(函数的默认参数,函数的占位参数,函数重载的基本语法以及注意事项) C++类和对象-封装(属性和行为作为 ...

  2. 类模板里面的函数写在类模板里面和写在类模板的外面,且在1个.cpp文件中

    和类一样,类模板里面声明的函数,其函数体写在类里面和外面都行,但是有一点需要注意:1.用友元函数重载<< 或 >>运算符,函数声明写在类模板里面,函数实现写在类模板外面,要在函 ...

  3. Django模板系统(十分 非常详细)

    转载:http://www.czug.org/python/django/04.html 翻译www.djangobook.com之第四章:Django模板系统 The Django Book:第4章 ...

  4. think PHP全栈开发教程,【Thinkphp教程】模板中使用函数

    仅仅是输出变量并不能满足模板输出的需要,内置模板引擎支持对模板变量使用调节器和格式化功能,其实也就是提供函数支持,并支持多个函数同时使用.用于模板标签的函数可以是PHP内置函数或者是用户自定义函数,和 ...

  5. C++ Primer 5th笔记(chap 16 模板和泛型编程) 类模板的成员函数

    1. 分类 1.1 定义在类模板内的成员函数 被隐式声明为内联函数. 1.2. 类模板外部的成员函数, 类模板的成员函数本身是一个普通函数.但是,类模板的每个实例都有其自己版本的成员函数.因此,类模板 ...

  6. 类模板,多种类型的类模板,自定义类模板,类模板的默认类型,数组的模板实现,友元和类模板,友元函数,类模板与静态变量,类模板与普通类之间互相继承,类模板作为模板参数,类嵌套,类模板嵌套,类包装器

     1.第一个最简单的类模板案例 #include "mainwindow.h" #include <QApplication> #include <QPush ...

  7. C++ 泛型编程(一):模板基础:函数模板,类模板,模板原理,模板匹配规则

    类模板 函数模板 泛型编程 泛型编程,泛型即是指具有在多种数据类型上皆可操作的含义,其实就是能够帮助开发者编写完全一般化并可重复使用的算法,同样的工作不需要做多次,同样的算法针对不同的类型也不应该写多 ...

  8. C++函数模板和普通函数的调用规则

    C++函数模板和普通函数的调用规则: 普通函数可以进行自动类型转换. 函数模板必须严格类型匹配. C++编译器优先考虑普通函数. 如果函数模板可以产生一个更好的匹配,那么选择模板. 可以通过空模板实参 ...

  9. c++模板---3(类模板碰到继承问题,类模板类外实现,类模板与友元函数)

    类模板碰到继承问题 基类如果是模板类,必须让子类告诉编译器 基类中的T到底是什么类型 如果不告诉,那么无法分配内存,编译不过 利用参数列表class Child :public Base<int ...

  10. C++ 泛型编程(一):模板基础:函数模板、类模板、模板推演成函数的机制、模板实例化、模板匹配规则

    文章目录 泛型编程 函数模板 函数模板实例化 隐式实例化 显式实例化 函数模板的匹配规则 类模板 类模板的实例化 泛型编程 泛型编程旨在削减重复工作,如: 将一个函数多次重载不如将他写成泛型. voi ...

最新文章

  1. 安装 Fedora 22 后要做的事情
  2. ASP.NET的Cookie跨域问题
  3. 训练超大规模图模型,PyTorchBigGraph如何做到?
  4. python快速排序解析_快速排序python实现总结
  5. LSMW批处理使用方法(08)_步骤8、9
  6. 应用MVP模式写出可维护的优美Android应用
  7. Scrum基础知识体系和感想
  8. C++学习(一)之Visual Studio安装以及首次使用
  9. 使用 Composer 为 ThinkPHP(3.2.3)框架添加和管理组件
  10. 现代科技概论_现代科技概论课程:力与运动1
  11. 诗词文中的地名(名人生卒地、生卒年、名人籍贯)
  12. 【网络攻防原理与技术】第6章:特洛伊木马
  13. 前端零基础入门: 用css设置文字样式
  14. Python 计算彩色图像信噪比
  15. 利用ESP8266与米思齐从零制作模拟温室大棚--接线篇
  16. pad平板性能测试软件,苹果iPad 2020款评测,最便宜的iPad,性能碾压安卓?
  17. 声明圆锥体类,实现Area和Volume接口,计算表面积和体积,按体积比较大小。
  18. Analyzing the Linux boot process-分析Linux启动过程
  19. 【实用工具】Gephi下载与安装
  20. kotlin_基础_密封类(sealed)

热门文章

  1. 【Pygame小游戏】这款休闲游戏你肯定了解过《开心消消乐》更新版本上线,好土好喜欢
  2. 【Flask项目2】模型类的映射方式新版本和旧版本(4)
  3. WebsocketWebSSH
  4. (20)PDE_PTE属性(U/S PS A D 有效位)
  5. 从一个被Tomcat拒绝的漏洞到特殊内存马
  6. c++ requests网络请求库
  7. 9、Java Swing JRadioButton:单选按钮组件
  8. 5、MySQL事务隔离级别详解
  9. 1.9 多态性:什么是多态?
  10. 通过Cookie实现客户端与服务端会话的维持;