c++多态

目录

 

3、抽象类

3.1概念

3.2、接口继承和实现继承

4、多态的原理:虚函数表

4.1、单继承

4.3、动态绑定和静态绑定

5、菱形继承

菱形继承:


3、抽象类

3.1概念

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

抽象类在系统编程时用的比较广泛,Windows系统为开发者提供了三千多个API函数,这些函数能够直接操作Windows系统,但是这么多的接口函数,记忆起来相当困难,于是微软就提供了比如MFC框架应用程序,将这几千个API 封装成好几个大的基类,每个基类包含了不同领域的功能。在这些基类当中,继承、多态使用的非常广泛,学好这些基本概念,对理解mfc有很大的帮助。

3.2、接口继承和实现继承

普通的继承是实现继承,继承了基类成员函数的功能,虚函数的继承是接口继承,就是提供了一个入口,让派生类去重写,实现新的功能,但是一般常用,所以如果不是为了实现多态,最好不要用虚函数。

4、多态的原理:虚函数表

这里不卖关子了,直接开门见山吧。

每个包含虚函数的类,都会有一个函数指针数组去管理它众多的虚函数,当然普通函数根本不占用类的内存空间存在公共区域,虚表不会管理它。

虚表是一个二级函数指针,只要类中包含虚函数,就会在对象的头部包含一个虚表指针(vfptr)
 
虚表相当于一个函数指针数组,里面存放的就是虚函数的地址。
 
当子类继承父类时,会继承虚表,当子类有新的虚函数时,会在虚表后面新增新的项,当子类重写父类的虚函数时,会把虚表中原有的某一项覆盖。

4.1、单继承

基类虚表会通过函数指针数组维护自己的虚表,派生类也有一张虚表管理继承下来的虚函数,和自己的虚函数。

我们来看

#include <iostream>
using namespace std;

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;

};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
	virtual void derive()
	{
		cout << "deriver" << endl;
	}
private:
	int _d = 2;
};
int main()
{
	Base b;
	Derive d;

	system("pause");
	return 0;
}

 

class Base	size(8):
1>	+---
1> 0	| {vfptr}
1> 4	| _b
1>	+---
1>
1>Base::$vftable@:
1>	| &Base_meta
1>	|  0
1> 0	| &Base::Func1
1> 1	| &Base::Func2
1>
1>Base::Func1 this adjustor: 0
1>Base::Func2 this adjustor: 0
1>
1>class std::is_lvalue_reference<class std::basic_ostream<char,struct std::char_traits<char> > &>	size(1):
1>	+---
1> 0	| +--- (base class std::integral_constant<bool,1>)
1>	| +---
1>	+---
1>
1>class std::is_error_code_enum<char const *>	size(1):
1>	+---
1> 0	| +--- (base class std::integral_constant<bool,0>)
1>	| +---
1>	+---
1>
1>class Derive	size(12):
1>	+---
1> 0	| +--- (base class Base)
1> 0	| | {vfptr}
1> 4	| | _b
1>	| +---
1> 8	| _d
1>	+---
1>
1>Derive::$vftable@:
1>	| &Derive_meta
1>	|  0
1> 0	| &Derive::Func1
1> 1	| &Base::Func2
1> 2	| &Derive::derive
1>
1>Derive::Func1 this adjustor: 0
1>Derive::derive this adjustor: 0

打印出虚表信息,清楚地看到,虚表名字叫vfptr,主要看派生类

重写了基类的虚函数,和继承基类的虚函数,最后是自己的虚函数。

 4.2、多继承

#include <iostream>
using namespace std;
class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};
class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};
class Derive : public Base1, public Base2 {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	cout << " 虚表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}
int main()
{
	Derive d;
	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
	PrintVTable(vTableb1);
	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
	PrintVTable(vTableb2);
	system("pause");
	return 0;
}

我们可以很清楚的看到基类和继承了两个基类的派生类的虚表,我么重点来看一下派生类的虚表,不出所料,派生类也只有一个虚表, 但是他的虚表里面维护的是基类1和基类2的虚表,也就是说它里面存的是函数指针数组,他自己的虚函数在第一个基类的虚表里面维护。

并且,派生类重写两个基类的虚函数,第二个派生类虚表里面用goto 直接指向了基类1里面的重写后的虚函数。

 
一个派生类可以来源于多个基类,多个基类间用逗号隔开。
 
如果多个父类中有重名的成员,那么会产生二义,必须用父类名::的方式指明用谁的成员。
 
如果继承的多个父类中有多个虚表,那么子类将全部继承下来。如果子类出现了新的虚函数,那么会加在第一个虚表(第一个继承的父类的虚表)的后面,如果多个父类中含有相同的虚函数,子类重写后,将会只出现一个虚函数。
 
 

4.3、动态绑定和静态绑定

静态绑定(早绑定):在程序的编译阶段就应经确定了程序的行为,调用具体的函数,也称为静态多态,比如函数的重载

动态绑定(迟绑定):在程序运行期间,根据具体拿到的类型确定程序的行为,调用具体的函数称为动态多态。

5、菱形继承

#include <iostream>
using namespace std;
class base
{
	virtual void func()
	{
		cout << "base" << endl;
	}
	virtual void funcbase()
	{
		cout << "funcbase" << endl;

	}
};
class base1 :public base
{
	virtual void func()
	{
		cout << "base1" << endl;

	}
	virtual void funbase1()
	{
		cout << "funbase1" << endl;
	}
};
class base2 :public base
{
	virtual void func()
	{
		cout << "base2" << endl;

	}
	virtual void funcbase2()
	{
		cout << "funcbase2" << endl;

	}
};
class son :public base1, public base2
{
	virtual void func()
	{
		cout << "son" << endl;
	}
	virtual void funcson()
	{
		cout << "funcson" << endl;
	}
};
int main()
{
	base grandfarth;
	base1 farth1;
	base2 farth2;
	son Son;
	system("pause");
	return 0;
}

