C++模板

探索C++模板

在我们学习C++的过程中,经常会遇到一类问题就是很多地方代码相同只是类型不同,我们就需要将同样的逻辑实现两遍,比如当我们实现一个交换函数swap的时候,交换int类型要用到,交换string类型也要用到,而由于类型不同我们就需要将这个函数实现两遍,C++中提供了模板来实现泛型编程,模板可以理解为一个模具,当我们需要int类型的代码时,系统去这个模具实例化一份int代码出来,需要string代码的时候实例化一份string代码出来。
模板是为了实现复用,可以实现类型参数化
模板分为类模板和函数模板,类模板就是整个类的内置类型未定,函数模板就是函数里边的内置类型未定,我们先来看函数模板
语法是 template <class/typename 形参名1,形参名2…>
形参的定义可以用calss也可以用typename
接下来我们自己实现一个交换模板函数

template<class T>
void my_swap(T& x, T& y)
{
 T z;
 z = x;
 x = y;
 y = z;
 return;
}

void TestSeqList()
{
 int a = 1;
 int b = 2;
 string c = "111";
 string d = "222";
 my_swap(a,b);
 cout << a << b << endl;
 my_swap(c,d);
 cout << c << d << endl;
}

在这里插入图片描述
从结果来看int类型与string类型均完成了函数调用
可是我们要记住只能同类型的交换,在第一个模板T就是实例化出int类型,第二次调用模板实例化出的是string类型,T也就i是string类型,我们不能在一份实例化中T同时是int也是string。

显示实例化

模板的原理是当编译的时候编译器会从调用的函数那里推演出类型,然后利用模板实例化出代码,同时我们也可以在调用的时候就指定类型,这被叫做显示实例化

my_swap<int>(b,a);

模板类

模板类与模板函数的语法相同,声明了这个类是模板类之后,类里边的成员函数,成员变量等一切可以用于内置类型的地方。

template<class/typename 形参1,class 形参2>
class A
{}

下面是用模板类实现的一个链表

#pragma once
#include<iostream>
#include<string>
using namespace std;
template<class T>
struct ListNode
{
 T _data;
 ListNode* _next;
 ListNode* _prev;
};
template<class T>
class List
{
 typedef ListNode<T> Node;
public:
 List()
 {
  _head = new Node;
  _head->_next = _head;
  _head->_prev = _head;
 }
 List(const List& l)
 { 
  _head = new Node;
  _head->_next = _head;
  _head->_prev = _head;
  Node* ptr = l._head;
  while (ptr->_next != l._head)
  {
   ptr = ptr->_next;
   PushBack(ptr->_data);
  }
 }
 List& operator=(const List l)
 {
  swap(_head,l._head);
  return *this;
 }
 ~List()
 {
  Clear();
  _head->_next = NULL;
  _head->_prev = NULL;
  delete _head;
 }
 void Clear()
 {
  while (_head->_next != _head)
  {
   Erase(_head->_next);
  }
  return;
 }
 void PushBack(const T& x)
 {
  Insert(_head,x);
  return;
 }
 void PopBack()
 {
  Erase(_head->_prev);
  return;
 }
 void PushFront(const T& x)
 {
  Insert(_head->_next, x);
  return;
 }
 void PopFront()
 {
  Erase(_head->_next);
 }
 T& Front()
 {
  return _head->_next->_data;
 }
 T& Back()
 {
  return _head->_prev->_data;
 }
 void Insert(Node* pos, const T& x)
 {
  Node* newnode = new Node;
  newnode->_next = pos;
  newnode->_prev = pos->_prev;
  newnode->_data = x; 
  pos->_prev->_next = newnode;
  pos->_prev = newnode;
 }
 void Erase(Node* pos)
 {
  pos->_prev->_next = pos->_next;
  pos->_next->_prev = pos->_prev;
  pos->_next = NULL;
  pos->_prev = NULL;
  delete pos;
 }
 void Print()
 {
  Node* node = _head->_next;
  while (node != _head)
  {
   cout << node->_data;
   node = node->_next;
   cout << endl;
  }
 }
 void Empty()
 {
  if (_head->_next == _head)
  {
   return 1;
  }
  return 0;
 }
 size_t Size()
 {
  size_t count = 0;
  Node* ptr = _head;
  while (ptr->_next !=_head)
  {
   count++;
   ptr = ptr->_next;
  }
  return count;
 }
private:
 Node* _head;
};

非类型模板形参

1.非类型模板形参:模板的非类型形参也就是内置类型形参如:template<class T, int a> class B{};其中int a就是非类型的模板形参。
2、 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
3、 非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。
4、 调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。
5 、注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
6、 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
7 、sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
8 、当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量比如:template <class T, int a> class A{};如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。
9. 非类型模板形参的形参和实参间所允许的转换:

1、允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A m;即数组到指针的转换
2、const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。
3、提升转换。如:template class A{}; const short b=2; A m; 即从short到int 的提升转换
4、整值转换。如:template class A{}; A<3> m; 即从int 到unsigned int的转换

适配器

当我们实现完了链表的时候,试着用模板在实现一个队列,这个时候我们发现队列的代码基本与链表一致,这个时候我们可以用适配器来实现,具体的我们来看代码实现

template<class T,template<class> class Container = List>
class Queue
{
public:
 void Push(const T& x)
 {
  return _con.PushBack(x);
 }
 void Pop()
 {
  return _con.PopBack();
 }
 T& Front()
 {
  return _con.Rront();
 }
 T& Back()
 {
  return _con.Back();
 }
 void Empty()
 {
  return _con.Empty();
 }
 size_t Size()
 {
  return _con.Size();
 }
 void Print()
 {
  return _con.Print();
 }
protected:
 Container<T> _con;
};

第一句是模板的模板参数,我们也可以用template<class T,class Container>代替,我们相当于在queue中实现了一个容器,任何相关的类型都可以适配这个容器,然后我们将List适配进去,当然我们可以给缺省参数,这样默认适配List,然后我们在Queue中的成员函数实现就可以套用List的成员函数。

模板的总结

优点:
1.增加了代码的复用性,节省资源,产生了STL来提高效率
2.让代码更加灵活
缺点:
1.代码不易维护,编译时间变长
2.代码更加繁复,编译错误的时候不好定位错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值