类模板(一)


在程序实现过程中,发现类模板的声明和定义必须放在同一个头文件中,如果分开将声明放在头文件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 intss
template Stack<int>;

//实例化some member function of Stack<> for strings
template void Stack<string>::push(string const&);
template string Stack<string>::top() const;
template void Stack<string>::pop();





  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值