【C++】C++中的模板函数和模板类

泛型:通用类型,模板就是一种泛型(通用类型)
泛型编程:只要程序员写代码的时候用到了模板,就说这个代码中采用了泛型编程技术
旨在编写独立于数据类型的代码(不必考虑模板究竟是类型)

1.引入模板

C++把参数和返回值的类型做了抽象
模板:把参数或者返回值的类型用变量来替换
模板本质是类型变量

int  add(int,int) 
double  add(double,double)
T add(T,T)  //C++把数据类型做了抽象,抽象为参数
示例代码:模板函数使用多个模板
#include <iostream>
using namespace std;

/*
    模板函数使用多个模板
*/
//定义一个模板函数
template<typename T1,typename T2>  //声明了两个模板,模板的名字叫T1,T2
T1 add(T1 a,T2 b)  //模板函数,a和b类型可以相同,也可以不相同,但是返回值的类型跟a类型相同
//T2 add(T1 a,T2 b)  //模板函数,a和b类型可以相同,也可以不相同,但是返回值的类型跟b类型相同
//int add(T1 a,T2 b)
{
    cout<<"模板函数add被调用了"<<endl;
    return a+b;
}

int main()
{
    int n1=89;
    int n2=67;
    double n3=12.5;
    double n4=45.9;
    //调用模板函数--》跟普通函数的写法一样
    //cout<<"函数返回值: "<<add(n1,n2)<<endl;   //T1 int  T2 int 返回值int
    //cout<<"函数返回值: "<<add(n3,n4)<<endl;     //T1 double  T2 double 返回值double
    cout<<"函数返回值: "<<add(n1,n3)<<endl;     //T1 int  T2 double 返回值int
}

2.模板的定义

2.1 模板不能共用,每个模板函数,模板类都必须独立声明模板

template<typename 模板的名字>
或者template<class 模板的名字>
模板  函数名(模板)
{

}

模板的名字也叫做泛型名

示例代码:模板不能共用
#include <iostream>
using namespace std;

/*
    模板函数不可以共用
*/
//定义一个模板函数
template<typename T1,typename T2>  //声明了两个模板,模板的名字叫T1,T2
T1 add(T1 a,T2 b)  
{
    cout<<"模板函数add被调用了"<<endl;
    return a+b;
}

// 错误  若未定义模板函数,则编译器会报错  error: ‘T1’ does not name a type [T1未命名类型]
T1 sub(T1 a,T2 b)  
{
    cout<<"模板函数add被调用了"<<endl;
    return a+b;
}

int main()
{

}
示例代码:模板函数写在主函数的后面
#include <iostream>
using namespace std;

/*
    模板函数写在主函数的后面
*/
//定义一个模板函数
template<typename T1,typename T2>  //声明了模板,模板的名字叫T1,T2
T1 add(T1 a,T2 b);  //函数声明

int main()
{

}

//如果放在主函数的下面,还得再声明一次模板
// 若不声明编译会报错:error: ‘T1’ does not name a type
template<typename T1,typename T2>  // 正确
T1 add(T1 a,T2 b)  
{
    cout<<"模板函数add被调用了"<<endl;
    return a+b;
}

2.2 模板分为两大类

  • 模板函数:只要一个函数定义的时候使用了模板,那么这个函数就叫做模板函数
  • 模板类:只要一个类中使用了模板,该类就是模板类
类名<传递给模板的类型>  对象; //声明了模板,但是类中没有用到,也必须这么写

2.3 模板类的底层原理:

Array<int> a1;     //此时编译器会生成一个类声明,类的名字就叫做Array<int>
Array<string> a2;  //此时编译器会生成另一个类声明,类的名字就叫做Array<string>
示例代码:模板类的定义
#include <iostream>
using namespace std;

/*
    模板类的定义
        定义类:要求这个类可以给任意类型的指针申请堆空间
*/

template<typename T>
class Test  //模板类 
{
public:
    Test()
    {
        cout<<"构造函数调用,申请堆空间"<<endl;
        p=new T[10]; //p=new 类型[10]
    }
    ~Test()
    {
        cout<<"析构函数调用,释放堆空间"<<endl;
        delete []p;
    }
private:
    T *p; //万能类型的指针
};

