【C/C++】【类和对象】多态和虚函数

基类指针/派生类指针

基类指针可以new派生类对象,但是子类对象不能new基类指针;
基类指针new了子类对象,但是还是无法调用子类对象,需要借助虚函数;

#include <iostream>
using namespace std;
class Human
{
public:
	Human();
	Human(int);
public:
	int m_Age;
	char m_Name[100];
public:
	void func_human();
};

Human::Human()
{
	cout << "Human::Human()" << endl;
}
Human::Human(int tmp)
{
	cout << "Human::Human(int tmp)" << endl;
}
void Human::func_human()
{
	cout << "void Human::func_human()" << endl;
}

//Men是Human的子类
class Men:public Human
{
public:
	Men();
	Men(int);

private:

public:
	void func_man();
};

Men::Men()
{
	cout << "Men::Men()" << endl;
}
Men::Men(int tmp)
{
	cout << "Men::Men(int tmp)" << endl;
}
void Men::func_man()
{
	cout << "void Men::func_man()" << endl;
}
int main()
{

	Human* p_human = new Human();
	Men* p_men = new Men();

	//父类指针可以new子类对象,父类指针指向子类对象;反之不行;
	Human* p_h = new Men();
	p_h->func_human(); //父类类型指针调用父类的成员函数;
	//p_h->func_man(); //错误,不可以,虽然是new子类对象,但是你是父类指针,不可以调用子类成员函数
	//Men* p_m = new Human(); 子类指针无法new父类对象;	
	//父类指针没有办法调用子类的成员函数,为什么还让父类指针new一个子类对象呢???
        //借助虚函数

}

虚函数

如何用父类指针来调用一个子类对象呢?
定义一个对象指针,就可以调用父类和各个子类的同名,同参函数

  1. 这个对象指针,必须是父类指针;
  2. 这个同名同参函数,父类中声明之前必须要加virtual;声明为虚函数;子类中不加也可以,一旦某个函数在父类中声明虚函数,子类继承后都是虚函数
    子类中添加virtual可以增加可读性;函数重写,子类和父类的函数名、参数、返回值一样;
  • 可以通过new不同的类对象,来调用不同的同名函数;
  • 也可以直接使用类名::函数名的方式通过指针来调用父类的同名函数;
#include <iostream>
using namespace std;
class Human
{
public:
	Human();
	Human(int);
public:
	int m_Age;
	char m_Name[100];
public:
	void func_human();
	virtual void eat();
};
Human::Human()
{
	cout << "Human::Human()" << endl;
}
Human::Human(int tmp)
{
	cout << "Human::Human(int tmp)" << endl;
}
void Human::func_human()
{
	cout << "void Human::func_human()" << endl;
}
void Human::eat()
{
	cout << "human eat food" << endl;
}

//Men是Human的子类
class Men:public Human
{
public:
	Men();
	Men(int);
public:
	void func_man();
	virtual void eat();
};
Men::Men()
{
	cout << "Men::Men()" << endl;
}
Men::Men(int tmp)
{
	cout << "Men::Men(int tmp)" << endl;
}
void Men::func_man()
{
	cout << "void Men::func_man()" << endl;
}
void Men::eat()
{
	cout << "men eat mi" << endl;
}
//Women是Human的子类
class Women :public Human
{
public:
	Women();
	Women(int);
public:
	void func_women();
	virtual void eat();
};
Women::Women()
{
	cout << "Women::Women()" << endl;
}
Women::Women(int tmp)
{
	cout << "Women::Women(int tmp)" << endl;
}
void Women::func_women()
{
	cout << "void Women::func_women()" << endl;
}
void Women::eat()
{
	cout << "women eat miantiao" << endl;
}
int main()
{

	/*
	Human* p_h = new Men();
	p_h->eat(); //调用的是父类的eat函数,因为phuman是父类指针;
	//如何调用Men和Women中的eat()函数;

	//1. 比较繁琐,定义多个指针来解决
	Men* p_m = new Men();
	p_m->eat(); //调用子类men的eat


	Women* p_w = new Women();
	p_w->eat(); //调用子类women的eat
	*/

	//2. 定义一个对象指针,就可以调用父类和各个子类的同名,同参函数
	/*
	Human* p_h = new Men();
	p_h->eat(); //调用Men的虚函数,new的是什么调用什么;
	delete p_h;
	
	p_h = new Women();
	p_h->eat(); //调用的是Women的虚函数
	delete p_h;

	p_h = new Human();
	p_h->eat(); //调用的是Human的虚函数
	delete p_h;
	*/

        //3. 不重新new,直接调用Human的虚函数;
	/*
	Human* p_h_1 = new Men();
	p_h_1 -> eat(); //调用Men的虚函数
	p_h_1->Human::eat(); //调用的是Human的虚函数
	delete p_h_1;
	*/
	
}

