#include<iostream>
#include<string>
usingnamespace std;
class Student
{
public :
Student(string , int);
void display();
protected:
string name;
int number;
};
Student::Student(stringnam, int num)
{
name = nam;
number = num;
}
voidStudent::display()
{
cout<<”name:”<<this ->name<<endl;
cout<<”number:”<<this ->number<<endl;
}
class Graduate :public Student
{
public :
Graduate(string , int,float);
void display();
protected :
float pay;
};
Graduate ::Graduate(string nam, int num, float p):Student(nam, num),pay(p){}//构造函数
void Graduate ::display()
{
cout<<”name:”<<this->name<<endl;
cout<<”number:”<<number<<endl;
cout<<”pay:”<<pay<<endl;
}
int main(void)
{
Student std1(“wang chong yang”, 1001);
Graduate std2(“huang yao shi”, 1002,6700);
Student *pt = &std1;
Pt -> display();
pt = & std2;
pt -> display();
return 0;
}
编译运行上述代码:
name:wang chongyang
number:1001
name:huang yaoshi
number:1002
可以看到我们在派生类和基类中定义了同名方法display(),然后定义了指向基类的指针变量,接着用指针变量分别指向基类和派生类的对象然后调用display()方法,当调用pt->display()时并没有将std2对象的数据成员完全输出,这是因为pt是基类指针变量,所以执行上述语句时实际上输出的是派生类继承自基类的数据成员。
那么有没有什么方法可以区分基类和派生类中的同名方法呢?请在上述代码main中做添加如下内容:
std2.display();
std2.Student::display();
编译运行后会发现执行std2.display()时可以将std2的数据成员完全输出,执行第二条语句时输出std1的数据成员。
但是上面的方法太过繁琐,不够直观,我们可以通过另外一种方法来实现这个目的,接下来我们将使用虚函数来完成上述功能。
首先介绍虚函数的作用:允许在派生类中定义与基类同名的方法,通过基类指针或对象引用来访问基类或派生类中的同名方法。
接着介绍虚函数的用法:首先定义一个基类对象指针,然后将它指向需要调用该虚函数的对象,通过指针变量调用该虚函数,此时调用的就是指针变量指向的对象的同名函数。
我们来看看虚函数的定义方法:
virtual返回值类型函数名(参数列表);
对上述代码做如下修改:
将基类中的中的display做如下修改:
void display();-----à virtual void display();
然后编译运行代码结果如下:
name:wang chongyang
number:1001
name:huang yaoshi
number:1002
pay:6700
我们的目的已经到达。
可以看到虚函数能够为我们提供一种方法------在派生类中去重写基类中的同名方法,然后我们可以方便的通过基类指针来访问各个派生类中的同名方法,由于这个特性我们可以知道不能将一个普通的方法定义为虚函数。
在基类中定义了虚函数后派生类中的同名函数C++编译器会自动将它指定为虚函数,所以在派生类中的同名函数我们可以指定其为virtual,也可不指定,但是为了程序的直观性还是建议指定为virtual。
在有了上面的基础后,我们在来看看另外一个概念,虚析构函数和纯虚函数。
我们知道析构方法的调用顺序和构造方法正好相反,所以当我们用new运算符动态的开辟一个派生类对象空间并将它赋值给基类指针对象,当我们应用delete 清理这个对象空间后将会先调用派生类对象的析构方法然后再调用基类的析构方法,但是实际上是这样的吗?我们来看看如下的测试代码。
class Test
{
public:
Test(int a, int b):number(a),score(b){}
~Test();
private :
int number;
int score;
};
Test::~Test()
{
Cout<<”executing Test ”<<endl;
}
class Test1 :public Test
{
public:
Test1(int a, int b, floatp):Test(a, b),pay(p){}
~Test1();
private:
float pay;
}
Test1::~Test1
{
cout<<”exectuting Test1”<<endl;
}
int main(void)
{
Test a(1001, 85);
Test *pt = new Test1(1002, 77, 3500);//动态开辟内存空间并赋初值
delete pt;
return 0;
}
编译并运行上述代码:
executing Test
可以看到并没有指向派生类的析构函数而是执行了基类的析构函数。
当我们在基类的析构函数前面加上virtual后,编译并执行上述代码运行结果如下:
executing Test1
executing Test
这次和我们预想的一致,这点同虚函数一样,采用了动态关联来完成相应对象析构函数的调用。
我们需要明确一点的是如果在基类中对析构函数做了virtual的声明,那么派生类中的析构函数都将是virtual类型而不管基类和派生类的析构函数是否同名。因此将基类的析构函数显示的声明为virtual将是个很好的习惯,以保证进行delete操作时能得到正确的结果。
最后讨论一下纯虚函数,在基类中定义一个虚函数时采用如下形式:
vrtual 函数名(参数列表) const=0;
这条语句将告诉C++编译系统这个是纯虚函数,在基类中什么也不做将会在派生类中被重新定义。如果在派生类中没有对这个纯虚函数做任何重写,那么将会是简单的继承,在派生类中仍然为纯虚函数。如果在一个类中含有纯虚函数那么这个类将不能被用来定义对象,因为纯虚函数是没有函数体的不能被引用。所以如果我们在基类中定义了纯虚函数,在其派生类中要重写虚函数,不然此派生类将不能用来定义对象。