函数模板、 内联函数

   函数重载就是有相同的函数名但参数的个数或类型不同从而根据不同的参数个数和参数类型来调用相应的方法。

   我们发现函数重载只是解决了函数命名的问题,但函数体虽然相同我们还是要重复的写,为了解决这一问题c++中有了函数模板。

函数模板的定义:


     模板提供了一种机制,通过它我们可以保留函数定义和函数调用的语义(在一个程序位置上封装了一段代码,确保在函数调用之前实参只被计算一次)而无需像宏方案那样绕过C++的强类型检查。

     关键字template 总是放在模板的定义与声明的最前面。关键字后面是用逗号分隔的模板参数表,它用尖括号<> 括起来。该列表是模板参数表,不能为空。模板参数可以是一个模板类型参数( template type parameter),它代表了一种类型,也可以是一个模板非类型参数( template nontype parameter),它代表了一个常量表达式。

    模板类型参数由关键字class 或typename 后加一个标识符构成。在函数的模板参数表中,这两个关键字的意义相同。它们表示后面的参数名代表一个潜在的内置或用户定义的类型。模板参数名由程序员选择,在本例中,我们用Type 来命名min()的模板参数。但实际上可以是任何名字。譬如:

   template <class Glorp> 
        Glorp min( Glorp a, Glorp b )

       {
               return a < b ? a : b;
         }

     当调用模板函数时首先是使用用户定义的类型来替换模板的类型,其次再是传值。

     当模板被实例化时,实际的内置或用户定义类型将替换模板的类型参数。

     非类型参数由一个普通的参数声明构成。模板非类型参数表示该参数名代表了一个潜在的值,而该值代表了模板定义中的一个常量。例如,size 是一个模板非类型参数,它代表arr 指向的数组的长度。

   template <class Type, int size> 
        Type min( Type (&arr) [size] );

    当函数模板min()被实例化时,size 的值会被一个编译时刻已知的常量值代替。

    函定义或声明跟在模板参数表后,除了模板参数是类型指示符或常量值外,函数模板的定义看起来与非模板函数的定义相同。

    在程序的运行过程中,Type 会被各种内置类型和用户定义的类型所代替。而size 会被各种常量值所取代,这些常量值是由实际使用的min()决定的(记住,一个函数的两种用法是调用它和取它的地址 )。类型和值的替换过程被称为模板实例化(template instantiation)。

    当一个名字被声明为模板参数之后,它就可以被使用了,一直到模板声明或定义结束为止。模板类型参数被用作一个类型指示符,可以出现在模板定义的余下部分,它的使用方式与内置或用户定义的类型完全一样,比如用来声明变量和强制类型转换。模扳非类型参数被用作一个常量值,可以出现在模板定义的余下部分,它可以用在要求常量的地方,或许是在数组声明中指定数组的大小或作为枚举常量的初始值。

        // size 指定数组参数的大小并初始化一个 const int 值
        template <class Type, int size>
        Type min( const Type (&r_array)[size] )
        {
              const int loc_size = size;
              Type loc_array[loc_size];
              // ...
        }


函数模板的异常处理

函数模板中的模板形参可实例化为各种类型,但当实例化模板形参的各模板实参之间不完全一致时,就可能发生错误,如:

template<typename T>      

void min(T &x, T &y)

{  return (x<y)?x:y;  }

void func(int i, char j)

{

   min(i, i);

   min(j, j);

   min(i, j);

   min(j, i);

}

例子中的后两个调用是错误的,出现错误的原因是,在调用时,编译器按最先遇到的实参的类型隐含地生成一个模板函数,并用它对所有模板函数进行一致性检查,例如对语句

min(i, j);

先遇到的实参i是整型的,编译器就将模板形参解释为整型,此后出现的模板实参j不能解释为整型而产生错误,此时没有隐含的类型转换功能。解决此种异常的方法有两种:

⑴采用强制类型转换,如将语句min(i, j);改写为min(i,int( j));

⑵用非模板函数重载函数模板

方法有两种:

① 借用函数模板的函数体

此时只声明非模板函数的原型,它的函数体借用函数模板的函数体。如改写上面的例子如下:

template<typename T>      

void min(T &x, T &y)

{  return (x<y)?x:y;  }

int min(int,int);

void func(int i, char j)

{

   min(i, i);

   min(j, j);

   min(i, j);

   min(j, i);

}

执行该程序就不会出错了,因为重载函数支持数据间的隐式类型转换。

② 重新定义函数体

就像一般的重载函数一样,重新定义一个完整的非模板函数,它所带的参数可以随意。C++中,函数模板与同名的非模板函数重载时,应遵循下列调用原则:

  1. 寻找一个参数完全匹配的函数,若找到就调用它。若参数完全匹配的函数多于一个,则这个调用是一个错误的调用。
  2. 寻找一个函数模板,若找到就将其实例化生成一个匹配的模板函数并调用它。
  3. 若上面两条都失败,则使用函数重载的方法,通过类型转换产生参数匹配,若找到就调用它。
  4. 若上面三条都失败,还没有找都匹配的函数,则这个调用是一个错误的调用。  

