
版权声明:本文为博主原创文章,转载请注明出处http://blog.youkuaiyun.com/blues1021。
C++用模板来实现泛型编程,模板分为函数模板和类模板。
基本概念:泛型编程范式GP:模板也叫参数类型多态化。泛型在编译时期确定,相比面向对象的虚函数多态,能够有更高的效率。
泛型编程是从一个抽象层面描述一种类型的算法,不管容器类型是什么,是一种不同于OOP的角度来抽象具体算法。
C++0X目前对GP的支持的趋势来看,确实如此,auto/varadic templates这些特性的加入象征着C++ GP的形式正越来越转向一种更纯粹的泛性语法描述。
GP的一个主要的弱点就是它不是二进制可复用的,源码基本是公开的,因为编译时候才决定具体类型,决定了类型后就不能更改了不像OOP一样拥有关闭开放的原则。
本文主要对函数模板和类模板,在定义语法,模板内部使用泛型类型和非泛型类型,模板的声明和定义分离实现方法,外部客户调用泛型和非泛型参数类型的模板方法,这四个方面比较系统的总结了C++中泛型的使用经验,博客前面的文字有些凌乱建议想了解本文讲述的泛型使用经验的读者把代码拷贝下来,运行一遍分析代码的用意。
本文主要对函数模板和类模板,在定义语法,模板内部使用泛型类型和非泛型类型,模板的声明和定义分离实现方法,外部客户调用泛型和非泛型参数类型的模板方法,这四个方面比较系统的总结了C++中泛型的使用经验,博客前面的文字有些凌乱建议想了解本文讲述的泛型使用经验的读者把代码拷贝下来,运行一遍分析代码的用意。
1.1 模板的定义,用template和typename声明后,函数内直接使用;函数不支持template参数列表是空的,类重复具体定义时才支持。
1.2. 非泛型类型参数,直接使用就可以了,可以传入该类型的常量或者变量
关于形参:如果是类型形参,我们就知道该形参表示未知类型,如果是非类型形参,我们就知道它是一个未知值。
泛型类型定义后,函数内部直接使用即可,类型变量符合作用域规则,包括不可重定义或覆盖,可见作用域。非泛型类型直接使用。
将Template函数的声明和实现分离,声明在.h中,实现在.cpp中,其中.h需要包含.cpp;类模板中不能这样做,需要特殊处理。
需要分离的原因有:
真正原因:
1.简化条理化代码,实现声明和实现分离,高内聚低耦合,对外提供简单的接口,对内高内聚 。
1.简化条理化代码,实现声明和实现分离,高内聚低耦合,对外提供简单的接口,对内高内聚 。
几乎不会出现,STL都没有分离:
2.将模板类的声明与实现都放在.h中(在多个cpp中使用不同模板参数时可能会引起重复定义的编译错误)。
2.将模板类的声明与实现都放在.h中(在多个cpp中使用不同模板参数时可能会引起重复定义的编译错误)。
4.1 实参推演和类型匹配
实参推演是:模板函数不支持类型显式实例化声明的,直接用实参变量实例化调用就好。
类型匹配是:一种类型的只能是一种类型,不能两种类型传入一种类型的模板函数中,是类型引用别名的不能用实例值传入。
4.2 非泛型类型参数,直接使用就可以了,可以传入该类型的常量或者变量
1.1.定义类模板语法
例如:
template<class 形参名,class 形参名,…> class 类名
1.2.非泛型类型参数,直接使用就可以了,可以传入该类型的常量或者变量
关于形参:如果是类型形参,我们就知道该形参表示未知类型,如果是非类型形参,我们就知道它是一个未知值。
1.3 针对类模板声明附加特性,模板子类继承半具体化的模板父类,以及子类调用父类函数:
template< typename T, int N >
class TinyVector
{
public:
TinyVector();
TinyVector( const T& value );
template< typename T2 >
TinyVector( const TinyVector<T2,N>& v );
...
}
template< typename T >
class Point3D : public TinyVector<T,3>
{
public:
Point3D() {}
Point3D( T x, T y, T z )
{
(*this)(0) = x;
(*this)(1) = y;
(*this)(2) = z;
}
Point3D( const TinyVector<T,3>& v )
: TinyVector<T,3>(v)
{
}
const T& x() const { return this->at(0); }
const T& y() const { return this->at(1); }
const T& z() const { return this->at(2); }
T& x() { return (*this)(0); }
T& y() { return (*this)(1); }
T& z() { return (*this)(2); }
};
2)模板子类继承半具体化的模板父类
1).模板类的继承,继承还是和正常类一样的
例如:template< typename T, int N >
class TinyVector
{
public:
TinyVector();
TinyVector( const T& value );
template< typename T2 >
TinyVector( const TinyVector<T2,N>& v );
...
}
template< typename T >
class Point3D : public TinyVector<T,3>
{
public:
Point3D() {}
Point3D( T x, T y, T z )
{
(*this)(0) = x;
(*this)(1) = y;
(*this)(2) = z;
}
Point3D( const TinyVector<T,3>& v )
: TinyVector<T,3>(v)
{
}
const T& x() const { return this->at(0); }
const T& y() const { return this->at(1); }
const T& z() const { return this->at(2); }
T& x() { return (*this)(0); }
T& y() { return (*this)(1); }
T& z() { return (*this)(2); }
};
2)模板子类继承半具体化的模板父类
模板不能是空模板参数类型,但是声明了正常的声明了一个模板类后,如果要对这个模板类进行更加具体的限定那么可以重新定义这个模板的细节,这个时候模板参数类型可以为空,但是这个时候重定义的名称一定要相同,使用的模板参数也是要定义了的。
例如template<> class BinaryNumericTraits1会报错,template<> class BinaryNumericTraits<T1, double>也会报错,正确定义如下:
template< typename T1, typename T2 >
class BinaryNumericTraits
{
public:
typedef T1 OpResult;
};
template<>
class BinaryNumericTraits<int, double>
{
public:
typedef double OpResult;
};
template<>
class BinaryNumericTraits<double, int>
{
public:
typedef double OpResult;
};
3)子类调用父类函数方式
3)子类调用父类函数方式
// 继承中调用父类的方法
Array<T,2>::operator= ( Array<T,2>(value_) );
template <typename T, int N>
class Array
{
public:
Array()
{
for(int i = 0; i < N; i++)
{
data[i] = 0;
}
}
Array(T t1)
{
for(int i = 0; i < N; i++)
{
data[i] = t1;
}
}
int GetSize() {return N;}
T GetValue(int nIndex ){ return data[nIndex];}
private:
T data[N];
};
#include <iostream>
using namespace std;
template <typename T>
class Array2D: public Array<T,2>
{
public:
Array2D( int m, int n );
void Display()
{
cout<<"Array2D Value:";
for(int i = 0; i < GetSize(); i++)
{
cout<<GetValue(i)<<",";
}
cout<<endl;
}
};
template <typename T>
Array2D<T>::Array2D(int m, int n): Array<T,2>()
{
int value_ = m * n;
// 这个语法其实和 Array<T,2>(value_) 是一样的
Array<T,2>::operator= ( Array<T,2>(value_) );
}
调用端:
Array2D<int> arrayObj(1,2);
arrayObj.Display();
2.1使用类模板的类型声明,包括数据成员,和函数参数、函数返回值都可以直接使用泛型类型。类型变量符合作用域规则,包括不可重定义或覆盖,可见作用域。
非泛型类型直接使用。
非泛型类型直接使用。
2.2 在类声明外定义类函数(在CPP中也是可以的,但是包含情况就会发生变化),格式如下:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体}
// 例如
template< typename T, int N >
class TinyVector
{
public:
// 模板类里面的正常函数
TinyVector( const T& value );
// 模板类里面含有模板函数
template< typename T2 >
TinyVector<T,N>& operator= ( const TinyVector<T2,N>& v );
}
外部实现:
// 模板类的正常函数
template< typename T, int N >
TinyVector<T,N>::TinyVector( const T& value )
{
for ( int i = 0; i < N; ++i ) {
data_[i] = value;
}
}
// 模板类里面的模板函数
template< typename T, int N >
template< typename T2 >
TinyVector<T,N>& TinyVector<T,N>::operator= ( const TinyVector<T2,N>& v )
{
for ( int i = 0; i < N; ++i ) {
data_[i] = v(i);
}
return *this;
}
// 例如
template< typename T, int N >
class TinyVector
{
public:
// 模板类里面的正常函数
TinyVector( const T& value );
// 模板类里面含有模板函数
template< typename T2 >
TinyVector<T,N>& operator= ( const TinyVector<T2,N>& v );
}
外部实现:
// 模板类的正常函数
template< typename T, int N >
TinyVector<T,N>::TinyVector( const T& value )
{
for ( int i = 0; i < N; ++i ) {
data_[i] = value;
}
}
// 模板类里面的模板函数
template< typename T, int N >
template< typename T2 >
TinyVector<T,N>& TinyVector<T,N>::operator= ( const TinyVector<T2,N>& v )
{
for ( int i = 0; i < N; ++i ) {
data_[i] = v(i);
}
return *this;
}
模板不能分离的原因:C++标准明确表示,当一个模板不被用到的时侯它就不该被实例化出来,cpp中没有将模板类实例化,所以实际上.cpp编译出来的t.obj文件中关于模板实例类型的一行二进制代码,于是连接器就傻眼了,只好给出一个连接错误。所以这里将.cpp包含进来,其实还是保持模板的完整抽象,main中可以对完整抽象的模板实例化。
进行分离的方法:
类的声明必须在实现的前面,所以使用的时候包含cpp就可以把.h和.cpp中定义的内容一并包含了,其实是放在同一个文件中一个意思。
这种模板分离的方式缺点:如果这个模板类的cpp有非模板的定义,能够有效实例化,但是会导致重复包含定义而出错所以有些模板的实现和分离放到了.tcc格式或者.inl格式的文件中(这些格式的文件都是来自于txt文本的不能从cpp直接修改得到),在.h中直接包含进去,代码中就可以直接包含ClassTemplate.h了,避免包含.cpp奇怪的行为。
例如:
#include "Test.tcc"
4.1 模板类的使用,类对象显式类型声明不支持实参推演,类的成员函数要求实参推演和类型匹配。
4.2 非泛型类型形参,一般是简单类型,非泛型类型参数,直接使用就可以了,可以传入该类型的常量或者变量。