【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一个子类对象呢???
//借助虚函数
}
虚函数
如何用父类指针来调用一个子类对象呢?
定义一个对象指针,就可以调用父类和各个子类的同名,同参函数
- 这个对象指针,必须是父类指针;
- 这个同名同参函数,父类中声明之前必须要加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
注意:
- 成员函数有纯虚函数的类叫抽象类,不能用来生成对象,主要用来当做基类来生成子类用;
- 子类必须要完全实现该基类中定义的纯虚函数;
使用场景
很多情况下,基类实例化对象是没有实际意义的,比如说定义一个动物类,派生类可能有老虎类,狮子类,可以实例化为对象,但是如果动物类实例化对象,就不合适了,没有实际意义。
纯虚函数就是我们在定义虚函数时,不给出其具体的定义,具体的定义由不同的派生类去填充,含有纯虚函数的类称为抽象类,抽象类本身就是为了成为基类,让派生类去继承,去重写纯虚方法,派生类若没有将抽象基类的纯虚方法,那么派生类也称为抽象类。对于抽象类不能实例化对象,但可以定义指针或者引用,方便实现多态特性。
#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的;
- 对于派生类来讲,若父类实现了虚析构函数,那么在派生类的虚函数表中,父子析构函数在虚函数表中的位置是相同的,由于析构函数名称是不同的,那么派生类的析构函数将包含基类的析构函数,即释放了派生类的资源后,再调用基类的析构函数释放派生类从基类继承的成员。
结论
- 如果一个类,想要做基类,一定要把这个类的析构函数写成虚析构函数;
- 只要基类的析构函数是虚函数,能保证delete基类指针时候正确的运行析构函数版本;
- 普通类不写析构函数,但是如果是基类,必须是虚析构函数;
#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++ 不支持友元被继承,因此无法实现成虚函数。
构造函数,虚函数依赖于对象,构造函数执行过程就是对象正在构造的过程,只有对象构造起来才能使用多态机制。
内联函数,内联函数需要在编译期确定函数调用关系并展开,但虚函数是为了实现运行时期多态即动多态,编译器不报错,但不会展开

浙公网安备 33010602011771号