C++模板解析

1 篇文章 0 订阅
1 篇文章 0 订阅

要使参数类型不同的函数实现相同的功能,我们可以选择函数重载的方式,但函数重载大部分代码是相同的,每多一个类型就得多实现一次函数重载,也就造成了编程效率的损失,我们可以通过泛型编程来解决这个问题。
泛型编程:编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础。
模板分为函数模板和模板类。
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
模板类也是模板,必须以关键字template开头,后接模板形参表。
函数模板的格式:

template<typename Param1, typename Param2,...,class Paramn>
返回值类型 函数名(参数列表)
{
函数体
}

其中typename和class是关键字,class可以用typename 关键字代替,在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参不能为空。
模板函数也可以定义为inline函数,但inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前。

template<typename T>
inline T Add(const T _left, const T _right)
{
return (_left + _right);
}

编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。
模板被编译了两次:
1.实例化之前,检查模板代码本身,查看是否出现语法错误,如:遗漏分号。
2.在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用。

template<typename T>
T Add(T left, T right)
{
    return left + right;
}
int main()
{
    cout << Add(10, 20) << endl;
    cout << Add(10.0, 20.0) << endl;
    cout << Add(10, (int)20.0) << endl;
    cout << Add<int>(10, 20.0) << endl;
    return 0;
}

实参推演:从函数实参确定模板形参类型和值的过程称为模板实参推断多个类型形参的实参必须完全匹配。
在上面的四个输出中,第1,3,4均调用的是int型函数,但理由不同,第1个输出本身2个参数都是int型,匹配int型函数,第3个输出2个参数本身一个为int型,一个为double型,但double型进行了强制类型转换转换为int型,第4个是将参数类型全部强制转换为int型,而第2个因2个参数均为double型,调用double型函数。第1,2种输出调用的函数与本身参数类型完全匹配,称为隐式实例化,而第3,4种输出调用的函数是本身参数类型经过强制转换后的类型,称为显式实例化。

int Add(int left, int right)
{
    return left + right;
}
double Add(double left, double right)
{
    return left + right;
}

编译器只会执行两种转换:
1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。

函数模板有两种类型参数:模板参数和调用参数
模板形参的名字在同一模板形参列表中只能使用一次

template<typename T, typename T>
//如这里使用了两次T就是错误的,正确用法为只保留其中一个
T Add(T left, T right)
{
    return left + right;
}

所有模板形参前面必须加上class或者typename关键字修饰

template<typename T1T2>
//如这里要定义两种不同的类型T1T2,每个类型前面都必须加上class或者typename关键字修饰
T Add(T1 left, T2 right)
{
    return left + right;
}

注意:在函数模板的内部不能指定缺省的模板实参。

模板形参说明:
1、模板形参表使用<>括起来。
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同。
3、定义模板函数时模板形参表不能为空。
4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后。
5、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型。使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换。
6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。

//模板函数的重载:
int Max(const int& left, const int & right)
{
return left>right? left:right;
}
template<typename T>
T Max(const T& left, const T& right)
{
return left>right? left:right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{
return Max(Max(a, b), c);
};
int main()
{
Max(10, 20, 30);
Max<>(10, 20);
Max(10, 20);
Max(10, 20.12);
Max<int>(10.0, 20.0);
Max(10.0, 20.0);
return 0;
}

注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
说明:
1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

模板函数特化:
有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事情。这种时候就需要用到函数模板特化。

模板函数特化形式如下:
template<>
返回值 函数名<Type>(参数列表)
{
 函数体
}
//如比较P1,P2指针所指向值的大小写法为
template<>
int Compare<const char*>(const char* const p1,const char* const p2)
{
  return strcpy(p1,p2);
}

注意:1.在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例。
2.特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然后使用该特化版本的每个源文件包含该头格式。

模板类格式:
template <class 形参名1, class 形参名2, ...class 形参名n>
class 类名
{ ... };
//用模板类定义顺序表
template<typename T>
class SeqList
{
private :
T* _data ;
int _size ;
int _capacity ;
};
// 以模板类方式实现动态顺序表
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])
{}
template <typename T>
SeqList <T>::~ SeqList()
{
delete [] _data ;
}
void test1()
{
SeqList<int > sl1;
SeqList<double > sl2;
}
//模板类的实例化
//只要有一种不同的类型,编译器就会实例化出一个对应的类。
//SeqList<int> sl1;
//SeqList<double> sl2;
//当定义上述两种类型的顺序表时,编译器会使用int和double分别代替模/形参,重新编写SeqList类,最后创建名为SeqList<int>和SeqList<double>的类。
//模板类全特化:
template <typename T>
class SeqList
{
public :
SeqList();
~ SeqList();
private :
int _size ;
int _capacity ;
T* _data ;
};
//模板类全特化为int型,特化后定义成员函数不再需要模板形参
SeqList <int>:: SeqList(int capacity)
: _size(0)
, _capacity(capacity )
, _data(new int[ _capacity])
{
cout<<"SeqList<int>" <<endl;
}

类模板全特化限定死类模板实现的具体类型,类模板偏特化就是如果这个类模板有多个类型,那么只限定其中的一部分。

//模板类偏特化(局部特化):
template <typename T1, typename T2>
class Data
{
public :
Data();
private :
T1 _d1 ;
T2 _d2 ;
};
// 局部特化第二个参数为int型
template <typename T1>
class Data <T1, int>
{
public :
Data();
private :
T1 _d1 ;
int _d2 ;
};
// 局部特化第一个参数为引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public :
Data(const T1& d1, const T2& d2);
private :
const T1 &_d1;
T2 _d2;

模板类的全特化和偏特化都是在已定义的模板基础之上,不能单独存在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值