二十一、模板

泛型(Generic Programming)即是指具有在多种数据类型上皆可操作的含意。泛型编 程的代表作品STL是一种高效、泛型、可交互操作的软件组件
泛型编程最初诞生于 C++中,目的是为了实现C++的 STL(标准模板库)。其语 言支持机制就是模板(Templates)。模板的精神其实很简单:参数化类型。换句话说, 把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来做成模板参数 T

所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。

1.png

1.函数模板

在函数模板未引入之前,对同一功能函数的不同类型参数实现只能通过函数重载实现,而模板则是把这个过程泛化

语法格式
template<typename T>
template<class T>

template<typename 类型参数表>
返回类型 函数模板名(函数参数列表)
{
   函数模板定义体
}

-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐

#include <iostream>

using namespace std;


//模板就是对参数类型的参数化

//此时编译器不知道T是什么
//告诉编译器T是一个类型的泛化
template <typename T>//告诉编译器T是一个泛化类型
template <class A>  //上下两种写法是等价的!
void mySwap(T &a,T &b)
{
   T temp = a;
   a = b;
   b = temp;
}


template <typename T>//每个函数都要声明一次!
void print(T &a,T &b)
{
   cout<<"a = "<<a<<" b = "<<b<<endl;
}

int main(void)
{
    int a = 10;
    int b = 20;

    cout<<"交换之前"<<endl;
    //<>在调用模板函数的时候,要告诉编译器T泛化的类型,具体到底是什么类型
    print<int>(a,b); 
    mySwap<int>(a,b);
    cout<<"交换之后"<<endl;
    print<int>(a,b); 



    char x = 'x';
    char y = 'y';

    cout<<"交换之前"<<endl;
    print<char>(x,y); 
    mySwap<char>(x,y);
    cout<<"交换之后"<<endl;
    print<char>(x,y); 

    return 0;
}

template 是语义是模板的意思,尖括号中先写关键字 typename 或是class ,后 面跟一个类型 T,此类即是虚拟的类型。至于为什么用 T,用的人多了,也就是 T 了。

排序模板函数练习
//
#include <iostream>

using namespace std;

template<typename T>
int array_sort(T *array,int num)
{
   T temp;
   //len = sizeof(array)/sizeof(array[0]);
   //不能再这里写,这里的array已经退化成一个指针了,len只能是1
   for (int i = 0; i < num; ++i)
   {
      for (int j = i+1; j < num ; ++j)
      {
          if (array[j] > array[i])
          {
            temp = array[i];
            array[i] = array[j];
            array[j] = temp;
          }
      }
   }
   return 0;
}


//打印数组的方法
template<typename T>
void print_array(T *array,int num)
{
   for (int i = 0; i < num; ++i)
   {
      cout<<array[i]<<" ";
   }
   cout<<endl;
}


int main(void)
{
    char str[] = "dsagfsdhdsgbdfbgfdujhtfyhrd";
    int len = strlen(str);
     
    array_sort<char>(str,len);
    print_array<char>(str,len);


    cout<<"-------------------------"<<endl;
    //排序一个Int数组
    int array[] = {3,24,21,51,2,5,55,21};
    len = sizeof(array)/sizeof(array[0]);

    array_sort<int>(str,len);
    print_array<int>(str,len);


    return 0;
}

  • 当函数模板和普通函数都符合调用时,优先选择普通函数
  • 若显示使用函数模板,则使用<> 类型列表
  • 如果函数模板产生更好的匹配 使用函数模板

2.类模板

类模板与函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,所以将类中的类型进行泛化。

//基本语法
template<typename T>
class A
{
}
//----------------------------------------------------------
#include <iostream>

using namespace std;

template<typename T>
class A
{
public:
    A(int a){   
        this->a =a;
    }

    void printA(){
        cout<<"a = "<<a<<endl;
    }
private:
    T a;
};


//继承模板类,直接继承已经实例化的模板类
//这么继承完之后的B类,是普通类
class B:public A<int>
{
public:
    B(int a,int b):A(a){//A(a)加不加<int>都无所谓!
           this->b = b;            
    }

