- 什么是模板?
模板(Template)是允许函数或者类通过泛型(generic types)的形式表现或运行的特性。 - 模板有什么用?
模板可以使函数或者类只写一份代码而对应不同的类型。 - 模板编程/泛型编程
一种独立于特定类型的编码方式 - 模板分类
模板分为函数模板与类模板两类。
1、函数模板(Function template):使用泛型参数的函数(function with generic parameters)
2、类模板(Class template):使用泛型参数的类(class with generic parameters)
- 模板声明
template <模板形参表> 函数返回类型 函数(形参表);
- 模板定义
template <模板形参表>
函数返回类型 函数(形参表){
函数体;
};
例如:
templateT Max(T a,T b){ return a>b?a:b; }
- 模板实例化
函数(实参表)
产生模板特定类型的函数或者类的过程称为实例化
调用函数模板与调用函数完全一致
- 实例
最值函数Max()
字符串转数值StringToNumber()
#include#include using namespace std; //使用模板,避免代码大量重复 template T Max(T a,T b){ //根据a和b的输入,自动识别类型 return a>b?a:b; } template void Swap(T& a,T& b){ T c = a; a = b; b = c; } template string NumToString(T num){ ostringstream oss; oss << num; return oss.str(); } template T StringToNumber(const string& s){ T res; istringstream iss(s); iss >> res; return res; } int main(){ cout << Max(12,10) << endl; //cout << Max(1,3.4) << endl; //报错 //解决办法 cout << Max((double)1,2.2) << endl; //强转 double Max(double a,double b) cout << Max (10,2.2) << endl; //模板 double Max(double a,double b) cout << Max('a','b') << endl; int a = 1,b = 2; cout << a << "," << b << endl; Swap(a,b); cout << a << "," << b << endl; char c1 = 'c',c2 = 'd'; cout << c1 << "," << c2 << endl; Swap(c1,c2); cout << c1 << "," << c2 << endl; cout << NumToString(1234) << endl; cout << NumToString(1.234) << endl; cout << StringToNumber ("1234") << endl; //告诉模板传入参数类型 cout << StringToNumber ("1.234") << endl; //告诉模板传入参数类型 }
12 2.2 10 b 1,2 2,1 c,d d,c 1234 1.234 1234 1.2347.3 类模板
- 模板声明
template <模板形参表> class 类名;
- 模板定义
template <模板形参表>
class 类名 {
}
- 模板实例化
类名<模板实参表> 对象;
- 模板参数表
多个模板参数之间,分割。模板参数,模板参数,… - 模板参数
- 类型形参
class 类型形参或者typename 类型形参
注:类模板的声明与实现通常都写在头文件中,是不能够分开的。
- 实例1:类模板 求圆的周长和面积
#include#include using namespace std; template class Circle{ T r; public: Circle(T r):r(r){} float GetLength()const{ return 2*M_PI*r; } float GetArea()const{ return M_PI*r*r; } }; int main(){ //模板实例化 Circle c(3); cout << c.GetLength() << "," << c.GetArea() << endl; Circle c2(3.14); cout << c2.GetLength() << "," << c2.GetArea() << endl; }
18.8496,28.2743 19.7292,30.9748
- 实例2:类模板 实现数字list和字符list
#includeusing namespace std; template class SeqList{ T* list; size_t size; public: SeqList():list(NULL),size(0){} void Append(T num){ T* temp = new T[size+1]; for(int i = 0;i < size;++i){ temp[i] = list[i]; } temp[size] = num; ++size; delete [] list; list = temp; } size_t GetSize(){ return size; } T& operator[](T i){ return list[i]; } void ShowList(){ for(int i = 0;i < size;++i){ cout << list[i] << " "; } cout << endl; } }; int main(){ SeqList l; l.Append(1); l.Append(2); l.Append(3); l.Append(4); l.ShowList(); SeqList c; c.Append('H'); c.Append('E'); c.Append('L'); c.Append('L'); c.Append('O'); c.ShowList(); }
1 2 3 4 H E L L O7.4 模板参数推导/推演(deduction)
- 定义
模板参数推导/推演(deduction):由模板实参类型确定模板形参的过程。
实例化有两类:
显示实例化:代码中明确指定类型的实例化
隐式初始化:根据参数类型自动匹配的实例化
注:
1、类模板参数允许自动类型转换(隐式转换);
2、函数模板参数不允许自动类型转换(隐式转换)
3、在模板参数列表中,class和typename完全一样。但是在语义上,class表示类,typename代表所有类型(类以及基本类型)。
4、请尽量使用typename
函数模板实参类型不一致问题
templateinline const T& Max(const T& a, const T& b){ return a>b?a:b; }
模板实例化时,如果输入数据为:
Max(2,2.4)
参数推导会出现模板实参类型int与double不一致的错误。
解决方法:
1、每个模板参数独立类型
templateinline const T& Max(const T& a, const U& b){ return a>b?a:b; }
注意:这种解决方法还有一个问题,就是返回值只能强制设置为T或者U,不能自动推导。C++11的后置推导解决这个问题。
templateinline auto Max(const T& a, const U& b)->decltype(a>b?a:b) { return a>b?a:b; }
2、显示指定模板实参类型
Max(2,2.4)
或者
Max(2,2.4)
3、实参强制类型转换
Max(2,static_cast(2.4))
或者
Max(static_cast(2),2.4)
注:模板参数推导不允许类型自动转换,模板参数必须严格匹配。
函数模板实例显示指定模板实参可以显示指定模板实参,也可以不指定(类型自动推导),类模板实例化必须
7.5 特化- 模板特化(specialization):
模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化。 - 特化作用
1、对于某种特殊类型,可以做特殊处理或者优化。
2、避免实例化类的时候产生诡异行为。 - 模板特化分类
1、函数模板特化(Function specializations):对函数模板的全部模板类型指定具体类型。
2、类模板特化(Class specializations):对类模板的全部或者部分模板类型指定具体类型。
- 特点
函数模板,却只有全特化,不能偏特化。 - 步骤
与类的全特化相同 - 语法:
templatevoid Func(const T& n){} // 特化 template<> void Func(const int& n){}
实例:
#include#include #include using namespace std; //命名空间 namespace My{ template T max(T a,T b){ //根据a和b的输入,自动识别类型 cout << "Max(" << a << "," << b << ")="; return a>b?a:b; } //模板特化,特化的原因是有些输入参量,模板处理不了 template<> const char* max(const char* a,const char* b){ return strcmp(a,b) > 0? a:b; } }; //使用模板,避免代码大量重复 template T Max(T a,T b){ //根据a和b的输入,自动识别类型 return a>b?a:b; } template void Swap(T& a,T& b){ T c = a; a = b; b = c; } template string NumToString(T num){ ostringstream oss; oss << num; return oss.str(); } template T StringToNumber(const string& s){ T res; istringstream iss(s); iss >> res; return res; } int main(){ cout << My::max(1,2) << endl; cout << Max(12,10) << endl; //cout << Max(1,3.4) << endl; //报错 //解决办法 cout << Max((double)1,2.2) << endl; //强转 double Max(double a,double b) cout << Max (10,2.2) << endl; //模板 double Max(double a,double b) cout << Max('a','b') << endl; cout << max(string("abcd"),string("efg")) << endl; //cout << Max ("ancd","efg") << endl; // 这样也可以??? cout << Max("abcd","efg") << endl; //输出结果错误,因为字符串不能用>或<进行比较,所以不能调用模板 //解决办法:模板特化 cout << My::max("abcd","efg") << endl; int a = 1,b = 2; cout << a << "," << b << endl; Swap(a,b); cout << a << "," << b << endl; char c1 = 'c',c2 = 'd'; cout << c1 << "," << c2 << endl; Swap(c1,c2); cout << c1 << "," << c2 << endl; cout << NumToString(1234) << endl; cout << NumToString(1.234) << endl; cout << StringToNumber ("1234") << endl; //告诉模板传入参数类型 cout << StringToNumber ("1.234") << endl; //告诉模板传入参数类型 }
Max(1,2)=2 12 2.2 10 b efg efg abcd efg 1,2 2,1 c,d d,c 1234 1.234 1234 1.2347.5.2 类模板特化
- 特点
类模板特化,每个成员函数必须重新定义。 - 类模板特化分为两种:
全特化(Full specializations):具体指定模板的全部模板参数的类型。
局部特化(Partial specializations):具体指定模板的部分模板参数的类型。
- 步骤:
声明一个模板空参数列表template<>
在类名称后面的<>中显示指定类型。 - 语法:
// 模板 templateclass Test{}; // 全特化 template<> class Test {};
实例:
#include#include using namespace std; template class Circle{ T r; public: Circle(T r):r(r){} float GetLength()const{ return 2*M_PI*r; } float GetArea()const{ return M_PI*r*r; } }; //类特化 template<> class Circle { int r; public: Circle(int r):r(r){} float GetLength()const{ cout << "Circle :"; return 2*M_PI*r; } float GetArea()const{ cout << "Circle :"; return M_PI*r*r; } }; int main(){ Circle c(3); cout << c.GetLength() << "," << c.GetArea() << endl; //调用特化模板 Circle c2(3.14); cout << c2.GetLength() << "," << c2.GetArea() << endl; }
Circle7.5.2.2 偏特化:18.8496,28.2743 19.7292,30.9748
- 偏特化就是部分特化,分为两种情况
个数特化:只为部分模板参数指定具体类型(模板参数个数变少)
范围特化:模板参数不变,限制模板参数的匹配类型(指针、引用、const) - 步骤:
在一个模板类参数列表不指定或者指定部分具体类型。
在类名称后面的对应类型中显示指定该类型。 - 实例:
templateclass Test{};
将模板参数偏特化为相同类型
templateclass Test {};
将一个模板参数特化成具体类型
templateclass Test {};
把两个类型偏特化成指针类型
templateclass Test {};
注:类模板特化,相当于函数模板的重载
全特化和偏特化的编码区别:
全特化的模板参数列表为空template<>,偏特化的模板参数列表不为空。
实例:
#include#include using namespace std; // 引用类型模板 template bool Equal(const T& a,const T& b){ return a == b; } // 特化成浮点型 template<> bool Equal(const double& a,const double& b){ return abs(a-b) < 1e-6; } // ------------------------------------------------- // 指针类型模板(函数模板重载) template bool Equal(const T* a,const T* b){ return *a==*b; } // 特化成char* template<> bool Equal(const char* a,const char* b){ return strcmp(a,b)==0; } int main(){ cout << Equal(1,1) << endl; cout << Equal(1.2,1.2) << endl; cout << Equal(string("abc"),string("abc")) << endl; cout << Equal(1,2) << endl; cout << Equal(1.2,1.21) << endl; cout << Equal(string("abcd"),string("abc")) << endl; cout << Equal(1.2,(10.2-9)) << endl; int arr[] = {1,2,3,1}; cout << Equal(arr,arr+3) << endl; // bool Equal(int*,int*) cout << Equal("abc","abcd") << endl; }
综合应用:空间坐标中的点、线、三角形和直角三角形
#include#include #include using namespace std; class Point { protected: int x,y; public: Point(int x,int y):x(x),y(y) {} int GetX()const { return x; } int GetY()const { return y; } friend ostream& operator<<(ostream& os,const Point& p) { //流对象不能拷贝构造,所以要加引用 return os << "(" << p.x << "," << p.y << ")"; } }; // 继承的本质是A is a B // Circle is a Point ??? // 但是圆本身不是点 // 说圆包含一个点更合适 // A has a B // Circle has a Pointer class Circle { int r; Point center; public: Circle(int x,int y,int r):center(x,y),r(r) {} int GetR()const { return r; } Point GetCenter()const { return center; } friend ostream& operator<<(ostream& os,const Circle& c) { return os << "(" << c.center.GetX() << "," << c.center.GetY() << "," << c.r << ")"; } }; class Line { Point a; Point b; public: Line(const Point& a,const Point& b):a(a),b(b) {} float GetLength()const { int w = a.GetX() - b.GetX(); int h = a.GetY() - b.GetY(); return sqrt(w*w+h*h); } bool IsParallel(const Line& line)const { int x1 = a.GetX() - b.GetX(); int y1 = a.GetY() - b.GetY(); int x2 = line.a.GetX() - line.b.GetX(); int y2 = line.a.GetY() - line.b.GetY(); return x1*y2 == x2*y1; } bool IsVertical(const Line& line)const { int x1 = a.GetX() - b.GetX(); int y1 = a.GetY() - b.GetY(); int x2 = line.a.GetX() - line.b.GetX(); int y2 = line.a.GetY() - line.b.GetY(); return x1*x2 + y2*y1 == 0; } friend ostream& operator<<(ostream& os,const Line& l) { return os << l.a << "~" << l.b; } }; class Triangle { protected: Point a; Point b; Point c; public: Triangle(Point a,Point b,Point c):a(a),b(b),c(c) { ostringstream oss; oss << a << b << c; if(Line(a,b).IsParallel(Line(a,c))) throw invalid_argument(oss.str()+"三点不能共线"); } float GetLength()const { return Line(a,b).GetLength() + Line(a,c).GetLength() + Line(b,c).GetLength(); } virtual float GetArea()const { float p = GetLength()/2.0; return sqrt(p*Line(a,b).GetLength()+p*Line(a,c).GetLength()+p*Line(b,c).GetLength()); } friend ostream& operator<<(ostream& os,const Triangle& t) { return os << "[" << t.a << "," << t.b << "," << t.c << "]"; } }; class RightAngleTriangle:public Triangle { public: RightAngleTriangle(const Point& a,const Point& b,const Point& c):Triangle(a,b,c) { bool vertical = Line(a,b).IsVertical(Line(a,c)) || Line(a,c).IsVertical(Line(b,c)) || Line(a,b).IsVertical(Line(b,c)); if(!vertical) throw invalid_argument("不能构成直角三角形"); } float GetArea()const override { cout << "直角三角形面积:" << endl; Line l1(a,b); Line l2(a,c); Line l3(b,c); if(l1.IsVertical(l2)) { return l1.GetLength()*l2.GetLength()/2.0; } else if(l1.IsVertical(l3)) { return l1.GetLength()*l3.GetLength()/2.0; } else if(l2.IsVertical(l3)) { return l2.GetLength()*l3.GetLength()/2.0; } else { return 0; } } }; void PrintTriangle(const Triangle& t) { cout << t.GetLength() << " " << t.GetArea() << endl; } int main() { Point p(3,4); cout << p << endl; Circle c(1,2,3); cout << c << endl; Point o(0,0); Line l(o,p); cout << l << ":" << l.GetLength() << endl; Triangle t(p,o,Point(3,0)); cout << t << ": Length" << t.GetLength() << " Area:" << t.GetArea() << endl; Line l1(o,Point(0,1)); Line l2(o,Point(0,2)); cout << l1.IsParallel(l2) << " " << l1.IsVertical(l2) << endl; //Triangle t2(o,Point(0,1),Point(0,2)); RightAngleTriangle t3(o,p,Point(3,0)); cout << t3.GetArea() << endl; //多态 //常用写法 PrintTriangle(t3); }
(3,4) (1,2,3) (0,0)~(3,4):5 [(3,4),(0,0),(3,0)]: Length12 Area:8.48528 1 0 直角三角形面积: 6 12 直角三角形面积: 6
改进:加入抽象类Shape统一所有类
#include#include #include using namespace std; class Point; //前置声明,因为抽象类中用到Point //包含纯虚函数的类成为抽象类,抽象类只定义不实现 class Shape { public: //纯虚函数 virtual bool Contained(const Point& p) const = 0; }; //定义抽象类的目的是把下面所有类进行统一 class Point:public Shape { protected: int x,y; public: Point(int x,int y):x(x),y(y) {} int GetX()const { return x; } int GetY()const { return y; } friend ostream& operator<<(ostream& os,const Point& p) { //流对象不能拷贝构造,所以要加引用 return os << "(" << p.x << "," << p.y << ")"; } bool Contained(const Point& p)const override { return p.x == x && p.y == y; } }; // 继承的本质是A is a B // Circle is a Point ??? // 但是圆本身不是点 // 说圆包含一个点更合适 // A has a B // Circle has a Point // 可以说Circle is a Shape class Line:public Shape { Point a; Point b; public: Line(const Point& a,const Point& b):a(a),b(b) {} float GetLength()const { int w = a.GetX() - b.GetX(); int h = a.GetY() - b.GetY(); return sqrt(w*w+h*h); } bool IsParallel(const Line& line)const { int x1 = a.GetX() - b.GetX(); int y1 = a.GetY() - b.GetY(); int x2 = line.a.GetX() - line.b.GetX(); int y2 = line.a.GetY() - line.b.GetY(); return x1*y2 == x2*y1; } bool IsVertical(const Line& line)const { int x1 = a.GetX() - b.GetX(); int y1 = a.GetY() - b.GetY(); int x2 = line.a.GetX() - line.b.GetX(); int y2 = line.a.GetY() - line.b.GetY(); return x1*x2 + y2*y1 == 0; } friend ostream& operator<<(ostream& os,const Line& l) { return os << l.a << "~" << l.b; } bool Contained(const Point& p) const override { bool parallel = Line(a,p).IsParallel(Line(b,p)); return parallel && (min(a.GetX(),b.GetX()) <= p.GetX() && p.GetX() <= max(a.GetX(),b.GetX())) && (min(a.GetY(),b.GetY()) <= p.GetY() && p.GetY() <= max(a.GetY(),b.GetY())); } }; class Circle:public Shape { int r; Point center; public: Circle(int x,int y,int r):center(x,y),r(r) {} int GetR()const { return r; } Point GetCenter()const { return center; } friend ostream& operator<<(ostream& os,const Circle& c) { return os << "(" << c.center.GetX() << "," << c.center.GetY() << "," << c.r << ")"; } bool Contained(const Point& p)const override { return Line(p,center).GetLength() <= r; } }; class Triangle :public Shape { protected: Point a; Point b; Point c; public: Triangle(Point a,Point b,Point c):a(a),b(b),c(c) { ostringstream oss; oss << a << b << c; if(Line(a,b).IsParallel(Line(a,c))) throw invalid_argument(oss.str()+"三点不能共线"); } float GetLength()const { return Line(a,b).GetLength() + Line(a,c).GetLength() + Line(b,c).GetLength(); } virtual float GetArea()const { float p = GetLength()/2.0; return sqrt(p*Line(a,b).GetLength()+p*Line(a,c).GetLength()+p*Line(b,c).GetLength()); } friend ostream& operator<<(ostream& os,const Triangle& t) { return os << "[" << t.a << "," << t.b << "," << t.c << "]"; } bool Contained(const Point& p) const override { // TODO: return false; } }; class RightAngleTriangle:public Triangle { public: RightAngleTriangle(const Point& a,const Point& b,const Point& c):Triangle(a,b,c) { bool vertical = Line(a,b).IsVertical(Line(a,c)) || Line(a,c).IsVertical(Line(b,c)) || Line(a,b).IsVertical(Line(b,c)); if(!vertical) throw invalid_argument("不能构成直角三角形"); } float GetArea()const override { cout << "直角三角形面积:" << endl; Line l1(a,b); Line l2(a,c); Line l3(b,c); if(l1.IsVertical(l2)) { return l1.GetLength()*l2.GetLength()/2.0; } else if(l1.IsVertical(l3)) { return l1.GetLength()*l3.GetLength()/2.0; } else if(l2.IsVertical(l3)) { return l2.GetLength()*l3.GetLength()/2.0; } else { return 0; } } }; void PrintTriangle(const Triangle& t) { cout << t.GetLength() << " " << t.GetArea() << endl; } int main() { Point p(3,4); cout << p << endl; Circle c(1,2,3); cout << c << endl; Point o(0,0); Line l(o,p); cout << l << ":" << l.GetLength() << endl; Triangle t(p,o,Point(3,0)); cout << t << ": Length" << t.GetLength() << " Area:" << t.GetArea() << endl; Line l1(o,Point(0,1)); Line l2(o,Point(0,2)); cout << l1.IsParallel(l2) << " " << l1.IsVertical(l2) << endl; //Triangle t2(o,Point(0,1),Point(0,2)); RightAngleTriangle t3(o,p,Point(3,0)); cout << t3.GetArea() << endl; //多态 //常用写法 PrintTriangle(t3); Shape* arr[] = {&p,&c,&o,&l,&l1,&l2,&t,&t3}; Point point(0,0); cout << "相交图形"; for(int i = 0;i<8;++i){ if(arr[i]->Contained(point)){ cout << i << endl; } } }
(3,4) (1,2,3) (0,0)~(3,4):5 [(3,4),(0,0),(3,0)]: Length12 Area:8.48528 1 0 直角三角形面积: 6 12 直角三角形面积: 6 相交图形1 2 3 4 5



