欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++ -- 模板的基础知识

程序员文章站 2022-06-01 11:52:29
...

模板的由来

1.对于交换函数Swap来说,
(1)如果用于交换两个整型,则代码如下:

void Swap(int* a, int* b)
{
      int temp = *a;
      *a = *b;
      *b = temp;
}

(2)当用于交换两个整型指针时,代码如下:

void Swap(int** a, int** b)
{
      int* temp = *a;
      *a = *b;
      *b = temp;
}

2.对于交换函数Swap,当用于交换两个整型和两个整型指针时,代码基本一致,功能也是一样(都是用于交换)。对于这种代码基本一致,且只与类型有关的函数(或类),我们可以想到只要能够编写与类型无关的函数(或类)就可以达到任意类型都可以共用同一个函数。
3.模板的定义:模板是一种复用手段,模板是泛型编程的基础(泛型编程就是编写与类型无关的代码)。
4.模板的分类:模板分为函数模板与类模板。

函数模板的编写

1.用template关键字,template后面带一对尖括号,尖括号里用class或typename定义模板参数T,T表示数据的类型

template<class T>
template<typename T>
template<class T, class M>  //也可以定义多个模板参数

2.对于上面的代码,采用函数模板编写,代码如下:
C++ -- 模板的基础知识
3.则对于任意类型都可以用于交换
C++ -- 模板的基础知识
4.对于函数模板来说,虽然函数模板只有一份,但是当参数类型不同时,调用的不是同一个函数。

  • 对于上面的程序,汇编代码如下:(可以看到当调用同一个函数模板时,由于参数的类型不同,call的地址也不同,说明调用的不是同一个函数模板)
    C++ -- 模板的基础知识

函数模板的实例化

1.模板实例化的过程
C++ -- 模板的基础知识
2.模板的显示实例化(在函数模板的调用时,显示指定类型)
代码如下:

template<class T>
void Swap(T* a, T* b)
{
      T temp = *a;
      *a = *b;
      *b = temp;
}

int main()
{
      int a = 10;
      int b = 20;
      Swap<int>(&a, &b);   

      system("pause");
      return 0;
}

3.重载函数模板(函数模板的函数名相同,模板参数不同)
(1)测试代码:

template<class T>
bool Equal(const T& left, const T& right)
{
      cout << "Equal(const T& left, const T& right)" << endl;
      return left == right;
}

template<class T1,class T2>
bool Equal(const T1& left, const T2& right)
{
      cout << "Equal(const T1& left, const T2& right)" << endl;
      return left == right;
}

int main()
{
      Equal(1, 2);
      Equal(1, 1.2);
      system("pause");
      return 0;
}

(2)运行结果:(可以看到当参数不同时,调用的是不同的模板函数)
C++ -- 模板的基础知识

4.模板只有在实例化时才生成相应的代码,才去检查相应的错误
(1)例如:下面的模板函数内部缺少分号,但是该函数模板没有被调用也就没有进行实例化,因而没有检查出错误,所以也能成功编译。
C++ -- 模板的基础知识
(2)如果函数模板框架错误,编译器会检查出相应的错误
C++ -- 模板的基础知识
总结:模板只有在调用时才会实例化,才会生成相应的代码。

类模板

1.以顺序表为例:

#include<iostream>
using namespace std;

template<class T>
class SeqList
{
public:
      SeqList();
      ~SeqList();
private:
      int _size;
      int _capacity;
      T* _data;
};

2.对于类模板来说,它的类型为类名加上<T>,例如上面SeqList类的类型为SeqList<T>
3.因此对于类模板来说,如果将其成员函数的声明与定义分开,则定义时必须指明类型:

template<class T>
SeqList<T>::SeqList()
:_size(0)
, _capacity(0)
, _data(new T[_capacity])
{}

template<class T>
SeqList<T>::~SeqList()
{
      delete[] _data;
      _size = 0;
      _capacity = 0;
}

int main()
{
      SeqList<int> s;
      system("pause");
      return 0;
}

实现适配器模式的栈和队列

  • 因为栈要满足后进先出的性质,栈只能在栈顶进行插入和删除,栈顶类似于一个数据结构的尾巴处,而顺序表的尾插和尾删比较方便,所以可以采用顺序表实现栈。而队列是在队尾入,队头出,如果采用顺序表,就会挪动大量的数据,所以队列可以采用链表进行适配。
  • 缺省模板参数
    ①对于如下代码:
template<class T,class Container = SeqList<T>>  

②Container为模板参数,后面带一个等号,表示Container为缺省的模板参数,表示如果自己传两个参数时,就会用自己传的,如果没有给就用缺省的类型。
③模板参数只能从后向前缺省,不能再中间进行缺省,因为编译器无法识别。

(1)利用适配器模式实现栈(模板参数Container表示容器,当指定为list时就用list实现栈,当用vector时就用vector实现栈。(这里的SeqList是自己实现的顺序表,也可以用标准库里面的)

template<class T,class Container = SeqList<T>>  //Container为缺省的模板参数
class Stack
{
public:
      void Push(const T& x)
      {
           _con.PushBack(x);
      }

      void Pop()
      {
           _con.PopBack();
      }

      const T& Top()
      {
           return _con.Back();
      }

      bool Empty()
      {
           return _con.Empty();
      }

      size_t Size()
      {
           return _con.Size();
      }
private:
      Container _con;
};

void TestStack()
{
      Stack<int> s;
      s.Push(1);
      s.Push(2);
      s.Push(3);
      s.Push(4);

      while (!s.Empty())
      {
           cout << s.Top() << " ";
           s.Pop();
      }
      cout << endl;
}

(2)利用适配器模式实现队列

template<class T,class Container>
class Queue
{
public:
      void Push(const T& x)
      {
           _con.PushBack(x);
      }
      void Pop()
      {
           _con.PopFront();
      }
      const T& Front()
      {
           return _con.Front();
      }
      size_t Size()
      {
           return _con.Size();
      }
      bool Empty()
      {
           return _con.Empty();
      }
private:
      Container _con;
};

模板的模板参数

1.模板的模板参数的引入
(1)对于如下代码,如果不用缺省的模板参数,而是自己指定类型,就有可能第一个模板参数传int,第二个模板参数传char,这样就会造成类型不匹配。

Stack<int,List<char>> s;

(2)只要能够想到一个方法,使得显示实例化时某些模板参数不用指定类型,只用传一个类名就可以,这样就不会出错。

template<class T,template<class> class Container = List>

//或者可以加上T,这两句等价
template<class T,template<class T> class Container = List> 

//也可以不用缺省参数,此时实例化时Container对应的参数传的是类名而不在是类型
template<class T,template<class T> class Container>  

例如:

template<class T,template<class> class Container>   //Container为模板的模板参数,则Container就是一个类名,如果定义其类型,则必须在类名前面加上Container<T>,

class Stack
{
public:
      void Push(const T& x)
      {
           _con.PushBack(x);
      }
private:
      Container<T> _con;
};

void TestStack()
{
      Stack<int,List> s;  //例如第二个参数传的是List,这是一个类名
}

非类型的模板参数

  • 模板参数表示的不是类型

1.非类型模板参数的编写

template<size_t M>   //这里的M为非类型的模板参数,表示A里面数组a的大小
class A
{
public:
      int a[M];
};

2.非类型的模板参数只能是整型(如果不是整型就会出错)
C++ -- 模板的基础知识
3.非类型的模板参数实际上就是一个常量
由上面可以看出,在A类里面定义了一个数组,M作为该数组的大小,只有常量才能作为数组的大小。