    void printB(){
        cout<<"b = "<<b<<endl;
    }
private:
    int b;
};

//C类继承的是一个模板类,没有实例化,此时C类仍然是一个模板类
template<typename T>
class C:public A<T>
{
public:
    C(T a,T c):A(a){
        this->c = c;
    }

    void printC(){
        cout<<"c = "<<c<<endl;
    }
private:
    T c;
};


//把模板类当参数
template<typename T>
void func(A<T> &a)
{
    a.printA();
}

//普通函数
void func2(A<int> &a)
{
    a.printA();
}


int main(void)
{
   //普通实例化
    A<int> a(10);

    a.printA();

    A<double> b(30.0);

    b.printA();

   
    cout<<"-----------------------"<<endl;
   //类作为参数
    func<int>(a);
    func2(a);


    cout<<"-----------------------"<<endl;
   //模板类继承
    B b(100,200);
    b.printB();


    C<int> c(1000,2000);//c是一个模板类

    c.printC();


    return 0;
}

2.1类模板的实现(函数体写在类外 有坑!)
//-------------------------------------Complex.h-------------------------------------------------
#include <iostream>
using namespace std;


#ifndef __COMPLEX_H__
#define __COMPLEX_H__
template<class T>
class Complex;

template<class T>
Complex<T> mySub(Complex<T> &c1,Complex<T> &c2);
//前置声明发现缺了Complex,又要前置声明Complex,所以很不建议滥用友元!


template<typename T>
class Complex
{
public:
    Complex()
    Complex(T a,T b)

    void printComplex(){
        cout<<"("<<a<<"+"<<b<<"i)"<<endl;
    }

    Complex operator+(Complex &another);
    Complex operator-(Complex &another);

     //如果给一个模板类提供一个友元函数(在外部定义),例如<< >>重载
    //要在这个重载符号和参数列表之间添加<T>
    friend ostream & operator<< <T>(ostream &os,Complex<T> &c);

    //滥用友元 调用就报错了,只能前置声明了!!!
    //滥⽤用友元函数,本来可以当成员函数,却要⽤用友元函数
    //如果说是⾮非<< >>在模板类中当友元函数
    //在这个模板类之前声明这个函数
    friend Complex<T> mySub<T>(Complex<T> &c1,Complex<T> &c2);
    //最终的结论,模板类不要轻易写友元函数,要写的就写<<和>>。  
  
    #if 0
    //或者可以在内部定义,就可以省去<T>
    friend ostream & operator<<(ostream &os,Complex<T> &c){
        os<<"("<<c.a<<"+"<<c.b<<"i)"<<endl;
        return os;
    }
    #endif

private:
    T a;
    T b;
}
#endif

//-------------------------------------Complex.hpp-------------------------------------------------

#include "Complex.h"
template<class T>
Complex<T>::Complex()
{
}

template<class T>
Complex<T>::Complex<T>(T a,T b)
{
  this­‐>a = a;
  this­‐>b = b;
}

template<typename T>
Complex<T> Complex<T>::operator+(Complex<T> &another)
 {
    Complex temp(this->a+another.a,this->b+another+another.b);
    return temp;
 }

template<typename T>
Complex<T> Complex<T>::operator+(Complex<T> &another)
 {
    Complex temp(this->a - another.a , this->b - another+another.b);
    //这些构造可以不加<T>,因为在类内已经声明了!和下面的mySub完全不一样
    return temp;
 }

template<typename T>
ostream & operator<<(ostream &os,Complex<T> &c)
{
    os<<"("<<c.a<<"+"<<c.b<<"i)"<<endl;
    return os;
}

template<typename T>
Complex<T> mySub(Complex<T> &c1,Complex<T> &c2)
{
    Complex<T> temp(c1.a - c2.a ,c1.b - c2.b);//这里构造函数记得加<T>
    return temp;
}