override

为了避免在子类中写错虚函数,在C++11中,可以在函数声明中添加override关键字;这个关键字用在子类中,而且是虚函数专用;override就是用来说明派生类中的虚函数,用了这个关键字,编译器会认为这个eat是覆盖了基类中的同名函数(只有虚函数才存在子类可以覆盖父类中同名函数的问题);那么编译器就会在父类中找同名同参的虚函数,如果没找到,编译器就会把报错;如果函数名,参数返回值写错了,编译器会帮忙纠错;

  • 注意: 派生类的虚函数可以和基类的虚函数的返回值有差别;

final

虚函数专用,用在父类中,如果在父类的函数声明中加了final关键字;子类不能覆盖父类的该函数;

调用虚函数执行的还是“动态绑定”,动态:指的是在程序运行的时候才知道调用哪个函数;

多态性

多态性:

  • 体现在具有继承关系的父类和子类之间,子类重新定义父类的成员函数,同时父类把该成员函数声明为virtual
  • 通过父类的指针,只有到了程序运行的时候,找到动态绑定到父类指针上的对象,这个对象它有可能是某个子类对象,也可能是父类对象
  • 系统内部实际上要查一个虚函数表,找到函数的入口地址,从而调用父类或者子类的函数;这就是运行时期的多态性;

纯虚函数

  • 纯虚函数是在基类中声明的虚函数,但是在基类中没有定义;但是要求任何派生类都要定义该虚函数的实现方法;
  • 基类中实现纯虚函数的方法是在函数原型后增加=0

注意:

  1. 成员函数有纯虚函数的类叫抽象类,不能用来生成对象,主要用来当做基类来生成子类用;
  2. 子类必须要完全实现该基类中定义的纯虚函数;

使用场景
很多情况下,基类实例化对象是没有实际意义的,比如说定义一个动物类,派生类可能有老虎类,狮子类,可以实例化为对象,但是如果动物类实例化对象,就不合适了,没有实际意义。

纯虚函数就是我们在定义虚函数时,不给出其具体的定义,具体的定义由不同的派生类去填充,含有纯虚函数的类称为抽象类,抽象类本身就是为了成为基类,让派生类去继承,去重写纯虚方法,派生类若没有将抽象基类的纯虚方法,那么派生类也称为抽象类。对于抽象类不能实例化对象,但可以定义指针或者引用,方便实现多态特性。

#include <iostream>
using namespace std;
class Human
{
public:
	Human();
	Human(int);
public:
	int m_Age;
	char m_Name[100];
public:
	virtual void eat() =0;
};

Human::Human()
{
	cout << "Human::Human()" << endl;
}
Human::Human(int tmp)
{
	cout << "Human::Human(int tmp)" << endl;
}

//Men是Human的子类
class Men:public Human
{
public:
	Men();
	Men(int);
public:
	virtual void eat();
};
Men::Men()
{
	cout << "Men::Men()" << endl;
}
Men::Men(int tmp)
{
	cout << "Men::Men(int tmp)" << endl;
}
void Men::eat()
{
	cout << "men eat mi" << endl;
}
//Women是Human的子类
class Women :public Human
{
public:
	Women();
	Women(int);
public:
	virtual void eat();
};
Women::Women()
{
	cout << "Women::Women()" << endl;
}
Women::Women(int tmp)
{
	cout << "Women::Women(int tmp)" << endl;
}
void Women::eat()
{
	cout << "women eat miantiao" << endl;
}
int main()
{
	//抽象类,不能用来生成对象,主要用来统一管理子类对象
	//Human h; //一旦一个类中有纯虚函数,不能生成这个类的对象了,不能实例化抽象类
	//Human* p_h = new Human; 不合法
	Human* p_m = new Men;
	p_m->eat();
	Human* p_w = new Women;
	p_w->eat();
	
}

