C++模板学习3

今天看书的地方是关于类模板的特化,在实际的使用中,可以使用类模板实参来特化类模板,这种实现与函数模板的重载类似,

通过特化类模板,就可以优化基于某种特定类型的实现,

或者是客服某种特定类型在实例化类模板的时候所出现的不足。需要注意的是如果要特化一个类模板的话,还需要特化在该类模板中多定义的所有的成员函数。

如果仅仅是特化类模板中的某个成员函数的话, 这种做法并没有特化整个类中的模板,所以这种特化部分成员函数的做法病不能叫做特化整个模板类。


下面我们来看看应该如何特化一个模板类吧。

1,类模板的特化


首先,为了特化一个类模板,你必须在起始处声明一个template<> , 接下来声明用来特化类模板的类型。这个类型将会被用作是模板实参,

并且这个类型必须要在后面的代码中进行明确的指定。

template <>class Stack<std::string>{...}


在类模板的特化的时候,每个成员函数中的实现算子都必须重新定义为普通函数,原来在模板函数中的每个 T 
也是需要被进行类模板特化的类型来进行取代的:

void Stack<std::string>::push( std::string const & elem)
{
    elems.push_back( elem ) ;
//push the reference of elem with the type of  std::sting into the stack
}


下面我们来看使用std::string 特化 Stack<> 的完成代码实现:
代码总共分为两个部分,一个部分是实现stack1的头文件, 另一个部分是根据stack1中定义的方法来进行其中定义的template来进行模板进行特化。

//stack1.hpp
#include <vector>
#include <stdexcept>

template <typename T >
class Stack
{
private:
	std::vector<T> elems ;

public :
	void push ( T const & ) ;
	void pop ( ) ;
	T top () const ;
	bool empty () const 
	{
		return elems.empty() ;
	}
} ;

template <typename T >
void Stack<T>::push( T const &elem )
{
	elems.push_back( elem ) ;
}

template <typename T>
void Stack<T>::pop () 
{
	if ( elems.empty() )
	{
		throw std::out_of_range("Stack<>::pop() : empty stack ") ;
	}
	elems.pop_back() ;
}


template <typename T>
T Stack<T>::top () const 
{
	if ( elems.empty() )
	{
		throw std::out_of_range("Stack<>::top : empty stack ") ;
	}

	return elems.back() ;
	//method to return the last element pushed into the stack 
}

//stack2.hpp
#include <deque>
#include <string>
#include <stdexcept>
#include <iostream>
#include "stack1.hpp"

template <>
class Stack<std::string>
{
private :
	std::deque<std::string> elems ;
	//the capacity contains the elements
public :
	void push ( std::string const & ) ; //method to push element
	void pop( ) ;//method to pop elements
	std::string top () const ;
	bool empty () const
	{
		return elems.empty() ;
	}
} ;

void Stack<std::string >::push( std::string const & elem )
{
	elems.push_back( elem ) ;
}

void Stack<std::string>::pop() 
{
		if ( elems.empty() )
		{
		throw std::out_of_range("Stack<std::string>::pop() : empty stack ") ;
		}
		
		elems.pop_back() ;
}

std::string  Stack<std::string>::top () const
{
	if ( elems.empty () )
	{
		throw std::out_of_range("Stack<std::string>::top() : empty stack ") ;
	}
	return elems.back () ;
}

int main ()
{
	Stack<std::string> s;

	s.push("hello ") ;
	s.push("inuyasha") ;
	s.push("!") ;

	while (!s.empty())
	{
		std::string tempC = s.top() ;
		std::cout<<tempC<<std::endl ;
		s.pop() ;

	}
	system("pause") ;
	return 0 ;
}

在上面的例子里面,我们使用一个deque,而不是vector,来对stack内部的元素进行管理。我们使用这种用法并不在于获得某种好处,而只是为了说明:
 特化的实现可以和基本类模板(prinmary template)的实现是有所不同的。

<在这里需要说明一点的就是, 使用dequeu来代替vector 实现一个stack是有一定的好处的, 因为当删除元素的时候,
deque会对自己的内存空间进行释放,当需要重新分配内存的时候,deque 的元素并不需要被移动。
然而,这种好处对于string类型的变量是并不起任何作用的。正是因为这个原因,在基本的类模板中,
使用deque来作为内部使用的容器通常是一个很好的注意,但是如果想要作为特殊类型对象的内部容器的话,还是需要考虑一下的。>


2. 类模板的局部特化

在介绍了类模板的特化之后,下面来学习一下局部特化,类模板也是可以被局部特化的,即可以再特定的环境下指定类模板的特定的实现,
并且要求某一些模板参数仍然必须有用户自己来定义,
对于一个模板可以针对模板中的不同参数进行不同的特化,具体的操作请看下面的这个例子:

template <typename T1, typename T2>
class MyClass
{} ;

//局部特化: 两个模板参数具有相同的类型
template <typename T >
class MyClass<T , T>
{} ;

//局部特化: 第二个模板参数的类型是 int
template <typename T>
class MyClass< T, int >
{} ;

//局部特化: 两个模板参数都是指针类型的
template <typename T, typename T1>
class MyClass<T* , T1*>
{} ;

下面的这个例子展示了在各种的声明中会使用到哪一个模板:


MyClass<int , float> mif ; //this will use the MyClass< T1 , T2 > 
MyClass<float, float> mff ; //this will use the MyClass< T, T>
MyClass<float, int > mfi ; //this will call the MyClass<T , int >
MyClass<int* , float*> mp ; //this will call the MyClass<T*, T1*> 

