类模板(一)

来源:互联网 发布:怎样优化淘宝店铺 编辑:程序博客网 时间:2024/06/04 18:42


在程序实现过程中,发现类模板的声明和定义必须放在同一个头文件中,如果分开将声明放在头文件Stack.h中,定义实现放在Stack.cpp文件中,则运行主程序会报错error LNK2019: 无法解析的外部符号 "public: __thiscall Stack<int>::Stack<int>(void)" (??0?$Stack@H@@QAE@XZ),该符号在函数 _main 中被引用


这种错误的解释:

当将类模板声明放在头文件Stack.h中,定义实现放在Stack.cpp文件中,最后在另外一个.cpp文件中使用这个模板,并且将模板声明头文件包含进这个文件。我们在主函数中执行intStack.push(7) ,程序运行后,链接器报错,如上。这个错误的原因在于:类模板成员函数push()的定义还没有被实例化。为了使模板真正得到实例化,编译器必须知道:应该实例化哪个定义以及要基于哪个模板实参来进行实例化。遗憾的是,这两部分信息位于分开编译的不同文件里面。因此,当我们的编译器看到push()调用,但还没有看到基于int实例化的函数定义的时候,它只假设在别处提供了这个定义,并产生一个指向该定义的引用(让链接器利用该引用来解决这个问题)。另一方面,当编译器处理文件Stack.cpp的时候,它并没有指出:编译器必须基于特定实参对所包含的模板定义进行实例化。

第一种方法:使用包含模型

       对于这个问题,我们通常是采取对待宏或内联函数的解决办法,我们把模板的定义也包含在声明模板的头文件里面,所以这里我们在同一个头文件中声明和定义类Stack<>。我们称模板的这种组织方式为包含模型。


1、类模板Stack的实现Stack.h文件

//这里我们在同一个头文件中声明和定义类Stack<>#include<stdio.h>#include<vector>#include<iostream>using namespace std;//类声明template<typename T>class Stack{private:vector<T> elements; //存储元素的容器public://Stack();//构造函数  这里不能加这个构造函数,而是使用默认构造函数,加了会报错error LNK2019: 无法解析的外部符号 "public: __thiscall Stack<int>::Stack<int>(void)" (??0?$Stack@H@@QAE@XZ),该符号在函数 _main 中被引用void push(T const&);//压入元素void pop();//弹出元素T top() const;//返回栈顶元素bool empty() const{           //返回栈是否为空return elements.empty();}//Stack(Stack<T> const&);//拷贝构造函数 ,当没有自己声明构造函数时,使用拷贝构造函数会报错error C2512: “Stack<T>”: 没有合适的默认构造函数可用Stack<T>& operator=(Stack<T> const&); //赋值运算符};//类成员函数的实现(也可以将下面成员函数作为内联函数,将它们定义于类声明里面,就像empty()的实现一样)template<typename T>void Stack<T>::push(T const& e_em){elements.push_back(e_em);//} /***因为当vector为空时,它的back方法(返回末端元素的值)和pop_back()方法会具有未加定义色行为,因此在pop()实现中我们需要先检查该stack是否为空。如果为空,就抛出out_of_range异常。同样在top()实现中,也这样判断。****/template<typename T>void Stack<T>::pop(){if (elements.empty()){throw out_of_range("Stack<T>::pop(): empty stack");}elements.pop_back();//删除最后一个元素}template<typename T>T Stack<T>::top() const{if(elements.empty()){throw out_of_range("Stack<T>::pop(): empty stack");}return elements.back();//返回最后一个元素的拷贝}

2、类模板的使用stacktest1.cpp

#include<stdio.h>#include<iostream>#include<stdlib.h>#include<vector>#include<string>#include"Stack.h"using namespace std;//类模板Stack的使用int main(){try{Stack<int>  intStack;  //元素类型为int的栈Stack<string>  stringStack;  //元素类型为string的栈//使用int栈intStack.push(7);cout<<intStack.top()<<endl;//使用string栈stringStack.push("hello");cout<<stringStack.top()<<endl;stringStack.pop();stringStack.pop();}catch (exception const & ex){cerr<<"Exception: "<<ex.what()<<endl;//return EXIT_FAILURE; //程序退出,且带有ERROR标记}system("pause");}


2、整合包含模型和显式实例化