 虚函数表:

通过上面的介绍,base1 类和base2 类的虚表相信大家已经清楚了,直接看son类的虚表。

菱形继承:

某个类的两个父类拥有一个相同的父类。
 
冗余性:这个类包含了两份爷爷类
二义性:两个爷爷长得一样,分不清
 

虚继承:

含有一个虚基类指针(vbptr),指向自己的基类,作用是可以描述自己的父类。当发现被继承的另一个父类中也有这么一个相同的虚基类时,两个基类就会合并,只保留一个。
普通的继承只继承了爷爷的衣钵,不知道爷爷是谁。虚继承都知道,所以,发现我的两个爸爸的爸爸是同一个人的时候,干脆就只要一个爷爷。
虚继承只用于菱形继承的情况
虚继承可以解决菱形继承带来的问题。
#include <iostream>
using namespace std;
class A
{
public:
	int num;
	A()
	{
		num = 100;
		cout << "A" << endl;
	}
	~A()
	{
		cout << "~A" << endl;
	}


};

class B :virtual public A //虚继承
{
public:
	B()
	{
		cout << "B" << endl;
	}
	~B()
	{
		cout << "~B" << endl;
	}
};

class C :virtual public A //虚继承 
{
public:
	C()
	{
		cout << "C" << endl;
	}
	~C()
	{
		cout << "~C" << endl;
	}
};
class D :public B, public C
{
public:
	D()
	{
		cout << "D" << endl;
	}
	~D()
	{
		cout << "~D" << endl;
	}
};
int main()
{
	D d;
	//cout<<d.num<<endl;//error:访问不明确;没加virtual前;
	//第一种方案:清楚的表明作用域,以免出现歧义:
	//cout<<d.B::num<<endl;
	//cout<<d.C::num<<endl;
	//第二种方案:加上virtual后,就没有歧义了,B和C都指向同一份A;
	cout << d.num << endl;//加上virtual OK
	system("pause");
	return 0;
}

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的多(Polymorphism)是指在父类和子类之间的相互转换,以及在不同对象之间的相互转换。 C++中的多性有两种:静和动。 1. 静是指在编译时就已经确定了函数的调用,也称为编译时多C++中实现静的方式主要有函数重载和运算符重载。 函数重载是指在同一作用域内定义多个同名函数,但它们的参数列表不同。编译器根据传递给函数的参数类型和数量来确定调用哪个函数。例如: ```c++ void print(int num) { std::cout << "This is an integer: " << num << std::endl; } void print(double num) { std::cout << "This is a double: " << num << std::endl; } int main() { int a = 10; double b = 3.14; print(a); // 调用第一个print函数 print(b); // 调用第二个print函数 } ``` 运算符重载是指对C++中的运算符进行重新定义,使其能够用于自定义的数据类型。例如: ```c++ class Complex { public: Complex(double real, double imag) : m_real(real), m_imag(imag) {} Complex operator+(const Complex& other) const { return Complex(m_real + other.m_real, m_imag + other.m_imag); } private: double m_real; double m_imag; }; int main() { Complex a(1.0, 2.0); Complex b(3.0, 4.0); Complex c = a + b; // 调用Complex类中重载的+运算符 } ``` 2. 动是指在运行时根据对象的实际类型来确定调用哪个函数,也称为运行时多C++中实现动的方式主要有虚函数和纯虚函数。 虚函数是在父类中定义的可以被子类重写的函数,使用virtual关键字声明。当一个对象的指针或引用指向一个子类对象时,调用虚函数时会根据实际的对象类型来确定调用哪个函数。例如: ```c++ class Shape { public: virtual void draw() { std::cout << "Drawing a shape." << std::endl; } }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ``` 纯虚函数是在父类中定义的没有实现的虚函数,使用纯虚函数声明(如virtual void func() = 0;)。父类中包含纯虚函数的类称为抽象类,抽象类不能被实例化,只能作为基类来派生子类。子类必须实现父类的纯虚函数才能实例化。例如: ```c++ class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值