1.虚函数的作用:
允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
基类于派生类中有同名函数:
#include <iostream>
using namespace std;
#include <string>
//声明基类Student
class Student
{
public:
Student(int num, string name, float score) //构造函数
{
_num = num;
_name = name;
_score = score;
}
void display() //输出函数
{
cout<<"num:"<<_num<<endl;
cout<<"name:"<<_name<<endl;
cout<<"score:"<<_score<<endl;
cout<<endl;
}
protected:
int _num;
string _name;
float _score;
};
//声明派生类Graduate
class Graduate:public Student
{
public:
Graduate(int num, string name, float score, float pay)
:Student(num, name,score)
,_pay(pay)
{}
void display()
{
cout<<"num:"<<_num<<endl;
cout<<"name:"<<_name<<endl;
cout<<"score:"<<_score<<endl;
cout<<"pay:"<<_pay<<endl;
cout<<endl;
}
private:
float _pay;
};
int main()
{
Student stud1(1001, "li", 87.6); //定义Student类对象
Graduate grad1(2001,"wang", 98.7, 571.9); //定义Graduate类对象
Student* pt = &stud1; //定义指向基类对象的指针变量
pt->display();
pt = &grad1;
pt->display();
system("pause");
return 0;
}
运行结果:
由上述结果可以看出:在主函数中定义了指向基类对象的指针变量pt,并使pt指向stud1,然后调用display函数输出基类对象的数据成员,然后使pt指向grad1,在调用display函数,企图输出grad11的全部数据成员,但实际上只输出了grad1中的基类的数据成员,说明它调用的是stud1的display函数。假若想输出grad1的所有数据成员,可以通过对象名调用display函数,或者定义一个Graduate类对象的指针ptr,然后使ptr指向grad1,再用ptr->display()调用。这种方法不适用于多个同名函数的使用,所以虚函数就出现了,更好的解决了这个问题。
#include <iostream>
using namespace std;
#include <string>
//声明基类Student
class Student
{
public:
Student(int num, string name, float score) //构造函数
{
_num = num;
_name = name;
_score = score;
}
**virtual void display()** //输出函数
{
cout<<"num:"<<_num<<endl;
cout<<"name:"<<_name<<endl;
cout<<"score:"<<_score<<endl;
cout<<endl;
}
protected:
int _num;
string _name;
float _score;
};
//声明派生类Graduate
class Graduate:public Student
{
public:
Graduate(int num, string name, float score, float pay)
:Student(num, name,score)
,_pay(pay)
{}
void display()
{
cout<<"num:"<<_num<<endl;
cout<<"name:"<<_name<<endl;
cout<<"score:"<<_score<<endl;
cout<<"pay:"<<_pay<<endl;
cout<<endl;
}
private:
float _pay;
};
int main()
{
Student stud1(1001, "li", 87.6); //定义Student类对象
Graduate grad1(2001,"wang", 98.7, 571.9); //定义Graduate类对象
Student* pt = &stud1; //定义指向基类对象的指针变量
pt->display();
pt = &grad1;
pt->display();
system("pause");
return 0;
}
运行结果:
这样就达到了我们的预期的结果了。
当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中不同类的对象,从而调用其中的同名函数。
由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用做出不同的响应。
虚函数的使用方法:
(1)在基类用virtual声明成员函数为虚函数。
(2)在派生类中重新定义此函数,要求函数名,函数类型,函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
(3)定义一个指向积累对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
(4)通过该指针变量调用此虚函数,此时调用的是指针变量指向的对象的同名函数。
2.【静态多态】
静态多态(静态链编或早绑定):编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
【动态多态】
动态绑定(动态链编或晚绑定):在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调相应的方法。
使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
动态绑定条件:
(1)、必须是虚函数(派生类对基类的虚函数重写)
(2)、通过基类类型的引用或者指针调用虚函数
协变:返回值不一样,但是基类返回基类指针,派生类返回派生类指针。
使用虚函数的注意事项:
(1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。
(2)一个成员函数被声明为虚函数后,在同一类族中就不能在定义一个非virtual的但与该函数具有相同的参数(包括个数和类型)和返回值类型的同名函数。
成员函数什么情况下声明为虚函数:
(1)首先看成员函数所在的类是否为基类
(2)如果成员函数在类被继承后功能不需要修改,或派生类用不到该函数,则不要把他声明为虚函数。
(3)应考虑对成员函数的调用时通过对象名还是通过基类指针引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。
3.虚析构函数
基类中有非虚构函数的执行情况:
#include <iostream>
using namespace std;
class Point
{
public:
Point()
{}
~Point()
{
cout<<"~Point()"<<endl;
}
};
class Circle:public Point
{
public:
Circle()
{}
~Circle()
{
cout<<"~Circle()"<<endl;
}
private:
int radius;
};
int main()
{
Point* p = new Circle; //p是指向基类的指针变量,指向new开辟的动态存储空间
delete p; //delete释放p所指向的空间
system("pause");
return 0;
}
运行结果:
由结果看出,只执行了基类的析构函数,而没有执行派生类的析构函数。
如果希望执行派生类的析构函数,可以将基类的析构函数声明为虚函数。
#include <iostream>
using namespace std;
class Point
{
public:
Point()
{}
virtual ~Point()
{
cout<<"~Point()"<<endl;
}
};
class Circle:public Point
{
public:
Circle()
{}
~Circle()
{
cout<<"~Circle()"<<endl;
}
private:
int radius;
};
int main()
{
Point* p = new Circle; //p是指向基类的指针变量,指向new开辟的动态存储空间
delete p; //delete释放p所指向的空间
system("pause");
return 0;
}
运行结果:
先调用了派生类的析构函数,在调用了基类的析构函数,符合期望。
当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一类对象,系统会动态关联,调用相应的析构函数,对该对象进行清理工作。
如果将基类的析构函数声明为虚函数,由该基类所派生的析构函数自动成为虚函数,即使派生类的析构函数和积累的析构函数名字不相同。
最好把基类的析构函数声明为虚函数,这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显示的用了delete运算符删除一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。
构造函数不能声明为虚函数,这是因为在执行构造函数是的时候类对象还未完成建立过程,当然说不上函数与类对象的绑定。