template<typename T1,typename T2>
class Base  //模板类 
{
public:
private:
    T1 a;
    T2 b;
};


int main()
{
    //模板类创建对象
    // Test t1; //错误的(error: missing template arguments before ‘t1’),以前的类(非模板类)才可以这么写
    //Test<int> t1;     //模板类创建对象,必须要传递模板参数
    //Test<double> t2; //模板类创建对象,必须要传递模板参数
    
    /*
        模板类底层原理:编译器会依据你传递的模板参数类型,生成对应版本的类声明
          class Base<int,double>
          {
            public:
            private:
                int a;
                double b;
          }
    */
    Base<int,double> b1; //int对应T1   double对应T2
    Base<double,int> b2; //double对应T1   int对应T2
}

2.4 模板函数的底层原理:

add(n1,n2);       //此时编译器会生成一个int add(int a,int b){return a+b;}的函数定义

3.模板的几种写法:

3.1 成员函数写在类的外部

template<typename T>
class Stack
{
public:
   T pop();
}
template<typename T> //需要重新声明模板,如果有多个成员函数写在外面,每个都必须单独重新声明模板
T Stack<T>::pop()
{

}
示例代码:模板类成员函数写在类的外面
#include <iostream>
using namespace std;

/*
    模板类成员函数写在类的外面
*/

template<typename T>
class Test  //模板类 
{
public:
    Test()
    {
        cout<<"构造函数调用,申请堆空间"<<endl;
        p=new T[10]; //p=new 类型[10]
    }
    ~Test()
    {
        cout<<"析构函数调用,释放堆空间"<<endl;
        delete []p;
    }

    void show();

private:
    T *p; //万能类型的指针
};

template<typename T>    //模板声明必须写,无论是否用到,都要写
void Test<T>::show()    //类名<T>
{
    for (int i = 0; i < 10; i++)
    {
        cout<<p[i]<<" ";
    }
    
    cout<<endl<<"show()成员函数调用"<<endl;
}

int main()
{
    Test<int> t;
    t.show();
}   

/*
执行结果:
    构造函数调用,申请堆空间
    0 0 0 0 0 0 0 0 0 0 
    show()成员函数调用
    析构函数调用,释放堆空间
*/

3.2 模板函数先声明,定义写在main函数的后面

template<typename T>
T add(T a,T b);
int main()
{

} 
template<typename T>
T add(T a,T b)
{
 return a+b;
}

4.模板的显式具体化和隐式实例化

4.1 模板的显式具体化:

\quad 模板函数本来可以针对所有的数据类型,但是有些数据类型该模板函数代码无法处理,此时就必须把这些特殊的类型单独定义一个具体化的版本

(1)模板函数显式具体化的语法

template<typename T>
T fun(T n)                  //除string之外的其他类型
{

}
template<>
string  fun(string n)  //专门针对string的fun
{
      具体代码
}
示例代码:模板函数的显示具体化
#include <iostream>
#include <string>
using namespace std;

/*
    定义模板函数:可以比较两个变量的大小,如果a>b,返回值1  a<b,返回-1  a==b 返回值0
    模板函数:参数类型天下无敌,但是代码不是天下无敌的(代码不一定可以处理所有类型的变量)
    解决方法:
           针对某些特定的类型,单独定义函数出来,这些特定类型的函数专业术语叫做模板函数的显式具体化

*/

typedef struct 
{
    int age;
    string name;
}student_t;

class Person
{
public:
    Person(string name, int age)
    {
        this->name = name;
        this->age = age;
    }

    string name;
    int age;
};


template<class T1,class T2>
int compare(T1 a,T2 b)
{
    cout<<"调用非结构体版本的"<<endl;
    if(a>b)
        return 1;
    else if(a<b)
        return -1;
    else
        return 0;
} 

//单独定义一个针对学生结构体的模板函数--》模板函数的显式具体化
template<>
int compare(student_t stu1, student_t stu2)
{
    cout<<"调用专门针对结构体的显式具体化版本"<<endl;
    if(stu1.age>stu2.age)
        return 1;
    else if(stu1.age<stu2.age)
        return -1;
    else
        return 0;
}

