一、多态(只对指针和引用有用)
1、定义:多态是指当基类的指针(或引用)绑定到派生类对象上,通过此指针(或引用)调用基类的成员函数时,实际上调用到的是该函数在派生类中的覆盖函数版本。
2、多态的两种表现形式(其实如果一个基类中的一个函数为虚函数的话,若其派生类中有同名的函数,但是没有标明为虚函数的话,默认为虚函数,至于调用哪一个还是看到底指向哪里)
(一)表现形式一
通过基类指针调用基类和派生类中的同名虚函数时://即基类和派生类中有同名的虚函数。
(1)若该指针指向一个基类的对象,那么被调用的是基类的虚函数。
(2)若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。
这种机制就叫做“多态”。
例如:
#include <iostream>
using namespace std;
class CBase
{
public:
virtual void so(){cout<<"1"<<endl;}
};
class CD:public CBase
{
public:
virtual void so(){cout<<"2"<<endl;}
};
int main()
{
CD od;
CBase* p=&od;//这里p指向了CD;
p->so();//调用哪个虚函数取决于p指向那种类型的对象;
return 0;
}
这里的输出结果为2;
(二)表现形式二
派生类的对象可以赋给基类引用
通过基类一诺那个调用基类和派生类中的同名虚函数时:
(1)若该引用指向一个基类的对象,那么被调用的是基类的虚函数。
(2)若该引用指向一个派生类的对象,那么被调用的是派生类的虚函数。
这种机制也叫做“多态”。
例如:
#include <iostream>
using namespace std;
class CBase
{
public:
virtual void so(){cout<<"1"<<endl;}
};
class CD:public CBase
{
public:
virtual void so(){cout<<"2"<<endl;}
};
int main()
{
CD od;
CBase& p=od;//这里引用指向了派生类;
p.so();
return 0;
}
这里的输出结果为2;
3、实现多态必须具备的三个条件
(1)有继承(2)派生类要覆盖(重定义)基类的虚函数,即派生类具有和基类函数原型完全相同的虚成员函数(3)把基类的指针或引用绑定到派生类对象上。
例如:
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void sound(){cout<<"unknow!"<<endl;}
};
class Dog:public Animal
{
public:
void sound(){cout<<"wang!"<<endl;}
};
class Cat:public Animal
{
public:
void sound(){cout<<"miao!"<<endl;}
};
class Wlof:public Animal
{
public:
void sound(){cout<<"wu!"<<endl;}
};
int main()
{
Animal *pa;
Dog dog;
Cat cat;
Wlof wlof;
pa=&dog; pa->sound();
pa=&cat; pa->sound();
pa=&wlof; pa->sound();
return 0;
}
4、多态更多的体现在用基类对象的指针或引用作为函数的参数,通过它调用派生类对象中的覆盖函数版本。
二、虚函数
1、虚函数的形式很简单,只需要在成员函数前面加上virtual即可,即:virtual 返回类型 函数名称()
例如virtual int get()
2、在哪里可以使用虚函数?
(1)构造函数和静态函数中不能用虚函数,任何构造函数之外的非静态函数都可以是虚函数。
(2)只能把类的成员函数设置为虚函数,不属于任何类的普通函数不能被定义为虚函数。
3、virtual关键字只能在类内定义里的函数声明之前,而不能用于类外部成员函数定义,写函数体的时候不用。
4、虚函数可以参与多态,而普通函数不能参与多态。
5、当通过基类的指针或引用调用派生类对象中的虚函数时,编译器将执行动态绑定,调用到该指针(或引用)实际所指对象所在类中的虚函数版本。
6、当基类对象的指针指向派生类对象时,可以通过强制类型转换,将基类指针指向派生类对象的指针,这样就能够实现对派生类成员函数的访问。
例如:
#include <iostream>
using namespace std;
class Employee
{
void print(){}
};
class Manager:public Employee
{
void print(){}
};
int main()
{
Employee *pm;
((Manager*)pm)->print();//这一句访问的是派生类中的print();
return 0;
}
但是这种使用方法不灵活,虚函数可以更好地解决这个问题,即虚函数可以让基类不通过强制准换,直接调用派生类的同名函数。
三、虚函数的特性
1、派生类继承了基类的全部成员函数,但是对于从基类继承来的虚函数,派生类通常需要定义自己的覆盖函数版本,以实现派生类需要的新功能。
2、无论程序中是否使用了虚函数,都必须为每个虚函数提供定义。
3、虚函数其余特性
(1)一旦将某个成员函数声明为虚函数后,它在类的继承体系中就永远为虚函数了;(某一个函数在基类中是虚函数,写在派生类中,只是实现它的功能,不是重定义,所以不必要声明其为public类型,即使放在private中,实际上还是与基类中函数的类型相同)
(2)将类的成员函数定义为虚函数后,虚特性在定义它的类和之后继承它的派生类中有效,即使派生类在重定义该函数时并没有将它声明为虚函数,它仍然是虚函数;
(3)如果定义虚函数的类从其它类派生,这些虚函数不会影响基类中同名成员函数,基类中同名成员函数保持它的原有特性。
注意:
(1)虚函数特性只对自定义它之后的派生类有效,而对之前的基类则没有影响。
(2)如果基类中定义了虚函数,当通过指针或引用调用派生类对象时,会执行动态绑定,将访问到它们实际所指对象中的虚函数版本。
(3)只有通过基类对象的指针和引用访问派生类对象的虚函数时,才能体现虚函数的特性。当普通的基类对象访问派生类对象时,不能实现虚函数的特性,只能访问到派生类中基类继承到的成员。
#include <iostream>
using namespace std;
class B
{
public:
virtual void f(){cout << "B::f" << endl;}
};
class D : public B
{
public:
void f(){cout << "D::f" << endl;}
};
int main()
{
D d;
B *pB = &d, &rB = d, b;
b = d;//虽然赋值,但是因为非指针或者引用类型,所以,不会调用派生类中的函数。
b.f();
pB->f();
rB.f();
}
(4)派生类中的虚类要保持其虚特性,必须基类虚函数与函数原型完全相同(即要求每个形参的类型相同,函数返回类型也要相同),否则就是普通的函数重载,与基类的虚函数无关。
例如:
class B
{
public:
virtual void f(int i) { cout << "B::f" << endl; };
};
class D : public B
{
public:
int f(char c) { cout << "D::f..." << c << endl; }
};
不可
class B
{
public:
virtual void f(int i)const { cout << "B::f" << endl; };
};
class D : public B
{
public:
int f(int c) { cout << "D::f..." << c << endl; }
};
不可
(5)
内联函数和静态成员函数都不能是虚函数。
三、虚析构函数(和虚构函数一样,只需要在父类中加virtual即可,这样子类的析构函数都可以调用)(如果在main函数中没有delete掉基类的指针的话,而且也没有输出的话,也可以不写析构函数)
当在销毁基类指针或者引用的时候,只能调用基类的析构函数,不能调用派生类的析构函数,虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的
(1)总结一下虚析构函数的作用:
(1)如果父类的析构函数不加virtual关键字
当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。
(2)如果父类的析构函数加virtual关键字
当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。
四、纯虚函数和抽象类
在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。
它在基类中没有具体的函数实现码,要求继承它的派生类为纯虚函数提供实现码。
(1)纯虚函数声明的形式:
class X
{
virtual 返回类型 函数名()=0;
};
(2)类中可以有声明一个或者多个纯虚函数,只有有纯虚函数的类就是抽象类,抽象类只能作为起于类的基类,不能用来建立对象,所以又称为抽象基类。
(3)抽象类中含有纯虚函数,由于纯虚函数没有实现代码,所以不能建立抽象类的对象。
(4)抽象类只能作为其他类的基类,又称为抽象基类。但是,可以创建抽象类的指针或引用,并通过它们访问到生类对象,实现运行时的多态性。
(5)如果派生类只是简单地继承了抽象类的纯虚函数,而没有覆盖基类的纯虚函数,则派生类也是一个抽象类。
#include <iostream>
using namespace std;
class Figure
{
protected:
double x, y;
public:
void set(double i, double j) { x = i; y = j; }
virtual void area() = 0; //纯虚函数
};
class Triangle :public Figure
{
public:
void area() { cout << "三角形面积:" << x*y*0.5 << endl; } //重写基类纯虚函数
};
class Rectangle :public Figure
{
public:
void area(int i) { cout << "这是矩形,它的面积是:" << x*y << endl; }
};
int main() {
Figure *pF;
// Figure f1; //L1,错误
// Rectangle r; //L2,错误
Triangle t; //L3
t.set(10, 20);
pF = &t;
pF->area(); //L4
Figure &rF = t;
rF.set(20, 20);
rF.area(); //L5
}