多态概念

多态:C++中多态是面向对象编程的三大类型之一(封装,继承),它允许不同类的对象通过相同的接口调用不同的实现。多态=“一个接口,多种实现”

多态分类

类型描述实现方式绑定时间
编译多态在编译期间决定调用哪个函数函数重载,运算符重载,模板静态绑定
运行多态在运行期间决定调用哪个函数虚函数(virtual)动态绑定

多态定义和实现

多态是继承关系下的运用对象,去调用同一函数,产生不同对象的行为,例如买票,有的是全价买票,学生半价买票,军人优先买票

实现多态的必要条件

1:被调用的函数必须是虚函数,并且完成了虚函数的重写或覆盖

2:必须是基类的指针或引用调用虚函数

虚函数

类的成员函数前面加上virtual关键字修饰,那么这个成员函数就被称为虚函数。

注:不是成员函数不能用virtual修饰

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票-全价" << endl;
	}
};

虚函数的重写和覆盖

重写:在派生类中有一个与基类完全相同的虚函数(返回值类型,函数名字,参数列表完全相同),称为派生类的虚函数重写了基类的虚函数

注:在重写虚函数时,即使派生类不加virtual关键字也构成虚函数重写(因为继承后基类的虚函数被继承了下来,在派生类中依旧保持虚函数的属性)

基类函数一旦是 virtual,派生类同名同参的函数自动带虚属性,写不写 virtual 只影响可读性,不影响多态行为。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票-全价" << endl;
		return;
	}
};
class Student :public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票-半价" << endl;
		return;
	}
};

void Func(Person* ptr)
{
	ptr->BuyTicket();
}

int main()
{
	Person p;
	Student s;

	Func(&p);
	Func(&s);
	return 0;
}

析构函数的重写

如果基类的析构函数为虚函数,那么派生类的析构函数只需要定义,无论是否加virtual都与基类的析构函数构成了重写,虽然派生类的析构函数名称与基类不同,不符合重写的规则,但是实际上编译器已经对析构函数做了特殊处理,使其函数名称都统一处理成destructor,所以只要基类的析构函数加上virtual构成虚函数,派生类的析构函数都构成重写

class Person
{
public:
	 virtual~Person()//如果不构成重写,那么就会导致Student存在内存的泄露
	{
		cout << "~Person()" << endl;

	}
};

class Student :public Person
{
public:
	~Student()
	{
		delete[] _ptr;
		cout << "~Student()" << endl;
	}
protected:
	int* _ptr = new int[10];
};

int main()
{
	Person* p1 = new Student;
	delete[]p1;

	Person* p2 = new Person;
	delete[]p2;

	return 0;
}

override和final关键字

override:可以帮助我们检查是否构成重新

class Car
{
public:
     virtual void Drive()
     { }
};

class Benz :public Car
{
public:
    void Drive() override//如果基类没写virtual就报错
    {
        cout << "舒适" << endl;
    }
};

int main()
{
    Benz bz;

    bz.Drive();
    return 0;
}

final:如果不想让派生类重写函数就用该关键字修饰

class Car
{
public:
     virtual void Drive() final//加上final该成员函数就不能被重写
     { }
};

class Benz :public Car
{
public:
    void Drive() override//如果基类没写virtual就报错
    {
        cout << "舒适" << endl;
    }
};

int main()
{
    Benz bz;

    bz.Drive();
    return 0;
}

重载/重写/隐藏的对⽐

纯虚函数和抽象类

纯虚函数:在虚函数的后面加上=0就变成纯虚函数,纯虚函数不需要定义实现只需要声明即可

抽象类:包含纯虚函数的类就叫抽象类,抽象函数不能实例化出对象,如果派生类继承抽象类,如果派生类不重写纯虚函数那么派生类也是抽象类

class Car
{
public:
	virtual void Drive() = 0;
};

class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};

class BMW : public Car
{
public:
   virtual void Drive()
   {
     cout << "BMW操控" << endl;
    }
};
int main()
{
	//编译报错:error C2259 : “Car”:⽆法实例化抽象类

	Car car;
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
	return 0;
}

多态的原理

运行时多态是靠虚函数(virtual)

运行时多态的核心理念就是:虚函数表(vtable)+虚指针(vptr)

虚函数表(vtable)

1:每个包含虚函数的类,编译器都会为其生成一个虚函数表

2:表中存储的都是该类所有虚函数的函数指针

虚指针(vptr)

1:每一个函数对象内部都有一个指针,指向该类的虚函数表

2:当该函数对象被构造时,vptr被初始化成对应类的虚函数表指针的对应位置

动态绑定

通过基类的指针或引用调用虚函数时,运行时会通过虚指针(vptr)找到对应的虚函数表(vtable),再调用实际派生类的函数

class Base
{
public:
	virtual void Drive()
	{
		cout << "Base Spack" << endl;
	}
};

class Derived :public Base
{
public:
	void Drive() override
	{
		cout << "Derived Sapck" << endl;
	}
};

int main()
{

	Base* ptr = new Derived();

	ptr->Drive();//Derived Sapck
	return 0;
}

Base是静态类型,编译器在编译时会去查找,而new则是动态类型,在运行时会去查找,则ptr是调用Derived,在运行时调用谁就是谁的

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}

	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}

	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
	
};

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

void Func1(Base* p)
{
	p->Func1();

	p->Func3();
}

int main()
{

	Base b;
	Derive d;

	Func1(&b);
	Func1(&d);
	return 0;
}

两个类构成多态都各自生成一个虚函数表,通过调用Func1函数,形成虚函数表,根据虚指针,调用了成员函数Func1和Func3,但是Func3不构成重写不是虚函数就不在虚函数表中。

在Func1(&d)中Derive也生成了一个虚函数表,与基类的虚函数表内存位置不同,但是因为继承,实现方式是类似的,通过虚指针的指向就可以形成指向谁调用谁的结果。

如果指定类作用域就不存在这样的概念,但是在Derive中调用Func2就和基类的Func2的内存位置相同,是直接切割了派生类中父类的成员函数,给到了派生类

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