虚析构函数

基类的析构函数一般写成虚函数;如果基类的析构函数不是虚函数,用基类指针new子类的对象,在delete的时候,系统不会调用派生类的析构函数,delete 指针是会根据指针的类型进行析构,即仅仅释放了基类的数据成员,派生类的数据成员将无法释放,会导致内存泄露。
解决方法

  • 把父类的析构函数写成虚函数
  • 在public继承中,基类对派生类以及其对象的操作,只能影响到哪些从基类继承下来的成员;如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数,析构函数也是这样;
  • 此外,基类中析构函数的虚属性也会被继承给子类,这样的话子类中的析构函数自然就成了虚函数,虽然名字跟基类的析构函数不同;
  • detete puhuan; 肯定要调用父类的析构函数,但是在父类析构函数中它要想调用子类men的析构函数,那么human这个类的析构函数就要声明成virtual,也就是C++中的运行时多态,调用的函数必须是virtual的;
  • 对于派生类来讲,若父类实现了虚析构函数,那么在派生类的虚函数表中,父子析构函数在虚函数表中的位置是相同的,由于析构函数名称是不同的,那么派生类的析构函数将包含基类的析构函数,即释放了派生类的资源后,再调用基类的析构函数释放派生类从基类继承的成员。

结论

  1. 如果一个类,想要做基类,一定要把这个类的析构函数写成虚析构函数;
  2. 只要基类的析构函数是虚函数,能保证delete基类指针时候正确的运行析构函数版本;
  3. 普通类不写析构函数,但是如果是基类,必须是虚析构函数;
#include <iostream>
using namespace std;
class Human
{
public:
	Human();
	Human(int);
	virtual ~Human();
public:
	int m_Age;
	char m_Name[100];
public:
	virtual void eat() =0;
};
Human::Human()
{
	cout << "Human::Human()" << endl;
}
Human::~Human()
{
	cout << "Human::~Human()" << endl;
}
Human::Human(int tmp)
{
	cout << "Human::Human(int tmp)" << endl;
}

//Men是Human的子类
class Men:public Human
{
public:
	Men();
	Men(int);
	~Men();
public:
	virtual void eat();
};

Men::Men()
{
	cout << "Men::Men()" << endl;
}
Men::~Men()
{
	cout << "Men::~Men()" << endl;
}
Men::Men(int tmp)
{
	cout << "Men::Men(int tmp)" << endl;
}
void Men::eat()
{
	cout << "men eat mi" << endl;
}
//Women是Human的子类
class Women :public Human
{
public:
	Women();
	Women(int);
	~Women();
public:

	virtual void eat();
};
Women::Women()
{
	cout << "Women::Women()" << endl;
}
Women::~Women()
{
	cout << "Women::~Women()" << endl;
}

Women::Women(int tmp)
{
	cout << "Women::Women(int tmp)" << endl;
}
void Women::eat()
{
	cout << "women eat miantiao" << endl;
}
int main()
{
	//Men men;
	Human* p_m = new Men;
	delete p_m; //没有调用子类的析构函数 

}

总结

不能实现为虚函数的函数
普通函数,不属于类的函数,不能加 virtual,编译器报错。
静态成员函数,静态成员函数属于整个类,被所有对象所共享,没有动态绑定的必要性,编译器报错。
友元函数,友元函数不是类成员方法,C++ 不支持友元被继承,因此无法实现成虚函数。
构造函数,虚函数依赖于对象,构造函数执行过程就是对象正在构造的过程,只有对象构造起来才能使用多态机制。
内联函数,内联函数需要在编译期确定函数调用关系并展开,但虚函数是为了实现运行时期多态即动多态,编译器不报错,但不会展开

posted @ 2020-07-17 11:59  NaughtyCoder  阅读(153)  评论(0)    收藏  举报