多态——中度刨析

重写(覆盖)

子类重写定义父类相同名称、返回值和参数的虚函数 

       虚函数:就是父类用virtual声明的成员函数

       无论子类覆盖的函数是不是虚函数,都会被自动处理成虚函数

覆盖:指的是虚函数表中虚函数地址的覆盖

class father
{
public:
	virtual void fun() {
		cout << "father" << endl;
	}
};

class son:public father
{
public:
	virtual void fun() {
		cout << "son" << endl;
	}
};

int main() 
{
	son s1;
	s1.fun(); //重写成功:son
	s1.father::fun();//重写可使用作用域调用父类的
}

隐藏

是继承结构中的 把基类中的同名的函数隐藏掉 实际是作用域的隐藏

在子类中和父类的同名函数不是重写,就是隐藏。 

通过作用域  或 父类指针指向子类对象调用隐藏方法  仍可以访问被隐藏掉的基类函数

class Base
{
public:
	void A() { cout << " Base::A()" << endl; };
	void A(int val) { cout << " Base::A(int)" <<endl; }; //重载:同名 相同作用域 参数不同 
	virtual void B(int val) { cout << " Base::B(int)" << endl; }//虚函数 可以被重写
};

class Derive :public Base
{
public:
	void A() { cout << " Derive::A()" << endl; };//隐藏 基类中不是虚函数 基类的作用域被隐藏 可以通过基类+作用域来访问
	void B() { cout << " Derive::B()" << endl; };//隐藏 参数不同
	virtual void B(int a) { cout << " Derive::B(int)" << endl; };//重写 可被子类重写
};

int main() {
	Derive d;
	d.A(); //Derive::A() 
	//d.A(10);//报错 因为基类同名被隐藏掉 只能访问自己的以A为名的函数
			    //优先找派生类自己作用域的A名的成员 
	d.Base::A();//Base::A() 隐藏仍然可以通过+作用域来访问
	
	Base* pb = &d; //Base限制访问的是派生类里面继承来的 也就是作用域是基类的 
	pb->A(); //Base::A()
	//pb->B();//报错 pb的指针类型是基类
	((Derive*)pb)->B();// Derive::B() 可以通过强转 来访问d中作用域是派生类的成员

	Base& pb1 = d;  //引用和指针同理
	pb1.A();
	((Derive&)pb1).B();

	//Derive* pb2 = new Base();//错误 不能用派生类的指针(引用)指向基类对象
}

注意:在基类和派生类之间的指针和引用中 无法进行从上到下的转换 即不能用派生类的指针(引用)指向基类的对象

重写和隐藏

在子类和父类中,在函数名相同的前提下:

父类函数为虚函数、返回值相同、参数相同的为重写,其他是隐藏

多态

字面意思:一个东西具有多种多样的形态

  • 静态多态:在编译时期就确定好的调用的函数,函数重载、模板。

误区:继承中的隐藏不是静态多态,调用的时候虽然是编译期确定,但是他他并不是通过一个相同的东西产生不同的,就是普通的函数调用。

  • 动态多态:在运行时期确定的函数调用,即为基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数),同一个基类指针,指向哪个派生类对象就会调用派生类的同名覆盖方法,称为多态

继承中,父类指针指向子类对象,调用同名函数的时候分为三种情况:

  1. 隐藏:指针什么类型调用谁的  作用域
  2. 重写:调用子类重写后的函数 多态

启动动态多态条件:有继承关系,子类重写父类虚函数父类指针指向子类对象并且调用子类重写的虚函数。

class A
{
public:
	virtual void work()
	{
		cout << "A work()" << endl;
	}
	void fun()
	{
		cout << "A fun()" << endl;
	}
};

class B:public A
{
public:
	virtual void work()
	{
		cout << "B work()" << endl;
	}
	void fun()
	{
		cout << "B fun()" << endl;
	}
};

class C :public B
{
public:
	virtual void work()
	{
		cout << "C work()" << endl;
	}
	void fun()
	{
		cout << "C fun()" << endl;
	}
};

int main() 
{
	A* a = new C();  //父类指针指向子类对象
	a->work();	//1.重写:调用重写后的函数    多态
	a->fun();	//2.隐藏:调用指针类型的函数
}

多态的实现

多态的技术:动态绑定

动态绑定的核心:虚函数表

(1)虚函数表

  • 包含虚函数有一个虚表
  • 虚表是一个数组,其内元素是虚函数的函数指针
  • 虚表中虚函数指针的赋值发生再编译器编译阶段 所以在编译阶段 虚表就构造出来了
  • 当基类是由虚函数时,其派生类也有了自己的虚表(包含自己和基类的虚函数指针)

(2)虚表指针

  • 虚表属于类,同一个类的所有对象使用同一个虚表
  • 编译时编译器会往有虚函数的类中加上一个虚表指针_vptr,用来指向虚表vtable。当类的对象在创建时便拥有这个指针,指向同一个类的虚表。
  • 验证_vptr的指针(*_vptr不可访问):先求一个不含虚函数的类占的字节数sizeof()。然后再将一个函数变成虚函数求字节数sizeof(),会发现增加4个字节。
  • 一个类里面虚函数的个数,不影响对象内存的大小(vfptr),影响的是虚函数表的大小。

(3)静态、动态联编

联编:将模块或者函数合并在一起成为可执行代码的处理过程(函数调用),按照联编所进行的阶段不同,分成一下两种

静态联编(绑定):编译期阶段进绑定(函数调用)

动态联编(绑定):程序运行期间进绑定(函数调用)编译期看不出来(反汇编)

动态联编针对C++的多态,其他都是静态联编

动态编排的条件:

  • 基类必须有成员函数显示声明为virtual
  • 如果基类声明了虚函数,则派生类中可以不必再声明

在C++中,派生类中重写基类的虚函数时,即使不显式使用virtual关键字,编译器也会将这个函数视为虚函数,并将其添加到派生类的虚函数表所以相当于就是虚函数 对子类来说就是虚函数中。因此,即使在派生类中没有显式使用virtual关键字,也可以实现多态性。

动态联排案例:

class A	        //A虚表:vfun1函数指针 vfun2函数指针
{
private:
	int m_data1, m_data2;
public:
	virtual void vfun1() {};
	virtual void vfun2() {};
	void fun1() {};
	void fun2() {};
};

class B:public A	   //B虚表:继承A::vfun2不变 重写vfun1指针改变 
{
private:
	int m_data3;
public:
	virtual void vfun1() {};
	void fun1() {};
};

class C:public B  	//C虚表:继承B::vfun1指针不变 重写vfun2指针改变 
{
private:
	int m_data1, m_data4; //m_data1和A中重名 调用时用作用域区分二义性
public:
	virtual void vfun2() {};
	void fun2() {};
};

int main()
{
	B b;
	A* p = &b;    //父类指针指向子类对象
	p->vfun1();
}

代码分析:

  • b是B的对象,所以b有虚表指针_vptr指向类B的虚表
  • 执行p->vfun1()时 发现p是个指针 
    • 调用的是虚函数 
      • 首先,根据_vptr来访问对象b所对应的虚表,虽然指针p是基类A类型,但是_vprt也是基类的一部分,所以可以通过p->_vptr来访问对象对应的虚表。
      • 然后,在B虚表查找所调用的函数对应的条目。
      • 最后,根据虚表找到的函数指针,调用函数B:vfunc1();
    • 调用的是普通函数
      • 到b中作用域为指针类型的函数中寻找 有的话调用 没有的话报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值