//单独定义一个针对猫类的模板函数--》模板函数的显式具体化
template<>
int compare(Person c1,Person c2)
{
    cout<<"调用专门针对猫类显式具体化版本"<<endl;
    if(c1.age>c2.age)
        return 1;
    else if(c1.age<c2.age)
        return -1;
    else
        return 0;
}

//单独定义一个针对整数和猫类的模板函数--》模板函数的显式具体化
template<>
int compare(int n,Person c1)
{
    cout<<"调用专门针对整数和猫类显式具体化版本"<<endl;
    if(n>c1.age)
        return 1;
    else if(n<c1.age)
        return -1;
    else
        return 0;
}

int main()
{
    int ret = 0;
    int n1 = 10;
    int n2 = 20;
    ret = compare(n1, n2);

    int age1 = 18;
    int age2 = 15;
    student_t stu1 = {age1, "zhang1"};
    student_t stu2 = {age2, "zhang2"};
    ret = compare(stu1, stu2);

    Person per1("wang1", 25);
    Person per2("wang2", 30);
    ret = compare(per1, per1);  
    return 0;
}

/*
执行结果:
    调用非结构体版本的
    调用专门针对结构体的显式具体化版本
    调用专门针对猫类显式具体化版本
*/

示例代码:模板类的显示具体化(使用类中的公有变量,下面有使用类中私有变量版本 )
#include <iostream>
using namespace std;

/*
    模板类的显式具体化:跟模板函数的显式具体化概念类似(类型天下无敌,但是代码不是天下无敌)
*/

class Cat
{
public:
    Cat()
    {
        name="旺财";
        age=0;
    }
    Cat(string _name,int _age)
    {
        name=_name;
        age=_age;
    }

//private:
    string name;
    int age;
};

template<typename T>
class Test  //模板类 
{
public:
    Test()
    {
        cout<<"非猫的构造函数调用,申请堆空间"<<endl;
        p=new T[10]; //p=new 类型[10]
    }
    ~Test()
    {
        cout<<"非猫的析构函数调用,释放堆空间"<<endl;
        delete []p;
    }
    void show()
    {
        int i;
        for(i=0; i<10; i++)
            cout<<"非猫的堆空间里面的内容是: "<<p[i]<<endl;
    }
private:
    T *p; //万能类型的指针
};

//模板类的显式具体化--》针对Cat类
template<>
class Test<Cat>
{
public:
    Test()
    {
        cout<<"针对猫的构造函数调用,申请堆空间"<<endl;
        p=new Cat[10]; //p=new Cat[10]
    }
    ~Test()
    {
        cout<<"针对猫的析构函数调用,释放堆空间"<<endl;
        delete []p;
    }
    void show()
    {
        int i;
        for(i=0; i<10; i++)
        {
            cout<<"堆空间里面存放的是猫对象,猫的名字: "<<p[i].name<<"  猫的年龄: "<<p[i].age<<endl;
        }
    }
private:
    Cat *p; //万能类型的指针   
};


int main()
{
    Test<int> t1;
    Test<Cat> t2;
}

/*
执行结果:
    非猫的构造函数调用,申请堆空间
    针对猫的构造函数调用,申请堆空间
    针对猫的析构函数调用,释放堆空间
    非猫的析构函数调用,释放堆空间
*/

(2)模板类显式具体化的语法

template<typename T>
class Array               //除string之外的其他类型
{

}
template<>
class Array<string> //专门针对string的 Array<string>
{
      具体代码
}

4.2 模板的隐式实例化:

KaTeX parse error: Undefined control sequence: \qiuad at position 1: \̲q̲i̲u̲a̲d̲当我们调用模板函数的时候,传递的实参类型,编译器会自动生成对应版本的函数,这个生成过程我们程序员不可见(对应程序员的是透明的),因此就把这个过程叫做模板函数的隐式实例化

T add(T a,T b)
add(12,45);  //编译器帮你生成了int版本的add(隐式实例化了int版本的add)

练习:

示例代码1:模板函数声明为类的友元函数
#include <iostream>
using namespace std;

/*
    模板函数声明成类的友元函数
*/
struct student
{
    char name[20];
    int age;
};

class Cat
{
public:
    Cat(string _name,int _age)
    {
        name=_name;
        age=_age;
    }

    template<class T1,class T2>  
    friend int compare(T1 a,T2 b);  // 将通用的模板声明为类的友元函数
    
private:
    string name;
    int age;
};


