模板与泛型编程

一,模板定义:

1. 定义函数模板:

模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参间以逗号分隔,模板形参表不能为空:

template <typename T> int compare(const T &v1, const T &v2)
{
  if (v1 < v2)  return -1;
  if (v2 < v1)  return 1;
  return 0;
}

模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参.非类型形参跟在类型说明符之后声明.

使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参.一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例.

实际上,编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参.

函数模板可以用与非模板函数一样的方式声明为inline.说明符放在模板形参表之后,返回类型之前,不能放在关键字template之前:

template <typename T> inline T  min(const T&, const T&);

2. 定义类模板:

例如:

template <class Type> class Queue{
public:
  Queue();
  Type &front();
  const Type &front() const;
  void push (const Type &);
  void pop();
  bool empty() const;
private:
 // ...
};

像函数形参一样,程序员为模板形参选择的名字没有本质含义.

可以给模板形参赋予的唯一含义是区别形参是类型形参还是非类型形参.

模板形参遵循常规名字屏蔽规则;

用作模板形参的名字不能在模板内部重用,即模板形参的名字只能在同一模板形参表中使用一次,但模板形参的名字可以在不同模板中重用:

template <class T> T cals(const T &a, const T &b)
{
  typedef double T;   // error
  T tmp = a;  
  // ...
}


对于模板,可以只声明而不定义.

同一模板的声明和定义中,模板形参的名字不必相同.

每个模板类型形参钱必须带上关键字class 或typename,每个非类型形参前面必须带上类型名字,省去关键字活类型说明符是错误的:

template <typename T, U> T calc(const T&, const U&);    // error

在函数模板形参表中,typename和class具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用,

除了定义数据成员或函数成员之外,类还可以定义类型成员,通过在成员名前加上关键字typename作为前缀,可以告诉编译器将成员当做类型:

template <class Parm, class U> Parm fcn(Parm* array, U value)
{
  typename Parm::size_type *p;  // declares p to be a pointer
}

在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定.对模板的非类型形参而言,求值结果相同的表达式将认为是等价的:

template < class T, size_t N> void array_init(T (¶m)[N])
{
  for(size_t i = 0; i != N; ++i)
    para[i] = 0;
}

int x[42];
double y[10];
array_init(x); // 实例化为array_init(int(&)[42])
array_init(y); // 实例化为array_init(double(&)[10])

通过将形参设为const引用,就可以允许使用不允许复制的类型.


编译模板时,编译器可能会在三个阶段中标识错误:

  第一阶段是编译模板定义本身时;

  第二阶段是在编译器见到模板的使用时;

  第三阶段是在实例化的时候,只有在这个时候可以发现类型相关的错误.


模板时一个蓝图,它本身不是类或函数.编译器用模板产生指定的类或函数的特定类型版本.产生模板的特定类型实例的过程称为实例化.

类模板在引用实际模板类类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化.


从函数实参确定模板实参的类型和值的过程叫做模板实参推断,规则是:

  1. 多个类型形参的实参必须完全匹配;

  2. 类型形参的实参接受2种受限转换:

      const转换: jieshou const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化;

      数组或函数到指针的转换: 如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换.

  3. 用普通类型定义的形参可以使用常规转换.

  4. 可以使用函数模板对函数指针进行初始化或赋值,这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本.


函数模板的显式实参:

    在某些情况下,有必要覆盖模板实参的推断机制,并显示指定为模板形参所用的类型或值.这在函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现.

    在返回类型中使用类型形参:

    

template <class T1, class T2, class T3>
T1 sum(T2, T3)
int i = 3;
int lng = 4L;
long val3 = sum<long>(i,lng); // ok, calls long sum(int, long)

显式模板实参从左至右与对应模板形参相匹配,第一个模板实参与第一个模板形参匹配,第二个实参与第二个形参匹配,以此类推.假设可以从函数形参推断,则只有结尾(最右边)形参的显式模板实参可以省略:

template <class T1, class T2, class T3>
T3 alternative_sum(T2, T1);

long val3 = alternative_sum<long>(i, lng); // error: can't infer initial template parameters
long val2 = alternative_sum<long, int, long>(i, lng); // ok

