零碎知识:
1.编译时后面加-std=98,用c++98的方式来进行编译。
2.在ubuntu环境下,usr/include/c++/7里面可以查看库文件的一些源码。
一.为什么要定义模板
-
简化程序,少写代码,维持结构的清晰。
-
解决强类型语言严格性和灵活性之间的冲突。
二.模板的定义
template<class T, …> 或template<typename T, …>
<>内叫模板参数列表
三.模板的类型
模板的运行机制:在编译时,通过实参传递时,进行参数推导。
1. 函数模板
-
通过模板参数推导机制======》模板函数
template <class T> T add(T x, T y) { return x + y; }
-
隐式实例化和显式实例化,隐式实例化是由编译器进行推导出模板函数,显示实例化是程序员在调用函数模板的时候通过<类型>显式的将类型指定。
//隐式实例化,没有明确说明类型,需要编译器推导 cout << "add(i1, i2): " << add(i1, i2) << endl; //显式实例化,编译器无需推导 cout << "add(d1, d2): " << add<double>(d1, d2) << endl;
-
函数模板可以与普通函数进行重载,且优先调用普通函数,调用普通函数时可进行隐式转换,比如int add(int,int)可以传入add(1,1.25)。
-
函数模板与函数模板之间也是可以进行重载的
-
模板的特化(特殊化)[不能单独存在,特化是相对于非特化版本存在的]:全特化、偏特化(当有多个类型参数时,只特化出一部分,或者叫部分特化)
//希望模板能够推导出来的是该函数 //模板的特化版本(specialization) ==>全特化 //不再是一个普通函数 template <> const char * add<const char *>(const char * px, const char * py) { //希望执行字符串的拼接操作 char * ptmp = new char[strlen(px) + strlen(py) + 1](); strcpy(ptmp, px); strcat(ptmp, py); return ptmp; }
-
函数模板可以分成声明和实现。
-
但是将模板的声明和实现放在不同文件时,按照一般方式编译会报错,原因是:每一个.cc/.cpp都是一个独立的编译单元,要是在模板的实现文件中没有进行该函数的调用,在编译时就不会进行参数推导,因此就没有类型适应于程序运行时需要调用的函数产生。(没有调用就没有推导)
解决方案:
- 在.h文件中,将声明的函数模板的实现文件include进来,为了区别,可以将函数模板的实现文件的后缀该为.tcc。C++的一些标准模板库就是使用这种方式。
- 或者在模板的实现文件里写个测试文件调用一下相关类型的模板函数也行。
-
函数模板的参数类型
-
类型参数
-
非类型参数,常量表达式,只能是整型:bool/char/short/int/long/size_t
//对于函数模板的模板参数还可以指定默认值, //c++11标准才支持该规则 //C++98的标准并不支持该规则 template <class T, int kBase = 100> T multiply(T x, T y) { return x * y * kBase; } void test(){ int i1 = 10, i2 = 11; //常量的传递是在函数调用时完成的 cout << multiply<int, 10>(i1, i2) << endl; cout << multiply<int, 20>(i1, i2) << endl; cout << multiply(i1, i2) << endl; }
(最后一行可以正常调用时因为kBase有默认值,T类型推导而来,当然,类型参数也可以传默认值,比如上面函数模板中 class T = int)
-
-
成员函数模板:基本与普通的函数模板写法一样。
成员函数模板也是可以设置默认值。
class Point { public: Point(double dx = 0, double dy = 0) : _dx(dx) , _dy(dy) { cout << "Point(double = 0, double = 0)" << endl; } template <typename T = int> T func() { return (T)_dx; } ~Point() { cout << "~Point()" << endl; } private: double _dx; double _dy; };
当没有默认类型参数或者要调用的函数中类型与默认类型参数不一致时的调用方式:
void test(){ Point pt(3.3, 4.4); cout << "pt.func() = " << pt.func<int>() << endl; cout << "pt.func() = " << pt.func() << endl; //int cout << "pt.func() = " << pt.func<double>() << endl; return 0; }
2. 类模板
-
普通形式
template <class T, size_t kSize =10> class Stack { public: Stack() : _top(-1) , _pdata(new T[kSize]()) { } ~Stack(); T top() const; bool empty() const; bool full() const; void push(const T & t); void pop(); private: int _top; T * _pdata; }; //类外实现成员函数时,每个成员函数前都要重新写下template,且在类后加<>: template <class T, size_t kSize> Stack<T,kSize>::~Stack() { if(_pdata) { delete [] _pdata; } } template <class T, size_t kSize> void Stack<T, kSize>::push(const T & t) { if(full()) { cout << "stack is full, cannnot push data any more!" << endl; } else { _pdata[++_top] = t; } } //...... //定义对象时要申明类型:Stack<int> stack;
-
类模板嵌套函数模板
template<class T1> class Test //Test模版类定义 { public: template<class T2> T1 f(T2 b) //模板成员函数定义 { return T1(b); //将b由T2类型强制转换成T1类型 } }; //template<class T1> //template<class T2> //T1 Test<T1>::f(T2 b) //模板成员函数的实现 //{ // return T1(b); //将b由T2类型强制转换成T1类型 //} //嵌套模版类的使用 Test<int> t; cout<<t.f(3.14f)<<endl; cout<<t.f<double>(1.25)<<endl;
-
类模板嵌套类模板
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; }; //模板类的成员函数也可以在定义外实现 //但必须是在所有类定义的外边,不能放在Outside内Inside外去实现 //template<class T> //template<class R> //void Outside<T>::Inside<R>::disp() //{ // cout<<"Inside: "<<Outside<T>::Inside<R>::r<<endl; //} //template<class T> //void Outside<T>::disp() //{ // cout<<"Outside:"; // t.disp(); //} //使用 void test(){ Outside<int>::Inside<double> obin(3.5); //声明Inside类对象obin obin.disp(); Outside<int> obout(2); //创建Outside类对象obout obout.disp(); }
-
模板作为类型参数(仅作为了解)
//文件“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; }
四.可变模板参数
-
模板参数包
-
函数参数包
template <class T> void print(T t) { cout << t << endl;} //在可变模板参数包声明时, ... 在参数包的左边, 可以看成一个整体 //在调用时,需要将参数包展开, ... 在参数包的右边 template <class T, class... Args> //Args 模板参数包 void print(T t, Args... args) //args 函数参数包 { cout << t << " "; //调用的过程,对参数包的展开的过程 print(args...);//递归调用 } //使用 void test() { print(1, 2.2, "hello"); }
在C++11标准中,要求函数参数包必须唯一,且是函数的最后一个参数; 模板参数包则没有。
当使用参数包时,省略号位于参数名称的右侧,表示立即展开该参数,这个过程也被称为解包。
template <class T> T sum(T t) { return t; } template <class T, class... Args> T sum(T t, Args... args) { return t + sum(args...); } void test() { cout << sum(1, 2, 3, 4, 5) << endl; }
获取所传参数的数量:sizeof…()
template <class... Args> void printLength(Args... args) { cout << "sizeof...(Args):" << sizeof...(Args) << endl; cout << "sizeof...(args):" << sizeof...(args) << endl; }