七、C++语言初阶:模板

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 
#include 
using namespace std;
/*
int Max(int a,int b){
    return a>b?a:b;
}
double Max(double a,double b){
    return a>b?a:b;
}
char Max(char a,char b){
    return a>b?a:b;
}
*/
//使用模板,避免代码大量重复
template <typename T>
T Max(T a,T b){  //根据a和b的输入,自动识别类型
   return a>b?a:b;
}
/*
void Swap(int& a,int& b){
    int c = a;
    a = b;
    b = c;
}
void Swap(char& a,char& b){
    char c = a;
    a = b;
    b = c;
}
*/
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(1,3.4) << endl; //报错
    //解决办法
    cout << Max((double)1,2.2) << endl;  //强转  double Max(double a,double b)
    cout << Max<double>(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<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 类型形参

注:类模板的声明与实现通常都写在头文件中,是不能够分开的。

  • 实例1:类模板 求圆的周长和面积
#include 
#include 
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
  • 实例2:类模板 实现数字list和字符list
#include 
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)

参数推导会出现模板实参类型intdouble不一致的错误。
解决方法:
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 
#include 
#include 
using namespace std;
/*
int Max(int a,int b){
    return a>b?a:b;
}
double Max(double a,double b){
    return a>b?a:b;
}
char Max(char a,char b){
    return a>b?a:b;
}
*/
//命名空间
namespace My{
template <typename T>
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 <typename T>
T Max(T a,T b){  //根据a和b的输入,自动识别类型
   return a>b?a:b;
}
/*
void Swap(int& a,int& b){
    int c = a;
    a = b;
    b = c;
}
void Swap(char& a,char& b){
    char c = a;
    a = b;
    b = c;
}
*/
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(1,3.4) << endl; //报错
    //解决办法
    cout << Max((double)1,2.2) << endl;  //强转  double Max(double a,double b)
    cout << Max<double>(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<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 
#include 
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:";
        return 2*M_PI*r;
    }
    float GetArea()const{
	cout << "Circle:";
    	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 
#include 
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;
}

// 特化成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 { /*:public Point*/
    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;

    //多态
    /*  //不常用写法
    Triangle& t4 = t3;
    cout << t4.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;

    //多态
    /*  //不常用写法
    Triangle& t4 = t3;
    cout << t4.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

你可能感兴趣的:(C++初阶,c++,开发语言,c语言)