模板编译类型:

  1. 包含编译模型: 在包含编译模型中,编译器必须看到用到的所有模板的定义.一般而言,可以通过在声明函数模板或类模板的头文件中添加一条#include指示使定义可用,该#include引入了包含相关的定义的源文件(即在头文件末尾用#include指令将源文件包含进来). (经测试,VS2005使用此模型,需要注意,在头文件中#include了相应源文件后,源文件不应该再添加到工程中)

  2. 分别编译模型: 在分别编译模型中,编译器会为我们跟踪相关的模板定义.但是我们必须让编译器知道要记住的模板定义,可以使用export关键字来做这件事.

       一般在函数模板的定义中指明函数模板为导出的(在template之前使用export关键字),声明不必指定export.

      对类模板,类声明必须放在头文件中,头文件中的类定义体不应该使用关键字export,应该在类的实现文件中使用export.

export template<typename Type>
Type sum(Type t1, Type T2)
{
  ...
}

// header file
template <class Type> class Queue{...};

// Queue.cc
export template <class Type> class Queue;
#include "Queue.h"
// Queue成员函数定义.


通常,当使用类模板的名字的时候,必须制定模板形参.但在类本身的作用域内部,可以使用类模板的非限定名.

类模板成员函数的定义具有如下形式:

  1.  必须以关键字template开头,后接类的模板形参表;

  2.  必须指出它是哪个类的成员;

  3.  类名必须包含其模板形参.


类模板成员函数的实例化:

    像其他函数模板一样,需要使用类模板的成员函数时产生该成员的实例化.

    与其他函数模板不同的是,在实例化类模板成员函数的时候,编译器不执行模板实参推断,相反,类模板成员函数的模板形参由调用该函数的对象的类型确定.

    类模板的成员函数只有为程序所用才进行实例化.如果函数从未使用,则不会实例化该成员函数.

非类型模板的实参必须是编译时常量表达式


类模板中的友元声明:

在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体的友元关系:

  1. 普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数;

  2. 类模板或函数模板的友元声明,授予对友元所有实例的访问权;

  3. 只授予对类模板或函数模板的特定实例的访问权的友元关系.

// 1. 普通友元
template <class Type> class Bar{
  friend  class FooBar;
  friend void fcn();
};

// 2. 一般模板友元关系
template <class Type> class Bar{
  template <class T> friend class Fool;
  template <class T> friend void templ_fcn1(const T&);
};

// 3. 特定的模板友元关系
template <class T> class Foo2;
template <class T> void templ_fcn2(const T&);
template <class Type> class Bar{
  friend class Foo2<char*>;
  friend void templ_fcn3<char*>(char* const &);
};

// 下面形式的友元声明更为常见:
template <class T> class Foo3;
template <class T> void templ_fcn3(const T&);
template <class Type> class Bar{
  friend class Foo3(Type);
  friend void templ_fcn3<Type>(const Type &);
};


成员模板:

    任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员成为成员模板,成员模板不能为虚.

Queue类:

queue.h

// queue.h
#ifndef _QUEUE_H
#define _QUEUE_H
#include <ostream>

template <class Type> class Queue;

template <class T>
std::ostream& operator<< (std::ostream&, const Queue<T>&);


template <class Type> class QueueItem
{
	friend class Queue<Type>;
	friend std::ostream& operator<< <Type> (std::ostream&, const Queue<Type>&);

	// 私有成员
	QueueItem(const Type &t):item(t), next(0){}
	Type item;
	QueueItem* next;
};

template <class Type> class Queue
{
	friend std::ostream& operator<< <Type> (std::ostream&, const Queue<Type>&);
public:
	Queue():head(0),tail(0) {}
	template <class It> Queue(It beg, It end):head(0),tail(0) {copy_elems(beg, end);}
	Queue(const Queue &Q):head(0),tail(0) {copy_elems(Q);}
	~Queue() { destroy(); }
	template <class Iter> void assign(Iter, Iter);
	Type& front() {return head->item;}
	const Type &front() const {return head->item;}
	void push(const Type &);
	void pop();
	bool empty() const {return head == 0;}
	
private:
	QueueItem<Type> *head;
	QueueItem<Type> *tail;

	void destroy();
	void copy_elems(const Queue&);
	template <class Iter> void copy_elems(Iter, Iter);
};

