c++模板

假设我们有如下 比较 Fun

bool IsEqual (const int& left , const int& right)  
{
return left == right;

此时我们比较 int 值是否相等->函数调用->    isEqual( 1, 2 );

但如果我们想比较 两个 string 对象是否相等, 则需要重新写一个Fun:

bool IsEqual (const string& left , const string& right)  
{
return left == right;

string s1 = "yuanyuanyuan";

string s2 = "zhaobaba";

->isEqual( s1, s2 );


这样我们每比较一种类型,就需要重新写一个比较函数,做了大量的重复工作。

c++为了提高代码复用性,提出了模板函数概念:

下面是一个 比较 模板函数框架:

template<typename/*or class*/ T>
bool IsEqual( const T& left , const T& right )//传引用也是为了避免生成临时对象 or 临时变量  万一参数为string对象,则会拷贝构造生成临时对象,这里传引用是为了节省空间
{
	return left == right;//如果不是内置类型而是自己定义的类型, 则必须实现即重载 operator==( )函数
} 
T代表我们传参数时传入的类型, 我们只需要在模板函数中将参数写为T, 具体函数调用时,编译器会通过实参推演形参类型

void test1( )
{
	string s1( "s1" ), s2( "s2" );

	cout << IsEqual( s1, s2 ) << endl;			//调用不同Fun  T分别为 string类型 与 int类型(编译器去做推演)  模板函数的实例化(编译器根据类型自动生成相应函数)
	cout << IsEqual( 1,1 ) << endl;				//通过实参推演形参类型
	//他们通过编译器实例化后是 重载函数 (模板实例化后生成代码(他们参数类型不同)所以重载)
}

模板函数   不同类型  T   是调用不同函数.


如果有模板函数和具体类型函数,优先使用有参数的函数(非模板),没有才使用模板函数

例:

bool IsEqual (const int& left , const int& right)//如IsEqual( 1, 2 )优先调用它,而不是下面的Fun( )
{
	return left == right;
} 

template <typename T>
bool IsEqual (const T& left , const T& right )
{
	return left == right;
}


模板参数匹配及显示实例化:


如第一种 只有一个模板参数T时  传  int 与 double 时,    不匹配   ->  isEqual( 1, 1.2 );

template <typename T>
bool IsEqual (const T& left , const T& right )
{
	return left == right;
} 

void test1 ()
{
	cout<<IsEqual (1,1)<<endl;
	cout<<IsEqual(1,1.2)<<endl; // 模板参数不匹配
	//下面是一种解决办法:
	//templat<class T1, class T2>   ( const T1& left , const T2& right )  
	//万一 left 传 int, right 传 double 第一种会出错, 此时必须这样定义( 第一种解决办法,另一种方法是显示实例化 )
	cout<<IsEqual<int>(给定类型  编译器不会推演,直接生成该类型代码)(1,1.2)<< endl; // 显示实例化(第二种办法)(T规定为int)
	cout<<IsEqual<double>(1,1.2)<< endl; // 显示实例化
}
没有对象实例化时,模板函数编译器不生成响应代码 , 证明:

template <typename T>
bool isequal (const t& left , const t& right )
{
	return left == right						//这没有“;”时  编译通过, 并没有错,因为没生成响应代码
} 

不调用时:函数内部不进行语法检查
但 函数外部 会进行格式检查  如 bool IsEqual ( const T& x, const T& y   少个括号  编译时还是会报错的

模板函数,把属于我们的工作,从实参推演形参,交给编译器去做


说完了模板函数,我们来说说模板类。 同样的问题不只存在于模板函数中,模板类中也存在.

如 类中数据成员的 类型.  

但模板类不能自己推演, 不能根据你传的参数推演T的类型,必须显示实例化!

需要注意的是模板类 类型为ClassName<T>, 类名为ClassName

但普通类, 类名和类类型相同

构造函数还有拷贝构造函数名字和类名相同

But拷贝构造Fun()参数(这个类类型必须加上类型T):

ClassName( const ClassName<T>& s );   经测试有无<T> 无影响


.h中声明     

声明定义分离   标准!类中只有声明
.cpp中定义


类外面定义声明在类中的函数时:

类外定义时:
template<class T> //再写一次  模板
void SeqList<T>::Print( ) //然后注意,这里的返回类型 为   SeqList<T>    类类型需要加<T>
{
...;
}



是T就加引用, 万一T是string , 深拷贝代价太大, 不改变就加 const ->const T&

拷贝构造参数不给引用会循环递归!  想想因为->拷贝构造未完成,所以会一直构造参数,递归死循环。  必须传引用

调试 时 栈溢出错误, 一般都是(递归)死循环

存在才赋值(赋值运算符重载),  不存在就拷贝构造.  s1 = s2  s1,s2均存在属于第一种情况。 string s2 = s1.  s2不存在属于第二种情况。


有了类模板参数后,我们就可以实现容器适配器:


队列:队尾进,对头出,插入在队尾
栈:栈顶进,   栈底->栈顶


适配器模式实现一个队列:

使用  List 实现容器适配器:

template<class T, class Container>
class Queue
{
public:
	void Push( const T& x );
	{
		_con.PushBack( x );
	}

	void Pop( )
	{
		_con.PopFront( );
	}

	T& Front( )									//根据要实现的函数功能(此处为队列),再决定在里面调用容器(此处为List)的什么函数
	{
		return _con.Front( );
	}

	T& Back( )									//因为出了作用域还存在!  所以T&
	{
		return _con.Back( );
	}

	size_t Size( )
	{
		return _con.Size( );
	}

	bool IsEmpty( )
	{
		return _con.IsEmpty( );
	}
protected:
	Container _con;
};

void TestQueue( )
{
	Queue<int, List<int>> q;			//我们实现一个 List 容器(类模板List), 并且List中 有声明我们使用的Fun()

	q.Push( 1 );

	while (!q.IsEmpty( ))
	{
		cout << q.Front( ) << " ";

		q.Pop( );
	}
}


template<class T, class Container = List<T>>	//Stack<int> s 这样也可以   模板的缺省参数
class Stack
{
public:
	void Push( const T& x )
	{
		_con.PushBack( x );
	}

	T& Top( )									//因为栈后进先出,所以Top是最后进来的, 所以是返回Back( ).
	{
		return _con.Back( );
	}

protected:
	Container _con;
};

void TestStack( )								//若是链表,删除时    释放空间,插入又开空间。 不停释放开辟不好,但顺序表不需要,   原空间反复利用,  它删除只是 --size
{
	Stack<int, SeqList<int>> s;

	s.Push( 1 );

	while ( !s.IsEmpty( ) )
	{
		cout << s.Top( ) << " ";
		s.Pop( );
	}
}

模板的模板参数:接上面,若这样传参数,Stack<int, SeqList<char>> s;  也就是这个 char 传错 会存在隐患。

为了避免错误,我们进行改进:template<class T, template<class(不用传参数,和T类型一样)>class Container = SeqList(缺省的模板的模板参数)>  (传一个类名,而不是传一个类型)。  避免传char等  出错。
例: Stack<int, List> s;




const size_t N = 100;


template<class T>
class SeqList
{
protected:
T _a[N];
size_t size;
};

非类型模板参数:


现在生成对象,   _a大小只能是200。  不管定义几个。  为了解决这个问题 -> 非类型模板参数。 N是一个常量,可以给缺省参数  它也可以做模板函数的非类型模板参数。


template<class T(类型模板参数),size_t N = 200>


SeqList<int, 200> s1;
SeqList<double, 2000> s2;


浮点数和类对象不允许做非类型模板参数


模板的特化:

1.全特化::

template <typename T>
class SeqList
{ 
public :
	SeqList();
	~ SeqList();
private :
	int _size ;
	int _capacity ;
	T* _data ;
};

template<typename T>
SeqList <T>:: SeqList()
	: _size(0)
	, _capacity(10)
	, _data(new T[ _capacity])
{
	cout<<"SeqList<T>" <<endl;
}

template<typename T>
SeqList <T>::~ SeqList()
{
	delete[] _data ;
}


template <>
class SeqList <int>
{ 
public :
	SeqList(int capacity);
	~ SeqList();
private :
	int _size ;
	int _capacity ;
	int* _data ;
};


 特化后定义成员函数不再需要模板形参

SeqList <int>:: SeqList(int capacity)
	: _size(0)
	, _capacity(capacity )
	, _data(new int[ _capacity])
{
	cout<<"SeqList<int>" <<endl;
} 

// 特化后定义成员函数不再需要模板形参
SeqList <int>::~ SeqList()
{
	delete[] _data ;
} 

void test1 ()
{
	SeqList<double > sl2;
	SeqList<int > sl1(2);//优先调用特化部分(调用才生成代码,没有特化的,才是T)
}

template <typename T1, typename T2>
class Data
{ 
public :
	Data();
private :
	T1 _d1 ;
	T2 _d2 ;
};

template <typename T1, typename T2>
Data<T1 , T2>:: Data()
{
	cout<<"Data<T1, T2>" <<endl;
}
局部特化第二个参数:

template <typename T1>
class Data <T1, int>
{
public :
	Data();	
private :
	T1 _d1 ;
	int _d2 ;
};

template <typename T1>
Data<T1 , int>:: Data()
{
	cout<<"Data<T1, int>" <<endl;
} 


ps:下面的例子可以看出,偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
 局部特化两个参数为指针类型

template <typename T1, typename T2>
class Data <T1*, T2*>
{ 
public :
	Data();
private :
	T1 _d1 ;
	T2 _d2 ;
	T1* _d3 ;
	T2* _d4 ;
};

template <typename T1, typename T2>
Data<T1 *, T2*>:: Data()
{
	cout<<"Data<T1*, T2*>" <<endl;
}

局部特化两个参数为引用:

template <typename T1, typename T2>
class Data <T1&, T2&>
{ 
public :
	Data(const T1& d1, const T2& d2);
private :
	const T1& _d1;
	const T2& _d2;
	T1* _d3 ;
	T2* _d4 ;
};

template <typename T1, typename T2>
Data<T1&, T2&>:: Data(const T1& d1, const T2& d2)
	: _d1(d1 )
	, _d2(d2 )
{
	cout<<"Data<T1&, T2&>" <<endl;
} 

void test2 ()
{
	Data<double , int> d1;
	Data<int , double> d2;
	Data<int*, int*> d3;
	Data<int&, int&> d4(1, 2);
} 
模板的全特化和偏特化都是在已定义的模板基础之上,不能单独存在。


template <typename T1>			//如果都是具体类型  则 template<>里面啥都不用写,因为不是模板参数,都是具体类型
Date<T1, int>:: Date( )			//第二个参数特化为 int 类型
{
	;
}

template <typename T1, typename T2>
class Data<T1*, T2*>
调用:	Data<int*, double*>

template <typename T1, typename T2>
class Data<T1, T2*>
调用:	Data<int, double*>
1.全特化:template<class T>
	  class Name

2.偏特化:template<>
	  class Name<int>

	  template<class T>
	  class Name<T*>

1.进一步条件限制模板参数
2.多个模板参数时,特化部分参数

编译错误:语法
链接错误:找不到具体定义它地方


模板的分离编译:声明,定义分离

ClassName.h声明
ClassName.cpp实现(这样则链接错误)
Test.cpp测试

template<class T>
void Data<T>::F( )
{}

非模板类是可以分离编译的

模板不支持分离编译:

分离编译后,找不到定义!找不到cpp中的定义,因为大家分开编译(main cpp) 所以, cpp中模板未实例化,未生产相应代码。所以链接错误!
模板实例化才生成具体代码,才语法检查(但并不是不实例化时什么语法都不检查!)


main.cpp
Data.h
Data.cpp


预处理(预编译)(展开头文件,替换宏,去注释,条件编译,处理行号文件名函数名(__FILE__(也是一个宏)等))->main.i 和 Data.i
编译(检查语法错误,)->main.s  汇编代码   一句代码对应多句汇编代码 ++i  (mov  eax, i) -> (add eax) -> (mov  i, eax)  cpu不能识别汇编代码  main.s  Data.s
汇编   汇编代码->二进制机器码(这是一个过程)main.o  Data.o 符号表(函数名+地址  F:0x1122)
链接 ->.exe(Windows)     a.out(Linux)   可执行文件

函数变成指令,第一句指令即Fun 地址

分离编译->链接错误(没有找到定义->函数的地址)


非模板类 .cpp .h
模板类 .hpp(声明定义放一起)


.hpp:

//分离编译

template<class T>
class Data
{
public:
	void F( );
protected:
	T _d;
};

template<class T>
void Data<T>/*类型*/::F( )
{
	;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值