• 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、 发生重写、重定义后,遵循 同名覆盖 规则;

  • 相关阅读:
    五分钟免费搭建一个自己的网站
    网站大全-工具类,学习类网站
    vscode常用插件
    vscode常用快捷键
    IIS 如何设置多个Access-Control-Allow-Origin
    ajax跨域,这应该是最全的解决方案了
    Failed to execute ‘createObjectURL’ on ‘URL’: No function was found that matched the signature provided.
    Github新项目Dress(好耶是女装)
    Eclipse常用快捷键
    Javase、Javaee、Javame的区别
  • 原文地址:https://www.cnblogs.com/nbk-zyc/p/12356271.html
Copyright © 2020-2023  润新知