虚函数
定义:前面有virtual关键字的成员函数就是虚函数, virtual 关键字只用在类定义里的函数声明里,写函数体时不需要
class base{
virtul int get();
};
int base::get(){}
多态的表现形式一
- 派生类的指针可以赋值给基类指针
- 通过基类指针调用基类和派生类中的同名虚函数时
- 若指针指向一个基类对象,那么被调用的是基类的虚函数
- 若指针指向一个派生类对象,那么被调用的是派生类的虚函数
示例
class CBase{
public:
virtual void SomeVirtualFunction() { }
};
class CDerived:public Cbase{
public:
virtual void SomeVirtualFunction() { }
};
int main(){
CDerived ODerived;
CBase *p = & ODerived;
p->SomeVirtualFunction();
return 0;
}
多态的表现形式二
- 派生类的对象可以赋给基类的引用
- 通过基类引用可以调用基类和派生类中的同名虚函数时:
- 该引用引用的是一个基类对象,那么调用的是基类的虚函数
- 若该引用引用的是一个派生类的对象, 那么被调用的是派生类的虚函数
class CBase{
public:
virtual void SomeVitualFunction(){ }
};
class CDerived:public CBase{
public:
virtual void SomeVirtualFunction() { }
};
int main(){
CDerived ODerived;
CBase &r = ODerived;
r.SomeVirtualFunction();
return 0;
}
另外一个引例
class A {
public :
virtual void Print( )
{ cout << "A::Print"<<endl ; }
};
class B: public A {
public :
virtual void Print( ) { cout << "B::Print" <<endl; }
};
class D: public A {
public :
virtual void Print( ) { cout << "D::Print" << endl ; }
};
class E: public B {
public :
virtual void Print( ) { cout << "E::Print" << endl ; }
};
int main() {
A a; B b; E e; D d;
A * pa = &a; B * pb = &b;
D * pd = &d ; E * pe = &e;
pa->Print(); // 输出A::Print
pa = pb;
pa -> Print(); // 输出B::Print
pa = pd;
pa -> Print(); // 输出D::Print
pa = pe;
pa -> Print(); // 输出E::Print
return 0;
}
一个示例
几何形体处理
按面积从小到大依次输出每个几何形体的总类及面积,每个几何形体的输出格式为: 形体名称:面积
#inlcude<iostream>
#include<stdlib.h>
#include<math.h>
using namespace std;
class CShape{
public:
virtual double Area() = 0;
virtual void PrinInfo() = 0;
};
class CRectangle:public CShape{
public:
int w, h;
virtual double Area();
virtual void PrintInfo();
};
class CCircle:public CShape{
public:
int r;
virtual double Area();
virtual void PrintInfo();
};
class CTriangle: public CShape{
public:
int a, b, c;
virtual double Area();
virtual void Printlnfo();
};
double CTriangle::Area(){
return w*h;
}
void CRectangle::Printfo(){
cout << "Rectangle:" << Area << endl;
}
double CCircle::Area(){
return 3.14*r*r;
}
void CCircle::PrintInfo(){
cout << "CCircle:" << Area() << endl;
}
double CTriangle::Area(){
double p = (a+b+c)/2.0;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
void CTriangle::PrintInfo(){
cout << "Traiangle:" << Area() << endl;
}
CShape * pShapes[100];
int MyCompare(const void *s1, const void *s2);
int main(){
int i;int n;
CRectangle *pr;
CCircle *pc;
CTriangle *pt;
cin >> n;
for(int i=0; i<n;i++){
char c;
cin >> c;
switch(c){
case "R":
pr = new CRectangle();
cin >> pr->w >> pr->h;
pShapes[i] = pr;
break;
case "C":
pc = new CCircle();
cin >> pc-> r;
pShapeds[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a >> pt->b >> pt->c;
pShapes[i] = pt;
break;
}
}
qsort(pShapes, n, sizeof(CShape *), MyCompare);
for(int i=0;i,n;i++){
pShapes[i]->PrintInfo();
}
return 0;
}
int MyCompare(const void *s1, const void *s2){
double a1, a2;
CShape **p1;
CShape **p2;
p1 = (CShape**) p1;
p2 = (CShape**) p2;
a1 = (*p1)->Area();
a2 = (*p2)->Area();
if(a1 < a2) return -1;
else if (a2 < a1) return 1;
else return 0;
}
用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的做法
class Base{
public:
void fun1() { this -> fun2(); }
virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived:public Base{
virtual void fun2() { cout << "Derived:fun2() " << endl;}
};
int main(){
Derived d;
Base *pBase = &d;
pBase->fun1();
return 0;
}
// 输出: Derived:fun2()
// 在非构造函数中,非构造函数的成员函数调用虚函数,是多态!!!!!
在构造函数和析构函数中调用虚函数,不是多态,编译时可以确定,调用的函数是自己类或者基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数
class myclass{
public:
virtual void hello(){ cout << "hello from myclass" << endl; };
virtual void bye() { cout << "bye from myclas " << endl;}
};
class son:public myclass{
// 派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数
void hello() { cout << "hello from son " << endl; };
son() { hello(); };
~son() { bye(); };
};
class grandson: public son {
public:
void hello(){cout<<"hello from grandson"<<endl;};
void bye() { cout << "bye from grandson"<<endl;}
grandson(){cout<<"constructing grandson"<<endl;};
~grandson(){cout<<"destructing grandson"<<endl;};
};
int main(){
grandson gson;
son *pson;
pson=&gson;
pson->hello(); //多态
return 0;
}
//输出
//hello from son
//constructing grandson
//hello from grandson
//destructing grandson
//bye from myclass
虚函数的访问权限
class Base {
private:
virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived:public Base {
public:
virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
Derived d;
Base * pBase = & d;
pBase -> fun2(); // 编译出错
原因:编译出错的原因是fun2()是Base的私有成员,运行时不考虑调用的是哪个, 如果 将Base中的 private换成public,即使Derived中的fun2() 是private的,编译依然能通过,也能正确调用Derived::fun2()。
多态实现的原理
每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数表的指针。虚函数表中列出了该类的虚函数地址。
#include <iostream>
using namespace std;
class A {
public: virtual void Func() { cout << "A::Func" << endl; }
};
class B:public A {
public: virtual void Func() { cout << "B::Func" << endl; }
};
int main() {
A a;
A * pa = new B();
pa->Func();
//64位程序指针为8字节
long long * p1 = (long long * ) & a;
long long * p2 = (long long * ) pa;
* p2 = * p1;
pa->Func();
return 0;
}
// 输出
// B:Func
// A:Func
虚析构函数
- 通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数
但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。 - 解决办法:把基类的析构函数声明为virtual
派生类的析构函数可以virtual不进行声明
通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数 - 一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义成虚函数。
- 不允许以虚函数作为构造函数
class son{
public:
~son() {cout<<"bye from son"<<endl;};
};
class grandson:public son{
public:
~grandson(){cout<<"bye from grandson"<<endl;};
};
int main(){
son *pson;
pson=new grandson();
delete pson;
return 0;
}
// 输出: bye from son 没有执行grandson::~grandson()!!!
class son{
public:
virtual ~son() {cout<<"bye from son"<<endl;};
};
class grandson:public son{
public:
~grandson(){cout<<"bye from grandson"<<endl;};
};
int main() {
son *pson;
pson= new grandson();
delete pson;
return 0;
}
// 输出
//bye from grandson
//bye from son
//执行grandson::~grandson(),引起执行son::~son()!!!
纯虚函数和抽象类
纯虚函数: 没有函数体的虚函数
class A {
private: int a;
public:
virtual void Print( ) = 0 ; //纯虚函数
void fun() { cout << "fun"; }
};
- 包含纯虚函数的类叫抽象类
- 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象
- 抽象类的指针和引用可以指向由抽象类派生出来的类的对象
- 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内不能调用纯虚函数。
- 如果一个类从抽象类派生而来,那么当且仅当它实现了基类中的所有纯虚函
数,它才能成为非抽象类
class A {
public:
virtual void f() = 0; //纯虚函数
void g( ) { this->f( ) ; //ok
}
A( ){ //f( ); // 错误
}
};
class B:public A{
public:
void f(){ cout<<"B:f()"<<endl; }
};
int main(){
B b;
b.g();
return 0;
}
// 输出B:f()