#include "Queue.cpp"
#endif
queue.cpp

template <class Type>
std::ostream& operator<< (std::ostream &os, const Queue<Type> &q)
{
	os << "< ";
	QueueItem<Type> *p;
	for (p = q.head; p; p= p->next)
		os << p->item << " ";
	os << ">";
	return os;
}

template <class Type> void Queue<Type>::destroy()
{
	while(!empty())
		pop();
}

template <class Type> void Queue<Type>::pop()
{
	QueueItem<Type> *p = head;
	head = head->next;
	delete p;
}

template <class Type> void Queue<Type>::push(const Type &val)
{
	QueueItem<Type> *pt = new QueueItem<Type> (val);
	if(empty())
		head = tail = pt;
	else
	{
		tail->next = pt;
		tail = pt;
	}
}

template <class Type> void Queue<Type>::copy_elems(const Queue &orig)
{
	for (QueueItem<Type> *pt = orig.head; pt; pt = pt->next)
		push(pt->item);
}

template <class T> template <class Iter>
void Queue<T>::assign(Iter beg, Iter end)
{
	destroy();
	copy_elems(beg, end);
}

template <class Type> template <class Iter>
void Queue<Type>::copy_elems(Iter beg, Iter end)
{
	while( beg != end)
	{
		push(*beg);
		++beg;
	}
}




一,模板定义:

1. 定义函数模板:

模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参间以逗号分隔,模板形参表不能为空:

template <typename T> int compare(const T &v1, const T &v2)
{
  if (v1 < v2)  return -1;
  if (v2 < v1)  return 1;
  return 0;
}

模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参.非类型形参跟在类型说明符之后声明.

使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参.一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例.

实际上,编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参.

函数模板可以用与非模板函数一样的方式声明为inline.说明符放在模板形参表之后,返回类型之前,不能放在关键字template之前:

template <typename T> inline T  min(const T&, const T&);

2. 定义类模板:

例如:

template <class Type> class Queue{
public:
  Queue();
  Type &front();
  const Type &front() const;
  void push (const Type &);
  void pop();
  bool empty() const;
private:
 // ...
};

像函数形参一样,程序员为模板形参选择的名字没有本质含义.

可以给模板形参赋予的唯一含义是区别形参是类型形参还是非类型形参.

模板形参遵循常规名字屏蔽规则;

用作模板形参的名字不能在模板内部重用,即模板形参的名字只能在同一模板形参表中使用一次,但模板形参的名字可以在不同模板中重用:

template <class T> T cals(const T &a, const T &b)
{
  typedef double T;   // error
  T tmp = a;  
  // ...
}


对于模板,可以只声明而不定义.

同一模板的声明和定义中,模板形参的名字不必相同.

每个模板类型形参钱必须带上关键字class 或typename,每个非类型形参前面必须带上类型名字,省去关键字活类型说明符是错误的:

template <typename T, U> T calc(const T&, const U&);    // error

在函数模板形参表中,typename和class具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用,

除了定义数据成员或函数成员之外,类还可以定义类型成员,通过在成员名前加上关键字typename作为前缀,可以告诉编译器将成员当做类型:

template <class Parm, class U> Parm fcn(Parm* array, U value)
{
  typename Parm::size_type *p;  // declares p to be a pointer
}

在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定.对模板的非类型形参而言,求值结果相同的表达式将认为是等价的:

template < class T, size_t N> void array_init(T (¶m)[N])
{
  for(size_t i = 0; i != N; ++i)
    para[i] = 0;
}

int x[42];
double y[10];
array_init(x); // 实例化为array_init(int(&)[42])
array_init(y); // 实例化为array_init(double(&)[10])

通过将形参设为const引用,就可以允许使用不允许复制的类型.


编译模板时,编译器可能会在三个阶段中标识错误:

  第一阶段是编译模板定义本身时;

  第二阶段是在编译器见到模板的使用时;

  第三阶段是在实例化的时候,只有在这个时候可以发现类型相关的错误.


模板时一个蓝图,它本身不是类或函数.编译器用模板产生指定的类或函数的特定类型版本.产生模板的特定类型实例的过程称为实例化.

类模板在引用实际模板类类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化.


