2024年最新干!一文彻底搞懂C++重载、隐藏和覆盖(重写,2024年最新怒斩获了30家互联网公司offer

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

前言
之所以写这篇文章,一方面是想自己总结,彻底搞懂重载、隐藏和重写三者之间的关系,另一方面就是想帮助大家更清楚的认识这三者之间的关系。我在写这篇文章之前也大概搜索了一些关于这三者的博客,但是总感觉那些文章写的并不是我想要的,也并没有表达清楚这三者之间的关系,因此决定写下这篇博客。

我相信在看这篇文章之前肯定有很多小伙伴和我一样对这三者关系不是很清楚,总感觉是似懂非懂,总是差那么一点火候!有时候觉得自己懂了,可是换个时间,又感觉迷迷糊糊的,那么我相信你看了这篇文章后,这种感觉将不复存在!你将对重载、隐藏和重写有一个更深层次的认识!

我们一步一步来,同样,先看下这三者的定义,虽然说看完这个定义你还是迷迷糊糊。但是不急,温水煮青蛙,我们慢慢来,看到最后,拿下重载,隐藏和重写!

定义
重载:重载是C++继C而来特有的性质,相比于之前的C语言来讲,一个函数名只能出现一次。然而在C++中,函数名可以多次出现,但是想要发生重载必须要求函数名相同,参数列表不同(参数列表不同包括:参数个数不同,参数类型不同、参数出现的顺序不同)。
隐藏:隐藏,顾名思义就是被隐藏了导致我们看不到。那么隐藏的对象是什么?为什么会发生隐藏?隐藏只会发生在继承的关系中,当派生类(也叫子类)继承基类(也叫父类)时,如果派生类重新定义了基类中的同名函数,那么基类中重新定义的同名函数称作重写,未被重新定义的函数将被隐藏。
重写:重写也叫覆盖,是C++继承特性的另一种呈现,重写和隐藏很像,重写多被用于虚函数。

看望上面定义,我觉得大多仍然还是晕头转向的,这就对了!,想彻底搞懂这三者,那么我们接着往下看。

光看定义就想彻底搞懂重载、隐藏和重写?
没门!下面我们从具体代码实现来搞懂他们!我们依次对重载、隐藏和重写进行分析,先来看最简单的函数重载

重载

对于函数重载来说,他不看重继承关系。也就是有没有继承关系,只要我想,我都可以发生重载。

无继承关系重载

代码如下

#include <iostream>

using namespace std;

class Basic
{
public:
    Basic()
    {
        cout << "Basic:call construct\n";
    }
    ~Basic()
    {
        cout << "Basic:call destroy\n";
    }

public:
    void Show()
    {
        cout << "Basic:Show(void)\n";
    }
    void Show(int a)
    {
        cout << "Basic:Show(int):" << a << endl;
    }
    virtual void Show(double b)
    {
        cout << "Basic:virtual Show(double)" << b << endl;
    }
    virtual void Show(char c)
    {
        cout << "Basic:virtual Show(char)" << c << endl;
    }
};

int main(void)
{
    {
        Basic b;

        b.Show();    //void Show()
        b.Show(3);   //void Show(int a)
        b.Show(5.1); //virtual void Show(double b)
        b.Show('a'); //virtual void Show(char c)
    }

    system("pause");
    return 0;
}

运行结果如下
在这里插入图片描述
从上面我们可以看出,该程序先后调用了Basic类的构造函数、四个show方法和析构函数,可能有人会疑惑为什么main函数中会套一个大括号,其实这个目的是为了让我们能够显示的看到Basic类的析构函数被调用(即使不加大括号,Basic类的析构函数也会被调用,只是说不会被显示出来),加了大括号涉及到变量的生命周期问题,即此时b对象的生命周期只在大括号里,因此大括号结束即调用对象的析构函数,如果没有大括号,则调用析构函数会在main函数调用,不利于我们观察,因此这里加了大括号。

我们继续分析上述结果看能发生什么,首先我们能看到重载函数被正确调用。另外,这些重载函数中有虚函数和非虚函数,我们可以得出结论,可以对虚函数和非虚函数进行重载。

下面我们进一步分析:如果重载函数的返回值类型不一样呢?虚函数和非虚函数除了virtual外其余声明可以完全相同吗?
代码如下

#include <iostream>

using namespace std;

class Basic
{
public:
    Basic()
    {
        cout << "Basic:call construct\n";
    }
    ~Basic()
    {
        cout << "Basic:call destroy\n";
    }

public:
    void Show()
    {
        cout << "Basic:Show(void)\n";
    }
    int Show(int a)
    {
        cout << "Basic:Show(int):" << a << endl;

        return 1;
    }
    void Show(int a)
    {
        cout << "Basic:Show(int):" << a << endl;
    }
};

显示结果
在这里插入图片描述从结果,我们可以看到,编译器直接报错了“无法重载仅按返回类型区分的函数”,即仅按照返回类型来区分重载不可行。

那么虚函数可以与非虚函数重载只相差一个virtual吗?
代码如下

void Show(double b)
    {
        cout << "Basic:virtual Show(double)" << b << endl;
    }
    virtual void Show(double b)
    {
        cout << "Basic:virtual Show(double)" << b << endl;
    }
    virtual void Show(char c)
    {
        cout << "Basic:virtual Show(char)" << c << endl;
    }

结果如下
在这里插入图片描述

这里结果显示,同样不能进行重载,和上面原因呢基本相同。

其实,对于这种结果,我们稍微想一想就可以明白,如果参数类型相同和个数,那么我们调用的时候怎么区分?我们调用的时候有用到返回类型吗?我们调用的时候有显示的写virtual了吗?如果没有,那么编译器怎么知道你想调用哪个函数?他是你肚子里的蛔虫吗?这些结论都显而易见,你们自己思考吧!

上面介绍晚了非继承关系的重载,其实继承关系的重载和非继承关系的重载相同,下面将简单介绍继承关系的重载。

继承关系重载

我们下面这段代码很有意思,代码中分别在基类和派生类中定义了特有的方法。首先,我们在基类中定义了Show方法,并对其进行了重载;然后,我们又定义了Basicshow方法,并且也对其进行了重载。接下来,我们使用Derive派生类来继承基类Basic,并且,在派生类中我们重新定义了两个Show方法(virtual void Show(double,int) 方法未在派生类中定义,这里有没有virtual都一样);然后,我们又在派生类中定义了派生类特有的方法Deriveshow,并进行了重载,下面我们看代码实现

代码实现

#include <iostream>

using namespace std;

class Basic
{
public:
    Basic()
    {
        cout << "Basic:call construct\n";
    }
    ~Basic()
    {
        cout << "Basic:call destroy\n";
    }

public:
    void Show()
    {
        cout << "Basic:Show(void)\n";
    }
    void Show(int a)
    {
        cout << "Basic:Show(int):" << a << endl;
    }
    virtual void Show(double b, int a)
    {
        cout << "Basic:virtual Show(double,int):" << b << "," << a << endl;
    }
    void BasicShow()
    {
        cout << "Basic:BasicShow(void)\n";
    }
    void BasicShow(int a)
    {
        cout << "Basic:BasicShow(int)" << a << endl;
    }
};
class Derive : public Basic
{
public:
    Derive()
    {
        cout << "Derive:call construct\n";
    }
    ~Derive()
    {
        cout << "Derive:call destroy\n";
    }

public:
    void Show()
    {
        cout << "Derive:void Show(void)\n";
    }
    void Show(int a)
    {
        cout << "Derive:void Show(int):" << a << endl;
    }
    void DeriveShow()
    {
        cout << "Derive:DeriveShow(void)\n";
    }
    void DeriveShow(int a)
    {
        cout << "Derive:DeriveShow(int)" << a << endl;
    }
};
int main(void)
{
    {
        //定义派生类对象
        Derive d;

        //基类函数,未在派生类中重定义
        d.BasicShow();  //Basic::void BasicShow()
        d.BasicShow(5); //Basic::void BasicShow(int a)
        //派生类特有的单独定义方法
        d.DeriveShow();  //Derive::void DeriveShow()
        d.DeriveShow(8); //Derive::void DeriveShow(int a)
        //派生类重新定义的基类方法(基类和派生类都有的)
        d.Show();  //Derive::void Show()
        d.Show(9); //Derive::void Show(int a)
        // d.Show(3.4,7);//error:派生类中未重新定义此类型
    }

    system("pause");
    return 0;
}

结果显示
在这里插入图片描述
我们对输出结果进行分析,首先从代码中我们可以看到在最后对函数进行调用时,我把d.Show(3.4,7)这个函数调用给注释掉了,之所以注释掉他,是因为调用他时代码会发生错误,至于为什么会发生错误,我们接下来会进行详细分析。我们继续看上面输出结果,输出结果中第三和第四行调用基类的方法BasicShow,然而这两个方法我们并未在派生类中定义,之所以父类能够调用这两个方法,就是因为派生类的继承特性,我相信大家对继承会有所了解。然后接下来两行又调用了派生类特有的方法DeriveShow,这里我相信大家也没什么疑问。再接下来,我们又调用了Show方法,不过我们如果深入思考一下就会发现,这个Show方法我们在基类和派生类中都有定义,但是,我们在派生类中只定义了基类中的两个Show方法,而我们定义的这两个Show方法恰恰可以在派生类中成功调用,而未在派生类中定义的这个Show方法却不能使用派生类对象调用。接着,我们再深入思考一下,程序中的输出结果显示了基类中的BasicShow方法可以使用派生类对象调用,并且这个BasicShow方法没有在派生类中定义;然而基类中的Show方法有三个,可是派生类只能调用其中的两个,并且这两个还都是在派生类中定义的。大家思考到这里,可能就会有点头绪了,为什么调用不了Show(double,int),其实根本目的就是派生类的Show方法隐藏了基类的Show方法。
下面我们来详细探究一下C++的隐藏特性!

隐藏

何时发生隐藏?隐藏的结果是什么?

对于第一个问题,何时会发生隐藏呢,其实发生隐藏很简单,必须满足以下两个条件:

  • 有继承关系
  • 派生类中重新定义了基类的方法

对于第二个问题,隐藏的结果也很简单,就是派生类对象访问不了基类方法(只针对与隐藏的方法,不发生隐藏的仍然可以访问)

从上面那个代码就可以知道,派生类中重新定义了Show方法导致发生了隐藏,从而使得派生类对象访问不了基类中的Show方法。下面我们同样从代码分析隐藏,我们在分析之前可以先思考几个问题:

  • 发生隐藏的结果?
  • 虚函数会影响隐藏结果吗?

代码如下

#include <iostream>

using namespace std;

class Basic
{
public:
    Basic()
    {
        cout << "Basic:call construct\n";
    }
    ~Basic()
    {
        cout << "Basic:call destroy\n";
    }

public:
    void Show()
    {
        cout << "Basic:Show(void)\n";
    }
    void Show(int a)
    {
        cout << "Basic:Show(int):" << a << endl;
    }
    void Show(double b, int a)
    {
        cout << "Basic:virtual Show(double,int):" << b << "," << a << endl;
    }
    void Show(int a, int b, int c)
    {
        cout << "Basic:void Show(int , int , int ):" << a << "," << b << "," << c << endl;
    }
};
class Derive : public Basic
{
public:
    Derive()
    {
        cout << "Derive:call construct\n";
    }
    ~Derive()
    {
        cout << "Derive:call destroy\n";
    }

public:
    void Show()
    {
        cout << "Derive:void Show(void)\n";
    }
};
int main(void)
{
    {
        //定义派生类对象
        Derive d;
        //派生类重新定义的基类方法(基类和派生类都有的)
        d.Show(); //Derive::void Show()
        //d.Show(9); //error:发生了隐藏
        // d.Show(3.4,7);//error:发生了隐藏
        //d.Show(2,3,4);//error:发生了隐藏
    }

    system("pause");
    return 0;
}

结果如下
在这里插入图片描述
从结果可以看出,带派生类中只定义一个基类中的同名函数时,那么将只能调用这一个同名函数,剩余的几个将全部被隐藏。

如果基类定义的函数是虚方法并且派生类指定义了一个基类的同名方法呢?
这个结果和上面一样会隐藏,这里不再细说。
代码如下

#include <iostream>

using namespace std;

class Basic
{
public:
    Basic()
    {
        cout << "Basic:call construct\n";
    }
    ~Basic()
    {
        cout << "Basic:call destroy\n";
    }

public:
    void Show()
    {
        cout << "Basic:Show(void)\n";
    }
    void Show(int a)
    {
        cout << "Basic:Show(int):" << a << endl;
    }
    virtual void Show(double b, int a)
    {
        cout << "Basic:virtual Show(double,int):" << b << "," << a << endl;
    }
    virtual void Show(int a, int b, int c)
    {
        cout << "Basic:void Show(int , int , int ):" << a << "," << b << "," << c << endl;
    }
};
class Derive : public Basic
{
public:
    Derive()
    {
        cout << "Derive:call construct\n";
    }
    ~Derive()
    {
        cout << "Derive:call destroy\n";
    }

public:
    void Show()
    {
        cout << "Derive:Show(void)\n";
    }
};
int main(void)
{
    {
        Derive d;
        d.Show();
        d.Show(3);
        d.Show(3.5,6);
        d.Show(2,3,4);
    }

    system("pause");
    return 0;
}


![img](https://img-blog.csdnimg.cn/img_convert/73d9300c71db2b8bc452d77152006477.png)
![img](https://img-blog.csdnimg.cn/img_convert/d23934d78f7ae91dc6bf79789fa4e134.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  {
        Derive d;
        d.Show();
        d.Show(3);
        d.Show(3.5,6);
        d.Show(2,3,4);
    }

    system("pause");
    return 0;
}


[外链图片转存中...(img-6Eog3Yyu-1715670895198)]
[外链图片转存中...(img-fFuHE6s8-1715670895198)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它将数据和操作封装在对象中,通过对象之间的交互实现程序的设计和开发。下面是一些关键概念,帮助你更好地理解Python面向对象编程。 1. 类(Class):类是对象的蓝图或模板,描述了对象的属性和行为。它定义了对象的特征和方法。例如,我们可以定义一个名为"Car"的类来表示汽车,其中包含属性(如颜色、型号)和方法(如加速、刹车)。 2. 对象(Object):对象是类的实例,是具体的实体。通过实例化类,我们可以创建一个对象。例如,我们可以创建一个名为"my_car"的对象,它是基于"Car"类的实例。 3. 属性(Attribute):属性是对象的特征,用于描述对象的状态。每个对象都可以具有一组属性。例如,"Car"类的属性可以包括颜色、型号等。 4. 方法(Method):方法是对象的行为,用于定义对象的操作。每个对象都可以具有一组方法。例如,"Car"类的方法可以包括加速、刹车等。 5. 继承(Inheritance):继承是一种机制,允许我们创建一个新类(称为子类),从现有类(称为父类)继承属性和方法。子类可以扩展或修改父类的功能。继承可以实现代码重用和层次化设计。 6. 多态(Polymorphism):多态是一种特性,允许不同类的对象对同一方法做出不同的响应。多态提高了代码的灵活性和可扩展性。 7. 封装(Encapsulation):封装是一种将数据和操作封装在对象中的机制,隐藏了对象的内部实现细节,只暴露必要的接口给外部使用。这样可以保护数据的安全性,提供了更好的模块化和代码复用性。 通过理解这些概念,你可以更好地掌握Python面向对象编程。在实践中,你可以使用类来创建对象,操作对象的属性和调用对象的方法,通过继承和多态实现代码的灵活性和可扩展性,通过封装保护数据的安全性和提高代码的可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值