7:模板
7.1 基本概念
- 什么是模板?
模板(Template )是允许函数或者类通过泛型(generic types)的形式表现或运行的特性。 - 模板有什么用?
模板可以使函数或者类只写一份代码而对应不同的类型。 - 模板编程/泛型编程
一种独立于特定类型的编码方式 - 模板分类
模板分为函数模板与类模板两类。 1、函数模板(Function template):使用泛型参数的函数(function with generic parameters) 2、类模板(Class template):使用泛型参数的类(class with generic parameters)
7.2 函数模板
template <模板形参表> 函数返回类型 函数(形参表);
template <模板形参表>
函数返回类型 函数(形参表){
函数体;
};
例如:
template <typename T> T Max(T a,T b){
return a>b?a:b;
}
函数(实参表)
产生模板特定类型的函数或者类的过程称为实例化 调用函数模板与调用函数完全一致
- 实例
最值函数Max() 字符串转数值StringToNumber()
#include <iostream>
#include <sstream>
using namespace std;
template <typename T>
T Max(T a,T b){
return a>b?a:b;
}
template<typename T>
void Swap(T& a,T& b){
T c = a;
a = b;
b = c;
}
template <typename T>
string NumToString(T num){
ostringstream oss;
oss << num;
return oss.str();
}
template <typename T>
T StringToNumber(const string& s){
T res;
istringstream iss(s);
iss >> res;
return res;
}
int main(){
cout << Max(12,10) << endl;
cout << Max((double)1,2.2) << endl;
cout << Max<double>(10,2.2) << endl;
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<int>("1234") << endl;
cout << StringToNumber<float>("1.234") << endl;
}
12
2.2
10
b
1,2
2,1
c,d
d,c
1234
1.234
1234
1.234
7.3 类模板
template <模板形参表> class 类名;
template <模板形参表>
class 类名 {
}
类名<模板实参表> 对象;
- 模板参数表
多个模板参数之间,分割。模板参数,模板参数,… - 模板参数
- 类型形参
class 类型形参或者typename 类型形参
注:类模板的声明与实现通常都写在头文件中,是不能够分开的。
#include <iostream>
#include <cmath>
using namespace std;
template <typename T>
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<int> c(3);
cout << c.GetLength() << "," << c.GetArea() << endl;
Circle<float> c2(3.14);
cout << c2.GetLength() << "," << c2.GetArea() << endl;
}
18.8496,28.2743
19.7292,30.9748
#include <iostream>
using namespace std;
template <typename T>
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<int> l;
l.Append(1);
l.Append(2);
l.Append(3);
l.Append(4);
l.ShowList();
SeqList<char> 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 O
7.4 模板参数推导/推演(deduction)
- 定义
模板参数推导/推演(deduction):由模板实参类型确定模板形参的过程。
实例化有两类: 显示实例化:代码中明确指定类型的实例化 隐式初始化:根据参数类型自动匹配的实例化
注: 1、类模板参数允许自动类型转换(隐式转换); 2、函数模板参数不允许自动类型转换(隐式转换) 3、在模板参数列表中,class和typename完全一样。但是在语义上,class 表示类,typename 代表所有类型(类以及基本类型)。 4、请尽量使用typename
函数模板实参类型不一致问题
template <typename T>
inline const T& Max(const T& a, const T& b){
return a>b?a:b;
}
模板实例化时,如果输入数据为:
Max(2,2.4)
参数推导会出现模板实参类型int 与double 不一致的错误。 解决方法: 1、每个模板参数独立类型
template <typename T , typename U> inline const T& Max(const T& a, const U& b){
return a>b?a:b;
}
注意:这种解决方法还有一个问题,就是返回值只能强制设置为T或者U,不能自动推导。C++11的后置推导解决这个问题。
template <typename T, typename U>
inline auto Max(const T& a, const U& b)->decltype(a>b?a:b)
{
return a>b?a:b;
}
2、显示指定模板实参类型
Max<int>(2,2.4)
或者
Max<double>(2,2.4)
3、实参强制类型转换
Max(2,static_cast<int>(2.4))
或者
Max(static_cast<double>(2),2.4)
注:模板参数推导不允许类型自动转换,模板参数必须严格匹配。
函数模板实例显示指定模板实参可以显示指定模板实参,也可以不指定(类型自动推导),类模板实例化必须
7.5 特化
- 模板特化(specialization):
模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化。 - 特化作用
1、对于某种特殊类型,可以做特殊处理或者优化。 2、避免实例化类的时候产生诡异行为。 - 模板特化分类
1、函数模板特化(Function specializations):对函数模板的全部模板类型指定具体类型。 2、类模板特化(Class specializations):对类模板的全部或者部分模板类型指定具体类型。
7.5.1 函数模板特化
- 特点
函数模板,却只有全特化,不能偏特化。 - 步骤
与类的全特化相同 - 语法:
template<typename T>
void Func(const T& n){}
template<>
void Func(const int& n){}
实例:
#include <iostream>
#include <sstream>
#include <cstring>
using namespace std;
namespace My{
template <typename T>
T max(T a,T 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 <typename T>
T Max(T a,T b){
return a>b?a:b;
}
template<typename T>
void Swap(T& a,T& b){
T c = a;
a = b;
b = c;
}
template <typename T>
string NumToString(T num){
ostringstream oss;
oss << num;
return oss.str();
}
template <typename T>
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((double)1,2.2) << endl;
cout << Max<double>(10,2.2) << endl;
cout << Max('a','b') << endl;
cout << max(string("abcd"),string("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<int>("1234") << endl;
cout << StringToNumber<float>("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.234
7.5.2 类模板特化
- 特点
类模板特化,每个成员函数必须重新定义。 - 类模板特化分为两种:
全特化(Full specializations):具体指定模板的全部模板参数的类型。 局部特化(Partial specializations):具体指定模板的部分模板参数的类型。
7.5.2.1 全特化
- 步骤:
声明一个模板空参数列表template<> 在类名称后面的<>中显示指定类型。 - 语法:
template<class T>
class Test{};
template<>
class Test<int*>{};
实例:
#include <iostream>
#include <cmath>
using namespace std;
template <typename T>
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>{
int r;
public:
Circle(int r):r(r){}
float GetLength()const{
cout << "Circle<int>:";
return 2*M_PI*r;
}
float GetArea()const{
cout << "Circle<int>:";
return M_PI*r*r;
}
};
int main(){
Circle<int> c(3);
cout << c.GetLength() << "," << c.GetArea() << endl;
Circle<float> c2(3.14);
cout << c2.GetLength() << "," << c2.GetArea() << endl;
}
Circle<int>:18.8496,28.2743
19.7292,30.9748
7.5.2.2 偏特化
- 偏特化就是部分特化,分为两种情况
个数特化:只为部分模板参数指定具体类型(模板参数个数变少) 范围特化:模板参数不变,限制模板参数的匹配类型(指针、引用、const) - 步骤:
在一个模板类参数列表不指定或者指定部分具体类型。 在类名称后面的对应类型中显示指定该类型。 - 实例:
template<typename T1,typename T2>
class Test{};
将模板参数偏特化为相同类型
template<typename T>
class Test<T,T>{};
将一个模板参数特化成具体类型
template<typename T>
class Test<T,int>{};
把两个类型偏特化成指针类型
template<typename T1,typename T2>
class Test<T1*,T2*>{};
注:类模板特化,相当于函数模板的重载 全特化和偏特化的编码区别: 全特化的模板参数列表为空template<> ,偏特化的模板参数列表不为空。 实例:
#include <iostream>
#include <cstring>
using namespace std;
template <typename T>
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<typename T>
bool Equal(const T* a,const T* b){
return *a==*b;
}
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;
cout << Equal("abc","abcd") << endl;
}
综合应用:空间坐标中的点、线、三角形和直角三角形
#include <iostream>
#include <cmath>
#include <sstream>
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 << ")";
}
};
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;
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 <iostream>
#include <cmath>
#include <sstream>
using namespace std;
class 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;
}
};
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 {
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;
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
|