//-------------------------------------main.cpp-------------------------------------------------
#include <iostream>
#include "Complex.h"
//#include "Complex.cpp" //引入cpp文件之后才能运行,因为二次编译的缘故!
//但是因为cpp文件的封装性,不能够引入
#include "Complex.hpp"//改名!!!把cpp尾缀改成hpp
using namespace std;

//模板类的方法的实现不能够用多文件编写
//如果实现多文件 由于二次编译 真正地实现体是在cpp文件定义的,需要引入cpp头文件
int main(void)
{
   
   Complex<int> c1(10,20);
   c1.printComplex();

   Complex<int> c2(1,2);
   Complex<int> c3;

   c3 = c1 + c2;
   c3.printComplex();

   Complex<int> c4;
   c4 = c1 - c2;

   //重构<<
   cout<<c4;//报错了,这里有个c++编译器的坑

   Complex c5;
   c5 = mySub(c1,c2);//报错!!!//改完之后可以了,不建议滥用友元!

   return 0;
}
  • 模板类不要轻易使用友元函数。
  • 由于二次编译,模板类在.h在第一次编译之后,并没有最终确定类的具体实现,只是编译器的词法校验和分析。在第二次确定类的具体实现后,是在.hpp文件生成的最后的具体类,所以main函数需要引入.hpp文件。

综上:引入hpp文件一说也是曲线救国之计,所以实现模板方法建议在同一个文件.h中完成

3.模板类中的static

2.png
#include <iostream>

using namespace std;

template <typename T>
class A
{
public:
private:
    T value;
    static T a;
};

template <typename T>
T A<T>::a = 0;//类中的静态成员需要在类的外部进行初始化

int main(void)
{
    //1.模板类通过二次编译根据调用的代码生成了两个不同的类A
    //一个是A<int> 一个是A<char>
   
   A<int> a1,a2,a3;
   A<char> b1,b2,b3;
   //a1 b1不是同一种类,所以static静态变量不是共用的!
   //证明:
   A<int>::a = 20;   //改变A<int>的静态成员   
   A<char>::a = 'X'; //改变A<char>的静态成员
 

   cout<<"a1:a="<<a1.a<<endl;//20
   cout<<"b1:a="<<b1.a<<endl;//X

   //a1 a2 a3是同一个类,静态变量共用的,b1 b2 b3同理!

   cout<<"a1:a="<<a2.a<<endl;//20
   cout<<"b1:a="<<b2.a<<endl;//X

   cout<<"a1:a="<<a3.a<<endl;//20
   cout<<"b1:a="<<b3.a<<endl;//X

   return 0;
}

4.练习 实现一个模板数组类

1.请设计一个数组类模板(MyVector),完成对int、char、Teacher类型元素的管理
2.需要实现构造函数、拷贝构造函数、<<、[]、=操作符

//自定义一个模板数组类
//1.该数组能够存放int double 还有自定义类型
//2.[] array[i] = ??
//3.cout<<array
//4.array1 = array2
//5.array1(array2)
//6.array[i] = array[j]
#ifndef __MYVECTOR_H__
#define __MYVECTOR_H__
#include <iostream>
using namespace std;
template<typename T>
class MyVector  //数组:插入、删除、修改、查找
{
public:

  MyVector();        //无参构造
  MyVector(int len); //有参构造
  MyVector(const MyVector &another);//拷贝构造

  T & operator[](int index);
  MyVector & operator=(const MyVector &another);

  friend ostream &operator << <T>(ostream &os,MyVector<T> &vector);
  #if 0 //注意有两种写法
  friend ostream &operator <<(ostream &os,MyVector<T> &vector){
    for (int i = 0; i < vector.len; ++i)
    {
        os<<vector[i]<<" ";//[]重载了,不然就是vector.a[i]了
    }
    os<<endl;
    return os;
  }
  #endif

  ~MyVector();
private:
   T* a;
   int len;
}
#endif
------------------------------------------------MyVector.hpp-----------------------------------
#include "MyVector.h"
template<typename T>
MyVector<T>::MyVector(){
    this->a = NULL;
    this->len = 0;
}

template<typename T>
MyVector<T>::MyVector(int len){
    this->len = len;
    if (this->a = NULL)this->a = new T[len];//在堆上连续开辟了sizeof(T)*len的空间
}

