首页 > 编程语言 >C++中的虚函数重载

C++中的虚函数重载

时间:2023-09-06 11:33:02浏览次数:51  
标签:p2 函数 int void C++ Base 重载 父类


在一次修改代码过程中踩的坑,下来研究了一下,发现C++中虚函数重载后会产生很多有意思的情况,在这里总结了一下。

C++中有重载(overload)和重写(override)以及重定义这几个概念,1 overload:指的是相同作用域中的两个函数的函数名相同,但参数列表的个数、顺序、类型不同。而override指的是子类中重新定义的父类的虚函数。
2 override:overload要求两个函数的参数列表必须不同,但是不要求这两个函数必须是虚函数。而override要求必须是虚函数且父类的虚函数必须有virtual关键字,函数的参数列表和返回值也必须相同。子类中override的虚函数的访问修饰符可以不同。
3 重定义也是描述分别位于父类与子类中的同名函数的,但返回值可以不同。如果参数列表不同,这时子类中重定义的函数不论是否有virtual关键字,都会隐藏父类的同名函数。
如果参数列表相同,但父类中的同名函数没有virtual关键字修饰,此时父类中的函数仍然被隐藏。

虚函数的典型用法是:


#include <iostream>
using namespace std; 

class Base{
public:
    virtual void f();

};

void Base::f(){
    cout << "Base::f()" << endl;
}

class Derived:public Base{
public:
    virtual void f();
};

void Derived::f(){
    cout << "Derived::f()" << endl;
}

int main() {
    Base *p1 = new Base;
    Base *p2 = new Derived;
    p1->f();
    p2->f();
    
    delete p1;
    delete p2;

    return 0;
}


程序的输出是:


Base::f()
Derived::f()


那么如果虚函数之间又发生了overload,会出现什么情况?
我们先看最简单的情况:


#include <iostream>
using namespace std; 

class Base{
public:
    virtual void f(int);
};

void Base::f(int a){
    cout << "Base::f(int) " << a << endl;
}

class Derived:public Base{
public:
    virtual void f();
};

void Derived::f(){
    cout << "Derived::f()" << endl;
}

int main() {
    Base *p1 = new Base;
    p1->f(1);
    
    Base *p2 = new Derived;
    p2->f();
    
    delete p1;
    delete p2;
    
    return 0;
}

编译上面的代码,会发生如下错误:


test.cpp: In function ‘int main()’:
test.cpp:28:11: error: no matching function for call to ‘Base::f()’
     p2->f();
           ^
