如果要求两个数较大值函数max(a, b),对于a,b的不同类型,都有相同的处理形式。
return a < b? b : a;
我们可以使用宏定义来编写一个max函数。
#define max(a,b) ((a)<(b)?(b):(a))
但这样有一个问题,就是没有类型检查。如果想要有类型检查,我们可以使用重载,为每一个类型都重载一个函数。但这样的问题是,重载函数的功能相同,并且,如果出现了新的类型,还需要增加代码。
要比较好的解决这个问题,可以使用C++中的模板。
模板是一种参数化的多态工具,所谓参数化的多态性,是指将程序所处理的对象的类型参数化,是一段程序代码可以用于处理多不同类型的对象。采用模板编程,可以为各种逻辑功能相同而数据类型不同的程序提供一种代码共享的机制。
函数模板
函数模板的声明形式如下。
template<模板形参表>
返回值类型 函数名(模板函数形参表){
//函数体
}
函数模板的定义以关键字template开头,<>中是函数模板的类型参数列表,其类型为class或typename,但由于class是类的关键字,所以一般用typename,如下面的定义。
Template<typename T1, typename T2>
这里定义了两个类型参数,T1和T2。其实仔细想想,其实模板和宏定义是很类似的,相当于定义了一个宏参数。
模板形参在模板中作为一种类型使用,可以用于函数的形参、函数返回值和函数的局部变量。每个模板形参在函数的形参列表中至少出现一次。模板参数名的作用域局限是整个函数。
函数模板为所用的函数提供唯一的一段函数代码,增强了函数设计的通用性。使用函数模板的方法是先说明函数模板,然后实例化成相应的模板进行调用执行。函数模板不是函数,不能被执行。置换代码中的类型参数得到模板函数——实例化。实例化后的模板函数是真正的函数,可以被执行。
模板被编译了两次,实例化之前,先检查模板代码本身,查看语法是否正确;在这里会发现语法错误,如遗漏分号等。实例化期间,检查模板代码,查看是否所有的调用有效。在这里会发现无效的调用,如该实例化类型不支持某些函数调用等。普通函数只需要声明,即可顺利编译,而模板的编译需要查看模板的定义,也就是说,模板函数的头文件中需要给出函数的实现。下面是一个例子。
#include <iostream>
#include <string.h>
using namespace std;
/* 非函数模板 */
const int& max(const int& a, const int& b){
cout<<"const int& max(const int& a, const int& b)"<<endl;
return a>b?a:b;
}
/* 函数模板 */
template<typename T>
const T& max(const T& a, const T& b){
cout<<"const T& max(const T& a, const T& b)"<<endl;
return a>b?a:b;
}
/* 函数模板特化 */
template<>
const char* const& max(const char* const& a, const char* const& b){
cout<<"const char* const& max(const char* const& a, const char* const& b)"<<endl;
return strcmp(a,b)<0?b:a;
}
/* 函数模板重载 */
template<typename T>
const T& max(const T& a, const T& b, const T& c){
return ::max(a,b) < c ?c: ::max(a,b);
}
int main(int argc, char const *argv[])
{
const char *s1 = "hello",*s2 = "world";
::max(1,2);
::max(1.0,2.0);
::max(s1,s2);
::max(1,2,3);
return 0;
}
这个例子中,类型是编译器推导的,但也可以显示指定。函数模板其实和普通函数是一样的,但因为是模板,所以可以兼容多种类型,但是可能有的类型的处理逻辑不一样,所以可以使用模板的特化,对这种类型进行特殊处理。其实不同这种方式也可以,因为如果重载为一个普通函数,会优先调用这个普通函数,而不会调用模板函数。
对于编译器决定调用哪个函数,我认为有一种就近原则,就是尽量少进行转化。比如,子类重定义了一个函数,那么子类会优先调用子类的方法,因为父类的方法比较远。如果有两个重载函数,一个的参数是int和double,一个的类型是double和double,如果使用int和int调用该函数,那么会调用前者,因为前者进行的转化较少。在这个例子中,使用int和int调用max函数,虽然可以通过模板转化,但是这样的转化较多,所以会调用普通的max函数。
模板类
类模板,即一个类中含有参数化的数据类型。类模板实际上是函数模板的推广,可以用相同的类模板来组建任意类型的对象集合。类模板的实例化,即用具体的数据类型替换模板的参数以得到具体的类(模板类)。下面是一个例子。
#include <iostream>
#include <vector>
using namespace std;
template<typename T, typename CONT = vector<T>>
class Stack{
public:
void push(T t){
cont_.push_back(t);
}
const T& top(){
return cont_.back();
}
void pop(){
cont_.pop_back();
}
private:
CONT cont_;
};
int main(int argc, char const *argv[])
{
Stack<int> st;
for(int i=0;i<10;i++){
st.push(i);
}
for(int i=0;i<10;i++){
cout<<st.top()<<" ";
st.pop();
}
cout<<endl;
return 0;
}
这个例子可以看出,模板可以有多个参数,并且可以是非类型参数,如int类型,并且参数都可以有默认值。值得注意的是,在没有进行参数替换的时候,我们是没法知道该类型是不是有push_back这些方法。所以,在替换的时候,编译器必须要知道类的定义才行,不然无法进行类型检查。
在模板类中,成员函数也可以是模板函数;如下面这个例子。
#include <iostream>
using namespace std;
template<typename T>
class A{
public:
A(T val):val_(val){}
T getVal() const{
return val_;
}
template<typename T2>
void assign(const A<T2> &other){
val_ = other.getVal();
}
private:
T val_;
};
int main(int argc, char const *argv[])
{
A<double> a(2.0);
A<int> b(1);
a.assign(b);
cout<<a.getVal()<<endl;
return 0;
}
模板的两个例子
第一个例子是关于单例模式的,在这个例子中,使用一个类来包装一个类,通过这个类对包装类的访问保证是单例的,这里的实现不是线程安全的。
#include <iostream>
#include <cstdlib>
using namespace std;
class A{
public:
A(){
cout<<" A()"<<endl;
}
~A(){
cout<<"~A()"<<endl;
}
void func(){
cout<<"A::func()"<<endl;
}
};
template<typename T>
class Singleton{
public:
static T& getInstance(){
init();
return *instance_;
}
private:
static T* instance_;
static void init(){
if(instance_ == NULL){
instance_ = new T;
atexit(destroy);
}
}
static void destroy(){
delete instance_;
}
Singleton(){}
Singleton(const Singleton &other);
Singleton& operator=(const Singleton& other);
};
template<typename T>
T* Singleton<T>::instance_ = NULL;
int main(int argc, char const *argv[])
{
Singleton<A>::getInstance().func();
Singleton<A>::getInstance().func();
return 0;
}
第二个例子是关于工程模式的,想要达到的效果是,通过一个字符串使用默认构造函数创建一个对象。这里其实就是利用模板中可以使用new创建一个参数可变的对象。然后把这个函数注册到一个全局的map中,下面是具体的代码。
#include <iostream>
#include <string>
#include <map>
using namespace std;
typedef void* (*CREATE_FUNC)();
class ObjectFactory{
public:
static void* createObject(const string& name){
map<string,CREATE_FUNC>::iterator it;
it = mapClass.find(name);
if(it == mapClass.end()){
return NULL;
}
return it->second();
}
static void registerObject(const string& name, CREATE_FUNC func){
mapClass[name] = func;
}
private:
static map<string,CREATE_FUNC> mapClass;
};
map<string,CREATE_FUNC> ObjectFactory::mapClass;
template<typename T>
class DelegatingClass{
public:
DelegatingClass(const string& name){
ObjectFactory::registerObject(name,newInstance);
}
private:
static void* newInstance(){
return new T;
}
};
#define REGISTER_CLASS(class_name) DelegatingClass<class_name> class##class_name(#class_name)
class Base{
public:
virtual void print() = 0;
};
class A:public Base{
public:
void print(){
cout<<"A::print()"<<endl;
}
};
class B:public Base{
public:
void print(){
cout<<"B::print()"<<endl;
}
};
class C:public Base{
public:
void print(){
cout<<"C::print()"<<endl;
}
};
REGISTER_CLASS(A);
REGISTER_CLASS(B);
REGISTER_CLASS(C);
int main(int argc, char const *argv[])
{
A* a = static_cast<A*>(ObjectFactory::createObject("A"));
B* b = static_cast<B*>(ObjectFactory::createObject("B"));
C* c = static_cast<C*>(ObjectFactory::createObject("C"));
a->print();
b->print();
c->print();
return 0;
}
网友评论