多态---->C++的三大特性之二

本文详细介绍了C++中的多态性,包括多态的概念、静态多态和动态多态(虚函数)、函数重载、同名隐藏、重写、纯虚函数和抽象类的使用规则。此外,还探讨了虚表的原理以及多继承和虚拟继承中的虚表行为,帮助读者深入理解C++的多态机制。
摘要由CSDN通过智能技术生成

注:以下所有代码均是在VS2010环境下测试结果

1、多态引入---->数据的类型

        

       我们知道,不论我们定义一个怎么样的数据或者对象,我们都不会忘了它的类型,那么类型到底充当了一个怎样的角色呢?

        数据类型在数据结构中的定义是一个值的集合以及定义在这个值集上的一组操作。变量是用来存储值的所在处,它具有名字和数据类型。变量的数据类型决定了如何将代表这些值的位存储到计算机的内存中。在我看来,数据类型就好比一把尺子,它为我们在内存中存储数据提前做好了量度,告诉我们应该占用多大的内存。    

举例说明:




2、多态的概念

        所谓的多态性,就是不同的对象收到相同的消息时,产生不同的动作。直白点说,就是一个名字定义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的方式来调用这些具有不同功能的同名函数,即“一个接口,多种方法”。


 

静态多态举例:重载函数、模板等


动态多态举例:虚函数等

虚函数的引入:在C++中,基类的对象指针可以指向它公有派生的对象,但是当其指向公有派生类对象时,它只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员。

       使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定

eg:

class Base
{
public:
	Base(int x,int y)//构造函数
	{
		a = x;
		b = y;
	}
	virtual void show()//虚函数
	{
		cout<<"Base::"<<a<<" "<<b<<endl;
	}
private:
	int a;
	int b;
};

class Derived:public Base
{
public:
	Derived(int x,int y,int z):Base(x,y)//派生类的构造函数(为基类构造函数传参)
	{
		c = z;
	}
	void show()//重新定义虚函数show
	{
		cout<<"Derived::"<<c<<endl;
	}
private:
	int c;
};

int main()
{
	Base b(60,60);//创建基类对象b
	Base *pb;//创建基指针类pb
	Derived d(10,20,30);//创建派生类对象d

	pb = &b;//对象指针指向基类对象b
	pb->show();//调用基类的虚函数show()
	pb = &d;//对象指针指向派生类对象d
	pb->show();//调用派生类的虚函数show()

	system("pause");
	return 0;
}


运行结果:



动态绑定条件
1、必须是虚函数
2、通过基类类型的引用或者指针调用虚函数

两个条件必须同时满足才可形成动态绑定


虚函数总结:

<1>由于虚函数使用的基础是赋值兼容规则,而赋值兼容规则的前提条件是派生类从其基类公有派生,所以使用虚函数来实现多态时,派生类必须从它的基类公有派生

<2>必须首先在基类中定义虚函数

<3>在派生类中对基类中声明的虚函数进行重定义时,关键字virtual可以加也可以不加。为了不引起混乱,最好加上。

<4>虽然使用对象名和点运算符的方式也可以调用虚函数,但只有通过基类指针或引用访问虚函数时才能获得运行时的多态性

<5>一个虚函数无论被继承多少次,它仍然保持其虚函数的特性

<6>虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数

<7>内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。

<8>构造函数不能是虚函数,但是析构函数可以是虚函数,而且通常说明为虚函数


3、函数重载&同名隐藏&重写:


函数重载:

//函数重载
int add(int x,int y)
{
	return x+y;
}
double add(float a,float b)
{
	return a+b;
}
int main()
{
	int a = 3,b = 5;
	float x = 3.14f,y = 6.8f;
	int ret1 = add(a,b);
	cout<<"int----"<<ret1<<endl;
	double ret2 = add(x,y);
	cout<<"double----"<<ret2<<endl;
	system("pause");
	return 0;
}

运行结果:



重写举例:

//重写
class Base
{
public:
	virtual void show(int x,int y)//虚函数
	{
		a = x;
		b = y;
		cout<<"Base::"<<a<<" "<<b<<endl;
	}
private:
	int a;
	int b;
};

class Derived:public Base
{
public:
	void show(int x,int y)//重新定义虚函数show
	{
		c = x + y;
		cout<<"Derived::"<<c<<endl;
	}
private:
	int c;
};

