c++中的函数重载、函数重写、函数重定义

目录

  1、函数重载html

  2、函数重写linux

  3、函数重定义ios


为了更加深入的理解 函数重载、重写、重定义,咱们能够带着以下这两个问题去思考:c++

一、子类中是否能够定义父类中的同名成员?为何?程序员

  能够,由于子类与父类的命名空间不一样;ide

二、子类中定义的函数是否能够重载父类中的同名函数?函数

  不能够,由于函数重载必须在同一个做用域中。测试


 

1、函数重载(Function Overloading) 

一、什么是函数重载

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

二、为何使用函数重载(函数重载的好处)

  因为函数重载能够在同一个做用域内,使用同一个函数名 命名一组功能类似的函数,这样作减小了函数名的数量,避免了程序员因给函数名命名所带来的烦恼,从而提升程序的开发的效率。spa

三、函数重载的条件

  1. 必须在同一做用域下.net

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

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

  4. const属性相同

四、函数重载的原理(本质: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

五、函数重载的结论

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

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

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

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

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

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

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

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

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

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

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

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

  1. 将全部同名函数做为候选者;

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

  (1)精确匹配实参;

  (2)经过默认参数可以匹配实参;

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

  3. 匹配失败

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

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

七、函数重载与默认参数

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

  代码以下:  

 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 }
函数重载的二义性案例

八、函数重载 与 函数指针

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

  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\n", c);    // c = 1
31 
32     return 0; 33 }
函数重载与函数指针

 

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

一、什么是函数重写

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

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

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

二、函数重写的条件

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

  2. 必须发生在不一样的做用域中(基类与派生类中);

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

三、函数重写的意义

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

 

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

一、什么是函数重定义

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

二、重定义的表现形式

  1. 必须发生在不一样的做用域中(基类与派生类中);

  2. 函数名相同;

  3. 返回值能够不一样;

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

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

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

  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 */
重写案例

 


本节总结:

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

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

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

 

原文出处:https://www.cnblogs.com/nbk-zyc/p/12356271.html

相关文章
相关标签/搜索