从函数实参确定模板实参的类型和值的过程叫做模板实参推断,规则是:

  1. 多个类型形参的实参必须完全匹配;

  2. 类型形参的实参接受2种受限转换:

      const转换: jieshou const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化;

      数组或函数到指针的转换: 如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换.

  3. 用普通类型定义的形参可以使用常规转换.

  4. 可以使用函数模板对函数指针进行初始化或赋值,这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本.


函数模板的显式实参:

    在某些情况下,有必要覆盖模板实参的推断机制,并显示指定为模板形参所用的类型或值.这在函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现.

    在返回类型中使用类型形参:

    

template <class T1, class T2, class T3>
T1 sum(T2, T3)
int i = 3;
int lng = 4L;
long val3 = sum<long>(i,lng); // ok, calls long sum(int, long)

显式模板实参从左至右与对应模板形参相匹配,第一个模板实参与第一个模板形参匹配,第二个实参与第二个形参匹配,以此类推.假设可以从函数形参推断,则只有结尾(最右边)形参的显式模板实参可以省略:

template <class T1, class T2, class T3>
T3 alternative_sum(T2, T1);

long val3 = alternative_sum<long>(i, lng); // error: can't infer initial template parameters
long val2 = alternative_sum<long, int, long>(i, lng); // ok

模板编译类型:

  1. 包含编译模型: 在包含编译模型中,编译器必须看到用到的所有模板的定义.一般而言,可以通过在声明函数模板或类模板的头文件中添加一条#include指示使定义可用,该#include引入了包含相关的定义的源文件(即在头文件末尾用#include指令将源文件包含进来). (经测试,VS2005使用此模型,需要注意,在头文件中#include了相应源文件后,源文件不应该再添加到工程中)

  2. 分别编译模型: 在分别编译模型中,编译器会为我们跟踪相关的模板定义.但是我们必须让编译器知道要记住的模板定义,可以使用export关键字来做这件事.

       一般在函数模板的定义中指明函数模板为导出的(在template之前使用export关键字),声明不必指定export.

      对类模板,类声明必须放在头文件中,头文件中的类定义体不应该使用关键字export,应该在类的实现文件中使用export.

export template<typename Type>
Type sum(Type t1, Type T2)
{
  ...
}

// header file
template <class Type> class Queue{...};

// Queue.cc
export template <class Type> class Queue;
#include "Queue.h"
// Queue成员函数定义.


通常,当使用类模板的名字的时候,必须制定模板形参.但在类本身的作用域内部,可以使用类模板的非限定名.

类模板成员函数的定义具有如下形式:

  1.  必须以关键字template开头,后接类的模板形参表;

  2.  必须指出它是哪个类的成员;

  3.  类名必须包含其模板形参.


类模板成员函数的实例化:

    像其他函数模板一样,需要使用类模板的成员函数时产生该成员的实例化.

    与其他函数模板不同的是,在实例化类模板成员函数的时候,编译器不执行模板实参推断,相反,类模板成员函数的模板形参由调用该函数的对象的类型确定.

    类模板的成员函数只有为程序所用才进行实例化.如果函数从未使用,则不会实例化该成员函数.

非类型模板的实参必须是编译时常量表达式


类模板中的友元声明:

在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体的友元关系:

  1. 普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数;

  2. 类模板或函数模板的友元声明,授予对友元所有实例的访问权;

  3. 只授予对类模板或函数模板的特定实例的访问权的友元关系.

// 1. 普通友元
template <class Type> class Bar{
  friend  class FooBar;
  friend void fcn();
};

// 2. 一般模板友元关系
template <class Type> class Bar{
  template <class T> friend class Fool;
  template <class T> friend void templ_fcn1(const T&);
};

// 3. 特定的模板友元关系
template <class T> class Foo2;
template <class T> void templ_fcn2(const T&);
template <class Type> class Bar{
  friend class Foo2<char*>;
  friend void templ_fcn3<char*>(char* const &);
};

// 下面形式的友元声明更为常见:
template <class T> class Foo3;
template <class T> void templ_fcn3(const T&);
template <class Type> class Bar{
  friend class Foo3(Type);
  friend void templ_fcn3<Type>(const Type &);
};


成员模板:

    任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员成为成员模板,成员模板不能为虚.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值