为了让程序员能够根据实际情况,自由地选择包含模型或者显式实例化,我们可以把模板得定义和声明放在两个不同的头文中。通常的做法是在Stack.h文件中写模板声明,在另外一个头文件Stackdef.h中写模板定义,并在头文件Stackdef.h中要包含进Stack.h文件。如下,分开模板声明和模板定义

1)、Stack.h文件

#ifndef STACK_H#define STACK_H//这里我们在同一个头文件中声明类Stack<>#include<vector>#include<iostream>using namespace std;//类声明template<typename T>class Stack{private:vector<T> elements; //存储元素的容器public://Stack();//构造函数  这里不能加这个构造函数,而是使用默认构造函数,加了会报错error LNK2019: 无法解析的外部符号 "public: __thiscall Stack<int>::Stack<int>(void)" (??0?$Stack@H@@QAE@XZ),该符号在函数 _main 中被引用void push(T const&);//压入元素void pop();//弹出元素T top() const;//返回栈顶元素bool empty() const{           //返回栈是否为空return elements.empty();}//Stack(Stack<T> const&);//拷贝构造函数 ,当没有自己声明构造函数时,使用拷贝构造函数会报错error C2512: “Stack<T>”: 没有合适的默认构造函数可用Stack<T>& operator=(Stack<T> const&); //赋值运算符};#endif<span style="color:#3333ff;"></span>

2)、Stackdef.h中定义类模板

#ifndef STACKDEF_H#define STACKDEF_H#include"Stack.h"//包含进类声明的头文件#include<vector>#include<iostream>using namespace std;//类成员函数的实现(也可以将下面成员函数作为内联函数,将它们定义于类声明里面,就像empty()的实现一样)template<typename T>void Stack<T>::push(T const& e_em){elements.push_back(e_em);//} /***因为当vector为空时,它的back方法(返回末端元素的值)和pop_back()方法会具有未加定义色行为,因此在pop()实现中我们需要先检查该stack是否为空。如果为空,就抛出out_of_range异常。同样在top()实现中,也这样判断。****/template<typename T>void Stack<T>::pop(){if (elements.empty()){throw out_of_range("Stack<T>::pop(): empty stack");}elements.pop_back();//删除最后一个元素}template<typename T>T Stack<T>::top() const{if(elements.empty()){throw out_of_range("Stack<T>::pop(): empty stack");}return elements.back();//返回最后一个元素的拷贝}#endif


3)、之后只需要在主文件stacktest1.cpp中,包含入Stackdef.h文件,就可以使用类模板

#include<stdio.h>#include<iostream>#include<stdlib.h>#include<vector>#include<string>#include"Stackdef.h"using namespace std;//类模板Stack的使用int main(){try{Stack<int>  intStack;  //元素类型为int的栈Stack<string>  stringStack;  //元素类型为string的栈//使用int栈intStack.push(7);cout<<intStack.top()<<endl;//使用string栈stringStack.push("hello");cout<<stringStack.top()<<endl;stringStack.pop();stringStack.pop();}catch (exception const & ex){cerr<<"Exception: "<<ex.what()<<endl;//return EXIT_FAILURE; //程序退出,且带有ERROR标记}system("pause");}

另外如果我们希望实例化模板,我们就应该在主文件中包含进类声明Stack.h文件,然后提供一个含有所需要实例化指示符的c文件Stack_inst.cpp,这样即可。下面即是在拥有两个模板头文件的情况下,进行显式实例化。

主程序文件stacktest1.cpp

#include<stdio.h>#include<iostream>#include<stdlib.h>#include<vector>#include<string>#include"Stack.h"//应该包含类模板声明头文件using namespace std;//类模板Stack的使用int main(){try{Stack<int>  intStack;  //元素类型为int的栈Stack<string>  stringStack;  //元素类型为string的栈//使用int栈intStack.push(7);cout<<intStack.top()<<endl;//使用string栈stringStack.push("hello");cout<<stringStack.top()<<endl;stringStack.pop();stringStack.pop();}catch (exception const & ex){cerr<<"Exception: "<<ex.what()<<endl;//return EXIT_FAILURE; //程序退出,且带有ERROR标记}system("pause");}

显式实例化程序文件Stack_inst.cpp

#include"Stackdef.h"//包含类模板定义头文件#include<string>#include<iostream>using namespace std;//实例化类Stack<> for intsstemplate Stack<int>;//实例化some member function of Stack<> for stringstemplate void Stack<string>::push(string const&);template string Stack<string>::top() const;template void Stack<string>::pop();





0 0
原创粉丝点击