zoukankan      html  css  js  c++  java
  • 函数----函数重载,特殊用途语言特性,函数匹配,函数指针

    一、函数重载

      如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。

    1、定义重载函数

      对于重载的函数来说,它们应该在形参数量或形参类型上有所不同。

    2、重载和const形参

      一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来:

    1 int func(int x);
    2 int func(const int x); // 重复声明了func(int x)
    3 
    4 int func2(int *);
    5 int func2(int *const); // 重复声明了func2(int *)

      如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:

    1 int func(int &);
    2 int func(const int &); // 新函数,作用于常量引用
    3 
    4 int func2(int *);
    5 int func2(const int *); // 新函数,作用于指向常量的指针

     3、const_cast和重载

      const_cast在重载函数的情景中最有用。

     1 #include <iostream>
     2 #include <string>
     3 
     4 const std::string &shortString(const std::string &s1, const std::string &s2)
     5 {
     6     std::cout << "const" << std::endl;
     7     return s1.size() < s2.size() ? s1 : s2;
     8 }
     9 std::string shortString(std::string &s1, std::string s2)
    10 {
    11     std::cout << "not const" << std::endl;
    12     auto &r = shortString(const_cast<const std::string &>(s1), const_cast<const std::string &>(s2));
    13     return const_cast<std::string &>(r);
    14 }
    15 int main()
    16 {
    17     const std::string s1 = "abc", s2 = "ABC";
    18     std::cout << shortString(s1, s2) << std::endl;
    19     std::cout << "-----------------------" << std::endl;
    20     std::string s3 = "abc", s4 = "ABC";
    21     std::cout << shortString(s3, s4) << std::endl;
    22     return 0;
    23 }

    4、调用重载的函数

       定义了一组重载函数后,我们需要以合理的实参调用它们。函数匹配是指一个过程,在这个过程中我们把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫重载确定。编译器首先将调用的实参与重载集合中每一个函数的形参进行比较,然后根据比较的结果决定到底调用哪个函数。

      当个调用重载函数时有三种可能的结果:

      a、编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码。

      b、找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息。

      c、有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误信息,称为二义性调用。

    5、重载与作用域

      重载对作用域的一般性质并没有什么改变:如果我们在内层作用域中声明了一个函数名字,它将隐藏外层作用域中声明的同名实体。

    二、特殊用途语言特性

    1、默认实参

      我们可以为一个或多个形参定义默认值,不过需要注意的是,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。

      在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认值,而且该形参右侧的所有形参必须都有默认值。

      局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。用作默认实参的名字在函数声明所在的作用域解析,而这些名字的求值过程发生在函数调用时

    2、内联函数和constexpr函数

      在大多数机器上,一次函数调用其实包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。

    1)内联函数可避免函数调用的开销

       声明内联函数只要在函数的返回类型前面加上关键字inline就行了。

    2)constexpr函数

      constexpr函数是指能用于常量表达式的函数。定义constexpr函数的方法与其他函数类似,不过要遵循几项规定:函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句。

     1 #include <iostream>
     2 #include <string>
     3 
     4 constexpr int func() {
     5     return 1024;
     6 }
     7 int main()
     8 {
     9     constexpr int x = func();
    10     return 0;
    11 }
    View Code

      执行初始化任务时,编译器把对constexpr函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。

      constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。

      constexpr函数不一定返回常量表达式。当把constexpr函数用在需要常量表达式的上下文中时,由编译器负责检查函数的结果是否符合要求。如果结果恰好不是常量表达式。编译器将发出错误信息

    3、调试帮助

    1、assert预处理宏

      assert宏定义在cassert头文件中。assert是一种预处理宏。所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。assert宏使用一个表达式作为它的条件:

      assert(expr);

    首先对expr求值,如果表达式为假(即为0),assert输出信息并终止程序的执行。如果表达式为真(即非0),assert什么也不做。

    2、NDEBUG预处理变量

      assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。

      预处理器定义了几个对调试很有用的名字,如下表所示:

    名字 说明
    __FUNCTION__ 存放函数名的字符串字面值
    __FILE__ 存放文件名的字符串字面值
    __LINE__ 存放当前行号的整型字面值
    __TIME__ 存放文件编译时间的字符串字面值
    __DATE__ 存放文件编译日期的字符串字面值

    三、函数匹配

    1、实参类型转换

       为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级,具体排序如下所示:

      1)精确匹配,包括以下情况:

      a、实参类型和形参类型相同。

      b、实参从数组类型或函数类型转换成对应的指针类型。

      c、向实参添加顶层const或者从实参中删除顶层const。

      2)通过const转换实现的匹配。

      3)通过类型提升实现的匹配。

      4)通过算术类型转换(所有算术类型转换的级别都一样)或指针转换实现的匹配。

      5)通过类类型转换实现的匹配。

    四、函数指针

       函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。

    1 bool lengthCompare(const std::string &s1, const std::string &s2)
    2 {
    3     return s1.size() > s2.size();
    4 }

    该函数的类型是bool(const std::string &, const std::string &)。要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可。

    1     //pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
    2     bool (*pf)(const std::string &, const std::string &); // 未初始化,*pf两端的括号必不可少

    1、使用函数指针

    1     // 当我们把函数名作为一个值使用时,该函数自动地转换成指针
    2     pf = lengthCompare;
    3     pf = &lengthCompare; // 等价的赋值语句:取地址符是可选的
    4     // 此外,我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针
    5     bool b1 = pf("hello", "world");
    6     bool b2 = (*pf)("hello", "world");
    7     bool b3 = lengthCompare("hello", "world"); // 三条调用语句等价

      在指向不同函数类型的指针间不存在转换规则。但是,我们可以为函数指针赋一个nullptr或者值为0的整型表达式,表示该指针没有指向任何一个函数。

    2、函数指针形参

      和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。

    1 // 参数是函数类型,它会自动地转换成指向函数的指针
    2 void use(bool pf(const std::string &, const std::string &));
    3 // 等价的声明:显示地将形参定义成指向函数的指针
    4 void use(bool (*pf)(const std::string &, const std::string &));

      可以直接把函数作为实参使用,此时它会自动转换成指针

      use(lengthCompare);

      类型别名可以简化使用了函数指针的代码:

    1     // Func和Func2是函数类型
    2     typedef bool Func(const std::string &, const std::string &);
    3     typedef decltype(lengthCompare) Func2; // 等价的类型
    4     // FuncP和FuncP2是指向函数的指针
    5     typedef bool(*FuncP)(const std::string &, const std::string &);
    6     typedef decltype(lengthCompare) *FuncP2; // 等价的类型

    3、返回指向函数的指针

      和数组类似,虽然不能返回一个函数,但是能返回指向函数类型的指针。然而,必须显示地将返回类型写成指针形式,编译器不会自动地将函数返回类型当成对应的指针类型处理

    4、将auto和decltype用于函数指针类型

      当将decltype作用于某个函数时,它返回函数类型而非指针类型。因此,我们必须显示地加上*以表明我们需要返回指针。

  • 相关阅读:
    canvas游戏开发系列(1):基础知识
    HTML5拖拽实例
    Jfinal 源码分析之拦截器的使用
    jfinal ——AOP面向切面编程
    JFinal框架源码分析(二)——JFinal控制器
    企业级Tomcat部署配置
    KICKSTART无人值守安装
    ELK 企业内部日志分析系统
    全球性WannaCry蠕虫勒索病毒感染前后应对措施
    LAMP架构应用实战—Apache服务介绍与安装01
  • 原文地址:https://www.cnblogs.com/ACGame/p/10181078.html
Copyright © 2011-2022 走看看