int main()
{
	Base b;//创建基类对象b
	Derived d;//创建派生类对象d
	
    b.show(10,20);
	d.Derived::show(30,40);
	system("pause");
	return 0;
}

运行结果:



协变:与重写的条件基本一样,唯一不同的是协变的返回值可以不同,基类返回基类的指针/引用,派生类返回派生类的指针/引用。

<pre name="code" class="cpp">//重写(协变)
class Base
{
public:
	virtual Base& fun(int x,int y)//虚函数
	{
		a = x;
		b = y;
		cout<<"Base::"<<a<<" "<<b<<endl;
		return *this;
	}
private:
	int a;
	int b;
};

class Derived:public Base
{
public:
	Derived& fun(int x,int y)//重新定义虚函数show
	{
		c = x + y;
		cout<<"Derived::"<<c<<endl;
		return *this;
	}
private:
	int c;
};

int main()
{
	Base b;//创建基类对象b
	Derived d;//创建派生类对象d
	
    b.fun(10,20);
	d.Derived::fun(30,40);
	system("pause");
	return 0;
}


 
运行结果: 

同名隐藏举例:

<pre name="code" class="cpp">//同名隐藏
class Base
{
public:
	int show()
	{
		cout<<"Base::show()"<<endl;
		return 0;
	}
};

class Derived:public Base
{
public:
	void show()
	{
		cout<<"Derived::show()"<<endl;
	}
};

int main()
{
	Derived d;//创建派生类对象d

	d.show();//调用派生类自己的同名函数
	d.Base::show();//调用基类的同名函数
	system("pause");
	return 0;
}


 
运行结果: 

4、纯虚函数&抽象类

纯虚函数:

定义:纯虚函数是在声明虚函数时被“初始化”为0的函数。

一般格式:virtual 函数类型   函数名(参数表)= 0;

作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行重新定义。

纯虚函数举例:

<pre name="code" class="cpp">class Circle//基类
{
public:
	void setr(int x)
	{
		r = x;
	}
	virtual void show() = 0;//定义纯虚函数
protected:
	int r;
};
class Area:public Circle//派生类
{
public:
	void show()//重新定义虚函数show求圆形面积
	{
		cout<<"Area is "<<3.14*r*r<<endl;
	}
};
int main()
{
	Circle* p;//基类指针
	Area a;//派生类对象a
    a.setr(5);
	p = &a;
	p->show();//计算圆形面积

	system("pause");
    return 0;
}


 
运行结果: 

抽象类包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象

建立抽象类的目的:用它作为基类去建立派生类,抽象类作为一种基本类型提供给用户,用户在这个基础上根据自己的需要定义出功能各异的派生类,并用这些派生类去建立对象。

抽象类使用规则:

<1>抽象类只能作为其他类的基类来使用,不能建立抽象类对象。

<2>不允许从具体类派生出抽象类。

<3>抽象类不能用作函数的参数类型、函数的返回类型或者显式转换的类型。

<4>可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。

<5>如果派生类中没有定义纯虚函数的实现,而派生类只是继承基类的纯虚函数,则这个派生类仍然是一个抽象类。如果派生类给出了基类纯虚函数的实现,则该派生类不再是抽象类,而是可以建立对象的具体类。

5、虚表剖析

       对于有虚函数的类(即抽象类),编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针,虚表中按照虚函数在类中的声明顺序依次存放了各个虚函数的地址。

eg1:

//只有基类的虚函数

class B
{
public:
	virtual void Funtest1()
	{
		cout<<"B::Funtest1"<<endl;
	}
	virtual void Funtest2()
	{
		cout<<"B::Funtest2"<<endl;
	}
	virtual void Funtest3()
	{
		cout<<"B::Funtest3"<<endl;
	}
	virtual void Funtest4()
	{
		cout<<"B::Funtest4"<<endl;
	}
};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{
	B b;
	Vpf* fun = (Vpf*)*(int*)&b;

	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B)<<endl;
	PrintVft();
	system("pause");
	return 0;
}


 
该段代码中包含一个类,类中有4个虚函数,在打印虚表的函数中,我们创建了一个B类的对象。注意包含虚函数的类如果没有显示定义构造函数,编译器会自动替它合成,并且将虚表指针放在对象的前4个字节 

运行过程截图:

