C++面向对象,面向对象的三大特性,重载,重写,隐藏区别,什么是多态,多态如何实现(虚函数表和虚指针)

什么是面向对象?面向对象的三大特性

面向对象:对象是指具体的某一个事务,这些事务的抽象就是类,类中包含数据(成员变量)和动作(成员方法)。

面向对象的三大特性:

  • 封装:将具体的实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性。
  • 继承:子类继承父类的特征和行为,子类有父类的非private方法或成员变量,子类可以对父类的方法进行重写,增强了类之间的耦合性,但是当父类中的成员变量,成员函数或者类本身被final关键字修饰时,修饰的类不能继承,修饰的成员不能重写或修改。
  • 多态:多态就是不同继承类的对象,对同一消息做出不同的响应,基类的指针指向或者绑定到派生类的对象,使得基类指针呈现不同的表现方式。

重载,重写,隐藏的区别

重载

​ 重载是指同一可访问区内声明的几个具有不同参数列表的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。

注意:main函数不能重载

一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开

Record lookup(Phone);
Record lookup(const Phone); //重复生明了Record lookup(Phone)

Record lookup(Phone*);
Record lookup(Phone* const); //重复声明了Record lookup(Phone*)

如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的

Record lookup(Account&);
Record lookup(const Account&);

Record lookup(Account*);
Record lookup(const Account*);

隐藏

指派生类的函数屏蔽了与其同名的基类函数,只要函数名相同,不管参数列表是否相同,基类函数都会被隐藏。

派生类的成员将隐藏同名的基类成员

#include <iostream>
using namespace std;

class Base
{
public:
	void fun(int tmp, float temp1){}    
};

class Derive : public Base
{
public:
    void fun(int temp){}; //隐藏基类中的同名函数
}

int main(){
    Derive ex;
    ex.fun(1);  //调用派生类中的fun函数
    ex.fun(1, 2);  //出错,派生类隐藏了基类的同名函数
    return 0;
}

如果想调用基类的同名函数,可以加上类型名指明ex.Base::fun(1, 2),这样就可以调用基类中的同名函数。

重写(覆盖)

指派生类中存在重新定义的函数。函数名,参数列表,返回值类型都必须同基类中被重写的函数一致,只有函数体不同。

当我们使用指针或引用调用虚函数时,该调用将被动态绑定,根据引用或指针所绑定的对象类型的不同,该调用可能执行基类的版本,也可能执行某个派生类的版本。

成员函数如果被声明为虚函数,则其解析过程发生在运行时。

在C++语言中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定

重写和重载的区别

  • 范围区别:对于类中函数的重载或者重写而言,重载发生在同一个类的内部,重写发生在不同的类之间(子类和父类之间)。
  • 参数区别:重载的函数与原函数有相同的函数名,不同的参数列表,不关注函数的返回值类型;重写的函数的函数名,参数列表和返回值类型都需要和原函数相同,父类中被重写的函数需要需要有virtual修饰。
  • virtual关键字:在基类中,重写的函数必须有virtual修饰,重载的函数可以有virtual关键字的修饰也可以没有。

隐藏和重写,重载的区别

  • 范围区别:隐藏与重载范围不同,隐藏发生在不同类中。
  • 参数区别:隐藏函数和被隐藏函数的参数列表可以相同,也可以不同,但函数名一定相同;当参数不同时,无论基类中的函数是否被virtual修饰,基类函数都是被隐藏,而不是重写。

什么是多态?多态如何实现?

多态:多态就是不同继承类的对象,对同一消息做出不同的响应,基类的指针指向或绑定到派生类的对象,使得基类指针呈现不同的表现方式。在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

实现方法:多态是通过虚函数实现的,虚函数的地址保存在虚函数表中,虚函数表的地址保存在含有虚函数的类的实例对象的内存空间中。

实现过程

  1. 在类中用virtual关键字声明的函数叫做虚函数
  2. 存在虚函数的类都有一个虚函数表,当创建一个该类的对象时,该对象有一个指向虚函数表的虚表指针。(虚函数表和类对应,虚表指针和对象对应)
  3. 当基类指针指向派生类对象,基类指针调用虚函数时,基类指针指向派生类的虚表指针,由于该虚表指针指向派生类虚函数表,通过遍历虚表,寻找相应的虚函数。

虚函数表和虚指针

如果一个类存在一个或一个以上的虚函数时,那么这个类便会包含一张虚函数表。当子类重写基类的虚函数时,它也会有自己的一张虚函数表。

对于虚函数的调用是通过查虚函数表来进行的,每个虚函数在虚函数表中都存放自己的地址,通过虚指针可以定位到虚函数表。