template<typename T>
MyVector<T>::MyVector(const MyVector &another)
{
    // if (this == another)return; 
    // delete[] this->a;
    this->len = another->len;
    this->a = new T[this->len];
    for(int i = 0; i < another.len;i++){
        this->a[i] = another.a[i];//如果存储对象,那么这个=号也是要重载实现深拷贝的!
    }
}

template<typename T>
T & MyVector::operator[](int index){
    return this->a[index];
}

template<typename T>
MyVector<T> & MyVector::operator=(const MyVector &another){
    if (this == another)return; 
    if(this->a != NULL)delete[] this->a;

    this->len = another.len;
    this->a = new T[this->len];
    for(int i = 0; i < another.len;i++){
        this->a[i] = another.a[i];
    }

    return *this;
}


template<typename T>
ostream & operator <<(ostream &os,MyVector<T> &vector){//<< <T>不是在定义上面写,而是在声明处写
    for (int i = 0; i < vector.len; ++i)
    {
        os<<vector[i]<<" ";//[]重载了,不然就是vector.a[i]了
    }
    return os;
}


MyVector::~MyVector(){
    if (this->a != NULL)
    {
        delete[] this->a;
        this->a = NULL;
        this->len = 0;
    }
}
--------------------------------------Teacher.h------------------------------

class Teacher
{
public:
    Teacher(){
        this->name = NULL;
        this->id = 0;
    }
    
    Teacher(int id,char *name)
    {
       this->id = id;

       int len = strlen(name);

       this->name = new char[len + 1];

       strcpy(this->name,name);
    }

    Teahcer(const Teahcer &t){
        this->id = id;
        int len = strlen(t.name);

        this->name =new char[len + 1];
        strcpy(this->name,t.name);
    }


    Teahcer & operator = (const Teahcer &t){

        if (this->name != NULL)
        {
            delete[] this->name;
            this->name = NULL;
            this->id = 0;
        }

        this->id = t.id;
        int len = strlen(t.name);
        this->name = new char[len + 1];
        strcpy(this->name,t.name);

        return *this;
    }
  

    void printTeacher(){
        cout<<"id = "<<this->id<<",name = "<<this->name<<endl;
    }
    friend ostream & operator <<(ostream &os,Teahcer &t);

    ~Teahcer(){
        if (this->name != NULL)
        {
            delete[] this->name;
            this->name = NULL;
            this->id = 0;
        }
    }
private:
    int id;
    char *name;
}

ostream & operator <<(ostream &os,Teahcer &t)
{
    t.printTeacher();
    return os;
}
--------------------------------------main.cpp------------------------------
#include <iostream>
#include "MyVector.h"
#include "MyVector.hpp"
using namespace std;
void test1(){
    MyVector<int> myVector1(10);

   for (int i = 0; i < 10; ++i)
   {
      myVector1[i] = i + 10;
   }

   cout<<"vector1"<<endl;
   cout<<myVector1<<endl;


   MyVector<int> myVector2(myVector1);//拷贝构造

   cout<<"vector2"<<endl;
   cout<<myVector2<<endl;

   MyVector<int> myVector3;
   myVector3 = myVector2;//=重载

   cout<<"vector3"<<endl;
   cout<<myVector3<<endl;
}


void test2(){
    //创建一些Teacher对象
    Teahcer t1(5,"zhang3");
    Teahcer t2(1,"zhang43");
    Teahcer t3(2,"zhang5");
    Teahcer t4(3,"zhang6");
    Teahcer t5(4,"zhang7");
    
    MyVector<Teahcer> myVector1(5);

    myVector1[0] = t1;//myVector.opetator[](0) = t1 和 teacher.operator=(t1)
    myVector1[1] = t2;
    myVector1[2] = t3;
    myVector1[3] = t4;
    myVector1[4] = t5;

    for (int i = 0; i < 5; ++i)
    {
        cout<<myVector1[i];
    }

}

int main(void)
{
    test1();
    test2()

   return 0;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容