函数进阶(函数模板、重载解析)

函数探幽

            目录:

  • C++内联函数
  • 引用变量
  • 默认参数
  • 函数重载
  • 函数模板

一、C++内联函数:编译器将使用相应的函数代码替换函数调用。内联函数的运行速度比常规函数稍快,但需要占用更多内存。

方法:在函数声明(定义)前加上关键字inline

通常省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。

二、引用变量

1.必须在声明引用时将其初始化

引用更接近const指针:int & rodents = rats; //int * const pr = &rats;

2.引用常被用作函数参数,使得函数中的变量名成为调用函数中的变量的别名,即按引用传递,它允许被调函数能够访问调用函数中的变量。

只能通过原型或函数定义才能知道其是否是按引用传递的。

3.若引用参数是const,则编译器将在以下情况生成临时变量:实参类型正确,但不是左值;实参类型不正确,但可转换为正确的类型。

       a. 左值参数是可被引用的数据对象,如变量、数组元素、结构成员,引用和解除引用的指针都是左值。非左值包括字面常量(用引号括起的字符串除外,其由地址表示)和包含多项的表达式。

       b. 如果函数调用的参数不是左值或与相应的const引用参数的类型不匹配,则C++将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。

       c. 将引用参数声明为常量数据的引用:使用const可避免无意中修改数据的编程错误;使用const使函数能够处理const和非const实参,否则将只能接受非const数据;使用const引用使函数能正确生成并使用临时变量。因此,应尽可能将引用形参声明为const。右值可指向右值,它让库设计人员能够提供有些操作的更有效实现。

4 . 返回引用的函数实际上是被引用的变量的别名。返回引用时,应避免返回函数终止时不在存在的内存单元引用,同样,也应避免返回指向临时对象的指针。

    避免以上问题的方法:返回一个作为参数传递给函数的引用,用new来分配新的内存空间。常规(非引用)返回类型是右值—不能通过地址访问的值,这种返回值位于临时内存单元中,运行到下一条语句时,它们可能不再存在。

5. string类定义了一种 char* 到string的转换功能,这使得可以使用C-风格字符串来初始化string对象,若函数形参类型为const string & , 在调用函数时,使用的实参可以是string对象或C-风格字符串,如引号括起的字符串字面值。以空字符结尾的char数组或指向char的指针变量。

6. 使得能够将特性从一个类传递给另一个类的语言特性叫继承。ostream是基类(ofstream建立在它之上),而ofstream是派生类(从ostream派生而来),派生类继承了基类的方法,这意味着ofstream对象可以使用基类的特性,如格式化方法precision( )和setf( )。继承的另一个特征是,基类引用可以指向派生类对象,而无需进行强制类型转换。即可以定义一个接受基类引用作为参数的函数,调用该函数时可将基类对象或派生类对象作为参数。

7. 使用引用参数的主要原因:能修改调用函数中的数据对象;通过传递引用而不是整个数据对象,可提高程序运行速度。

  a.对于使用传递的值而不作修改的函数:若数据对象很小(内置数据类型,小型结构),则按值传递;若数据对象为数组,则将指针声明为指向const的指针;若是较大的结构,则使用const指针或const引用(提高效率);若数据对象是类对象则用const引用。传递类对象参数的标准方式是按引用传递。

  b. 对于修改调用函数中数据的函数:若数据对象是内置数据类型,则使用指针;若是指针,则只能使用指针;若是结构,则使用引用或指针;若数据对象是类对象,则使用引用。

三、 默认参数

       1. 默认参数指当函数调用中省略了实参时自动使用的一个值

       2. 设置默认值必须通过函数原型。由于编译器通过查看原型来了解函数所使用的参数数目,因此函数原型也必须将可能的默认值告知程序,方法是将值赋给原型中的参数。

       3. 对于带参数列表的函数,必须从右向左添加默认值。只有原型指定了默认值,函数定义与没有默认参数时完全相同。

四、 函数重载

       C++允许定义名称相同的函数,条件是它们的特征标(参数列表)不同。若参数数目和/或参数类型不同,则特征标也不同。编译器在检查函数特征标时,将把类型引用和类型本身视为同一特征标。是特征标,而不是函数类型使得可以对函数进行重载。仅当函数执行基本相同的任务,但使用不同形式的数据时,才应采用函数重载。

