zoukankan      html  css  js  c++  java
  • c++中的函数重载、函数重写、函数重定义

    目录

      一、函数重载

      二、函数重写

      三、函数重定义


    为了更加深刻的理解 函数重载、重写、重定义,我们可以带着如下这两个问题去思考:

    1、子类中是否可以定义父类中的同名成员?为什么?

      可以,因为子类与父类的命名空间不同;

    2、子类中定义的函数是否可以重载父类中的同名函数?

      不可以,因为函数重载必须在同一个作用域中。


    一、函数重载(Function Overloading) 

    1、什么是函数重载

      在同一个类中(同一个作用域中/在类的内部),存在一组函数名相同,函数的参数列表不同(参数的个数、类型、顺序),函数有无 virtual 关键字都可以,我们把这组函数称为函数重载。

    2、为什么使用函数重载(函数重载的好处)

      由于函数重载可以在同一个作用域内,使用同一个函数名 命名一组功能相似的函数,这样做减少了函数名的数量,避免了程序员因给函数名命名所带来的烦恼,从而提高程序的开发的效率。

    3、函数重载的条件

      1. 必须在同一作用域下

      2. 函数名相同但是参数列表不同(参数列表的 类型 or 个数 or 顺序 不同)

      3. 返回值的类型不会影响重载

      4. const属性相同

    4、函数重载的原理(本质:c++编译器对同名函数进行重命名)

      编译器在编译.cpp文件中当前使用的作用域里的同名函数时,根据函数形参的类型和顺序会对函数进行重命名(不同的编译器在编译时对函数的重命名标准不一样);

      但是总的来说,他们都把文件中的同一个函数名进行了重命名

    • 在vs编译器中:

      根据返回值类型(不起决定性作用)+形参类型和顺序(起决定性作用)的规则重命名并记录在map文件中。

    • 在linux g++ 编译器中:

      根据函数名字的字符数+形参类型和顺序的规则重命名记录在符号表中;从而产生不同的函数名,当外面的函数被调用时,便是根据这个记录的结果去寻找符合要求的函数名,进行调用;

      为什么c语言不能实现函数重载?

      编译器在编译.c文件时,只会给函数进行简单的重命名;

      具体的方法是给函数名之前加上”_”;所以加入两个函数名相同的函数在编译之后的函数名也照样相同;调用者会因为不知道到底调用那个而出错;

     1 #include<stdio.h>
     2 
     3 int Add(int a, int b)
     4 {
     5     return a + b;
     6 }
     7 
     8 
     9 float Add(float a, float b)
    10 {
    11     return a + b;
    12 }
    13 
    14 void testFunc()
    15 {
    16     Add(10, 20);
    17     Add(20.0f, 30.0f);
    18 }
    19 
    20 int main(int argc, char *argv[])
    21 {
    22     testFunc();
    23 
    24     return 0;
    25 }
    案例分析

    1.  将上述代码保存到.c文件中

      若上述代码用c编译器编译,由于c语言中无函数重载,所以,在程序运行时出错。

      出错原因:因为在c语言中,c编译器只是在函数名的前面加下划线进行简单的重命名

      为了验证结果,将上述的代码稍作修改( float Add(float a, float b) -> float Add1(float a, float b) )。然后用 vs Debug模式编译.c文件,之后在.map文件中就可以看到结果。

           

      在vs中,map文件生成的步骤设置:工程名右击—>属性—->配置属性—->链接器—–>调试—->生成映射文件—>选择是;

    2.  将上述代码保存到.cpp文件中

      若上述代码用c++编译器编译,由于c++语言支持函数重载,所以程序正常运行;但是,在不同c++编译器之间对函数重载的机制也是不一样,接下来分别用vs 和 g++介绍。

    (1)用 vs Debug模式编译.cpp文件,之后就可以在map文件中看到如下结果,

           

      // ‘?’表示名称开始,‘?’后边是函数名;“@@YA”表示参数表开始,后边的3个字符分别表示返回值类型,两个参数类型;“@Z”表示名称结束。

    (2)在Ubuntu下测试(需要安装g++编译器),执行以下指令:

      1)g++ test.cpp   

      2)objdump a.out -t > test.out    // -t是表示生成符号表,最后是将生成的符号表用重定向符号放在test.out文件。

      3)vi test.out

          

      打开test.out文件,就会发现,整形数相加的函数Add(int a,int b)生成的符号表中,Add函数名被记录为_Z3Addii。

      其中,_Z表示符号表名称开始, 3代表函数名的字符个数,ii代表参数列表顺序中2个形参的类型;

    综述,无论使用何种编译器,在.cpp文件中,虽然两个函数的函数名一样,但是他们在符号表中生成的名称不一样,所以是可以编译通过的。

    由上述分析可知,c编译器 与 c++编译器  对函数的重命名规则不一样;那么,在c++中如何确保将一段c代码以c编译器的方式被编译呢?---- 使用 extern 关键字

     1 // 使用方式1
     2 extern "C"
     3 {
     4       // C-Style Compilation
     5 }
     6 
     7 // 使用方式2
     8 //__cplusplus 是 c++ 编译器内置的标准宏定义
     9 //__cplusplus 的意义:确保C代码以统一的C方式被编译成目标文件
    10 
    11 #ifdef __cplusplus
    12 extern "C" {
    13 #endif
    14 
    15 // C-Style Compilation
    16 
    17 #ifdef __cplusplus
    18 }
    19 #endif
    extern "C" 的使用方式

     参考链接:https://blog.csdn.net/qq_37791134/article/details/81502017https://blog.csdn.net/gogogo_sky/article/details/71189499https://blog.csdn.net/fantian_/article/details/80719144

    5、函数重载的结论

      1. 函数重载的本质:多个不同的函数;

      2. 函数名和参数列表是唯一的标识;

      3. 函数重载必须发生在同一个作用域中;

      4. c++编译器 和 c编译器 对函数重命名的规则不同;

      5. 编译器决定符号表中函数名被编译后的最终目标名;

        c++ 编译器 将函数名和参数列表编译成目标名;

        c 编译器将函数名编译成目标名;

      6. 函数重载是在编译期间根据参数类型和个数决定函数调用

      7. 函数重载是一种静态多态;

      (1)多态:用同一个东西表示不同的形态;

      (2)多态分为:静态多态(编译时的多态)、动态多态(运行时的多态);

    6、编译器调用函数重载的规则

      1. 将所有同名函数作为候选者;

      2. 尝试寻找可行的候选者函数

      (1)精确匹配实参;

      (2)通过默认参数能够匹配实参;

      (3)通过默认类型转换匹配实参;

      3. 匹配失败

      (1)最终寻找的候选函数不唯一,则出现二义性,编译失败;

      (2)无法匹配所有的候选函数,函数没定义,编译失败;

    7、函数重载与默认参数

      当函数重载遇到默认参数时,就会发生二义性;

      代码如下:  

     1 #include<iostream>
     2 using namespace std;
     3 
     4 class A
     5 {
     6     void func(int a, int b, int c = 0) {}
     7     void func(int a, int b) {}
     8 };
     9 
    10 int main()
    11 {
    12     A a;
    13     a.func(1, 2); // 二义性出现
    14 
    15     return 0;
    16 }
    函数重载的二义性案例

    8、函数重载 与 函数指针

      将重载函数名赋值给函数指针时,

      1. 根据重载规则挑选与函数指针参数列表一致的候选者;

      2. 严格匹配候选者的函数类型与函数指针的函数类型;

     1 #include <stdio.h>
     2 #include <string.h>
     3 
     4 int func(int x)
     5 {
     6     return x;
     7 }
     8 
     9 int func(int a, int b)
    10 {
    11     return a + b;
    12 }
    13 
    14 int func(const char* s)
    15 {
    16     return strlen(s);
    17 }
    18 
    19 typedef int(*PFUNC)(int a);
    20 
    21 
    22 int main(int argc, char *argv[])
    23 {
    24     int c = 0;
    25 
    26     PFUNC p = func;
    27         
    28     c = p(1);   
    29     
    30     printf("c = %d
    ", c);    // c = 1
    31 
    32     return 0;
    33 }
    函数重载与函数指针

    二、函数重写(也称为覆盖, Function override)

    1、什么是函数重写

      函数重写分为 虚函数重写(会发生多态) 与 非虚函数重写(重定义的一种形式); 

      函数重写:也叫做覆盖。子类重新定义父类中有相同返回值、名称参数虚函数。函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。

      注:一般而言,函数重写 就是 虚函数重写,为的是实现多态调用; 

    2、函数重写的条件

      1. 函数的返回类型、方法名、参数列表完全相同;

      2. 必须发生在不同的作用域中(基类与派生类中);

      3. 基类中有 virtual 关键字声明,派生类中可有可无,不能有 static (虚函数重写);

    3、函数重写的意义

      在面向对象的继承关系中,我们了解到子类可以拥有父类中的所有属性与行为;但是,有时父类中提供的方法并不能满足现有的需求,所以,我们必须在子类中重写父类中已有的方法,来满足当前的需求。

    三、函数重定义(也称为隐藏,Function redefining)

    1、什么是函数重定义

      子类重新定义父类中有相同名称的函数 ( 不包括虚函数重写 ) 。

    2、重定义的表现形式

      1. 必须发生在不同的作用域中(基类与派生类中);

      2. 函数名相同;

      3. 返回值可以不同;

      4. 参数列表不同,此时,无论基类中的同名函数有无 virtual 关键字,基类中的同名函数都会被隐藏。

      5. 参数列表相同,此时,基类中的同名函数没有 virtual 关键字,则基类中的同名函数将会被隐藏 --- 非虚函数重写 。

    3、关于同名覆盖的结论(归纳:基类与派生类中存在同名成员;--- 同名覆盖

      1. 子类将隐藏父类中的同名成员;

      2. 父类中的同名成员依然存在于子类中;

      3. 可以通过作用域分辨符(::)访问被隐藏的父类中的同名成员;

      4. 不可以直接通过子类对象访问父类成员;

       注:同名覆盖规则适用于类的成员变量与成员函数;

      相关代码展示:

     1 #include <iostream>
     2 #include <string>
     3 
     4 using namespace std;
     5 
     6 class Parent
     7 {
     8 public:
     9     int mi;
    10     
    11     Parent()
    12     {
    13         cout << "Parent() : " << "&mi = " << &mi << endl;
    14     }
    15 };
    16 
    17 class Child : public Parent
    18 {
    19 public:
    20     int mi;
    21     
    22     Child()
    23     {
    24         cout << "Child() : " << "&mi = " << &mi << endl;
    25     }
    26 };
    27 
    28 int main()
    29 {
    30     Child c;
    31     
    32     c.mi = 100;    
    33         
    34     c.Parent::mi = 1000;
    35     
    36     cout << "&c.mi = " << &c.mi << endl;
    37     cout << "c.mi = " << c.mi << endl;
    38     
    39     cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
    40     cout << "c.Parent::mi = " << c.Parent::mi << endl;
    41     
    42     return 0;
    43 }
    44 
    45 /**
    46 * Parent() : &mi = 0x7ffe98191450
    47 * Child() : &mi = 0x7ffe98191454
    48 * &c.mi = 0x7ffe98191454
    49 * c.mi = 100
    50 * &c.Parent::mi = 0x7ffe98191450
    51 * c.Parent::mi = 1000
    52 */
    同名成员变量案例
     1 #include <iostream>
     2 #include <string>
     3 
     4 using namespace std;
     5 
     6 class Parent
     7 {
     8 public:
     9     int mi;
    10     
    11     void add(int v)
    12     {
    13         mi += v;
    14     }
    15     
    16     void add(int a, int b)
    17     {
    18         mi += (a + b);
    19     }
    20 };
    21 
    22 class Child : public Parent
    23 {
    24 public:
    25     int mi;
    26     
    27     void add(int v)
    28     {
    29         mi += v;
    30     }
    31     
    32     void add(int a, int b)
    33     {
    34         mi += (a + b);
    35     }
    36     
    37     void add(int x, int y, int z)
    38     {
    39         mi += (x + y + z);
    40     }
    41 };
    42 
    43 int main()
    44 {
    45     Child c;
    46     
    47     c.mi = 100;        
    48     c.Parent::mi = 1000;
    49     
    50     cout << "c.mi = " << c.mi << endl;    
    51     cout << "c.Parent::mi = " << c.Parent::mi << endl;
    52     
    53     c.add(1);
    54     c.add(2, 3);
    55     c.add(4, 5, 6);
    56     c.Parent::add(10);
    57     c.Parent::add(11, 12);
    58     
    59     cout << "c.mi = " << c.mi << endl;    
    60     cout << "c.Parent::mi = " << c.Parent::mi << endl;
    61     
    62     return 0;
    63 }
    64 /**
    65 * c.mi = 100
    66 * c.Parent::mi = 1000
    67 * c.mi = 121
    68 * c.Parent::mi = 1033
    69 */
    重定义案例
     1 #include <iostream>
     2 #include <string>
     3 
     4 using namespace std;
     5 
     6 class Parent
     7 {
     8 public:
     9     int mi;
    10     
    11     virtual void add(int v)
    12     {
    13         mi += v;
    14     }
    15 };
    16 
    17 class Child : public Parent
    18 {
    19 public:
    20     int mi;
    21     
    22     virtual void add(int v)
    23     {
    24         mi += v;
    25     }
    26     
    27     void add(int a, int b)
    28     {
    29         mi += (a + b);
    30     }
    31 };
    32 
    33 int main()
    34 {
    35     Child c;
    36     Parent &p = c;  // 父类引用指向子类对象,多态发生
    37     
    38     c.mi = 100;     
    39     c.Parent::mi = 1000;
    40     
    41     cout << "c.mi = " << c.mi << endl;   
    42     cout << "c.Parent::mi = " << c.Parent::mi << endl;
    43     
    44     c.add(1);
    45     c.add(2, 3);
    46     p.add(100);     // 实际调用的是子类中 add(int v) 函数
    47     c.Parent::add(10);
    48      
    49     cout << "c.mi = " << c.mi << endl;   // c.mi = 1 + 2 + 3 + 100
    50     cout << "c.Parent::mi = " << c.Parent::mi << endl; // c.Parent::mi = 1000 + 10
    51     
    52     return 0;
    53 }
    54 /**
    55 * c.mi = 100
    56 * c.Parent::mi = 1000
    57 * c.mi = 206
    58 * c.Parent::mi = 1010
    59 */
    重写案例

    本节总结:

    1、 重载 必须在 一个类之间, 而 重写、重定义 是在 2个类 之间

    2、 重载是在 编译期间 根据参数类型和个数决定函数调用; 多态(虚函数重写)是在 运行期间 根据具体对象的类型决定函数调用

    3、 发生重写、重定义后,遵循 同名覆盖 规则;

  • 相关阅读:
    jquery toggle(listenerOdd, listenerEven)
    struts quick start
    hdu 1518 Square (dfs)
    hdu 2544 最短路 (最短路径)
    hdu 1754 I Hate It (线段树)
    hdu 1856 More is better (并查集)
    hdu 1358 Period (KMP)
    hdu 2616 Kill the monster (DFS)
    hdu 2579 Dating with girls(2) (bfs)
    zoj 2110 Tempter of the Bone (dfs)
  • 原文地址:https://www.cnblogs.com/nbk-zyc/p/12356271.html
Copyright © 2011-2022 走看看