目录

  一、函数重载

  二、函数重写

  三、函数重定义


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

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

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

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

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


一、函数重载(Function Overloading) 

1、什么是函数重载

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

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

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

3、函数重载的条件

  1. 必须在同一作用域下

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

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

  4. const属性相同

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

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

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

  • 在vs编译器中:

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

  • 在linux g++ 编译器中:

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

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

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

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

 #include<stdio.h>

 int Add(int a, int b)
{
return a + b;
} float Add(float a, float b)
{
return a + b;
} void testFunc()
{
Add(, );
Add(20.0f, 30.0f);
} int main(int argc, char *argv[])
{
testFunc(); return ;
}

案例分析

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
extern "C"
{
// C-Style Compilation
} // 使用方式2
//__cplusplus 是 c++ 编译器内置的标准宏定义
//__cplusplus 的意义:确保C代码以统一的C方式被编译成目标文件 #ifdef __cplusplus
extern "C" {
#endif // C-Style Compilation #ifdef __cplusplus
}
#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、函数重载与默认参数

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

  代码如下:  

 #include<iostream>
using namespace std; class A
{
void func(int a, int b, int c = ) {}
void func(int a, int b) {}
}; int main()
{
A a;
a.func(, ); // 二义性出现 return ;
}

函数重载的二义性案例

8、函数重载 与 函数指针

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

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

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

 #include <stdio.h>
#include <string.h> int func(int x)
{
return x;
} int func(int a, int b)
{
return a + b;
} int func(const char* s)
{
return strlen(s);
} typedef int(*PFUNC)(int a); int main(int argc, char *argv[])
{
int c = ; PFUNC p = func; c = p(); printf("c = %d\n", c); // c = 1 return ;
}

函数重载与函数指针

