- 泛型程序设计:
在面向对象的程序设计中,允许将类中成员的类型设为一个可变的参数,使多个类变成一个类,这种程序设计机制称为泛型程序设计。泛型程序设计可以以独立于任何特定类型的方式编写代码,使用泛型程序时,必须提供具体的所操作的类型或值。 - 函数模板:
一组重载函数仅仅是参数的类型不一样,程序的逻辑完全一样,代码大致相同,类型不同,可以写成一个函数,称为函数模板,减少代码量。是实现类型的参数化(泛型化),即把函数中某些形式参数的类型设计成可变的参数,称为模板参数。编译器根据函数实际参数的类型确定模板参数的值,生成不同的模板参数。
定义形式:
注意:通常每个模板的形式参数都要在函数的形式参数表中至少出现一次,这样编译器才能通过函数调用确定模板参数的值。如果有些模板参数在函数的形式参数表中没有出现,编译器就无法推断出模板实际参数的类型,可以通过显式指定模板实参来解决。template<class T> T max(T a,T b){ return a > b ? a : b ; }
template<class T1, class T2, class T3> T3 calc(T1 x, T2 y){ return x+y; } //调用时 calc<int, char, char>(5, 'a'); //模板实例化 calc<int, char, int>(5, 'a');
类模板:
类中不确定的类型设计成一个模板参数,用模板参数代替类中的某些成员的类型。
定义形式:
类模板可以定义数据成员和成员函数,构造函数和析构函数,也可以重载运算符。template<模板形式参数表> class 类名(...);
成员函数的参数类型或返回值类型也可以是模板的形式参数,成员函数的某些局部变量的类型也可以是模板的形式参数。
- 类模板成员函数定义形式:
必须以template开头,后接模板形式参数表。
必须用作用域限定符 :: 说明其是哪个类的成员函数。
类名必须包含其模板形式参数。// 可指定下标范围的、支持任意类型的、安全的动态数组的定义 template <class T> // 模板参数T是数组元素的类型 class Array { int low; int high; T *storage; public: //根据low和high为数组分配空间。分配成功,返回值为true,否则返回值为false Array(int lh = 0, int rh = 0):low(lh),high(rh) { storage = new T [high - low + 1]; } //复制构造函数 Array(const Array &arr); //赋值运算符重载函数 Array &operator=(const Array & a); //下标运算符重载函数 T & operator[](int index); //回收数组空间 ~Array() { if (storage) delete [] storage; } }; // 类模板Array的成员函数的实现 // 复制构造函数 template <class T> Array<T>::Array(const Array<T> &arr) //函数名为Array { low =arr.low; high = arr.high; storage = new T [high - low + 1]; for (int i = 0; i < high -low + 1;++i) storage[i] = arr.storage[i]; } // 赋值运算符重载函数 template<class T> Array<T> &Array<T>::operator=(const Array<T> &other ) //函数名为operator= { if (this == &other) return *this; //防止自己复制自己 delete [] storage; //归还空间 low = other.low; high = other.high; storage = new T[high - low + 1]; //根据新的数组大小重新申请空间 for (int i=0; i <= high - low; ++i) //复制数组元素 storage[i] = other.storage[i]; return *this; } // 下标运算符重载函数 template<class T> T &Array<T>::operator[](int index) //函数名为operator[] { if(index < low || index > high) { cout<< "下标越界"; exit(-1); } return storage[index - low]; }
- 类模板实例化:
函数模板的实例化通常由编译器自动完成,编译器根据函数调用时的实际参数类型推断出模板参数的值,将模板参数的值代入函数模板生成一个真正可执行的模板函数。
对于类模板,编译器无法根据对象定义的过程确定模板参数的类型,因而需要用户明确指出模板形式参数的值,定义格式:
编译器首先将模板的实际参数值代入类模板,生成一个可真正使用的类,然后定义这个类的对象。类模板名<模板实际参数表> 对象表; Array<int> array1(20,30);
- 非类型参数和参数的默认值:
类型参数:模板参数都是用来表示一个尚未明确的类型,这些参数是类型参数。
非类型参数:模板的形式参数不一定都是类型,可以是整数型或实数型。
非类型模板实例化,非类型参数将用一个常量表达式作为实际参数。
模板参数和普通的函数参数一样,也可以指定默认值,可在类模板定义时指定默认值。// 将数组的下标范围作为模板的非类型参数 template <class T, int low, int high> class Array{ T storage[high - low + 1]; public: T & operator[](int index) ; //下标运算符重载函数 }; template <class T, int low, int high> T & Array<T, low, high>::operator[](int index) { if (index < low || index > high) { cout << "下标越界"; exit(-1); } return storage[index - low]; } Array<int, 10, 20>array;
这样就可以不指定模板的实参template<class T = int> class Array{...};
Array<> array
- 类模板友元:(两种友元)
普通友元:声明某个普通的类或全局函数为所定义的类模板的友元。
定义形式:
意味类B和函数f是类模板A所有的实例的友元,B的所有的成员函数和全局函数f可以访问类模板A的所有实例的私有成员。template<class type> class A{ friend class B; friend void f(); ..; };
模板的特定实例的友元:声明某个类模板或函数模板的特定实例是所定义类模板的友元。
将类模板B和函数模板f对应于模板参数为int时的那个实例,作为类模板A的所有实例的友元(如下)
将使用某一模板实参的类模板B和函数模板f的实例是使用同一模板参数的类模板A的特定实例的友元(如下)template<class T>class B; //类模板B的声明,B有一个模板参数 template<class T>void f(const T &); //函数模板f的声明,f有一个模板参数 template<class type> class A{ friend class B <int>; friend void f(const int &); ... };
当声明类模板B和函数模板f为类模板A的友元时,编译器必须知道有这样一个类模板和函数模板的存在,并且知道类模板B和函数模板f的原型,因此,必须在友元声明之前先声明B和f的存在。template<class T>class B; template<class T>void f(const T &); template<class type> class A{ friend class B <type>; friend void f(const type &); ... };
// 类模板的友元实例:重载输出运算符 template<class type> ostream &operator<<(ostream &os, const Array<type> &obj) { os << endl; for (int i=0; i < obj.high - obj.low + 1; ++i) os << obj.storage[i] << '\t'; return os; } // 类模板的友元实例:重载输出运算符 template <class T> class Array; //类模板Array的声明 template<class T> ostream &operator<<(ostream &os, const Array<T>&obj); //输出重载声明 //声明了函数模板operator<<的一个实参为T的实例是类模板Array的实参为T的实例的友元。 template <class T> class Array { friend ostream &operator<<(ostream &, const Array<T> &); private: int low; int high; T *storage; public: //根据low和high为数组分配空间。分配成功,返回值为true,否则返回值为false Array(int lh = 0, int rh = 0):low(lh),high(rh) { storage = new T [high - low + 1]; } //复制构造函数 Array(const Array &arr); //赋值运算符重载函数 Array &operator=(const Array & a); //下标运算符重载函数 T & operator[](int index) {return storage[index - low];} //回收数组空间 ~Array() {delete [] storage; } }; 对于Array的任何一个实例: Array <int> array(10, 20); 可以用 cout << array; 输出
- 类模板应用:
- 单链表类及结点类:
定义两个独立的类,由于链表操作经常需要访问结点的数据成员,因此,将同一模板参数的链表类设置为结点类的友元。
实现对任何类型的单链表的操作// 单链表模板类中的结点类和链表类的定义 template <class elemType> class linkList; template <class T> ostream &operator<<(ostream &, const linkList<T> &); template <class elemType> class Node ; template <class elemType> class Node { friend class linkList<elemType>; friend ostream &operator<<( ostream &, const linkList<elemType> &); private: elemType data; Node <elemType> *next; public: Node(const elemType &x, Node <elemType> *N = NULL) { data = x; next = N;} Node( ):next(NULL) {} ~Node() {} }; template <class elemType> class linkList { friend ostream &operator<<( ostream &, const linkList<elemType> &); protected: Node <elemType> *head; void makeEmpty(); // 清空链表 public: linkList() { head = new Node<elemType>; } ~linkList() {makeEmpty(); delete head;} void create(const elemType &flag); }; // 单链表类的成员函数及友元函数的定义 template <class elemType> void linkList<elemType>::makeEmpty() { Node <elemType> *p = head->next, *q; head->next=NULL; while (p != NULL) { q=p->next; delete p; p=q;} } template <class elemType> void linkList<elemType>::create(const elemType &flag) { elemType tmp; Node <elemType> *p, *q = head; cout << "请输入链表数据," << flag << "表示结束" << endl; while (true) { cin >> tmp; if (tmp == flag) break; p = new Node<elemType>(tmp); q->next = p; q = p; } } template <class T> ostream &operator<<(ostream &os, const linkList<T> &obj) { Node <T> *q = obj.head->next; os << endl; while (q != NULL){ os << q->data; q = q->next; } return os; }
linkList <int> intList; //实例化定义一个整型的单链表 intList.create(0); //输入链表中的元素值,直到输入0为止。 cout << intList; //输出链表的所有元素
- 栈的类模板:
类模板可以作为继承关系的基类,类模板的继承和普通的继承方法类似,只是在涉及基类时,都必须带上模板参数。
栈是特殊的线性表,插入和删除只能在表的一端进行,允许插入和删除的一端称为栈顶,另一端为栈底,常用操作为进栈(push)和出栈(pop)。
栈在单链表的基础上增加两个操作push和pop,push在单链表的表头插入一个元素,pop是删除单链表的表头元素。
为使Stack能访问Node的数据成员,必须将Stack设为Node的友元。// 在链表类的基础上派生一个栈类 template <class elemType> class Stack:public linkList<elemType> { public: void push(const elemType &data) { Node <elemType> *p = new Node<elemType>(data); p->next = head->next; head->next = p; } bool pop(elemType &data) //栈为空时返回false,出栈的值在data中 { Node <elemType> *p = head->next; if ( p == NULL) return false; head->next = p->next; data = p->data; delete p; return true; } };
- 编程规范:
继承和组合提供一种重用对象代码的方法
模板提供了重用源代码的方法,模板通过将类型作为参数,将处理不同类型数据的一组函数或类综合成一个函数或一个类,即函数模板和类模板,减轻代码量。
类模板的形式参数可以是普通类型,称为非类型参数,非类型参数的实际参数必须是编译时的常量。