五、 函数模板 template <typename T>

       1. 模板允许以泛型(而不是具体类型)的方式编写程序,因此也被称为通用编程,由于类型是用参数表示的,因此模板特性有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型;函数模板允许以任意类型的方式来定义函数。关键字template、typename(class)、尖括号是必须的。

          如果需要多个将同一种算法用于不同类型的函数,请使用模板。程序最终的代码不包括任何模板,而只包含为程序生成的实际函数。

          应将模板放在头文件中,并在需要使用模板的头文件中包含该头文件。

       2. 可以像重载常规函数定义那样重载模板定义,被重载的模板的函数特征标必须不同。

          并非所有的模板参数都必须是模板参数类型。如:

             void Swap (T &a, T &b);

             void Swap (T *a, T *b, int n);

       3. 显式具体化(具体化函数定义),包括所需代码,当编译器找到与函数调用匹配的具体化定义时,将使用该定义而不再寻找模板。

          标准方法:对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及其重载版本;显式具体化的原型和定义应以template < >打头,并通过名称来指出类型;非模板函数优先于具体化,具体化优先于常规函数。

         下面为用于交换job结构的非模板函数、模板函数和具体化的原型:

            (1). void Swap (job &, job &); //非模板

            (2). template <typename T>  //模板

                void Swap (T &, T &);

           

             (3). template< > void Swap <job> (job &, job &); //具体化,<job>可选

        4. 在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例。模板并非函数定义,但使用特定类型(如int)的模板实例是函数定义,这种实例化方式被称为隐式实例化。

           a.显式实例化,即直接命令编译器创建特定的实例。其语法是,声明所需的种类—用< >符号指示类型,并在声明前加template.

               template void Swap <int> (int ,int);

           b.相对显式具体化两种方法:

                template < > void Swap <int> (int &, int &);

                template < > void Swap (int &, int &);

            这些原型必须有自己的函数定义,显式具体化前缀为template < >,而显式实例化没有< >。试图在同一个文件(或转换单元)中 使用同一种类型的显式实例化和显式具体化将出错。

       隐式实例化、显式实例化和显式具体化统称为具体化,它们表示的都是使用具体类型的函数定义,而不是通用描述。

        5. 对于函数重载、函数模板和函数模板重载,C++需要(且有)一个定义良好的策略来决定为函数调用使用哪一个函数定义,尤其是有多个参数时,这个过程称为重载解析。

     第一步 创建候选函数列表(与被调用函数名称相同的函数和模板函数)

     第二步 使用候选函数列表创建可行参数列表(只考虑特征标,不考虑返回类型)。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。

     第三步 确定是否有最佳的可行函数。若有则使用它,否则该函数调用出错。对于两个完全匹配的函数:非模板函数优先于模板函数,两个模板函数中较具体的模板函数优先,即显式具体化优于使用模板隐式生成的具体化。

     用于找出最具体的模板的规则被称为函数模板的部分排序规则。

总之,重载解析将寻找最匹配的函数。若只存在一个这样的函数则选择它;若存在多个这样的函数,但其中只有一个是非模板函数,则选择该函数;若存在多个适用的函数且都为模板函数,则选择最具体的那个;若有多个同样合适的模板函数或非模板函数,但没有一个比其他函数更具体,则函数调用将是不确定的,因此是错误的。

        6. 关键字decltype

               decltype (expression) var ;

          遍历核对表顺序:

             (1)、若expression是一个没有用括号括起的标识符,则var的类型与该标识符类型相同,包括const等限定符。

              (2)、若expression是一个函数调用,则var类型与该函数的返回类型相同(编译器通过查看函数的原型来获悉返回类型,而无需实际调用)

               (3)、若expression是一个左值,则var为指向其类型的引用

               (4)、若前面条件都不满足,则var类型与expression类型相同

          若需多次声明,可结合使用typedef和decltype,如:

             template <class T1 , class T2>

             void ft(T1 x , T2 y)

             {

                ……

                typedef decltype (x + y) xytype ; //xytype为(x+y)类型的别名

                xytype xpy = x + y ;

                xytype arr [10] ;

                xytype & rxy = arr [2] ; //rxy为引用(与arr[2]等价)

                ……

             }

      7. auto语法

         auto h (int x , float y) ->double ; //这将返回类型移到参数声明后面。 ->double被称为后置返回类型。其中auto是一个占位符,表示后置返回类型提供的类型。

       即:

            template <class T1, class T2>

            auto gt(T1 x, T2 y) ->decltype(x+y)

            {

               ……

               return x+y ;

            }

   

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhugenmi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值