内存分析:

运行结果:

eg2:

//没有覆盖且派生类没有新添加虚函数
class B
{
public:
	virtual void Funtest1()
	{
		cout<<"B::Funtest1"<<endl;
	}
	virtual void Funtest2()
	{
		cout<<"B::Funtest2"<<endl;
	}
	virtual void Funtest3()
	{
		cout<<"B::Funtest3"<<endl;
	}
	virtual void Funtest4()
	{
		cout<<"B::Funtest4"<<endl;
	}
};

class D:public B
{};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{
	D d;//创建派生类对象d
	Vpf* fun = (Vpf*)*(int*)&d;

	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B)<<endl;
	cout<<sizeof(D)<<endl;
	PrintVft();
	system("pause");
	return 0;
}

运行过程截图:


内存分析:


运行结果:

结论:如果派生类没有重写基类的虚函数,并且没有添加自己的虚函数,则直接继承基类的虚函数。


eg3:

//没有覆盖且派生类新添加虚函数
class B
{
public:
	virtual void Funtest1()
	{
		cout<<"B::Funtest1"<<endl;
	}
	virtual void Funtest2()
	{
		cout<<"B::Funtest2"<<endl;
	}
};

class D:public B
{
public:
	virtual void Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
	virtual void Funtest4()
	{
		cout<<"D::Funtest4"<<endl;
	}
};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{
	D d;//创建派生类对象d
	Vpf* fun = (Vpf*)*(int*)&d;

	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B)<<endl;
	cout<<sizeof(D)<<endl;
	PrintVft();
	system("pause");
	return 0;
}


内存分析:


运行结果:


结论:要是派生类新增了虚函数,则虚表先存放从基类继承来的虚函数,再存放派生类自己的虚函数。


eg4:

//有部分覆盖且派生类新添加虚函数
class B
{
public:
	virtual void Funtest1()
	{
		cout<<"B::Funtest1"<<endl;
	}
	virtual void Funtest2()
	{
		cout<<"B::Funtest2"<<endl;
	}
};

class D:public B
{
public:
	virtual void Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
	virtual void Funtest1()//重写基类的虚函数Funtest1()
	{
		cout<<"D::Funtest1"<<endl;
	}
	virtual void Funtest4()
	{
		cout<<"D::Funtest4"<<endl;
	}
};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{
	D d;//创建派生类对象d
	Vpf* fun = (Vpf*)*(int*)&d;

	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B)<<endl;
	cout<<sizeof(D)<<endl;
	PrintVft();
	system("pause");
	return 0;
}


内存分析:


运行结果:


结论:有部分重写的虚函数,在打印派生类的虚表时,先打印基类的虚表,在派生类中重写了的虚函数将替换对应位置基类自己的虚函数,最后是派生类新添加的虚函数


eg5:

//全部覆盖且派生类新添加虚函数
class B
{
public:
	virtual void Funtest1()
	{
		cout<<"B::Funtest1"<<endl;
	}
	virtual void Funtest2()
	{
		cout<<"B::Funtest2"<<endl;
	}
};

class D:public B
{
public:
	virtual void Funtest2()//重写基类的虚函数Funtest2()
	{
		cout<<"D::Funtest2"<<endl;
	}
	virtual void Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
	virtual void Funtest1()//重写基类的虚函数Funtest1()
	{
		cout<<"D::Funtest1"<<endl;
	}
	virtual void Funtest4()
	{
		cout<<"D::Funtest4"<<endl;
	}
};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{
	D d;//创建派生类对象d
	Vpf* fun = (Vpf*)*(int*)&d;

	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B)<<endl;
	cout<<sizeof(D)<<endl;
	PrintVft();
	system("pause");
	return 0;
}


内存分析:


运行结果:


结论:当基类的虚函数全部在派生类中重写时,此时打印派生类的虚表,全部打印基类中重写的虚函数,且顺序与基类中虚函数声明的顺序保持一致,与基类中重写的顺序无关,最后在添上派生类新添加的虚函数


多继承:

eg6:

//没有覆盖的多继承的虚表
class B1
{
public:
	virtual void Funtest1()
	{
		cout<<"B1::Funtest1"<<endl;
	}
};
class B2
{
	virtual void Funtest2()
	{
		cout<<"B2::Funtest2"<<endl;
	}
};
class D:public B1,public B2
{
public:
	virtual void Funtest3()//新增派生类的虚函数Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{