class A{
public:
	virtual void vfunc1();
    virtual voud vfunc2();
    void func1();
    void func2();
private:
    int m_data1, m_data2;
};
class B : public A{
public:
    virtual void vfunc1();
    void func2();
private:
    int m_data3;
};
class C : public B{
public:
    virtual void vfunc1();
    void func2();
private:
    int m_data1, m_data4;
};
  1. 对于非虚函数,三个类中虽然都有一个叫func2的函数,但它们彼此互不关联,因此都是各自独立的,不存在重载一说,在调用的时候也不需要进行查表的操作,直接调用即可。
  2. 由于子类B和子类C都是继承于基类A,因此它们都会存在一个虚指针用于指向虚函数表。注意,假如子类B和子类C中不存在虚函数,那么它们将共用基类A的一张虚函数表,在B和C中用虚指针指向该虚函数表即可。但是上面的代码设计时,子类B和子类C中都有一个虚函数vfunc1,因此它们需要各自产生一个虚函数表,并用各自的虚指针指向该表。由于子类B和子类C都对vfunc1做了重写,因此它们有三种不同的实现方式,函数地址也不相同,在使用的时候需要从各自类的虚函数表中去查找对应的vfunc1地址。
  3. 对于虚函数vfunc2,两个子类都没有进行重写操作,所以子类B和子类C将公用一个vfun2,该虚函数的地址会保存在三个类的虚函数表中,但它们的地址是相同的。

虚函数表和对象的虚函数指针

如果一个类中有虚函数,那么该类就有一个虚函数表。

这个虚函数表是属于类的,所有该类的实例化对象都会有一个虚函数表指针去指向该类的虚函数表。

一个类只能有一个虚函数表,在编译时,一个类的虚函数表就确定了。

在这里插入图片描述

多重继承情况

class A {
public:
    A() {}
    virtual ~A() {}

    void func1() { cout << "A::func1()" << endl; }
    void func2() { cout << "A::func2()" << endl; }

    virtual void vfunc1() { cout << "A::vfunc1()" << endl; }
    virtual void vfunc2() { cout << "A::vfunc2()" << endl; }
private:
    int aData;
};

class B : public A {
public:
    B() {}
    virtual ~B() {}

    void func1() { cout << "B::func1()" << endl; }
    virtual void vfunc1() { cout << "B::vfunc1()" << endl; }
private:
    int bData;
};

class C : public B
{
public:
    C() {}
    virtual ~C() {}

    void func2() { cout << "C::func2()" << endl; }
    virtual void vfunc2() { cout << "A::vfunc2()" << endl; }
private:
    int cData;
};

在这里插入图片描述

ClassA *a = new ClassB();
a->func1();                    // "ClassA::func1()"   隐藏了ClassB的func1()
a->func2();                    // "ClassA::func2()"
a->vfunc1();                   // "ClassB::vfunc1()"  重写了ClassA的vfunc1()
a->vfunc2();                   // "ClassA::vfunc2()"

A的指针能操作的范围只能是黑框中的范围。
在这里插入图片描述

ClassA* a = new ClassC;
a->func1();          // "ClassA::func1()"   隐藏ClassB::func1()               
a->func2();          // "ClassA::func2()"	隐藏ClassC::func2()
a->vfunc1();	     // "ClassB::vfunc1()"	ClassB把ClassA::vfunc1()覆盖了
a->vfunc2();	     // "ClassC::vfunc2()"	ClassC把ClassA::vfunc2()覆盖了

ClassB* b = new ClassC;
b->func1();				// "ClassB::func1()"	有权限操作时,子类优先
b->func2();				// "ClassA::func2()"	隐藏ClassC::func2()
b->vfunc1();			// "ClassB::vfunc1()"	ClassB把ClassA::vfunc1()覆盖了
b->vfunc2();			// "ClassB::vfunc2()"	ClassC把ClassA::vfunc2()覆盖了

多继承下的虚函数表

class A1{
public:
    A1() {}
    virtual ~A1(){}
    
    void func1(){}
    
    virtual void vfunc1(){}
    virtual void vfunc2(){}
private:
    int a1Data;
};

class A2{
public:
    A2(){}
    virtual ~A2(){}
    
    void func1() {}
    
    virtual void vfunc1(){}
    virtual void vfunc2(){}
    virtual void vfunc4()
private:
    int a2Data;
};

class C : public A1, public A2
{
public:
    C(){}
    virtual ~C(){}
    
    void func1(){}
    
    virtual void vfunc1(){}
    virtual void vfunc2(){}
    virtual void vfunc3(){}
};

A1是第一个基类,拥有普通函数func1(),虚函数vfunc1()和vfunc2();

A2是第二个基类,拥有普通函数func1(),虚函数vfunc1(),vfunc2(),vfunc4();

C依次继承A1,A2,普通函数func1(),虚函数vfunc1(),vfunc2(),vfunc3();

在这里插入图片描述

在多继承情况下,有多少个基类就有多少个虚函数表指针,前提是基类必须要有虚函数才可以算上这个基类。如图,虚函数表指针01指向的虚函数表是以A1的虚函数表为基础的,子类的C::vfunc1(),C::vfunc2()的函数指针覆盖了虚函数表01中的虚函数指针01的位置,02的位置。

当子类有多出来的虚函数时,添加在第一个虚函数表中。

当有多个虚函数表时,虚函数表的最后一个位置的结果为0,表示没有下一个虚函数表,“*”号位置在不同操作系统中实现不同,代表有下一个虚函数表。

注意:

  • 子类虚函数会覆盖每一个父类的每一个同名虚函数;

  • 父类中没有的虚函数而子类有,填入第一个虚函数表中,且用父类指针不能调用;

  • 父类有的虚函数而子类没有,则不覆盖。子类和该父类指针能调用;

参考
https://zhuanlan.zhihu.com/p/98776075
https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/ej8n9m/
https://blog.csdn.net/qq_36359022/article/details/81870219

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值