模板(一)----->函数模板

本文深入探讨C++模板的概念与应用,介绍如何通过模板减少代码冗余,提高程序的可重用性和可维护性。覆盖函数模板的基本定义、重载、特化等内容。

模板是C++语言的一个重要特性。利用模板机制可以显著减少冗余信息,能大幅度地节约程序代码,进一步提高面向对象程序的可重要性和可维护性。模板是实现代码重用机制的一种工具,他可以实现类型参数化,即把类型定义为参数,从而实现代码的复用,使得程序可以用于处理多种不同类型的对象,大幅度的提高程序设计的效率。

一、引子
如何编写一个通用加法
(1)函数重载(相同作用域、函数名相同、函数参数列表不同)
//函数重载
int Add(int left, int right)
{
      return left + right;
}
double Add(double left, double right)
{
      return left + right;
}
优点:
1.避免了函数名的污染
2.使得调用方便
缺点:
1.只要有新的类的出现,就要重新添加新的函数
2.除类型外,函数的实现体一致,代码的复用率差
3.函数只有返回值不同,则不能形成重载
4.由于函数的实现体一致,只要一个有问题,其他的都会出现问题,因此代码的可维护性差

(2)公共基类+虚函数
缺点:
1.借助公共基类来编写通用函数,将失去类型检测,不安全
2.对于以后要实现的很多类,都必须继承自某个特定的类,可维护性差
class A
{
public:
      A()//在构造函数中,完成加法的运算,但是在此时,不进行类型的检测,安全性差
      {
            int c;
            c = _a + _b;
      }
private:
      int _a;
      int _b;
};
(3)宏定义
优点:
1.在预处理的时候直接进行替换,效率较高
2.因为是代码的直接替换,因此不需要函数的压栈
缺点:
1.可调试(因为是直接替换)
2.检查类型,不够严谨,可读性差,可维护性差
3.宏的每次使用均是直接替换,增加了程序的代码长度(代码膨胀)
4.会有运算符优先级的问题,从而导致程序出错

引入模板 

二、基本概念
1、定义:简单来说,模板就是依葫芦画瓢的做法,给出一个模型,根据这个模型,编译器为你生成不同的函数
2、泛型编程:编写与类型无关的逻辑代码,是代码复用的一种手段,模板是泛型编程的基础。
3、分类:

4、总结模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。

三、函数模板-----建立一个通用的函数 
1、定义:函数模板代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据调用函数的实参类型从而产生相对应的函数。
2、形式:
template<typename(此处也可用class) T1, typename T2......, typename Tn>
返回类型 函数名(参数列表)
{
      //函数体的实现
}
注意:在此不能使用struct替代class
说明:在上述模板的定义形式中,模板的形参,每个形参前必须要有关键字(typename或者class),在函数模板的内部不能指定缺省的模板实参,且每个模板参数中的形参的名字只能使用一次,如果多次使用,则会出现错误。 
template<class T1,class T2=0>//错误,因为在模板实参中带有缺省参数
举例说明: 
//类型一
template<class T>
T MAX(T a, T b)
{
      return ((a > b) ? (a) : (b));
}
int main()
{
      cout << MAX(1, 2) << endl;
      //cout << MAX(1, '1') << endl;//在此处的第二个参数,在代码生成中,经过编译器的推演,产生不了与之相匹配的函数,因此会报错
      //因为他的函数模板中的参数列表类型只有一个
      cout << MAX(1, (int)'1') << endl;//进行强制类型转换便可
      cout << MAX<int>(1, '1') << endl;//对模板进行显示实例化,说明里面的实参均是int型
      system("pause");
      return 0;
}
//类型二
//template<class T1,class T2>//如果在此处定义两个形参名,则在下面定义之时,必须全部使用,否则就会报错
//T1 MAX(T1 a, T2 b)
//{
//    cout << typeid(a).name << endl;//打印一个类型的名字
//    return ((a > b) ? (a) : (b));
//}
//
//int main()
//{
//    cout << MAX(1, 2)<<endl;//模板类型的实例化
//    cout << MAX(1, '1') << endl;
//    system("pause");
//    return 0;
//}
结果:

类型一:

类型二: 



cout << MAX(1, '1') << endl;

3、用inline定义的模板函数
形式:
template<typename T>
inline T Add(const T _left, const T _right)
{
      return (_left + _right);
}
注意:inline关键字必须放在模板形参之后,返回值之前,不能放在template之前

4、函数模板不是一个真正意义上的函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板的实例化
5、在真正使用这个模板之时,模板被编译了两次
         (1)实例化之前,检查模板的本身,查看是否出现简单的语法错误,例如模板参数列表的使用是否正确,模板参数名是否正确,模板形参表是否匹配,但是对于是否遗漏分号等是检测不出来的
         (2)在实例化期间,检查代码本身,查看所有的调用是否都有效,并且在此时,真正生成一个代码块,进行更为严格的类型等的检查。
6、有关实参推演:从函数实参确定模板形参类型和值的过程称为模板实参推演,多个类型形参的实参必须完全匹配。
7、有关类型形参转换:一般不会转换实参以匹配已有的实例化,相反会产生新的实例
            注:编译器只会执行的两种转换:(1)const转换,接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用(即强的限制可以转换为较弱的限制)
                                                             (2)数组或函数到指针的转换,如果模板形参不是引用类型,则对数组或者函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数                                                                                            类型的指针。
8、模板形参
(1)分类:

注:模板形参名字只能在模板形参之后到模板声明或者定义的末尾之间使用,遵循名字屏蔽原则(即就近原则),如若想继续使用这个模板形参的名字,需要在一次定义。
(2)非模板类型参数
template<typename T,int N>
void FunTest(T(&_array)[N])//此处表示数组的引用
{}
如果在传参之时,传过来的N如果不同的话,则会实例化处不同的函数

9、总结:模板形参说明
    a、模板形参表使用<>括起来
    b、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
    c、定义模板函数时模板形参表不能为空(特化除外)
    d、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
    e、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类,  使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
    f、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
10、模板函数重载
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;
}

通过以上可以看出,模板函数的重载,可以极大的方便我们的使用,从而使得我们可以处理实参个数不同的各种情况。
注:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
说明:
a、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
b、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
c、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
d、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
但是,从上面的函数可以看出,他不能处理有关字符串的比较,在传递字符串之时,比较的仅仅只是两个字符串的地址,从而返回错误的答案,因此,我们在此就需要特化出一个具体的函数,从而替我们处理这种情况。

11、函数模板的特化------处理某种特定类型
有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事情。
(1)形式
template<>
返回值 函数名<Type>(参数列表)
{
// 函数体
}
(2)说明
a、关键字template后面接一对空的尖括号<>
b、函数名后接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
c、函数形参表
d、函数体
(3)注意
特化的声明必须与特定的模板相匹配,否则将会出现错误,即在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例。
(4)如果不匹配的话:只是定义了一个普通函数,该函数含有返回类型和与模板实例化相匹配的形参表。
对于上面的函数,我们可以特化出下面的函数
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)//此处的const修饰的是a,b变量,引用是对变量的引用
{
      return Max(Max(a, b), c);
};
template<>
char*Max<char *>(char* const &_pleft, char* const &_pright)//特化的具体函数
{
      if (strcmp(_pleft, _pright) > 0)
      {
            return _pleft;
      }
      return _pright;
}
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);
      char*ptr1 = "11111";
      char*ptr2 = "22222";
      Max(ptr1, ptr2);
      system("pause");
      return 0;
}

注意:特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然后使用该特化版本的每个源文件包含该头件。
如若对这个函数没有进行特化,可以将他们声明为普通函数,这样更有利于函数的定义和声明,也有利于程序的书写(与特化功能相似的函数)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值