	D d;//创建派生类对象d
	Vpf* fun = (Vpf*)*(int*)&d;

	cout<<"D"<<endl;
	while(*fun)
	{
		(*fun)();
		fun++;
	}

	B2 &b2 = d;//打印B2的虚表
	fun = (Vpf*)*(int*)&b2;
	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B1)<<endl;
	cout<<sizeof(D)<<endl;
	PrintVft();
	system("pause");
	return 0;
}

运行过程截图:


内存分析:


运行结果:


结论:没有重写的多继承的虚表,先按照继承次序打印第一个基类的虚函数表,再打印派生类新添加的虚函数表,然后再接着打印第二个继承基类的虚表,直至所有基类的虚函数表打印完成


eg7:

//部分覆盖的多继承的虚表
class B1
{
public:
	virtual void Funtest1()
	{
		cout<<"B1::Funtest1"<<endl;
	}
};
class B2
{
	virtual void Funtest2()
	{
		cout<<"B2::Funtest2"<<endl;
	}
};
class D:public B1,public B2
{
public:
	virtual void Funtest2()
	{
		cout<<"D::Funtest2"<<endl;
	}
	virtual void Funtest3()//新增派生类的虚函数Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{

	D d;//创建派生类对象d
	Vpf* fun = (Vpf*)*(int*)&d;

	cout<<"D"<<endl;
	while(*fun)
	{
		(*fun)();
		fun++;
	}

	B2 &b2 = d;//打印B2的虚表
	fun = (Vpf*)*(int*)&b2;
	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B1)<<endl;
	cout<<sizeof(D)<<endl;
	PrintVft();
	system("pause");
	return 0;
}


内存分析:


运行结果:


结论:有重写的多继承的虚表,先按照继承次序打印第一个基类的虚函数表(若重写,则用派生类的对应虚函数替换),再打印派生类新添加的虚函数表,然后再接着打印第二个继承基类的虚表,直至所有基类的虚函数表打印完成,(若重写,则用派生类的对应虚函数替换)。


虚拟继承:

eg1:

//带构造函数的虚拟继承
class B
{
public:
	virtual void Funtest1()
	{
		cout<<"B::Funtest1"<<endl;
	}
	virtual void Funtest2()
	{
		cout<<"B::Funtest2"<<endl;
	}
};
class D:virtual public B
{
public:
	D()
	{}//派生类构造函数
	virtual void Funtest2()//重写基类的虚函数Funtest2()
	{
		cout<<"D::Funtest2"<<endl;
	}
	virtual void Funtest3()//新增派生类的虚函数Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{

	D d;//创建派生类对象d
	Vpf* fun = (Vpf*)*(int*)&d;

	cout<<"D"<<endl;
	while(*fun)
	{
		(*fun)();
		fun++;
	}

    cout<<endl<<endl;

	fun = (Vpf*)*((int*)&d + 3);
	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B)<<endl;
	cout<<sizeof(D)<<endl;
	PrintVft();
	system("pause");
	return 0;
}



内存分析:



运行结果:



eg2:

<pre name="code" class="cpp">//部分覆盖的虚拟继承
class B1
{
public:
	virtual void Funtest1()
	{
		cout<<"B1::Funtest1"<<endl;
	}
	virtual void Funtest2()
	{
		cout<<"D::Funtest2"<<endl;
	}
};
class D:virtual public B1
{
public:
	virtual void Funtest2()//重写基类的虚函数Funtest2()
	{
		cout<<"D::Funtest2"<<endl;
	}
	virtual void Funtest3()//新增派生类的虚函数Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
};
typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{

	D d;//创建派生类对象d
	Vpf* fun = (Vpf*)*(int*)&d;

	cout<<"D"<<endl;
	while(*fun)
	{
		(*fun)();
		fun++;
	}

    cout<<endl<<endl;

	fun = (Vpf*)*((int*)&d + 2);
	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<sizeof(B1)<<endl;
	cout<<sizeof(D)<<endl;
	PrintVft();
	system("pause");
	return 0;
}


 

内存分析:

运行结果:

结论:带有重写的虚拟继承中,先打印派生类新添加的虚函数表,再打印从基类继承来的虚函数表,重写的部分用派生类对应的虚函数替换