几个需要注意的地方:      

  • 如果在全局域中声明了与模板参数同名的对象、函数、或类型,则该全局名将被隐藏。在下面的例子中tmp的类型不是double ,而是模板参数Type:

typedef double Type;

template <class Type>

Type min( Type a, Type b )

{

// tmp类型为模板参数Type,不是全局typedef

 Type tmp = a < b ? a : b;

return tmp;

}

  • 在函数模板定义中声明的对象或类型不能与模板参数同名。

template <class Type>

Type min( Type a, Type b )

{

//错误:重新声明模板参数Type

typedef double Type;

Type tmp = a < b ? a : b;

return tmp;

}

  • 模板类型参数名可以被用来指定函数模板的返回值。

// ok: T1表示min() 的返回类型,T2T3 表示参数类型

template <class T1, class T2, class T3>

T1 min( T2, T3 );

  • 模板参数名在同一模板参数表中只能被使用一次,但是模板参数名可以在多个函数模板声明或定义之间被重复使用。

//错误:模板参数名Type 的非法重复使用

template <class Type, class Type>

Type min( Type, Type );

 

// ok:名字Type在不同模板之间重复使用

template <class Type>

Type min( Type, Type );

template <class Type>

Type max( Type, Type );

  • 如果一个函数模板有一个以上的模板类型参数,则每个模板类型参数前面都必须有关键字classtypename。

// ok:关键字typenameclass可以混用

template <typename T, class U>

T minus( T*, U );

//错误:必须是<typename T, class U><typename T, typename U>

template <typename T, U>

T sum( T*, U );

  • 为了分析模板定义,编译器必须能够区分出是类型以及不是类型的表达式。对于编译器来说,它并不总是能够区分出模板定义中的哪些表达式是类型。例如,如果编译器在模板定义中遇到表达式Parm::name ,Parm这个模板类型参数代表了一个类。那么name引用的是Parm的一个类型成员吗.

template <class Parm, class U>

Parm minus( Parm* array, U value )

{

      Parm::name * p;//这是一个指针声明还是乘法?

}

编译器不知道name是否为一个类型,因为它只有在模板被实例化之后才能找到Parm表示的类的定义。为了让编译器能够分析模板定义,用户必须指示编译器哪些表达式是类型表达式,告诉编译器一个表达式是类型表达式的机制是在表达式前加上关键字typename例如,如果我们想让函数模板minus()的表达式Parm::name是个类型名,因而使整个表达式是一个指针声明。我们应如下修改:

template <class Parm, class U>

Parm minus( Parm* array, U value )

{

      typename Parm::name * p;// ok:指针声明

}

关键字typename也可以被用在模板参数表中,以指示一个模板参数是一个类型。

  • 如同非模板函数一样,函数模板也可以被声明为inlineextern。应该把指示符放在模板参数表后面,而不是在关键字template前面。

    // ok: 关键字跟在模板参数表之后

template <typename Type>

       inline Type min( Type, Type );


内联函数:


 (1)什么是内联函数?
内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。

(2)为什么要引入内联函数?
当然,引入内联函数的主要目的是:解决程序中函数调用的效率问题。另外,前面我们讲到了宏,里面有这么一个例子:
#define ABS(x) ((x)>0? (x):-(x))
当++i出现时,宏就会歪曲我们的意思,换句话说就是:宏的定义很容易产生二意性。
  
我们可以看到宏有一些难以避免的问题,怎么解决呢?前面我们已经尽力替换了。

下面我们用内联函数来解决这些问题。

(3)为什么inline能取代宏?
1、 inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
2、 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3、 inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。

(4)内联函数和宏的区别?
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。内联函数与带参数的宏定义进行下比较,它们的代码效率是一样,但是内联欢函数要优于宏定义,因为内联函数遵循的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关上内联扩展,将与一般函数一样进行调用,比较方便。

(5)什么时候用内联函数?
内联函数在C++类中,应用最广的,应该是用来定义存取函数。我们定义的类中一般会把数据成员定义成私有的或者保护的,这样,外界就不能直接读写我们类成员的数据了。对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把这些读写成
员函数定义成内联函数的话,将会获得比较好的效率。
Class A
{
Private:
int nTest;
 Public:
int readtest() { return nTest;}
void settest(int I) { nTest=I; }
}

(6)如何使用内联函数?
我们可以用inline来定义内联函数。
inline int A (int x) { return 2*x; }
不过,任何在类的说明部分定义的函数都会被自动的认为是内联函数。


(7)如何禁止函数进行内联?
如果使用VC++,可以使用/Ob命令行参数。当然,也可以在程序中使用 #pragma auto_inline达到相同的目的。


内联函数的优缺点:

   我们可以把它作为一般的函数一样调用,但是由于内联函数在需要的时候,会像宏一样展开,所以执行速度确比一般函数的执行速度要快。当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。(换句话说就是,你使用内联函数,只不过是向编译器提出一个申请,编译器可以拒绝你的申请)这样,内联函数就和普通函数执行效率一样了。


注意事项:
1.在内联函数内不允许用循环语句和开关语句。
2.内联函数的定义必须出现在内联函数第一次被调用之前。










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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值