二、函数重写(也称为覆盖, 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. 不可以直接通过子类对象访问父类成员;

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

  相关代码展示:

 #include <iostream>
#include <string> using namespace std; class Parent
{
public:
int mi; Parent()
{
cout << "Parent() : " << "&mi = " << &mi << endl;
}
}; class Child : public Parent
{
public:
int mi; Child()
{
cout << "Child() : " << "&mi = " << &mi << endl;
}
}; int main()
{
Child c; c.mi = ; c.Parent::mi = ; cout << "&c.mi = " << &c.mi << endl;
cout << "c.mi = " << c.mi << endl; cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl; return ;
} /**
* Parent() : &mi = 0x7ffe98191450
* Child() : &mi = 0x7ffe98191454
* &c.mi = 0x7ffe98191454
* c.mi = 100
* &c.Parent::mi = 0x7ffe98191450
* c.Parent::mi = 1000
*/

同名成员变量案例

 #include <iostream>
#include <string> using namespace std; class Parent
{
public:
int mi; void add(int v)
{
mi += v;
} void add(int a, int b)
{
mi += (a + b);
}
}; class Child : public Parent
{
public:
int mi; void add(int v)
{
mi += v;
} void add(int a, int b)
{
mi += (a + b);
} void add(int x, int y, int z)
{
mi += (x + y + z);
}
}; int main()
{
Child c; c.mi = ;
c.Parent::mi = ; cout << "c.mi = " << c.mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl; c.add();
c.add(, );
c.add(, , );
c.Parent::add();
c.Parent::add(, ); cout << "c.mi = " << c.mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl; return ;
}
/**
* c.mi = 100
* c.Parent::mi = 1000
* c.mi = 121
* c.Parent::mi = 1033
*/

重定义案例

 #include <iostream>
#include <string> using namespace std; class Parent
{
public:
int mi; virtual void add(int v)
{
mi += v;
}
}; class Child : public Parent
{
public:
int mi; virtual void add(int v)
{
mi += v;
} void add(int a, int b)
{
mi += (a + b);
}
}; int main()
{
Child c;
Parent &p = c; // 父类引用指向子类对象,多态发生 c.mi = ;
c.Parent::mi = ; cout << "c.mi = " << c.mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl; c.add();
c.add(, );
p.add(); // 实际调用的是子类中 add(int v) 函数
c.Parent::add(); cout << "c.mi = " << c.mi << endl; // c.mi = 1 + 2 + 3 + 100
cout << "c.Parent::mi = " << c.Parent::mi << endl; // c.Parent::mi = 1000 + 10 return ;
}
/**
* c.mi = 100
* c.Parent::mi = 1000
* c.mi = 206
* c.Parent::mi = 1010
*/

重写案例


本节总结:

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

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

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

c++中的函数重载、函数重写、函数重定义的更多相关文章

  1. C++函数重载,重写,重定义

    目录 1 重载 2 重写 3 重定义 4 函数重载二义性   笔者原创,转载请注明出处   C++中经常会提到重载,除了重载,还有重写,重定义,下面对这三个概念逐一进行区分 1 重载   函数重载是同 ...

  2. C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义

    首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不 ...

  3. C++ 重载、重写、重定义的区别

    C++ 中 重载.重写.重定义的区别 重载(overload) 定义: 在同一个作用域内,两函数的函数名相同, 参数不相同(可以是参数类型不同或者是参数个数不同), 那么就说这两个 函数重载. 分类: ...

  4. c++继承关系中成员函数的重载、重写、重定义之间的区别

    1.Override.Overload.Redefine Overload 重载只能发生在类内部,不能发生在子类和父类的继承中.具体来说,如果子类中有父类同名.同返回值类型,但是不同参数列表,这两个在 ...

  5. C++重载,重写,重定义

    1.重载:重载是在一个类中,函数名一样,参数类型或参数个数不同的一系列函数需要注意的是,与返回类型无关. class Test { public: void show(); void show(int ...

  6. c++之——重载、重写、重定义

    函数重载: 必须在同一个类中进行: 子类无法重载父类的函数,父类同名函数将被子类名称覆盖: 重载是在编译期间根据参数类型和个数决定函数的调用(静态联编). 函数重写与重定义: 重写: 必须发生在基类和 ...

  7. C++ 重载、重写、重定义

    出自:http://blog.163.com/clevertanglei900@126/blog/ 1 成员函数重载特征: a 相同的范围(在同一个类中) b 函数名字相同 c 参数不同 d virt ...

  8. 【校招面试 之 C/C++】第12题 C++ 重载、重写和重定义

    1.成员函数重载特征:   a.相同的范围(在同一个类中): b.函数名字相同: c.参数不同(参数个数不同或者参数类型不同,但是返回值不同不能使重载): d.virtual关键字可有可无. 2.重写 ...

  9. c++ 重载、重写、重定义(隐藏)

    1.重载overload:函数名相同,参数列表不同. 重载只是在类的内部存在,或者同为全局范围.(同名,同参函数返回值不同时,会编译出错.因为系统无法知晓你到底要调用哪一个.)   2.重写overr ...

  10. c++三大概念要分清--重载,隐藏(重定义),覆盖(重写)

    重载,隐藏(重定义),覆盖(重写)—这几个名词看着好像很像,不过其实一样都不一样!! 综述: 说明:覆盖中的访问修饰符可以不同是指可以不用显示地用virtual:当访问修饰符改为const或者stat ...

随机推荐

  1. Codeforces Round #603 (Div. 2)E

    http://codeforces.com/contest/1263/problem/E 题意:求合法的括号序列 #include<bits/stdc++.h> using namespa ...

  2. TPO1-3Timberline Vegetation on Mountains

    At the upper timberline the trees begin to become twisted and deformed. This is particularly true fo ...

  3. C实现日志等级控制

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h&g ...

  4. Softether使用本地网桥

    https://maytalkhao.com/archives/826 以下步骤都是按照上面这篇文章来的,具体细节如下 一.使用Softether  VPN Server Manager软件添加本地网 ...

  5. Perl: 单引号里面的直接给当做标量了,而直接输出($`)的话就是变量值,即相符段落的前置字符会存到这里。输出‘$`’ 就变成标量值了

    print '$`'."\n";print '$&'."\n";print $'."\n"; 输出: $`$& 而直接输出( ...

  6. 吴裕雄--天生自然python Google深度学习框架:人工智能、深度学习与机器学习相互关系介绍

  7. linux通过grep根据关键字查找日志文件上下文

    linux通过grep根据关键字查找日志文件上下文 1.在标准unix/linux下的grep命令中,通过以下参数控制上下文的显示: grep -C 10 keyword catalina.out 显 ...

  8. python开发常见应用第一卷(OS遍历文件并存储文件路径到数据库)

    之前我们爬取完指定网站的图片后,会将它门保存到服务器或本地的数据库中,真正的工作中大多数是采用分布式的方式来爬取的,所以这些图片会分布在很多的主机上面,当被引用时需要根据IP+图片路径来引用并且加载, ...

  9. fidder 抓包工具设置只拦截指定ip(服务ip)

    直接上图:

  10. MergeSort(归并排序)原理及C++代码实现

    归并排序利用分治策略进行排序.原理如下 分解:分解待排的n个元素的序列成个具n/2个元素的两个子序列. 解决:使用归并排序递归地排序两个子序列. 合并:合并两个已排序的子序列以产生已排序的答案. 归并 ...