目录
一、为什么要定义模板
模板的优点:
二、模板的定义
三、模板的类型
3.1、函数模板
3.1.1、实例化:隐式实例化与显示实例化
3.1.2、函数模板、普通函数间的关系
3.1.2.1易错点:
3.1.2.2重载例子:
3.1.2.3优先级与执行顺序:
3.1.3、模板头文件与实现文件
3.1.4、模板的特化:偏特化与全特化
3.1.5、函数模板的参数类型
3.1.6、成员函数模板
3.2、类模板
注意:
模板的嵌套:
模板做参数:
四、可变模板参数
4.1、模板参数包
4.2、函数参数包
4.3、可变模板参数的优势(有两条)
一、为什么要定义模板
现在的 C++ 编译器实现了一项新的特性:模板( Template ),简单地说, 模板 是一种通用的描述机制,也就是说,使用模板允许使用 通用类型 来定义函数或类等,在使用时,通用类型可被具体的类型,如 int 、 double 甚至是用户自定义的类型来代替。模板引入一种全新的编程思维方式,称为 “ 泛型编程 ” 或 “ 通用编程 ” 。泛型编程:不是针对某一种具体的类型进行编程,而是针对一类类型进行编程,将类型抽象成T(类型参数化)
#形象地说,把函数比喻为一个游戏过程,函数的流程就相当于 游戏规则。#在以往的函数定义中,总是指明参数是 int 型还是 double 型等等,这就像是为张三(好比 int 型)和李四(好比 double 型)比赛制定规则。可如果王五( char* 型)和赵六( bool 型)要比赛,还得提供一套函数的定义,这相当于又制定了一次规则,显然这是很麻烦的。#模板的的引入解决了这一问题 ,不管是谁和谁比赛,都把他们定义成 A 与 B 比赛,制定好了 A 与 B 比赛的规则(定义了关于 A 和 B 的函数)后,比赛时只要把 A 替换成张三,把 B 替换成李四就可以了, 大大简化了程序代码量 , 维持了结构的清晰 , 大大提高了 程序设计 的效率 。该过程称为 “ 类型参数化 ” 。# 强类型程序设计中,参与运算的所有对象的类型在编译时即确定下来,并且编译程序将进行严格的类型检查。为了解决 强类型的严格性和灵活性的冲突。有以下3中方式解决:#带参数宏定义 (原样替换)#重载函数 (函数名相同,函数参数不同)#模板 (将数据类型作为参数)#include <iostream> using namespace std; int add(int x, int y) //定义两个int类型相加的函数 { return x + y; } double add(double x, double y) //重载两个double类型相加的函数 { return x + y; } char* add(char* px, char* py) //重载两个字符数组相加的函数 { return strcat(px, py); //调用库函数strcat } int main() { cout << add(1, 2) << endl; //调用add(const int,const int) cout << add(3.0, 4.0) << endl; //调用add(const double,const double) char x[10] = "Hello "; //创建字符数组,注意要留够大小 char y[] = "C++"; cout << add(x, y) << endl; //调用add(char*,char*) return 0; }
模板的优点:
1、简化程序,少写代码,维持结构的清晰,大大提高程序的效率。
2、解决强类型语言的严格性和灵活性之间的冲突。
2.1、带参数的宏定义(原样替换)
2.2、函数重载(函数名字相同,参数不同)
2.3、模板(将数据类型作为参数)
3、强类型语言程序设计:C/C++/Java等,有严格的类型检查,如int a = 10,在编译时候明确变量的类型,如果有 问题就可以在编译时发现错误,安全,但是不够灵活,C++引进auto其实就是借鉴弱类型语言的特征。
弱类型程序语言设计:js/python等,虽然也有类型,但是在使用的时候直接使用let/var number,不知道变量具体类型,由编译器解释变量类型,属于解释型语言。如果有错,到运行时才发现,虽然灵活,但是不安全。
二、模板的定义
#模板的引入使得函数定义摆脱了类型的束缚,代码更为高效灵活。 C ++中,通过下述形式定义一个模板:template <class T,...>
或
template<typename T,....>
#早期模板定义使用的是 class ,关键字 typename 是最近才加入到标准中的,相比 class , typename 更容易体现 “ 类型 ” 的观点,虽然两个关键字在模板定义时是等价的,但从代码兼容的角度讲,使用 class 较好一些。#模板有 函数模板 和 类模板 之分。通过 参数实例化 构造出具体的函数或类,称为 模板函数 或 模板类 。//模板 #include <iostream> #include <string> //使用标准类库中提供的string类时必须包含此头文件 using namespace std; template <typename T> T add(const T &a, const T &b) { return a + b; } int main() { cout << add(10, 20) << endl; //调用add(const int,const int) cout << add(1.0, 2.0) << endl; //调用add(const double,const double) string x("Hello,"), y("world"); cout << add(x, y) << endl; //调用add(string,string) return 0; }
#代码中的 add 函数便是一个函数模板, 编译器 根据函数模板的定义,检查传入的参数类型,生成相应的函数,并调用之。 函数模板 的定义形式如下:template <模板参数表>
返回类型 函数名(参数列表)
{ //函数体 }
#关键字 template 放在模板的定义与声明的最前面,其后是用逗号分隔的 模板参数表 ,用尖括号( <> )括起来。 模板参数表不能为空 ,模板参数有两种类型:#class 或 typename 修饰的类型参数,代表一种类型 ;# 非类型参数 表达式 , 必须是 整型类型 ,使用已知类型符,代表一个常量//带非类型参数 template <typename T, int NUM> T fun(T a) { return a * NUM; } //调用 cout << fun<int, 4>(3) << endl;
#返回类型和函数的参数列表中可以包含类型参数,在函数中可以使用模板参数表中的常量表达式,如:template <class T1,class T2,int number> double fun(T1 a,int b,T2 c) { return a * (number + b) * c; //函数体,其中number可以作为一个int型常量来使用 }
三、模板的类型
函数模板与类模板。通过参数实例化构造出具体的函数或者类,称为模板函数或者模板类。
3.1、函数模板
template <typename T>//模板参数列表 T add(T x, T y) { cout << "T add(T, T)" << endl; return x + y; }
模板参数推导(在实参传递的时候进行推导)
函数模板------------------------------------------------------------------------模板函数
实例化
3.1.1、实例化:隐式实例化与显示实例化
#函数模板实际上不是个完整的函数定义,因为其中的类型参数还不确定,只是定义了某些类型的角色(或变量)在函数中的操作形式,因此,必须将 模板参数实例化 才能使用函数,用模板实例化后的函数也称为模板函数 .#分为 隐式实例化 和 显式实例化显示实例化:显示的将类型参数写出来,以免靠编译器去推导。
隐式实例化:不主动写出参数类型,靠编译器自己推导(类似与auto)
//12-3 函数模版的隐式实例化 #include <iostream> using namespace std; template <class T> T Max(T x, T y); //函数模版的申明 int main() { int intX = 1, intY = 2; double dblX = 3.9, dblY = 2.9; cout << Max(intX, intY) << endl; //实参为int型,生成int型模板函数,并对第二个参数进行检查 //或者cout << Max<int>(intX, intY) << endl; cout << Max(dblX, dblY) << endl; //实参为double型,生成double型模板函数,并对第二个参数进行检查 //或者cout << Max<double>(dblX, dblY) << endl; return 0; } template <class T> T Max(T x, T y) //函数模版的实现 { return (x > y ? x : y); }
3.1.2、函数模板、普通函数间的关系
1、函数模板与普通函数是可以进行重载的
2、普通函数优先于函数模板执行
3、函数模板与函数模板之间也是可以进行重载的
3.1.2.1易错点:
# 函数模板支持重载 ,既可以模板之间重载(同名模板),也可以实现模板和普通函数间的重载,但模板的重载相比普通函数的重载要复杂一点,首先看一个例子:template <class T1,class T2>
T1 Max(T1 a,T2 b){……}
与
template <class T3,class T4>
T3 Max(T3 c,T4 d){……}
#看似不同的两个模板,仔细分析后发现,其本质是一样的,如果调用 “ Max(2,3.5); ” ,都实例化为 “ Max(int,double); ” ,会出现重复定义的错误。仅仅依靠返回值不同的模板重载也是不合法的,如:
template <class T1,class T2>
T1 Greater(T1 a,T2 b){……}
与
template <class T3,class T4>
T3* Greater(T3 c,T4 d){……}
3.1.2.2重载例子:
//1函数模板和确定数据类型的函数的重载 #include <iostream.h> template < class T > T Max(T x, T y); int Max(int x, int y) { return x > y ? x : y; } int main() { int intX = 1, intY = 2; double dblX = 3.0, dblY = 2.9; cout << Max(intX, intY) << endl; //调用Max(int,int) cout << Max<double>(dblX, dblY) << endl; //显示实例化为double型,生成double型模板函数 cout << Max('A', '8') << endl; //隐式实例化char型,生成char型模板函数 return 0; } template <class T> T Max(T x, T y) { return x > y ? x : y; } //2函数模板和函数模板的重载 #include <iostream.h> template < class T > T Max(T x, T y); template <class T> T Max(T x, T y, T z) { return x > y ? (x > z ? x : z) : (y > z ? y : z); } int main() { int intX = 1, intY = 2, intZ = 3; double dblX = 3.0, dblY = 2.9; cout << Max<int>(intX, intY) << endl; //调用实例化的Max(int,int) cout << Max<int>(intX, intY, intZ) << endl; //调用实例化的Max(int,int,int) cout << Max<double>(dblX, dblY) << endl; //显示实例化为double型,生成double型模板函数 cout << Max('A', '8') << endl; //隐式实例化char型,生成char型模板函数 return 0; } template <class T> T Max(T x, T y) { return x > y ? x : y; } //3 普通函数模板和数组的重载 #include <iostream> using namespace std; template <typename T> T MAX(T a, T b) { return a > b ? a : b; } template <typename T> T MAX(T a[], int n) { T max = a[0]; for(int i = 1; i < n; i++) { if(max < a[i]) { max = a[i]; } } return max; } int main() { cout << MAX(3, 4) << endl; //或cout << MAX<int>(3, 4) << endl; int a[] = {2, 9, 7, 3, 8, 5}; cout << MAX(a, sizeof(a) / sizeof(a[0])) << endl;//或cout << MAX<int>(a, sizeof(a) / sizeof(a[0])) << endl; return 0; }
3.1.2.3优先级与执行顺序:
int Max(int i1,int i2) { cout<<"Normal Max"<<endl; return i1>i2? i1:i2; } template<class T> T Max(T t1, T t2) { cout<<"Template Max,sizeof(t1):"<<sizeof(t1)<<endl; return t1>t2? t1:t2; } int main(int argc, char* argv[]) { int i1=1,i2=9; char c1='a',c2='b'; int iRet=Max(i1,i2); //调用普通函数int Max(int i1,int i2) char cRet=Max(c1,c2); //调用模板实例化生成的char Max(char a,char b) int iRet2=Max(c1,c2); //调用模板实例化生成的char Max(char a,char b),最后将返回值隐式转换成int型 int cRet2=Max<char>(i1,i2); //调用模板实例化生成的char Max(char a,char b), return 0; }
3.1.3、模板头文件与实现文件
注:模板不能写成头文件与实现文件形式(类似inline函数),或者说不能将声明与实现分开,这样会导致编译报错。分开可以编译,但是在链接的时候是有问题的。
3.1.4、模板的特化:偏特化与全特化
template <> //此处模板的参数只有一个,全部特化出来就是全特化 const char *add(const char *pstr1, const char *pstr2) { size_t len = strlen(pstr1) + strlen(pstr2) + 1; char *ptmp = new char(len); strcpy(ptmp, pstr1); strcat(ptmp, pstr2); return ptmp;; }
具体的模板特化例子:
template <typename T> T add(T x, T y) { cout << "T add(T, T)" << endl; return x + y; } template <typename T> T add(T x, T y, T z) { cout << "T add(T, T, T)" << endl; return x + y + z; } int add(int x, int y) { cout << "int add(int, int)" << endl; return x + y; } double add(double x, double y) { cout << "double add(double, double)" << endl; return x + y; } template <> const char *add(const char *pstr1, const char *pstr2) { size_t len = strlen(pstr1) + strlen(pstr2) + 1; char *ptmp = new char(len); strcpy(ptmp, pstr1); strcat(ptmp, pstr2); return ptmp;; } void test() { int ia = 3, ib = 4, ic = 5; double da = 1.1, db = 5.5; char ca = 'a', cb = 1; string s1 = "hello"; string s2 = "world"; const char *pstr1 = "hubei"; const char *pstr2 = ",wuhan"; cout << "add(ia, ib) = " << add(ia, ib) << endl;//隐式实例化 cout << "add(da, db) = " << add<double>(da, db) << endl;//显示实例化 cout << "add(ca, cb) = " << add(ca, cb) << endl; cout << "add(s1, s2) = " << add(s1, s2) << endl; /* cout << "add(ia, db) = " << add(ia, db) << endl;//函数模板必须进行严格的 推导,如果没有普通函数形式,这就话就error */ cout << "add(ia, ib, ic) = " << add(ia, ib, ic) << endl; cout << "add(pstr1, pstr2) = " << add(pstr1, pstr2) << endl; }
3.1.5、函数模板的参数类型
1、类型参数,class T 这种就是类型参数
2、非类型参数 常量表达式,整型:bool/char/short/int/long/size_t,注意:float/double这些就不是整型代码例子:
#include<iostream> using namespace std; template <typename T = int, short kMin = 10> T multiply(T x, T y) { return x * y * kMin; } int main() { int ia = 3, ib = 4; double da = 3.3, db = 4.4; cout << "multiply(ia, ib) = " << multiply(ia, ib) << endl; cout << "multiply(ia, ib) = " << multiply<int, 4>(ia, ib) << endl; cout << "multiply(ia, ib) = " << multiply<double, 4>(da, db) << endl; }
代码运行结果:
3.1.6、成员函数模板
就是类的成员函数也可以设置为模板
class Point { public: //............. //成员函数模板,成员函数模板也是可以设置默认值 template <typename T = int> T func() { return (T)_dx; } private: double _dx; double _dy; }; void test() { Point pt(1.1, 2.2); cout << "pt.func() = " << pt.func<int>() << endl; cout << "pt.func() = " << pt.func<double>() << endl; cout << "pt.func() = " << pt.func() << endl; }
3.2、类模板
使用与函数模板也差不多,只是要注意模板的嵌套(函数模板与类模板都可以嵌套,比如函数参数是模板,类模板里面还有类模板),直接使用例子看类模板。
#include<iostream> using namespace std; //类模板 template <typename T, size_t kSize = 10>//类型参数T与非类型参数kSize class Stack { public: Stack() : _top(-1) , _data(new T[kSize]()) { } ~Stack(); bool empty() const; bool full() const; void push(const T &t); void pop(); T top() const; private: int _top; T *_data; }; //类模板在类外面定义成员函数时候需要注意,模板是有类型的,需要使用参数加类型 template <typename T, size_t kSize> Stack<T, kSize>::~Stack() { if (_data) { delete[] _data; _data = nullptr; } } template <typename T, size_t kSize> bool Stack<T, kSize>::empty() const { return -1 == _top;//_top = -1 } template <typename T, size_t kSize> bool Stack<T, kSize>::full() const { return _top == kSize - 1; } template <typename T, size_t kSize> void Stack<T, kSize>::push(const T &t) { if (!full()) { _data[++_top] = t; } else { cout << "The Stack is full, cannot push any data" << endl; } } template <typename T, size_t kSize> void Stack<T, kSize>::pop() { if (!empty()) { --_top; } else { cout << "The Stack is empty" << endl; } } template <typename T, size_t kSize> T Stack<T, kSize>::top() const { return _data[_top]; } void test() { Stack<int, 8> st; } void test1() { Stack<string> st; } int main() { test(); test1(); return 0; }
注意:
模板的嵌套:
//嵌套模版类的模版类 #include <iostream> using namespace std; template<class T> class Outside //外部Outside类定义 { public: template <class R> class Inside //嵌套类模板定义 { private: R r; public: Inside(R x) //模板类的成员函数可以在定义时实现 { r=x; } //void disp(); void disp() {cout << "Inside: " << r << endl;} }; Outside(T x) : t(x) //Outside类的构造函数 {} //void disp(); void disp() { cout<<"Outside:"; t.disp(); } private: Inside<T> t; }; //template<class T> //template<class R> //void Outside<T>::Inside<R>::disp() //模板类的成员函数也可以在定义外实现 //{ //但必须是在所有类定义的外边,不能放在Outside内Inside外去实现. // cout<<"Inside: "<<Outside<T>::Inside<R>::r<<endl; //} //template<class T> //void Outside<T>::disp() //{ // cout<<"Outside:"; // t.disp(); //} int main() { Outside<int>::Inside<double> obin(3.5); //声明Inside类对象obin obin.disp(); Outside<int> obout(2); //创建Outside类对象obout obout.disp(); return 0; }
代码运行结果:
#在 Outside 类内使用 “ Inside<T> t; ” 语句声明了 Inside<T> 类的对象,在 Outside 模板类对象创建时,首先采用 隐式实例化 先生成 Inside<T> 类的定义,而后根据此定义创建对象成员 t总结:
#模板的套嵌可以理解成在另外一个模板里面定义一个模板。以模板(类,或者函数)作为另一个模板(类,或者函数)的成员,也称 成员模板 。# 成员模版不能声明为 virtual
模板做参数:
#模板包含类型参数(如 class Type )和非类型参数(如 int NUM , NUM 是常量),实际上, 模板的参数可以是另一个模板 ,也就是说,下述形式是合法的:template<template <class T1> class T2, class T3,int Num>
#上述简单示例将原来简单的 “ class T2 ” 或 “ Typename T2 ” 扩充为 “ template <class T1> class T2 ” ,来看一段示例代码。//A模板做为B模板的参数 //文件“Stack.h”的内容如下 template <class T,int num> //类型参数表 class Stack //Stack类定义 { private: T sz[num]; //存储空间,用数组表示 public: int ReturnNum(); //判断栈是否为空 }; template<class T1,int num1> //参数列表不要求字字相同,但形式要相同 int Stack<T1, num1>::ReturnNum() { return num1; //返回数组大小 } #include <iostream> //#include "Stack.h" using namespace std; template<template<class Type,int NUM> class TypeClass, class T1, int N> void disp() //函数模板,其类型参数表中包含一个类模板 { TypeClass<T1,N> ob; //类模板的隐式实例化,创建对象ob cout<<ob.ReturnNum()<<endl; //调用ob的public成员函数 } int main() { disp<Stack,int,8>(); //函数模板的隐式实例化,并调用 system("pause"); return 0; }
#上述代码中定义了函数模板disp(),该模板的类型参数表中又包含了一个类模板TypeClass,在函数模板disp内可以对类TypeClass进行实例化处理。
四、可变模板参数
是C++11新增的最强大的特性之一,它对参数进行了高度的泛化,它能表示0到任意个数、任意类型的参数。
4.1、模板参数包
template<typename… Args> class tuple;//tuple是元组的意思,其模板参数就是模板参数包
Args标识符的左侧使用了省略号,在C++11中Args被称为“模板参数包”,表示可以接受任意多个参数作为模板参数,编译器将多个模板参数打包成“单个”的模板参数包.
4.2、函数参数包
template<typename…T> void f(T…args);//args就是函数参数包
args 被称为函数参数包,表示函数可以接受多个任意类型的参数.
在C++11标准中,要求函数参数包必须唯一,且是函数的最后一个参数; 模板参数包则没有。
当使用参数包时,省略号位于参数名称的右侧,表示立即展开该参数,这个过程也被称为解包。
4.3、可变模板参数的优势(有两条)
1、参数个数,那么对于模板来说,在模板推导的时候,就已经知道参数的个数了,也就是说在编译的时候就确定 了,这样编译器就存在可能去优化代码
#获取可变模板参数的个数sizeof...(Args)sizeof...(args)2、参数类型,推导的时候也已经确定了,模板函数就可以知道参数类型了。
#include<iostream> #include<string> using namespace std; template <typename... Args> void print(Args... args) { cout << "sizeof...(Agrs) = " << sizeof...(Args) << endl; cout << "sizeof...(agrs) = " << sizeof...(args) << endl; } void display() { cout << endl; } template <typename T, typename... Args> void display(T t, Args... args) { cout << t << " "; display(args...);//当... 位于args右边的时候叫做解包 } void test() { string s1 = "hello"; print(); print(1, 2.2); print('a', true, s1); print(1, 2.2, 'b', "hello"); } void test2() { string s1 = "hello"; display(); display(1, 2.2); display('a', true, s1); display(1, 2.2, 'b', "hello"); } template <class T> T sum(T t) { return t; } template <typename T, typename... Args> T sum(T t, Args... args) { return t + sum(args...); } void test3() { cout << "sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) = " << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << endl; }
test2运行结果:
test3运行结果: