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
3int Add(int a, int b)
4 {
5return a + b;
6 }
7
8
9float Add(float a, float b)
10 {
11return a + b;
12 }
13
14void testFunc()
15 {
16    Add(10, 20);
17    Add(20.0f, 30.0f);
18 }
19
20int main(int argc, char *argv[])
21 {
22    testFunc();
23
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
2extern"C"
3 {
4// C-Style Compilation
5 }
6
7// 使⽤⽅式2
8//__cplusplus 是 c++ 编译器内置的标准宏定义
9//__cplusplus 的意义:确保C代码以统⼀的C⽅式被编译成⽬标⽂件
10
11 #ifdef __cplusplus
12extern"C" {
13#endif
14
15// C-Style Compilation
16
17 #ifdef __cplusplus
18 }
19#endif
extern "C" 的使⽤⽅式
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>
2using namespace std;
3
4class A
5 {
6void func(int a, int b, int c = 0) {}
7void func(int a, int b) {}
8 };
9
10int main()
11 {
12    A a;
13    a.func(1, 2); // ⼆义性出现
14
15return0;
16 }
函数重载的⼆义性案例
8、函数重载与函数指针
  将重载函数名赋值给函数指针时,
  1. 根据重载规则挑选与函数指针参数列表⼀致的候选者;
  2. 严格匹配候选者的函数类型与函数指针的函数类型;
1 #include <stdio.h>
2 #include <string.h>
3
4int func(int x)
5 {
6return x;
7 }
8
9int func(int a, int b)
10 {
11return a + b;
12 }
13
14int func(const char* s)
15 {
16return strlen(s);
17 }
18
19 typedef int(*PFUNC)(int a);
20
21
22int main(int argc, char *argv[])
23 {
24int c = 0;
25
26    PFUNC p = func;
27
28    c = p(1);
29
30    printf("c = %d\n", c);    // c = 1
31
32return0;
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
4using namespace std;
5
6class Parent
7 {
8public:
9int mi;
10
11    Parent()
12    {
13        cout << "Parent() : " << "&mi = " << &mi << endl;
14    }
15 };
16
17class Child : public Parent
18 {
19public:
20int mi;
21
22    Child()
23    {
24        cout << "Child() : " << "&mi = " << &mi << endl;
25    }
26 };
27
28int 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
42return0;
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
4using namespace std;
5
6class Parent
7 {
8public:
9int mi;
const的作用10
11void add(int v)
12    {
13        mi += v;
14    }
15
16void add(int a, int b)
17    {
18        mi += (a + b);
19    }
20 };
21
22class Child : public Parent
23 {
24public:
25int mi;
26
27void add(int v)
28    {

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。