如果有多个局部特化同等程度低匹配某个声明,那么就声称这个声明是具有二义性的:


MyClass <int , int > m ;
//call MyClass <T , int > and MyClass <T,T>
Myclass <int* , int*> mpp;
//call both MyClass <T*, T1*> and MyClass <T, T> 

为了在实际的编写代码的是偶解决这种二义性,可以另外提供一个指向相同类型的特化:

template<typename T>
class MyClass<T* , T* >
{...} ;


3.缺省模板实参

最后一个要介绍的就是,缺省模板实参

对于我们常使用的模板类,除了对其进行模板参数进行特化或是部分特化以外,还可以为其定义缺省值; 
这些定义的模板参数被定义为缺省值的话,那么这些值就被称为模板实参;
而且它们还可以引用之前的模板函数;
例如,在类Stack<>中,可以把用于管理元素的容器定义为第二个模板参数,并且使用std::vector<> 来作为它的缺省值,
下面我们来一起看一下实现缺省值模板实参的代码吧:

//basics/stack3.hpp
#include <vector>
#include <stdexcept>

template <typename T , typename CONT = std::vector<T> >
class Stack
{
private :
	CONT elems ;

public :
	void push( T const & ) ;
	void pop () ;
	T top () const ;
	bool empty () const 
	{
		return elems.empty () ;
	}
} ;

template <typename T , typename CONT >
void Stack<T,CONT>::push ( T const & elem )
{
	elems.push_back(elem ) ;
}

template <typename T , typename CONT >
void Stack<T, CONT>::pop ()
{
	if ( elems.empty() )
	{
		throw std::out_of_range("Stack<T,CONT>::pop() : empty stack" ) ;
	}

	elems.pop_back() ;
}

template < typename T , typename CONT >
T Stack<T, CONT>::top () const 
{
		if ( elems.empty() )
		{
			throw std::out_of_range("Stack<T, CONT>::top() : empty stack ") ;
		}

		return elems.back () ;
}


从上面的代码中可以看到, 我们的类模板含有两个模板参数,因此每个成员函数的定义都必须具有这两个参数:

template <typename T , typename CONT >
void Stack<T,CONT>::push ( T const & elem )
{
	elems.push_back(elem ) ;
}

这种声明模板的方式和前面的声明调用方法是一样的,不过这个类型的模板可以允许你通过模板参数指定运行对象实体类型的同时,
还可以执行在程序里面用来更改底层的用来存放Stack 的数据结构,
即可以在Stack数据结构的定义中,可以通过此种方式来指定存放Stack中底层数据的容器类型。 具体的调用方法可以通过下面的代码来判断出来:

//博客中的程序均已经调试成功,并显示出正确的结果
//basics/stack3.hpp
#include <vector>
#include <stdexcept>
#include <iostream>
#include <deque>
#include <cstdlib>

template <typename T , typename CONT = std::vector<T> >
class Stack
{
private :
	CONT elems ;

public :
	void push( T const & ) ;
	void pop () ;
	T top () const ;
	bool empty () const 
	{
		return elems.empty () ;
	}
} ;

template <typename T , typename CONT >
void Stack<T,CONT>::push ( T const & elem )
{
	elems.push_back(elem ) ;
}

template <typename T , typename CONT >
void Stack<T, CONT>::pop ()
{
	if ( elems.empty() )
	{
		throw std::out_of_range("Stack<T,CONT>::pop() : empty stack" ) ;
	}

	elems.pop_back() ;
}

template < typename T , typename CONT >
T Stack<T, CONT>::top () const 
{
		if ( elems.empty() )
		{
			throw std::out_of_range("Stack<T, CONT>::top() : empty stack ") ;
		}

		return elems.back () ;
}

int main ()
{
		try
		{
			Stack<int> intStack ;

			//double stack ,managed by the std::deque
			Stack <double , std::deque<double> > dblStack ;

			intStack.push( 7 ) ;
			std::cout<<intStack.top() <<std::endl ;
			intStack.pop() ;

			//usages of the double stack
			dblStack.push( 42.42 ) ;
			std::cout<<dblStack.top() <<std::endl ;

			dblStack.pop() ;

			Stack<char , std::vector<char> > charStack ;

			charStack.push('k') ;
			charStack.push('o') ;
			charStack.push('k' ) ;
			charStack.push('i') ;
			charStack.push('a') ;

			while ( !charStack.empty() )
			{
			
				std::cout<<charStack.top() <<std::endl ;
				charStack.pop() ;
			}
		}
		catch ( std::exception const & ex )
		{
			std::cerr<< "Exception :" <<ex.what() << std::endl ;
			return EXIT_FAILURE ;
		}

		system("pause") ;

		return 0 ;
}


4.小节

最后,对开始学习类模板中的知识点进行总结:

类模板是具有一下性质的类: 在类的实现中,可以有一个或是多个类型还没有被指定,这些类型的指定根据使用者在编写代码中进行指定,并且具体的绑定时期是在程序运行时动态绑定的。

为了使用类模板,可以传入某个具体类型来作为模板实参; 然后编译期将会基于该类型来实例化对应类模板所指定的类实例

可以使用某种特定类型特化类模板

可以使用某种特定的类型来局部化类模板

可以为类模板的参数指定缺省值,并且这些值还可以引用之前的模板参数。


参考书目《C++ Template 中文版》
今天先到这里,今天开始做高中数学题了,希望自己的思维能灵光一些(笑) ~
快要分布式期末考试了,希望一切顺利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值