C++大学教程之模板初探
重载,通常是对不同数据类型进行相同或相似的操作。如果如果是对不同数据类型进行相同操作,那么使用函数模板将会更加简洁。(注意:重载只是具有相同的函数名字罢了,完全可以在重载的函数中进行完全不同的操作。而重载必须是完全相同的操作)。在编写程序过程中,只需要提供函数模板的定义,编译器会通过模板函数调用过程中提供的实参来产生不同的目标代码函数(函数模板特化)。
模板函数的定义
template<typename T> //在实现每个模板函数是必须在前面加上template<>关键字 //typename 也可以改成class,写成template<class T>是完全一样的 //T在这里代表一个数据类型,也完全可以使用其他的符号来代替 //在<>里面可以有多个形参类别,之间用逗号隔开。比如 //template<typename T, typename M, typename A>..等等。 T ADD(const T &left, const T &right ){
return left + right;//这里T所代表的数据类型必须要支持+运算。
}
所有的函数模板定义都是以template<>开始,后面跟函数的定义。注意是每个函数之前都必须有,而不能两个函数共用一个template<>定义。如下面的代码会产生编译错误:error: 'T' does not name a type
template<typename T> T ADD(const T& t1, const T& t2){ return t1 - t2; } //SUB函数省略了template<typename T>,与ADD函数共用,编译错误 T SUB(const T &t1, const T &t2){ return t1 - t2; }
//error: 'T' does not name a type
函数模板的重载与与函数无关,只与template<>里面的列表有关,如下所示,代码编译没问题。编译器具体选择哪个代码我不知道,还需进一步研究
template<typename T> void ADD(T t){ cout << "template<typename T>" << endl; } template<typename T, typename M> void ADD(T t){//同上一个函数的代码定义完全一样 cout << "template<typename T, typename M>" << endl; } int main(){ ADD(0); return 0; } //程序输出:template<typename T>
类模板
我们也可以定义一个类模板,以Stack类为例:
#ifndef STACK_H #define STACK_H
using namespace std; template<typename T> class Stack{ struct Node{//我们采用队列的方式来存储数据 T t; Node *next; Node(const T& t1):t(t1),next(nullptr){} }; public: Stack();//构造函数 ~Stack();//析构函数 bool push(const T&);//入栈操作,成功返回true,失败返回false bool pop();//出栈操作,同样成功返回true,失败返回false const T& top()const;//获取栈顶数据,成功返回栈顶数据的引用,失败的话现在不好处理,我们就直接退出 bool empty()const{return s_size == 0;}//判断栈是否为空 unsigned get_size()const{return s_size;}//返回栈大小 private: unsigned int s_size;//记录栈的大小 Node *head;//表头指针,我们对表的操作都在表头进行 }; template<typename T> Stack<T>::Stack():s_size(0),head(nullptr){ //默认构造函数 } template<typename T> Stack<T>::~Stack(){//析构函数 } template<typename T> bool Stack<T>::push(const T& t){//入栈操作 Node *pn = new Node(t);//申请节点 if( nullptr == pn ) return false;//申请空间失败 pn->next = head; //入栈和出栈都在对头操作。 head = pn;//head指向新的表头 s_size++;//栈空间增加 return true;//插入成功 } template<typename T> bool Stack<T>::pop(){//出栈 Node *pn = head;//保存表头 if( nullptr == pn )//队为空,直接返回失败 return false; pn = pn->next;//队不为空,指向下一节点 delete head;//删除头节点 head = pn;//头节点指向新的头节点 s_size--;//容量减少 return true;//出栈成功 } template<typename T> const T& Stack<T>::top()const{//返回栈顶数据 if(nullptr == head){ //空栈,我们直接退出 cout << "栈为空" <<endl ; exit(0); } return head->t;//返回数据的引用 } #endif // STACK_H
接下是在主函数中使用我们定义的模板类
int main(){ Stack<string> s; //Stack模板类对象,存储的数据为string for(int i= 0; i < 5; i++){ string str; cin >> str; s.push(str);// } cout<< endl; for(int j = 0; j < 10; j++){//我们这里让它出栈10次,而入栈才5次 cout << s.top() << endl;//读取栈顶数据 //当第6次读取的时候栈已经是空的了。 s.pop(); } return 0; }
测试结果如下所示:
注意:在编写模板类的时候,我同定义普通类一样将模板类定义和它的实现分开在不同的文件中,导致编译错误;其次在类外实现成员函数的时候,同一般模板函数定义一样,每个成员函数的实现之前需要加上template<>关键字;和一般类的成员函数实现时不同,使用Stack<T>,其中,T是在template<>中指定的:返回值 类名<T>::成员函数名(函数参数){}。
模板类的操作符重载和友元函数
模板类的运算符重载同普通的类的运算符重载一样,在模板类直接实现重载操作。如下:
template<typename T> class A{ public: A(const T &t1):t(t1){}//构造函数 A operator+(const A& a){//重载+运算 return t + a.t; } T t;//成员变量 }; int main(){ A<int> a(10), b(20);// cout << (a+b).t << endl; return 0; }
也可以在类外定义:
template<typename T> class A{ public: A(const T &t1):t(t1){}//构造函数 A operator+(const A& a); T t;//成员变量 }; template<typename T> A<T> A<T>::operator+(const A<T>& a){//重载+运算 return t + a.t; }
而友元函数不是模板类的成员函数,因此要在模板类的声明时候添加额外添加template<>关键字
template<typename T> class A{//模板类 template<typename M>//这里要添加 friend istream& operator>>(istream&, A<M>&);//友元函数声明 template<typename M>//这里同样要添加 friend ostream& operator<<(ostream&, const A<M>&);//友元函数声明 public: A(const T &t1):t(t1){}//构造函数 T t;//成员变量 }; //接下来友元函数的实现同其他的模板函数一样 template<typename T> istream& operator>>(istream& in, A<T>& a){ in >> a.t; return in; } template<typename T> ostream& operator<<(ostream& out, const A<T> &a){ out << a.t ; return out; } int main(){//主函数 A<int> a(10), b(10); cout << a << ' ' << b << endl; cin >> a >> b; cout << a << ' ' << b << endl; return 0; }