函数模板---上

如果让我们编写一个整型加法函数,我们可以很快的给出代码

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

但是,我们同时还要求计算出Add(1.0,2.0),Add(“123”,“456”)时,这时上面的那个Add函数将不能满足计算。那么我们将要怎么办呢?
有人说我们可以使用函数重载,还可以使用宏函数替换,还可以使用公共基类,但这三个方法都有各自的缺点。

使用函数重载的缺点:
1. 只要有新类型的出现,就要添加对应函数
2. 除函数类型外,所有的函数体都是相同的,代码的复用率不高
3. 如果函数只有返回值类型的不同,函数重载不能解决
4. 一个方法如果有问题,所有的方法都将有问题,不好维护

使用宏函数替换:

#define Max(a,b) (a)>(b) ? (a):(b)

int main()
{
    int a = 1;
    printf("%d\n",Max(2,1));//运行结果正确,为2
    printf("%d\n",Max(++a,1));//运行结果错误,结果为3
    system("pause");
    return 0;
}

宏函数替换的缺点:
1. 容易出错
2. 不是函数,不进行类型的检测,安全性不高
3. 不能调试

使用公共基类的缺点:
1. 借助公共基类来编写通用代码,将失去类型检测的优点
2. 对于以后实现的许多类,都必须继承自摸个特定的基类,代码维护更加的困难

为了解决上述的问题以及解决方案的缺点,C++采用泛型编程
泛型编程:编写与类型无关的逻辑代码,是代码复用的一种手段,其中模板是泛型编程的基础。
模板包含函数模板和类模板,我们先来了解函数模板
函数模板:代表了一个函数家族,该函数与类型无关,在使用是被参数化,根据实参类型产生函数的特定类型版本。
1.函数模板的格式

template<typename T1,typename T2,...>
返回值类型  函数名(参数列表)
{
  ....
}

typename是定义模板函数的关键字,也可以使用class,建议尽量使用typename,因为便于辨认,用class容易混淆C++中的类

C++中struct和class的区别:
1. struct默认的访问权限为public,class的默认访问权限是private
2. class可以做模板的关键字定义函数模板,而struct不可以

模板函数细小知识

  • 模板函数可以定义为inline函数,inline关键字必须放在模板的形参列表后,函数返回值之前。
template<typename T>
inline T Add(T left,T right)
{
    return (left+right);
}

    • 函数模板只适用于函数个参数个数相同而类型不同的,且函数体相同的情况,若参数个数不同,则不能用模板函数
  • 模板被编译了两次
    实例化之前,检查模板代码本身,查看是否出现语法错误(模板缺少分号不能被检测出)
    实例化期间,检查模板代码,查看是否都调用有效
  • 模板实例化:产生模板特定类型的过程,
  • 模板实参推演:用函数实参类型来决定模板实参的类型和值得过程;多个类型的形参要与实参一般情况下要完全匹配;
    编译器会执行的两种转化:
    1.const 转化,接受const引用或则指针的函数可以分别用非const对象的引用或则指针来调用
template<typename T>
const T& Add(const T& left,const T& right)
{
    return (left+right);
}
int main()
{
    int a = 1;
    int b = 2;
    Add(a,b);
    return 0;
}

2.模板参数
函数模板有两种类型参数:模板类型参数和模板非类型参数

  • 模板形参名字只能在模板形参列表之后到模板声明或定义的末尾之间使用;
  • 遵循名字屏蔽规则,如果在全局域中申明了与模板参数名同名的对象、函数或则类型,则该全局变量将被隐藏;或是宏定义的类型名与模板类型名相同,在模板作用域内也将被屏蔽
typedef int T;
template<class T>
void FunTest(T t)
{
    cout<<"t Type="<<typeid(t).name()<<endl;
}

T global;
int main()
{
    FunTest(10.0);
    cout<<"golbal Type = "<<typeid(global).name()<<endl;
    system("pause");
    return 0;
}

这里写图片描述

-模板形参的名字在同一模板形参表中只能使用一次,但是,模板形参名可以在多个函数模板声明或定义之间重复使用
- 所有模板参数前都需要加上class或typename关键字
- 在函数模板的内部不能指定缺省的模板实参,即使是非类型参数也可以
非模板类型参数:模板内部定义的常量。

template<typename 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;
}

![这里写图片描述](https://img-blog.csdn.net/20170313221415830?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZmVybl9naXJs/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

模板函数的重载

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(a,Max(b,c));
}

int main()
{
    Max(10,20,30);
    Max<>(10,20);//构建模板实例,T Max()
    Max(10,20);//没有构建模板实例,int Max()
    Max(10,20.02);//int Max(),模板不能进行类型转换
    Max(10.0,20.0);//int Max,发生隐式类型转化
    Max<>(10.0,20.0);//模板构建实例
    return 0;
}

说明:
-一个非模板函数可和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板
- 显示指定一个空的模板实参列表,该语法告诉编译器只有模板才能匹配这个调用,而且所有的模板参数都应该根据实参演绎而来
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
但当上面两个实例比较字符串的大小时:

char *p1 = "hello";
    char *p2 = "world";
    printf("%s\n",Max(p1,p2));

这里写图片描述
和预想的结果不同,针对这种情况,我们有函数模板的实例化

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

接下来我们学习模板函数的特化
对于上述hello world的例子,我们用函数模板特化解决,代码如下:
根据函数模板写函数模板特例化

template<typename T>
T Max(const T& left,const T& right)//注意
{
    if(strcmp(left,right)>0)
    {
        return left;
    }
    return right;
}

template<>
char* Max(const char* &left,const char* &right)//注意
{
    if(strcmp(left,right)>0)
    {
        return left;
    }
    return right;
}
int main()
{
    int p1 = "hello";
    int p2 = "world";

    cout<<Max(p1,p2)<<endl;

    system("pause");
    return 0;
}

代码出错:
错误原因
这里写图片描述

对于错误原因的分析之前,我们首先看下面的例子

template<class T>
void FunTest(const T& p)      //此时根据实参推演,T为int*,const int* &p,const修饰的是*p,
{                             //表示*p(截引用)的值不能改变,可以改变p的值,即地址(指向可以改变)
    *p =1;    //可以正确运行
    p++;     //编译出错,与上面推断的相反
}

int main()
{
    int a = 0;
    FunTest(&a);
    system("pause");
    return 0;
}

可以看出,const修饰T& left时,指向不可以改变,值可以改变,与const修饰int *a正好相反。于是有正确的代码如下:

template<typename T>
T Max(const T& left,const T& right)  //const修饰的是left(地址,不能改变指向),而值可以发生改变
{
    if(strcmp(left,right)>0)
    {
        return left;
    }
    return right;
}

template<>
char* Max( char* const &left, char* const &right)
{
    if(strcmp(left,right)>0)
    {
        return left;
    }
    return right;
}

int main()
{
    char* p1 = "hello";
    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、付费专栏及课程。

余额充值