test.cpp:10:6: note: candidate: virtual void Base::f(int)
 void Base::f(int a){
      ^
test.cpp:10:6: note:   candidate expects 1 argument, 0 provided


这就是因为父类中虚函数的参数列表已经发生变化,这时不论子类中重定义的函数不论是否有virtual关键字,都会隐藏父类的同名函数。这时子类中只是重定义了一个自己的函数virtual void f(),而并没有override父类中对应的虚函数。
p2是一个指向Base类型的指针,根据虚函数的特性,对p2→f();的处理取决于是否override了父类的虚函数,如果没有,仍然会调用调用父类中被override的虚函数,但是现在父类中的函数已经成为了virtual void f(int),因此在执行p2→f()时会由于缺少输入参数而出现上述错误。
为了证明上述论断,可以在执行p2→f()时传入参数来判断:


#include <iostream>
using namespace std; 

class Base{
public:
    virtual void f(int);
};

void Base::f(int a){
    cout << "Base::f(int) " << a << endl;
}

class Derived:public Base{
public:
    virtual void f();
};

void Derived::f(){
    cout << "Derived::f()" << endl;
}

int main() {
    Base *p1 = new Base;
    p1->f(1);
    
    Base *p2 = new Derived;
    p2->f(2);
    
    delete p1;
    delete p2;
    
    return 0;
}


这时可以通过编译,执行结果为


Base::f(int) 1
Base::f(int) 2


可以看到,子类自己定义的virtual void f()其实是父类的virtual void f(int)的一个重定义的函数,这时尽管p2实际指向了一个Derived对象,但由于没有override父类对应的虚函数,在执行 p2→f(2)时将执行父类的virtual void f(int)。
也可以这样修改:


#include <iostream>
using namespace std; 

class Base{
public:
    virtual void f(int);
    virtual void f();
};

void Base::f(int a){
    cout << "Base::f(int) " << a << endl;
}

void Base::f(){
    cout << "Base::f() " << endl;
}

class Derived:public Base{
public:
    virtual void f();
};

void Derived::f(){
    cout << "Derived::f()" << endl;
}

int main() {
    Base *p1 = new Base;
    p1->f(1);
    
    Base *p2 = new Derived;
    p2->f();
    
    delete p1;
    delete p2;
    
    return 0;
}


因为父类中定义了可被子类override的函数,所以这时执行p2→f()又会重新执行子类的virtual void f():


Base::f(int) 1
Derived::f()


我们甚至还可以这样验证:


#include <iostream>
using namespace std; 

class Base {
public:
    virtual void f(int);
    virtual void f();
};

void Base::f(int a) {
    cout << "Base::f(int) " << a << endl;
}

void Base::f() {
    cout << "Base::f() " << endl;
}

class Derived:public Base {
public:
    virtual void f(float);
};

void Derived::f(float a) {
    cout << "Derived::f(float)" << showpoint << a << endl;
}


int main() {
    Base *p1 = new Base;
    p1->f(1);
    
    Base *p2 = new Derived;
    p2->f(**2.0**);
    
    delete p1;
    delete p2;
    
    return 0;
}


这时输出仍然为


Base::f(int) 1
Base::f(int) 2


这说明如果通过指向父类的指针,调用虚函数时,如果子类重定义了该虚函数(参数列表发生变化),则实际调用的仍是父类中的虚函数。
上面都是通过指向父类的指针来调用虚函数的,那么如果通过指向子类的指针调用虚函数会发生什么:


#include <iostream>
using namespace std; 

class Base {
public:
    virtual void f(int);
    virtual void f();

};

void Base::f(int a) {
    cout << "Base::f(int) " << a << endl;
}

void Base::f() {
    cout << "Base::f() " << endl;
}

class Derived:public Base {
public:
    virtual void f(float);
};

void Derived::f(float a) {
    cout << "Derived::f(float)" << showpoint << a << endl;
}

int main() {
    Base *p1 = new Base;
    p1->f(1);
    
    Derived *p2 = new Derived;
    p2->f(2.0);
    p2->f(3);
    
    delete p1;
    delete p2;
    
    return 0;
}


这时输出就变为了:


Base::f(int) 1
Derived::f(float)2.00000
Derived::f(float)3.00000


这说明,如果通过指向子类的指针调用虚函数,并且子类重定义了父类的虚函数,这时实际调用的就将是子类中的虚函数。



标签:p2,函数,int,void,C++,Base,重载,父类
From: https://blog.51cto.com/u_16248677/7384802

相关文章

  • Pandas中的to_datetime函数用法
    Pandas中的to_datetime函数用法importdatetimeimportpandasaspdimportnumpyasnp将字符串转换为日期时间:pd.to_datetime('2023-09-06')Timestamp('2023-09-0600:00:00')将多个字符串转换为日期时间:pd.to_datetime(['2023-09-06','2023-09-07'......
  • C++异步框架workflow分析
    简述workflow项目地址:https://github.com/sogou/workflowworkflow是搜狗开源的一个开发框架。可以满足绝大多数日常服务器开发,性能优异,给上层业务提供了易于开发的接口,却只用了少量的代码,举重若轻,而且代码整洁干净易读。搜狗官方宣传强调,workflow是一个异步任务调度编程范式,封......
  • 基础函数
    读取数据read_csv():读取csv文件数据并转换成DataFrame格式。示例:data=pd.read_csv('data.csv')read_excel():读取Excel文件数据并转换成DataFrame格式。示例:data=pd.read_excel('data.xlsx') 数据清洗dropna():删除包含空值的行或列。示例:data.dropna()fillna():将空......
  • C++系列十:日常学习-操作符重载
    目录介绍:案例:介绍:在C++中,操作符重载(OperatorOverloading)是一种允许我们自定义或改变某些操作符的行为的技术。案例:单个参数的简单例子:#include<iostream>classMyNumber{private:doublevalue;public:MyNumber(doublev):value(v){}......
  • C++ auto用作函数参数
    c++11中auto不能用作函数参数。在gccversion8.1.0(x86_64-win32-seh-rev0,BuiltbyMinGW-W64project)--C++14中可以运行,会警告:useof'auto'inparameterdeclarationonlyavailablewith-fconcepts......
  • python函数的应用(一)九九乘法表
    函数实现99乘法表的打印#1.使用函数重构乘法口诀表并调用defmultiplication(n):foriinrange(1,n+1):forjinrange(1,i+1):print(j,"*",i,"=",j*i,end="\t")print()#调用函数a=int(input("请输入您想打印的乘法口诀表部分"))mult......
  • 《C++并发编程实战》读书笔记(4):原子变量
    1、标准原子类型标准原子类型的定义位于头文件<atomic>内。原子操作的关键用途是取代需要互斥的同步方式,但假设原子操作本身也在内部使用了互斥,就很可能无法达到期望的性能提升。有三种方法来判断一个原子类型是否属于无锁数据结构:所有标准原子类型(std::atomic_flag除外,因为它......
  • C++学习笔记
    C++:C的编译器叫gcc,C++的编译器叫g++c++最容易发生的问题是内存泄漏问题,例如释放p所指的空间时,应该是free(p);p=NULL;很多人忘记将p归零,这样下次不小心调用了p就会出现内存泄漏问题,如果要把释放内存写成函数,示例如下(两层指针)voidfree_mem(int**pp){if(pp==NULL......
  • C++中虚继承时的构造函数
    在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。下面我们以菱形继承为例来......
  • C++语言学习09
    STL标准模版库STL是StandardTemplateLibrary的缩写,中文名标准模版库,由惠普实验室提供(使用C++模板语言封装的常用的数据结构与算法)STL中有六大组件:算法:以函数模板形式实现的常用算法,例如:swap\max\min\find\sort容器:以类模板的形式实现的常用的数据结构,例如:vector\list\arra......