多态

多态

多态定义

  • C++中所谓的多态是指:由继承而产生的子类,对同一消息而产生不同的行为

多态举例代码

#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;

class Person
{
public:
	Person(string name):m_name(name)
	{
	}

	virtual void reaction() {}

protected:
	string m_name;
};


class StudentA :public Person
{
public:
	StudentA(string name):Person(name)
	{

	}
	virtual void reaction() 
	{
		cout << m_name << "准备看书" << endl;
	}
};



class StudentB :public Person
{
public:
	StudentB(string name) :Person(name)
	{

	}
	virtual void reaction()
	{
		cout << m_name << "准备写字" << endl;
	}
};

class Stranger :public StudentA  //孙子
{
public:
	Stranger(string name):StudentA(name)
	{}
	virtual void reaction()
	{
		cout << m_name << "准备跑" << endl;
	}
};

void TeacherComing(Person* p) //用父类指针去接子类地址
{
	cout << "老师来了" << endl;
	p->reaction();      //传哪个对象,调用哪个对象的reaction
}


int main()
{
	StudentA s1("julian");
	StudentB s2("kerr");
    Stranger s3("mmmm");

	TeacherComing(&s1);
	TeacherComing(&s2);
    TeacherComing(&s3);

    return 0;
}



多态发生的三个必要条件

  1. 要有继承
  2. 父类有虚函数(virtual关键字),子类要有虚函数重写(重写是一模一样,不是重载,重载是不同作用域下:函数名相同,函数列表不同)
  3. 父类指针或引用指向子类对象(孙子也行)

注意:子类的虚函数只要是对父类的虚函数重写,子类重写的函数默认就是虚函数,前面加不加关键字virtual都无所谓,但是为了代码可读性,一般加上。如果子类加virtual,父类没加,基本没有任何意义(但是也存在于子类虚函数表内,如果父类指针指向子类对象,那么此函数无法得到,如果是子类指针指向孙类对象,则能够得到此虚函数),除非孙类重写虚函数,发生多态,那和父类没有关系了。



虚析构函数

举例:代码构造出如下图的类结构,A是父类,B是子类

多态


class A
{
public:
	A(const char* name)
	{
		cout << "A()" << endl;
		int len = strlen(name);
		pf = new char[len + 1];
		strcpy_s(pf, len+1, name);
	}

	virtual void Fun()
	{
		cout << "A_Fun" << endl;
	}

	virtual ~A()
	{
		if (pf != NULL)
		{
			delete [] pf;
			pf = NULL;
		}
		cout << "~A()" << endl;
	}
protected:
	char* pf;
	
};


class B :public A
{
public:
	B(const char* str1, const char* name):A(str1)
	{
		cout << "B()" << endl;
		int len = strlen(name);
		ps = new char[len + 1];
		strcpy_s(ps, len+1, name);
	}

	virtual void Fun()
	{
		cout << "B_Fun" << endl;
	}

	~B()
	{
		if (ps != NULL)
		{
			delete [] ps;
			ps = NULL;
		}
		cout << "~B()" << endl;
	}
private:
	char* ps;
};


void Test(A* p)  //此处发生多态
{
	p->Fun();  //传谁调用谁的Fun(),因为Fun()是虚函数
	delete p;  //调用析构, 如果父类A的析构函数没有写成虚函数,则只调用 ~A()。
	           //因为参数p是A*指针,delete p只调用~A(),B的ps所指的空间泄露了
}

int main()
{
	 B* p=new B("julian", "kerr");
	Test(p);
	
    return 0;
}

结果:左边是父类是普通的析构函数, 右边是父类的析构函数写成虚函数的结果
多态

结论:当设计的类具有多态现象的时候,析构函数尽量都写成虚析构函数,以防子类的析构函数没有调用



多态发生的原理

  • 问题:为什么传递不同的子类地址给父类指针,调用的是子类各自的函数?

首先明白两个概念:

  1. VPTR(虚指针,指向虚函数表):只要类中申明有虚函数,默认自动添加的成员变量,32位:sizeof类的话自动加4字节
  2. 虚函数表(存放的是类的虚函数入口地址): 肯定是编译之后就不能修改的,一定存放在只读区域

仔细阅读以下代码:有父类,子类,孙类



class Father
{
public:
	virtual void Fun(int a)
	{
		cout << "virtual Father Fun(int)" << endl;
	}

	void Fun(int a, int b)
	 {
		 cout << "Father Fun(int, int)" << endl;
	 }

private:
	int a;

};


class Son : public Father
{
public:
	virtual void Fun(int a)
	{
		cout << "virtual Son Fun(int)" << endl;
	}

	virtual void Fun(int a, int b)
	{
		cout << "Son Fun(int, int)"  << endl;
	}
};


class Grand_Son : public Son
{
public:
	virtual void Fun(int a)
	{
		cout << "virtual G_Son Fun(int)" << endl;
	}

	 void Fun(int a, int b)
	{
		cout << "virtual G_Son Fun(int, int)" << endl;
	}
};


int main()
{

// 
	Son s;
	Father* p = &s;
	p->Fun(1);      //virtual Son Fun(int)
	p->Fun(1, 2);   //Father Fun(int, int), 相当于继承,但是Son自己的Fun()无法访问



//
	Grand_Son G_s;
	Son* p1 = &G_s;
	p1->Fun(1);    //virtual G_Son Fun(int)
	p1->Fun(1, 2); //virtual G_Son Fun(int,int)
    return 0;
}

解释:每个具有虚函数的类,在创建的时候都默认有创建一个虚函数指针和虚函数表,当调用虚函数的时候先从虚函数表中去找(偏移量根据指针类型,如果是父类指针指向子类对象,那么虚函数表的偏移只能到父类虚函数表所有的),没有的话在去普通函数去找。如下图:

多态

	Son s;
	Father* p = &s;
	p->Fun(1);     
	p->Fun(1, 2);   不会在虚函数表里面找,尽管有,但是由于p是Father*类型,只能偏移到Fun(int),无法偏移到Fun(int,int),所以会在普通函数里面找(父类若没有提供,编译则报错)

结论:通过虚函数表指针去查表是在程序运行的时候进行的,普通函数在编译的时候就确定了调用的函数,所以虚函数的调用效率要低与普通函数。只需要将需要多态的函数申明为虚函数即可。



VPTR指针的分布初始化

  • 是VPTR指针转变的过程(从父类转向子类)
#include "stdafx.h"
#include <iostream>
using namespace std;



class Father
{
public:
	Father(int a)
	{
		this->a = a;
		Print();  
	}
	virtual void Print()
	{
		cout << "Father Print()" << endl;
	}
private:
	int a;

};


class Son : public Father
{
public:
	Son(int a, int b):Father(a)
	{
		this->b = b;
	}
	virtual void Print()
	{
		cout << "Son Print()" << endl;
	}
private:
	int b;
};



int main()
{
	Son s(1, 2); 
    return 0;
}


在上面的代码中, main函数创建Son s(1,2),//先调用Father构造,再调用子类的构造,在调用Father构造的时候,Print打印是Father还是Son?

结果:Father Print()

为什么调用的是父类Print(),在构造s的时候先调用Father的构造函数(红色部分),这时候VPTR当做Father类型,会临时指向Father的虚函数表;再调用Son的构造函数(蓝色部分),VPTR当做Son类型,将由指向Father虚函数表重新指向Son的虚函数表。

多态

结论:虚函数指针VPTR是分布初始化的,所以不要在构造函数中写业务,也不要去调用虚函数,否则会可能出错。