菱形继承:

1、非虚拟继承

//菱形继承
class B
{
public:
	virtual void Funtest1()
	{
		cout<<"B::Funtest1"<<endl;
	}

    int _data1;
};
class C1:public B
{
public:
	virtual void Funtest2()//新添
	{
		cout<<"C1::Funtest2"<<endl;
	}

	int _data2;
};
class C2:public B
{
public:
	virtual void Funtest3()//新添加虚函数
	{
		cout<<"C2::Funtest3"<<endl;
	}
	virtual void Funtest1()//重写
	{
		cout<<"C2::Funtest1"<<endl;
	}

	int _data3;
};
class D:public C1,public C2
{
public:
	virtual void Funtest3()//重写基类的虚函数Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
	virtual void Funtest4()//新添加虚函数Funtest4()
	{
		cout<<"D::Funtest4"<<endl;
	}

	int _data4;
};

typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{

	D d;//创建派生类对象d
	d.C1::_data1 = 1;
	d.C2::_data1 = 5;
	d._data2 = 2;
	d._data3 = 3;
	d._data4 = 4;
	Vpf* fun = (Vpf*)*(int*)&d;

	cout<<"D"<<endl;
	while(*fun)
	{
		(*fun)();
		fun++;
	}
	
	cout<<endl<<endl;

	fun = (Vpf*)*((int*)&d + 3);//向后偏移12个字节找到C2的虚表指针
	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<"B  "<<sizeof(B)<<endl;
	cout<<"C1  "<<sizeof(C1)<<endl;
	cout<<"C2  "<<sizeof(C2)<<endl;
	cout<<"D  "<<sizeof(D)<<endl<<endl;
	PrintVft();
	system("pause");
	return 0;
}


分析截图:



运行结果:



结论:非虚拟的菱形继承中,派生类的对象的内存分布结构为:先存放第一个基类的虚表指针和从最基类那继承来的数据和基类自己的数据,接着将派生类新添的虚函数按声明顺序放在其虚表末尾,接着存放第二个基类的虚表指针和从最基类那继承来的数据和基类自己的数据,最后是派生类自己的数据部分


2、虚拟继承

//菱形继承(虚拟继承)
class B
{
public:
	virtual void Funtest1()
	{
		cout<<"B::Funtest1"<<endl;
	}

	int _data1;
};
class C1:virtual public B
{
public:
	virtual void Funtest2()//新添
	{
		cout<<"C1::Funtest2"<<endl;
	}

	int _data2;
};
class C2:virtual public B
{
public:
	virtual void Funtest3()//新添加虚函数
	{
		cout<<"C2::Funtest3"<<endl;
	}
	virtual void Funtest1()//重写
	{
		cout<<"C2::Funtest1"<<endl;
	}

	int _data3;
};
class D:public C1,public C2
{
public:
	virtual void Funtest3()//重写基类的虚函数Funtest3()
	{
		cout<<"D::Funtest3"<<endl;
	}
	virtual void Funtest4()//新添加虚函数Funtest4()
	{
		cout<<"D::Funtest4"<<endl;
	}

	int _data4;
};

typedef void (*Vpf)();//函数指针

void PrintVft()//打印虚表
{

	D d;//创建派生类对象d
	d._data1 = 1;
	d._data2 = 2;
	d._data3 = 3;
	d._data4 = 4;
	Vpf* fun = (Vpf*)*(int*)&d;

	cout<<"D"<<endl;
	while(*fun)
	{
		(*fun)();
		fun++;
	}

	cout<<endl<<endl;

	fun = (Vpf*)*((int*)&d + 3);//向后偏移12个字节找到C2的虚表指针
	while(*fun)
	{
		(*fun)();
		fun++;
	}

	cout<<endl<<endl;

	fun = (Vpf*)*((int*)&d + 7);//向后偏移28个字节找到B的虚表指针
	while(*fun)
	{
		(*fun)();
		fun++;
	}
}
int main()
{
	cout<<"B  "<<sizeof(B)<<endl;
	cout<<"C1  "<<sizeof(C1)<<endl;
	cout<<"C2  "<<sizeof(C2)<<endl;
	cout<<"D  "<<sizeof(D)<<endl<<endl;
	PrintVft();
	system("pause");
	return 0;
}

分析过程:



运行结果:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值