template<class T1,class T2>
int compare(T1 a,T2 b)
{
    cout<<"调用非结构体版本的"<<endl;
    if(a>b)
        return 1;
    else if(a<b)
        return -1;
    else
        return 0;
} 

//单独定义一个针对学生结构体的模板函数--》模板函数的显式具体化
template<>
int compare(struct student stu1,struct student stu2)
{
    cout<<"调用专门针对结构体的显式具体化版本"<<endl;
    if(stu1.age>stu2.age)
        return 1;
    else if(stu1.age<stu2.age)
        return -1;
    else
        return 0;
}

//单独定义一个针对猫类的模板函数--》模板函数的显式具体化
template<>
int compare(Cat c1,Cat c2)
{
    cout<<"调用专门针对猫类显式具体化版本"<<endl;
    if(c1.age>c2.age)
        return 1;
    else if(c1.age<c2.age)
        return -1;
    else
        return 0;
}

//单独定义一个针对整数和猫类的模板函数--》模板函数的显式具体化
template<>
int compare(int n,Cat c1)
{
    cout<<"调用专门针对整数和猫类显式具体化版本"<<endl;
    if(n>c1.age)
        return 1;
    else if(n<c1.age)
        return -1;
    else
        return 0;
}

int main()
{
    int ret;
    int n1=78;
    int n2=89;
    struct student stu1={"张三",18};
    struct student stu2={"李四",20};
    Cat c1("旺财",4);
    Cat c2("来福",5);
    
    ret=compare(n1,n2);
    ret=compare(stu1,stu2);
    compare(c1,c2);
    compare(n1,c1);
}

/*
执行结果:
    调用非结构体版本的
    调用专门针对结构体的显式具体化版本
    调用专门针对猫类显式具体化版本
    调用专门针对整数和猫类显式具体化版本
*/
示例代码2:模板类声明为类的友元类(使用类中的私有变量)
#include <iostream>
using namespace std;

/*
    模板类声明成友元类
*/

class Cat
{
public:
    Cat()
    {
        name="旺财";
        age=0;
    }
    Cat(string _name,int _age)
    {
        name=_name;
        age=_age;
    }
    template<typename T>
    friend class Test;
private:
    string name;
    int age;
};

template<typename T>
class Test  //模板类 
{
public:
    Test()
    {
        cout<<"非猫的构造函数调用,申请堆空间"<<endl;
        p=new T[10]; //p=new 类型[10]
    }
    ~Test()
    {
        cout<<"非猫的析构函数调用,释放堆空间"<<endl;
        delete []p;
    }
    void show()
    {
        int i;
        for(i=0; i<3; i++)
            cout<<"非猫的堆空间里面的内容是: "<<p[i]<<endl;
    }
private:
    T *p; //万能类型的指针
};

//模板类的显式具体化--》针对Cat类
template<>
class Test<Cat>
{
public:
    Test()
    {
        cout<<"针对猫的构造函数调用,申请堆空间"<<endl;
        p=new Cat[10]; //p=new Cat[10]
    }
    ~Test()
    {
        cout<<"针对猫的析构函数调用,释放堆空间"<<endl;
        delete []p;
    }
    void show()
    {
        int i;
        for(i=0; i<3; i++)
        {
            cout<<"堆空间里面存放的是猫对象,猫的名字: "<<p[i].name<<"  猫的年龄: "<<p[i].age<<endl;
        }
    }
private:
    Cat *p; //万能类型的指针   
};


int main()
{
    Test<int> t1;
    Test<Cat> t2;
    
    t1.show();
    t2.show();
}

/**
执行结果:
    非猫的构造函数调用,申请堆空间
    针对猫的构造函数调用,申请堆空间
    非猫的堆空间里面的内容是: 0
    非猫的堆空间里面的内容是: 0
    非猫的堆空间里面的内容是: 0
    堆空间里面存放的是猫对象,猫的名字: 旺财  猫的年龄: 0
    堆空间里面存放的是猫对象,猫的名字: 旺财  猫的年龄: 0
    堆空间里面存放的是猫对象,猫的名字: 旺财  猫的年龄: 0
    针对猫的析构函数调用,释放堆空间
    非猫的析构函数调用,释放堆空间
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值