模板函数

一.提出问题:

0.如何编写一个高效的加法函数?

1.什么是模板函数?

2.模板函数怎么写?

3.模板如何进行实例化? 隐式实例化? 显式实例化?

4.什么是参数推演?

5.模板函数如何编译?

6.模板函数的模板参数列表? 类型参数? 非类型参数?

7.模板函数的重载?

8.什么是模板的特化?

二.解决问题:

0.如何编写一个高效的加法函数?

学完C语言,若要写一个整型的加法函数, 那么,写出如下代码可谓小轻松:

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

如上是一个计算整型的加法函数,那么若要计算实型数据的加法,如Add(1.0,2.0),或者字符型加法Add(“1”,“2”)时,显然,以上的Add函数就不能满足计算,那么该怎么办呢?

当学完C++ ,我们知道,实现该问题,可以使用如下四种方法:

A–函数重载

B–宏函数替换

C–使用公共基类

D–泛型编程

前三种方法都有各自的优缺点,那么下面就来具体的谈一谈:

A:使用函数重载优点和缺点:
1.优点:
1.1 函数重载易于理解, 可读性高
1.2.调用哪个函数按其实参类型匹配相应的函数即可。
1.3.可以调试程序,进行类型的检测

2.缺点:
2.1. 只要有新类型的出现,就需要添加与之对应类型的函数
2.2. 函数只有类型不同,函数体均相同,从而使得代码复用率不高
2.3. 一个方法如果有问题,所有的方法都将有问题,不好维护

B:

1.优点:

使用宏函数替换:简洁,方便

#include<stdio.h>
#include<windows.h>

#define Add(a,b) ((a)+(b))

int main()
{
    int a = 1;
    printf("%d\n",Add(2,1));//运行结果正确,为3
    printf("%d\n",Add(++a,1));
    printf("打印a的值为:%d\n",a);
    printf("%d\n",Add(a++,1));
    printf("打印a的值为:%d\n",a);
    system("pause");
    return 0;
}

图一

2.缺点:

2.1. 不能调试程序,容易出错
2.2. 宏定义不是函数,不进行类型的检测,安全性不高

C:使用公共基类的缺点:

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

为了解决上述的问题,以及解决方案的缺点,继承其优点,C++采用泛型编程

D: 泛型编程:

概念:泛型编程是编写与类型无关的逻辑代码,是实现代码复用的一种手段,其中模板是泛型编程的基础,以下具体来看:

1.模板函数概念

1.模板:模板包含函数模板和类模板;
2.函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

2.模板函数书写

1.函数模板的格式

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

2.说明:typename是定义模板函数的关键字,在这里也可以使用class,不可使用struct,为了便于区分,建议尽量使用typename,使用class容易与C++中的类混淆。

3.在C++中struct和class的区别:
1. 在定义类时,两者均可使用,但是,struct默认的访问权限为public,class的默认访问权限是private ;
2. class可以做模板函数的关键字定义函数模板,而struct不可以。

4.函数模板其他知识:

A: inline:
模板函数可以定义为inline函数,inline关键字必须放在模板的形参列表后,函数返回值之前。

template<typenameT>
inline T Add(T left,T right)
{
    return (left+right);
}

B.函数模板只适用于函数参数个数相同而类型不同,且函数体相同的情况,若参数个数不同,则不能用模板函数

3.模板实例化

!代码执行过程中,模板被编译了两次 !

1.实例化之前,检查模板代码本身,查看是否出现语法错误;(模板缺少分号不能被检测出)
2.实例化期间,检查模板代码,查看是否所有调用都有效

3.模板实例化:产生模板特定类型的过程 ,分为隐式实例化和 显式实例化,

4.参数推演

模板实参推演:根据函数实参类型来决定模板实参的类型和值得过程,若是多个类型的形参,也要与实参类型完全匹配;

5.模板函数编译

编译器会执行的两种转化:

**511.**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;
}

6.模板函数的模板参数列表

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

2.模板形参名字只能在模板形参列表之后到模板声明或定义的末尾之间使用;
3.遵循名字屏蔽规则,如果在函数模板之外申明了与模板参数名同名的对象、函数或类型,则该全局变量将被隐藏;宏定义的类型名与模板类型名相同,在模板作用域内也将被屏蔽

如下所示:

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

图二

!特别注意:

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

template<typename T,int N>
void FunTest(T(&a)[N])//数组的引用
{
   cout<<N<<endl;
  cout<<typeid(a).name()<<endl;//typeid.(变量名).name 查看变量的类型
}

int main()
{
    int array[10];
    FunTest(array);
    system("pause");
    return 0;
}

图三

7.模板函数的重载
1.举个例子:

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

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

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

由肉眼可得,应该返回的是 world ,但是,和预想的结果恰恰相反,针对这类情况,我们有函数模板的实例化

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

8.模板的特化

对于以上字符串比较hello, world的例子,这里采用函数模板特化解决,代码如下:
根据函数模板写函数模板特化:

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

运行结果:

注意:
const修饰变量时,const修饰T& left时,指向不能改变,值可改变,与const修饰int *a正好相反。

总结:

函数模板不是函数,它只是一个模子, 根据具体的要求来刻画相应的实体;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值