精华内容
下载资源
问答
  • 函数重载

    2018-04-28 14:06:05
    函数多态又称为函数重载函数重载即同一个函数名,不同的形参列表。 函数重载的关键是函数的参数列表,若两个函数的参数数目... 不是重载函数重载使用原则;不要滥用,只有当函数基本上执行相同任务时,但使用不同形...

    函数多态又称为函数重载,函数重载即同一个函数名,不同的形参列表。 函数重载的关键是函数的参数列表,若两个函数的参数数目和类型相同,参数的排序也相同,则它们的参数列表就相同。函数返回值类型不能作为重载条件。

    long gronk (int n,float m);

    double gronk (int n,float m);  不是重载


    函数重载使用原则;不要滥用,只有当函数基本上执行相同任务时,但使用不同形式的数据时,才使用重载。

    判断数字多少位  :

    unsigned digits = 1;

    while (n /= 10)
    {
    digits++;

    }

    假设已知数字共有5位,即disits=5,要删除最后两位,只保留前三位,即ct初值为2,可执行以下程序。

    ct = disits - ct;
    while (ct--)
    {
    num /= 10;
    }
    return num;

    展开全文
  • 函数重载和const形参的函数重载

    千次阅读 2014-09-04 22:09:34
    1、函数重载和重复声明的区别 如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的: Record ...

    C++primer中文版:

    重载函数:出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同。切记main 函数不能重载

    1、函数重载和重复声明的区别

    如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的:
    Record lookup(const Account&);
    bool lookup(const Account&); // error: only return type is different
    函数不能仅仅基于不同的返回类型而实现重载。
        值得注意的是,形参与 const 形参的等价性仅适用于非引用形参。有 const 引用形参的函数与有非 const 引用形参的函数是不同的。类似地,如果函数带有指向 const 类型的指针形参,则与带有指向相同类型的非 const 对象的指针形参的函数不相同。

    2、函数匹配与实参转换

             函数重载确定,即函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程。通过自动提取函数调用中实际使用的实参与重载集合中各个函数提供的形参做比较,编译器实现该调用与函数的匹配。匹配结果有三种可能:
    1.编译器找到与实参最佳匹配的函数,并生成调用该函数的代码。
    2.找不到形参与函数调用的实参匹配的函数,在这种情况下,编译器将给出编译错误信息。
    3.存在多个与实参匹配的函数,但没有一个是明显的最佳选择。这种情况也是,该调用具有二义性。
        大多数情况下,编译器都可以直接明确地判断一个实际的调用是否合法,如果合法,则应该调用哪一个函数。重载集合中的函数通常有不同个数的参数或无关联的参数类型。当多个函数的形参具有可通过隐式转换关联起来的类型,则函数匹配将相当灵活。在这种情况下,需要程序员充分地掌握函数匹配的过程。

    3、重载确定的三个步骤

    考虑下面的这组函数和函数调用:
    void f();
    void f(int);
    void f(int, int);
    void f(double, double = 3.14);
    f(5.6); // calls void f(double, double)

    3.1 候选函数

    函数重载确定的第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数。候选函数是与被调函数同名的函数,并且在调用点上,它的声明可见。在这个例子中,有四个名为 f 的候选函数。

    3.2 选择可行函数

        第二步是从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数。可行函数必须满足两个条件:第一,函数的形参个数与该调用的实参个数相同;第二,每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。
        对于函数调用 f(5.6),可首先排除两个实参个数不匹配的候选函数。没有形参的 f 函数和有两个 int 型形参的 f 函数对于这个函数调用来说都不可行。例中的调用只有一个实参,而这些函数分别带有零个和两个形参。
    另一方面,有两个 double 型参数的 f 函数可能是可行的。调用带有默认实参的函数时可忽略这个实参。编译器自动将默认实参的值提供给被忽略的实参。因此,某个调用拥有的实参可能比显式给出的多。
        根据实参个数选出潜在的可行函数后,必须检查实参的类型是否与对应的形参类型匹配。与任意函数调用一样,实参必须与它的形参匹配,它们的类型要么精确匹配,要么实参类型能够转换为形参类型。在这个例子中,余下的两个函数都是是可行的。
    •f(int) 是一个可行函数,因为通过隐式转换可将函数调用中的 double型实参转换为该函数唯一的 int 型形参。
    •f(double, double) 也是一个可行函数,因为该函数为其第二个形参提供了默认实参,而且第一个形参是 double 类型,与实参类型精确匹配。
        如果没有找到可行函数,则该调用错误。

    3.3 寻找最佳匹配(如果有的话)

        函数重载确定的第三步是确定与函数调用中使用的实际参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行函数。这里所谓“最佳”的细节将在下一节中解释,其原则是实参类型与形参类型越接近则匹配越佳。因此,实参类型与形参类型之间的精确类型匹配比需要转换的匹配好。
        在上述例子中,只需考虑一个 double 类型的显式实参。如果调用 f(int),实参需从 double 型转换为 int 型。而另一个可行函数 f(double, double) 则与该实参精确匹配。由于精确匹配优于需要类型转换的匹配,因此编译器将会把函数调用 f(5.6) 解释为对带有两个 double 形参的 f 函数的调用。

    4、含有多个形参的重载确定

        如果函数调用使用了两个或两个以上的显式实参,则函数匹配会更加复杂。假设有两样的名为 f 的函数,分析下面的函数调用:
    f(42, 2.56);
        可行函数将以同样的方式选出。编译器将选出形参个数和类型都与实参匹配的函数。在本例中,可行函数是 f(int, int) 和 f(double, double)。接下来,编译器通过依次检查每一个实参来决定哪个或哪些函数匹配最佳。如果有且仅有一个函数满足下列条件,则匹配成功:
    1.其每个实参的匹配都不劣于其他可行函数需要的匹配。
    2.至少有一个实参的匹配优于其他可行函数提供的匹配。
        如果在检查了所有实参后,仍找不到唯一最佳匹配函数,则该调用错误。编译器将提示该调用具有二义性。
        在本例子的调用中,首先分析第一个实参,发现函数 f(int, int) 匹配精确。如果使之与第二个函数匹配,就必须将 int 型实参 42 转换为 double 型的值。通过内置转换的匹配“劣于”精确匹配。所以,如果只考虑这个形参,带有两个int 型形参的函数比带有两个 double 型形参的函数匹配更佳。
        但是,当分析第二个实参时,有两个 double 型形参的函数为实参 2.56 提供了精确匹配。而调用两个 int 型形参的 f 函数版本则需要把 2.56 从 double 型转换为 int 型。所以只考虑第二个形参的话,函数 f(double, double) 匹配更佳。
        如果在检查了所有实参后,仍找不到唯一最佳匹配函数,则该调用错误。编译器将提示该调用具有二义性。
        在本例子的调用中,首先分析第一个实参,发现函数 f(int, int) 匹配精确。如果使之与第二个函数匹配,就必须将 int 型实参 42 转换为 double 型的值。通过内置转换的匹配“劣于”精确匹配。所以,如果只考虑这个形参,带有两个int 型形参的函数比带有两个 double 型形参的函数匹配更佳。
        但是,当分析第二个实参时,有两个 double 型形参的函数为实参 2.56 提供了精确匹配。而调用两个 int 型形参的 f 函数版本则需要把 2.56 从 double 型转换为 int 型。所以只考虑第二个形参的话,函数 f(double, double) 匹配更佳。
        因此,这个调用有二义性:每个可行函数都对函数调用的一个实参实现更好的匹配。编译器将产生错误。解决这样的二义性,可通过显式的强制类型转换强制函数匹配:
    f(static_cast<double>(42), 2.56); // calls f(double, double)
    f(42, static_cast<int>(2.56)); // calls f(int, int)
        在实际应用中,调用重载函数时应尽量避免对实参做强制类型转换:需要使用强制类型转换意味着所设计的形
    参集合不合理。

    5、实参类型转换

        为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:
    1.精确匹配。实参与形参类型相同。
    2.通过类型提升实现的匹配。
    3.通过标准转换实现的匹配。
    4.通过类类型转换实现的匹配。
        内置类型的提升和转换可能会使函数匹配产生意想不到的结果。但幸运的是,设计良好的系统很少会包含与下面例子类似的形参类型如此接近的函数。
    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    (转载)含有const形参函数的重载:

    record lookup( phone);

    record lookup(const phone);

    这两个属于函数重载吗?

    这个还需要从实参与形参之间的关系中去寻找答案,这对的区别仅仅在于是否将形参定义为const,这种差异并不影响传递到函数的对象,因此第二个函数声明被视为第一个的重复声明。

    起因:实参传递方式。复制实参的副本给形参时并不考虑形参是否为const,因为函数操纵的只是副本。函数无法修改实参。因此,既可将const对象传递给const形参,也可传递给非const形参,这两种形参并无本质区别。

    例如:

    record lookup(15001805405);//这里就假设传递个本人的手机号码吧

    此时该调用上面哪个函数呢?很明显两个都可以,形参跟实参都有属于各自的爹妈,各自的空间,互不干涉。

    然而这个时候编译器就会傻眼,二义性问题,编译器就成了sb了,因此结论:这组函数不属于函数重载。

    ps:形参与const形参的等价性仅仅适用于非引用形参。有const引用形参的函数与有非const引用形参的函数是不同的。近似的,如果函数带有指向const类型的指针形参,则与带有指向相同类型的非const对象的指针形参的函数不形同。

    record lookup(const phone&);

    record lookup(phone&);

    那这两个呢?属于函数重载吗?

    答案是属于。肯定属于。

    例如:

    const phone a=15001805405;//贡献第二次

    record lookup(a);这个该调用那个呢?

    很明显大家都晓得,上面那个。理由就不需要唠叨了吧。

    有人会问这个问题:

    phone a=15001805405;

    那这个该调用那个呢?

    下面那个?上面那个?

    结论:这种情况优先匹配非const那个,如果没有那也可以匹配const这个。

    有依据没? 有的,经试验测试得出的结论:

    试验如下:

    #include<iostream>
    using namespace std;
    int sb(const int& b)
    {
    cout<<"今天晚上我让她反感了!这是我的错!"<<endl;
    return b;
    }
    int sb(int &b)
    {
    cout<<"不过我认为第一次嘛,可以原谅吧,做人好难,也好累!"<<endl;
    return b;
    }
    int main()
    {
    int a=2;
    cout<<sb(a)<<endl;

    cout<<"有些事想明白了就是幸福,想不明白就是痛苦!"<<endl;
    return EXIT_SUCCESS;
    }

    -------------------------------------------------------------------------------------------------------------------------------------
    《C++ primer》中提到“仅当形参是引用或指针的时候,形参是否为const才对重载有影响。”
     
    int add(int a, int b);
    int add(const int a, const int b);
    我想通过定义这两个函数来实现实参是否为const的重载,可事与愿违,这里的第二个函数并没有对第一个进行overloading,而是redefinition。因为,在此的两个函数的形参并不会直接关联到实参,在调用这两个函数的时候,形参都只是实参的一个副本,不管add函数内部对形参做什么处理,都不会影响到实参,也就是说——第二个函数形参中的const没有任何的用处,只是多此一举罢了。所以在此的第二个定义只是对第一个的重定义罢了。

    int add(int &a, int &b);
    int add(const int &a, const int &b);
    这次定义的两个函数与上面不同的地方就是形参使用了引用。这个时候编译器就完全可以根据实参是否为const确定调用哪一个函数了。调用如下:
    //非const变量x, y
    int x = 1;
    int y = 2;

    add(x, y); //call add(int &a, int &b)

    //const变量x, y
    const int x = 1;
    const int y = 2;

    add(x, y); //call add(const int &a, const int &b)
    上述第一种情况:实参为非const对象的时候,其实两个函数都可以被调用,都与之匹配,因为非const对象不但可以初始化非const引用,也可以初始化const引用。但由于非const对象初始化const引用的时候涉及到类型转换,所以此时带非const引用形参的函数为最佳匹配。
    上述第二种情况:实参为const对象的时候,就不能将此对象传递给带非const引用的形参的函数了,因为const对象只能用来初始化const引用。

    int add(int *a, int *b);
    int add(const int *a, const int *b);

    //非const对象
    int x = 1;
    int y = 2;
    //cosnt对象
    const int r = 1;
    const int s = 2;

    add(&x, &y); //call add(int *a, int *b);
    add(&r, &s); //call add(cosnt int *a, cosnt int *b);

    展开全文
  • 关于函数重载

    2017-09-20 23:11:10
    构成函数重载的条件如下: 1.函数名相同。 2.这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,即参数列表不同。 3.返回值类型不同不构成重载函数。 二.重载函数的匹配原则: 1、严格匹配,找到后就...

    一.构成函数重载的条件如下:

    1.函数名相同。

    2.这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,即参数列表不同。

    3.返回值类型不同不构成重载函数。

    二.重载函数的匹配原则:

    1、严格匹配,找到后就调用。

    2、没有找到严格匹配的函数,但是可以通过隐式转化,找到适合的重载函数。

    例:

    1.参数类型不一样

    void print(int a)

    {

    printf("void print(int a)\n");

    }

    void print(string a)

    {

    printf("void print(string a)\n");

    }

    2.参数个数不同(较1)

    void print(int a ,string b);

    3.参数顺序不同(较2)

    void print(string a ,int b);

    为了估计哪个重载函数最适合,需要依次按照下列规则来判断:

    • 精确匹配:参数匹配而不做转换,或者只是做微不足道的转换,如数组名到指针、函数名到指向函数的指针、T到const T;
    • 提升匹配:即整数提升(如bool 到 int、char到int、short 到int),float到double
    • 使用标准转换匹配:如int 到double、double到int、double到long double、Derived*到Base*、T*到void*、int到unsigned int;
    • 使用用户自定义匹配
    • 使用省略号匹配:类似printf中省略号参数

    “同一作用域”也是在函数重载的定义中的一个限定,如果不在一个作用域,不能算是函数重载,如下面的代码:

    void f(int);
    
    void g()
    {
            void f(double);
            f(1); //这里调用的是f(double),而不是f(int)
    }
    内层作用域的函数会隐藏外层的同名函数同样的派生类的成员函数会隐藏基类的同名函数。这很好理解,变量的访问也是如此,如一个函数体内要访问全局的同名变量要用“::”限定。

    本文只是作粗略基本介绍,了解更多请参照大神http://www.cnblogs.com/skynet/archive/2010/09/05/1818636.html


    展开全文
  • C++的函数重载

    千次阅读 多人点赞 2018-07-11 13:44:56
    ——每个现象后面都隐藏一个本质,关键在于我们是否去挖掘 ...函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的)
    原文地址为:C++的函数重载

    ——每个现象后面都隐藏一个本质,关键在于我们是否去挖掘

    写在前面:

    函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的)?这个可以分解为下面两个问题

    • 1、声明/定义重载函数时,是如何解决命名冲突的?(抛开函数重载不谈,using就是一种解决命名冲突的方法,解决命名冲突还有很多其它的方法,这里就不论述了)
    • 2、当我们调用一个重载的函数时,又是如何去解析的?(即怎么知道调用的是哪个函数呢)

    这两个问题是任何支持函数重载的语言都必须要解决的问题!带着这两个问题,我们开始本文的探讨。本文的主要内容如下:

    •  1、例子引入(现象)
      • 什么是函数重载(what)?
      • 为什么需要函数重载(why)?
    • 2、编译器如何解决命名冲突的?
      • 函数重载为什么不考虑返回值类型
    • 3、重载函数的调用匹配
      • 模凌两可的情况
    • 4、编译器是如何解析重载函数调用的?
      • 根据函数名确定候选函数集
      • 确定可用函数
      • 确定最佳匹配函数
    • 5、总结

    1、例子引入(现象)

    1.1、什么是函数重载(what)?

    函数重载是指在同一作用域内,可以有一组具有相同函数名不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

    When two or more different declarations are specified for a single name in the same scope,  that name is said to overloaded.  By extension, two declarations in the same scope that declare the same name but with different types are called overloaded declarations. Only function declarations can be overloaded; object and type declarations cannot be overloaded. ——摘自《ANSI C++ Standard. P290》

    看下面的一个例子,来体会一下:实现一个打印函数,既可以打印int型、也可以打印字符串型。在C++中,我们可以这样做:

    #include<iostream>
    using namespace std;

    void print(int i)
    {
    cout<<"print a integer :"<<i<<endl;
    }

    void print(string str)
    {
    cout<<"print a string :"<<str<<endl;
    }

    int main()
    {
    print(12);
    print("hello world!");
    return 0;
    }

    通过上面代码的实现,可以根据具体的print()的参数去调用print(int)还是print(string)。上面print(12)会去调用print(int),print("hello world")会去调用print(string),如下面的结果:(先用g++ test.c编译,然后执行)

    C  函数重载例子1

    1.2、为什么需要函数重载(why)?

    • 试想如果没有函数重载机制,如在C中,你必须要这样去做:为这个print函数取不同的名字,如print_int、print_string。这里还只是两个的情况,如果是很多个的话,就需要为实现同一个功能的函数取很多个名字,如加入打印long型、char*、各种类型的数组等等。这样做很不友好!
    • 类的构造函数跟类名相同,也就是说:构造函数都同名。如果没有函数重载机制,要想实例化不同的对象,那是相当的麻烦!
    • 操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用,如+可用于连接字符串等!

    通过上面的介绍我们对函数重载,应该唤醒了我们对函数重载的大概记忆。下面我们就来分析,C++是如何实现函数重载机制的。

    2、编译器如何解决命名冲突的?

    为了了解编译器是如何处理这些重载函数的,我们反编译下上面我们生成的执行文件,看下汇编代码(全文都是在Linux下面做的实验,Windows类似,你也可以参考《一道简单的题目引发的思考》一文,那里既用到Linux下面的反汇编和Windows下面的反汇编,并注明了Linux和Windows汇编语言的区别)。我们执行命令objdump -d a.out >log.txt反汇编并将结果重定向到log.txt文件中,然后分析log.txt文件。

    发现函数void print(int i) 编译之后为:(注意它的函数签名变为——_Z5printi

    image

    发现函数void print(string str) 编译之后为:(注意它的函数签名变为——_Z5printSs

    image

    我们可以发现编译之后,重载函数的名字变了不再都是print!这样不存在命名冲突的问题了,但又有新的问题了——变名机制是怎样的,即如何将一个重载函数的签名映射到一个新的标识?我的第一反应是:函数名+参数列表,因为函数重载取决于参数的类型、个数,而跟返回类型无关。但看下面的映射关系:

    void print(int i)                    -->         _Z5printi
    void print(string str)         -->         _Z5printSs

    进一步猜想,前面的Z5表示返回值类型,print函数名,i表示整型int,Ss表示字符串string,即映射为返回类型+函数名+参数列表。最后在main函数中就是通过_Z5printi_Z5printSs来调用对应的函数的:

    80489bc:       e8 73 ff ff ff          call   8048934 <_Z5printi>
    ……………
    80489f0:       e8 7a ff ff ff          call   804896f <_Z5printSs>

    我们再写几个重载函数来验证一下猜想,如:

    void print(long l)           -->           _Z5printl
    void print(char str)      -->           _Z5printc
    可以发现大概是int->i,long->l,char->c,string->Ss….基本上都是用首字母代表,现在我们来现在一个函数的返回值类型是否真的对函数变名有影响,如:

    #include<iostream>
    using namespace std;

    int max(int a,int b)
    {
    return a>=b?a:b;
    }

    double max(double a,double b)
    {
    return a>=b?a:b;
    }
    int main()
    {
    cout<<"max int is: "<<max(1,3)<<endl;
    cout<<"max double is: "<<max(1.2,1.3)<<endl;
    return 0;
    }

    int max(int a,int b) 映射为_Z3maxiidouble max(double a,double b) 映射为_Z3maxdd,这证实了我的猜想,Z后面的数字代码各种返回类型。更加详细的对应关系,如那个数字对应那个返回类型,哪个字符代表哪重参数类型,就不去具体研究了,因为这个东西跟编译器有关,上面的研究都是基于g++编译器,如果用的是vs编译器的话,对应关系跟这个肯定不一样。但是规则是一样的:“返回类型+函数名+参数列表”。

    既然返回类型也考虑到映射机制中,这样不同的返回类型映射之后的函数名肯定不一样了,但为什么不将函数返回类型考虑到函数重载中呢?——这是为了保持解析操作符或函数调用时,独立于上下文(不依赖于上下文),看下面的例子

    float sqrt(float);
    double sqrt(double);

    void f(double da, float fla)
    {
    float fl=sqrt(da);//调用sqrt(double)
    double d=sqrt(da);//调用sqrt(double)

    fl=sqrt(fla);//调用sqrt(float)
    d=sqrt(fla);//调用sqrt(float)
    }

    如果返回类型考虑到函数重载中,这样将不可能再独立于上下文决定调用哪个函数。

    至此似乎已经完全分析清楚了,但我们还漏了函数重载的重要限定——作用域。上面我们介绍的函数重载都是全局函数,下面我们来看一下一个类中的函数重载,用类的对象调用print函数,并根据实参调用不同的函数:

    #include<iostream>
    using namespace std;

    class test{
    public:
    void print(int i)
    {
    cout<<"int"<<endl;
    }
    void print(char c)
    {
    cout<<"char"<<endl;
    }
    };
    int main()
    {
    test t;
    t.print(1);
    t.print('a');
    return 0;
    }

    我们现在再来看一下这时print函数映射之后的函数名:

    void print(int i)                    -->            _ZN4test5printEi

    void print(char c)               -->            _ZN4test5printEc

    注意前面的N4test,我们可以很容易猜到应该表示作用域,N4可能为命名空间、test类名等等。这说明最准确的映射机制为:作用域+返回类型+函数名+参数列表

    3、重载函数的调用匹配

    现在已经解决了重载函数命名冲突的问题,在定义完重载函数之后,用函数名调用的时候是如何去解析的?为了估计哪个重载函数最适合,需要依次按照下列规则来判断:

    • 精确匹配:参数匹配而不做转换,或者只是做微不足道的转换,如数组名到指针、函数名到指向函数的指针、T到const T;
    • 提升匹配:即整数提升(如bool 到 int、char到int、short 到int),float到double
    • 使用标准转换匹配:如int 到double、double到int、double到long double、Derived*到Base*、T*到void*、int到unsigned int;
    • 使用用户自定义匹配
    • 使用省略号匹配:类似printf中省略号参数

    如果在最高层有多个匹配函数找到,调用将被拒绝(因为有歧义、模凌两可)。看下面的例子:

    void print(int);
    void print(const char*);
    void print(double);
    void print(long);
    void print(char);

    void h(char c,int i,short s, float f)
    {
    print(c);//精确匹配,调用print(char)
    print(i);//精确匹配,调用print(int)
    print(s);//整数提升,调用print(int)
    print(f);//float到double的提升,调用print(double)

    print('a');//精确匹配,调用print(char)
    print(49);//精确匹配,调用print(int)
    print(0);//精确匹配,调用print(int)
    print("a");//精确匹配,调用print(const char*)
    }

    定义太少或太多的重载函数,都有可能导致模凌两可,看下面的一个例子:

    void f1(char);
    void f1(long);

    void f2(char*);
    void f2(int*);

    void k(int i)
    {
    f1(i);//调用f1(char)? f1(long)?
    f2(0);//调用f2(char*)?f2(int*)?
    }

    这时侯编译器就会报错,将错误抛给用户自己来处理:通过显示类型转换来调用等等(如f2(static_cast<int *>(0),当然这样做很丑,而且你想调用别的方法时有用做转换)。上面的例子只是一个参数的情况,下面我们再来看一个两个参数的情况:

    int pow(int ,int);
    double pow(double,double);

    void g()
    {
    double d=pow(2.0,2)//调用pow(int(2.0),2)? pow(2.0,double(2))?
    }

    4、编译器是如何解析重载函数调用的?

    编译器实现调用重载函数解析机制的时候,肯定是首先找出同名的一些候选函数,然后从候选函数中找出最符合的,如果找不到就报错。下面介绍一种重载函数解析的方法:编译器在对重载函数调用进行处理时,由语法分析、C++文法、符号表、抽象语法树交互处理,交互图大致如下:

    image 

    这个四个解析步骤所做的事情大致如下:

    • 由匹配文法中的函数调用,获取函数名;
    • 获得函数各参数表达式类型;
    • 语法分析器查找重载函数,符号表内部经过重载解析返回最佳的函数
    • 语法分析器创建抽象语法树,将符号表中存储的最佳函数绑定到抽象语法树上

     

    下面我们重点解释一下重载解析,重载解析要满足前面《3、重载函数的调用匹配》中介绍的匹配顺序和规则。重载函数解析大致可以分为三步:

    • 根据函数名确定候选函数集
    • 从候选函数集中选择可用函数集合
    • 从可用函数集中确定最佳函数,或由于模凌两可返回错误

    4.1、根据函数名确定候选函数集

    根据函数在同一作用域内所有同名的函数,并且要求是可见的(像private、protected、public、friend之类)。“同一作用域”也是在函数重载的定义中的一个限定,如果不在一个作用域,不能算是函数重载,如下面的代码:

    void f(int);

    void g()
    {
    void f(double);
    f(1); //这里调用的是f(double),而不是f(int)
    }

    内层作用域的函数会隐藏外层的同名函数同样的派生类的成员函数会隐藏基类的同名函数。这很好理解,变量的访问也是如此,如一个函数体内要访问全局的同名变量要用“::”限定。

    为了查找候选函数集,一般采用深度优选搜索算法:

    step1:从函数调用点开始查找,逐层作用域向外查找可见的候选函数
    step2:如果上一步收集的不在用户自定义命名空间中,则用到了using机制引入的命名空间中的候选函数,否则结束

    在收集候选函数时,如果调用函数的实参类型为非结构体类型,候选函数仅包含调用点可见的函数;如果调用函数的实参类型包括类类型对象、类类型指针、类类型引用或指向类成员的指针,候选函数为下面集合的并:

    • (1)在调用点上可见的函数;
    • (2)在定义该类类型的名字空间或定义该类的基类的名字空间中声明的函数;
    • (3)该类或其基类的友元函数;

    下面我们来看一个例子更直观:

    void f();
    void f(int);
    void f(double, double = 314);
    names pace N
    {
    void f(char3 ,char3);
    }
    classA{
    public: operat or double() { }
    };
    int main ( )
    {
    using names pace N; //using指示符
    A a;
    f(a);
    return 0;
    }

     

    根据上述方法,由于实参是类类型的对象,候选函数的收集分为3步:

    (1)从函数调用所在的main函数作用域内开始查找函数f的声明, 结果未找到。到main函数
    作用域的外层作用域查找,此时在全局作用域找到3个函数f的声明,将它们放入候选集合;

    (2)到using指示符所指向的命名空间 N中收集f ( char3 , char3 ) ;

    (3)考虑2类集合。其一为定义该类类型的名字空间或定义该类的基类的名字空间中声明的函
    数;其二为该类或其基类的友元函数。本例中这2类集合为空。

    最终候选集合为上述所列的 4个函数f。

    4.2、确定可用函数

    可用的函数是指:函数参数个数匹配并且每一个参数都有隐式转换序列。

    • (1)如果实参有m个参数,所有候选参数中,有且只有 m个参数;
    • (2)所有候选参数中,参数个数不足m个,当前仅当参数列表中有省略号;
    • (3)所有候选参数中,参数个数超过 m个,当前仅当第m + 1个参数以后都有缺省值。如果可用
      集合为空,函数调用会失败。

    这些规则在前面的《3、重载函数的调用匹配》中就有所体现了。

    4.3、确定最佳匹配函数

    确定可用函数之后,对可用函数集中的每一个函数,如果调用函数的实参要调用它计算优先级,最后选出优先级最高的。如对《3、重载函数的调用匹配》中介绍的匹配规则中按顺序分配权重,然后计算总的优先级,最后选出最优的函数。

     

    5、总结

    本文介绍了什么是函数重载、为什么需要函数重载、编译器如何解决函数重名问题、编译器如何解析重载函数的调用。通过本文,我想大家对C++中的重载应该算是比较清楚了。说明:在介绍函数名映射机制是基于g++编译器,不同的编译器映射有些差别;编译器解析重载函数的调用,也只是所有编译器中的一种。如果你对某个编译器感兴趣,请自己深入去研究。

    最后我抛给大家两个问题:

    • 1、在C++中加号+,即可用于两个int型之间的相加、也可以用于浮点数数之间的相加、字符串之间的连接,那+算不算是操作符重载呢?换个场景C语言中加号+,即可用于两个int型之间的相加、也可以用于浮点数数之间的相加,那算不算操作符重载呢?
    • 2、模板(template)的重载时怎么样的?模板函数和普通函数构成的重载,调用时又是如何匹配的呢?

    附录:一种C++函数重载机制

    这个机制是由张素琴等人提出并实现的,他们写了一个C++的编译系统COC++(开发在国产机上,UNIX操作系统环境下具有中国自己版权的C、C++和FORTRAN语言编译系统,这些编译系统分别满足了ISOC90、AT&T的C++85和ISOFORTRAN90标准)。COC++中的函数重载处理过程主要包括两个子过程:

    • 1、在函数声明时的处理过程中,编译系统建立函数声明原型链表,按照换名规则进行换名并在函数声明原型链表中记录函数换名后的名字(换名规则跟本文上面描述的差不多,只是那个int-》为哪个字符、char-》为哪个字符等等类似的差异)

    image

    图附1、过程1-建立函数链表(说明,函数名的编码格式为:<原函数名>_<作用域换名><函数参数表编码>,这跟g++中的有点不一样)

    • 2、在函数调用语句翻译过程中,访问符号表,查找相应函数声明原型链表,按照类型匹配原则,查找最优匹配函数节点,并输出换名后的名字下面给出两个子过程的算法建立函数声明原型链表算法流程如图附1,函数调用语句翻译算法流程如图附2。

    image

    图附2、过程2- 重载函数调用,查找链表

    附-模板函数和普通函数构成的重载,调用时又是如何匹配的呢?

    下面是C++创始人Bjarne Stroustrup的回答:

    1)Find the set of function template specializations that will take part in overload resolution.

    2)if two template functions can be called and one is more specified than the other, consider only the most specialized template function in the following steps.

    3)Do overload resolution for this set of functions, plus any ordinary functions as for ordinary functions.

    4)If a function and a specialization are equally good matches, the function is perferred.

    5)If no match is found, the call is an error.

     


    转载请注明本文地址:C++的函数重载
    展开全文
  • c++函数重载详解

    2017-07-26 09:54:34
    函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的)?这个可以分解为下面两个问题 1、声明/定义重载函数时,是如何解决命名冲突...
  • 重载之函数重载

    2016-04-17 12:43:57
    重载函数函数的一种特殊情况,为方便使用,C++允许在同一范围中声明几个功能类似的同名函数函数重载其实就是"一物多用"的思想(这里指的"物"是"函数名")。但是这些同名函数的形式参数(指参数的个数、类型或者...
  • C++函数重载

    千次阅读 2016-10-07 13:31:31
    函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的)?这个可以分解为下面两个问题 1、声明/定义重载函数时,是如何解决命名冲突的...
  • C++ 运算符重载:成员、非成员函数重载

    千次阅读 多人点赞 2020-06-19 19:41:34
    C++ 运算符重载运算符重载1、背景2、运算符函数重载的两种形式1、成员函数重载1、定义格式2、非成员函数重载(友元)1、定义格式3、重载原则4、参数和返回值5、成员函数重载1、双目运算符重载1、定义2、调用格式2、...
  • C++函数重载的理解

    2013-04-20 17:25:06
    函数重载 写在前面: 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的)?这个可以分解为下面两个问题 1、声明/定义重载函数...
  • 函数重载的本质

    2016-08-14 23:12:53
    C++的函数重载 2010-09-05 21:26 by 吴秦, 67631 阅读, 16 评论, 收藏, 编辑 ——每个现象后面都隐藏一个本质,关键在于我们是否去挖掘 写在前面: 函数重载的重要性不言而明,但是你知道C++中函数...
  • 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的)?这个可以分解为下面两个问题 1、声明/定义重载函数时,是如何解决命名冲突的...
  • 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的)?这个可以分解为下面两个问题 1、声明/定义重载函数时,是如何解决命名冲突...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 28,487
精华内容 11,394
关键字:

判断函数重载的原则是