C++之模板函数

如何编写一个通用加法函数?
(1)使用函数重载,针对每个所需相同行为的不同类型重新实现它

int Add(const int &Left, const int &Right)
{
    return (Left + Right);
}

float Add(const float &Left, const float &Right)
{
    (Left + Right);
}

char Add(const char &Left, const char &Right)
{
    (Left + Right);
}

int main()
{
    Add(1, 2);
    Add(3.0f, 4.5f);
    Add('1','1');

    system("pause");
    return 0;
}

【缺点】
a、只要有新类型出现,就要重新添加对应函数。
b、除类型外,所有函数的函数体都相同,代码的复用率不高
c、如果函数只是返回值类型不同,函数重载不能解决
d、一个方法有问题,所有的方法都有问题,不好维护。
(2)使用特殊的预处理程序#define ADD(a, b) ((a) + (b))
【缺点】
不是函数,不进行参数类型检测,安全性不高
(3)还有什么办法吗:函数模版
函数模板
a.定义
代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
b.格式:

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

举例:

template <class T>
T Add(T left, T right)
{
    cout<<typeid(left).name()<<endl;
    return left+right;
}

typename是用来定义模板参数关键字,也可以使用class。
注意:不能使用struct代替typename。
模板函数也可以定义为inline函数

template <class T>
inline T Add(T left, T right)
{
    cout<<typeid(left).name()<<endl;
    return left+right;
}

注意:inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前.
函数模板实例化:编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。

这里写图片描述

注意:模板被编译了两次:
(1)实例化之前,检查模板代码本身,查看是否出现语法错误;
(2)在实例化期间,检查模板代码,查看是否所有的调用都有效。

【类型形参转换】:
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。

编译器只会执行两种转换:
(1)、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用

void FunTest( const T  &b)
{}

int main()
{
    int b= 0;
    FunTest(b);

    system("pause");
    return 0;
}

(2)、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。

void FunTest1()
{}

template <typename T>
void FunTest(T p)
{
    cout<<typeid(p).name()<<endl;
}

int main()
{
    int array[10];
    FunTest(array);     //输出结果:int*
    FunTest(FunTest1); //输出结果:void (_cdecl*)(void)


    system("pause");
    return 0;
}

c.模板参数
函数模板有两种类型参数:模板参数和调用参数

注意:
(1)模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则
(2)模板形参的名字在同一模板形参列表中只能使用一次
(3)所有模板形参前必须加上class或者typename关键字修饰
(4)在函数模板内部不能定义缺省的模板实参
d.非模板类型参数
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。

template <class T, int N>
void FunTest(T (&a) [N])
{
    cout<<N<<endl;
    cout<<typeid(a).name()<<endl;
}
int main()
{
    int array[10];
    FunTest(array);
    system("pause");
    return 0;
}

类型等价性:

template <class T, int N>
void FunTest(T (&a) [N])
{
    cout<<N<<endl;
    cout<<typeid(a).name()<<endl;
}
int main()
{
    const int iByteCnt = 9;
    int b[iByteCnt+1];
    int a[10];
    FunTest(a); // FunTest<int, 10> 两个数组等价
    FunTest(b); // FunTest<int, 10> 编译器不会合成新的函数
    system("pause");
    return 0;
}

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

e.模板函数重载

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);  //调用第二个函数

    system("pause");
    return 0;
}

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

下面实现比较大小的函数:

template<class T>
const T& Max(const T& left, const T& right)
{
    return (left>right)?left:right;

}

template<class T>
const T& Max(const T& left, const T& mid,const T& right)
{
    return (left>right)?left:right;

}

int main()
{   
    cout<<Max('1', '4')<<endl;
    cout<<Max(1, 4)<<endl;
    cout<<Max(1, 4, 7)<<endl;

    system("pause");
    return 0;
}

但是当模板函数实现字符串的比较时,会出现问题,如下:

const char* Max(const char *left, const char* right)//实现两个字符串的大小比较
{
    if(strcmp(left, right)>0)
    {
        return left;
    }
    return right;
}

int main()
{
    const char *p1 = "hello";
    const char *p2 = "world";

    cout<<Max(p1, p2)<<endl;//输出world

    system("pause");
    return 0;
}
template<class T> //用模版函数实现
const T& Max(const T& left, const T& right)
{
    return (left>right)?left:right;

}

int main()
{
    const char *p1 = "hello";
    const char *p2 = "world";

    cout<<Max(p1, p2)<<endl;  //hello与实际输出不符合

    system("pause");
    return 0;
}

由上述结果可知,模板函数的实现不能保证字符串之间的正确比较大小,由此引出模板函数特化这一概念。

模板函数特化形式如下:
(1)、关键字template后面接一对空的尖括号<>
(2)、再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
(3)、函数形参表
(4)、函数体

template<>
返回值 函数名<Type>(参数列表)
{
// 函数体
}

注意:特化的声明必须与特定的模板相匹配;

所以上述模板可以改成如下:

template <class T>
const T& Max(const T& left,const T& right)
{
    if(strcmp(left,right)>0)
        return left;

    return right;
}

template <>//函数特化
char* const & Max<char*>(char* const& left,char* const& right)
{
    if(strcmp(left,right)>0)
        return left;

    return right;
}

int main()
{
    const char* p1="hello";
    const char* p2="world";
    cout<<Max(p1,p2)<<endl;   //输出world,符合预想
    system("pause");
    return 0;
}

注意:在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例。

注意:特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然后使用该特化版本的每个源文件包含该头文件。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值