c++多态实现

        多态是面向对象最基本的特征之一,那么多态是什么呢?个人的理解是,根据赋值给基类的指针或引用的对象,在运行时刻正确调用相应对象的成员函数。说白了,就是一个接口,多个方法。由于c++默认是运行前绑定(早绑定),这和Java不同,Java默认就是运行时绑定。因此c++实现运行时多态需要使用virtual关键字(这是最常用的一种实现方式,另一种就是虚函数无法实现时,使用RTTI,有空会写篇文章介绍下)。若在一类基类中定义了成员函数为虚函数,那么在其派生类重新定义此成员函数(覆盖),并且基类中的virtual是隐形继承下来的,为了提高代码的可读性,最好显示的将virtual加上。


       首先简单解释一下重载、覆盖和隐藏的区别。重载是发生在同一个类中,声明同名函数,通过形参类型不同或形参个数不同或者都不相同来实现重载(返回值不同并不能实现重载)。覆盖是发生在有继承关系的不同类中,基类通过将成员函数声明为virtual类型,派生类中重新定义一抹一样的成员函数。隐藏是指派生类的成员函数隐藏了与其同名的基类成员函数,①若基类和派生类的函数同名,但参数不同,无论是否有virtual关键字,基类的函数被隐藏;②若派生类和基类的函数名相同,参数也相同,基类没有virtual,基类的函数被隐藏。看看如下的代码片段

#include <iostream>

class Base {
public:
	/*
	重载
	*/
	int m_f_ol(int i); 

	//对m_f重载,参数个数不同
	int m_f_ol(int i, double d);

	//对m_f重载,参数类型不同
	int m_f_ol(std::string s); 

	//对m_f重载,参数个数和类型都不同
	int m_f_ol(int i, std::string s);

	//编译错误,不能通过返回值不同实现重载
	//std::string m_f_ol(int i);

	/*
	覆盖(基类)
	*/
	virtual void m_f_or() {
		std::cout << "基类的m_f_or函数" << std::endl;
	}

	/*
	隐藏(基类)
	*/
	void m_f_h1() {
		std::cout << "基类的m_f_h1函数" << std::endl;
	}

	void m_f_h2() {
		std::cout << "基类的m_f_h2函数" << std::endl;
	}
};

class Derived : public Base {
public:
	/*
	覆盖(派生类)
	*/
	virtual void m_f_or() {
		std::cout << "派生类的m_f_or函数,对基类m_f_or的覆盖" << std::endl;
	}

	/*
	隐藏(派生类)
	*/
	//基类的m_f_h没有virtual关键字,基类的m_f_h1被隐藏
	void m_f_h1() {
		std::cout << "派生类的m_f_h1函数" << std::endl;
	}
	
	//无论基类有无virtual关键字,基类的m_f_h2被隐藏
	void m_f_h2(int i) {
		std::cout << "派生类的m_f_h2函数" << std::endl;
	}
};
       

        现在我们了解到在c++中实现多态就是靠覆盖(ps:重载不是多态),基类的指针或者引用根据指向对象的不同,在运行时调用不同对象的成员函数。看一个最简单的多态例子

#include <iostream>

class Base {
public:
	/*
	覆盖(基类)
	*/
	virtual void m_f_or() {
		std::cout << "基类的m_f_or函数" << std::endl;
	}
};

class Derived : public Base {
public:
	/*
	覆盖(派生类)
	*/
	virtual void m_f_or() {
		std::cout << "派生类的m_f_or函数,对基类m_f_or的覆盖" << std::endl;
	}
};

int main() {

	//基类的指针b指向基类对象
	Base *b = new Base;

	//派生类的指针指向派生类对象
	Derived *d = new Derived;


	b->m_f_or();
	d->m_f_or();

	system("pause");

	return 0;
}
运行结果:


        代码确实是一个最简单实现多态的例子,那么c++是如何实现这一规则的呢?

        其实c++编译器对于每一个含有虚函数的类都创建一个虚函数表,这个表称为vtable。编译器对带有虚函数的类都自动设置一个虚拟指针vptr(vpointer),它指向这个对象的虚函数表vtable。通过基类指针调用虚函数时,编译器静态的插入这个取到这个vptr,并在vtable表中查找函数地址的代码,这样就能实现晚绑定,当然这一切的操作都是编译器自动完成。

        看看下面的代码倒是是不是插入了虚拟指针vptr。

#include <iostream>

class A {
public:
	void set_i(int i) {
		this->i = i;
	}

	int get_i() const {
		return i;
	}

private:
	int i;
};

class B {
public:
	virtual void set_i(int i) {
		this->i = i;
	}

	int get_i() const {
		return i;
	}

private:
	int i;
};

int main() {

	std::cout << "class A size: " << sizeof(A) << std::endl;
	std::cout << "class B size: " << sizeof(B) << std::endl;
		 
	system("pause");

	return 0;
}

看看运行结果:

        从上面的结果可以看出,没有virtual关键字的含有一个int类型数据成员的类大小正好是4字节(32机器),而含有virtual后的类确有8个字节,原因就是除了4字节的int数据外编译器增加了一个vptr指针,32位机器指针大小4字节,正好8字节。

        后面会写一篇文章尽量详细介绍下虚函数实现多态的原理。

猜你喜欢

转载自blog.csdn.net/u014489596/article/details/38368807