关于面向对象的三大特性,封装、继承、多态

最近面试被拷打,发现基础很不牢靠啊,于是找了找一些C++的基础知识,看到了三大特性相关的内容

封装

简单来说就是把自己包起来,让别人看不到,这样有三个优点:
1、调用这类的时候别人不用看我具体是怎么实现的,只需要知道我放出来(public)的输入输出是什么就可以了。
2、别人也看不到我是怎么实现的(万一有些关键的内容我并不想让别人看到)
3、我可以更方便的控制我内部的成员和行为
可以看看这篇文章前面关于封装的描述

继承

儿子可以用老子的东西(成员)和办法(函数),前提是老子的内部实现权限都是(public或者protected的话)。也有两个优点:
1、子类不用重写父类的成员,减少冗余,提高复用性。
2、搭配后面一个多态可以实现同名函数但是不同行为。
但是C++允许多继承,这就带来了一个问题,当菱形继承发生时,会有数据冗余二义性的问题,为此,又高出了一个虚继承的方法(在我看来完全就是多继承的坑用虚继承来填,除此以外,虚继承没有啥用,如果有什么不对也可以在评论区指出来)
怕ps:其实这里还有一个组合,用来做一些,继承不好做或者做不了的事,但是还没是一知半解,希望有大手子不吝赐教。

多态

多态简单来说就是,子类继承了父类的东西(一般是函数),但是不满意,就自己的类里面写了一个实现,然后调用的时候呢还是用之前的函数名。从而导致不同的输出(行为)。
具可以看看这个几个大佬的帖子,都写的很好:

【c++ 封装、继承、多态】
虚函数表详解
为啥构造函数不能是虚函数
虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。
C++ 多态的原理,虚函数,虚函数的重写,虚函数表,静态绑定,动态绑定
PS:在这里面其实有一个关于函数重载的概念,就是同样的函数名吗,但是由于传入参数的不同,导致调用不同的函数,但是这个需要在同一个作用域下面才有用(同一个函数里或者同一个类里),有些教程把这个也归为多态,但作者个人认为不能算。

多态其实分为动态多态和静态多态。
先画个重点:只有虚函数的地方才可能有动态多态度

静态多态这么一回事:

1、编译时就类会产生一个虚函数表(简称虚表,用来保存虚函数的地址)的,然后每一个通过这个类实例化的对象又有一个虚表指针,指向这个虚表,这些实例化对象要用的时候就在按照这个指针来找虚表,再按照这个虚表上的指针去找对应的虚函数。
2、子类继承父类的时候,也会生成一个虚表,但是父类的虚表在前面,子类的在后面。(仅仅这样还构不成多态,关键在下面)
3、子类重写父类的虚函数时,这个时候子类的虚表里面关于父类的那个被重写的虚函数的指针就指向子类的虚函数了,而不指向原来父类的虚函数了。
4、多态的静态实现就是这样按图索骥找到虚函数,然后运行。
关于虚函数表可以看看这个大佬的:C++ 虚函数表解析(呜呜呜,顺便一提,这就是前段时间去世的左耳朵陈皓大佬的帖子,很清晰)

但是动态不一样

总的来说和上面差不多,但是问题出行在:父类指针指向子类对象的时候
请看代码:

#include <iostream>
#include <vector>
using namespace std;
class TakeBus
{
public:
    void TakeBusToSubway()
    {
        cout << "go to Subway--->please take bus of 318" << endl;
    }
    void TakeBusToStation()
    {
        cout << "go to Station--->pelase Take Bus of 306 or 915" << endl;
    }
};
//知道了去哪要做什么车可不行,我们还得知道有没有这个车
class Bus
{
public:
    virtual void TakeBusToSomewhere(TakeBus& tb) = 0;  //???为什么要等于0
};

class Subway :public Bus
{
public:
    virtual void TakeBusToSomewhere(TakeBus& tb)
    {
        tb.TakeBusToSubway();
    }
};
class Station :public Bus
{
public:
    virtual void TakeBusToSomewhere(TakeBus& tb)
    {
        tb.TakeBusToStation();
    }
};

int main()
{
    TakeBus tb;
    Bus* b = NULL;
    b = new Subway;
    b->TakeBusToSomewhere(tb);
    b = new Station;
    b->TakeBusToSomewhere(tb);
    delete b;
    return 0;
}

结果:

go to Subway—>please take bus of 318
go to Station—>pelase Take Bus of 306 or 915

以上代码抄自这个大佬
另外还有一些其他的内容。我就直接贴了:
重写 :
(a)基类中将被重写的函数必须为虚函数
(b)基类和派生类中虚函数的原型必须保持一致,协变和析构函数除外
(c)访问限定符可以不同

那么问题又来了,什么是协变?
协变:基类(或者派生类)的虚函数返回基类(派生类)的指针(引用)

and问题2:为啥析构除外?
因为C++底层所有类的析构函数都改了个名,虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
PS:其实不建议把析构函数定义为非虚函数,因为很有可能没法把子类析构掉(当父类指针指向子类对象的时候,如果父类的析构函数不是虚函数,因为析构函数是按照指针调用的),导致内存泄漏
总结一道面试题:那些函数不能定义为虚函数?
经检验下面的几个函数都不能定义为虚函数:
1)友元函数,它不是类的成员函数
2)全局函数
3)静态成员函数,它没有this指针
3)构造函数,拷贝构造函数,以及赋值运算符重载(可以但是一般不建议作为虚函数)

还有俩关键字:

final关键字和override关键字
final关键字:修饰虚函数,表示该虚函数不能再被继承,如果被子类重写,则会报错
override关键字:检查子类虚函数是否重写了父类某个虚函数,如果没有重写则编译报错

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值