下面的一个简单的继承:
#include<iostream> using namespace std; class Animal { public: Animal(int height,int weight) { cout<<"animal construct"<<endl; } ~Animal() { cout<<"animal destruct"<<endl; } }; class Fish:public Animal { public: Fish() { cout<<"fish construct"<<endl; } ~Fish() { cout<<"fish destruct"<<endl; } }; int main() { Fish fh; }
上面的编译不通过,没有适合的构造函数 。'Animal' : no appropriate default constructor available??
因为:当构造Fish 的fh对象时,需要先Animal对象。调用Animal默认的构造函数,而在我们的程序中,只有一个带有参数的构造函数,因此,找不到构造函数而错误。
因此,构造fish时,要想办法调用Animal带参的构造函数。显示地调用.改为:
Fish():Animal(300,500)
派生类转换为基类
#include<iostream> using namespace std; class Animal { public: Animal(int height,int weight) { cout<<"animal construct"<<endl; } ~Animal() { cout<<"animal destruct"<<endl; } void breath() { cout<<"animal breath"<<endl; } }; class Fish:public Animal { public: Fish():Animal(300,500) { cout<<"fish construct"<<endl; } ~Fish() { cout<<"fish destruct"<<endl; } void breath() { cout<<"fish breath"<<endl; } }; void func(Animal *p) { p->breath(); } int main() { Fish fh; Animal *pAn; pAn=&fh; func(pAn); }
会输出animal breath;
因为fish 对象地址赋给pAn时,c++编译器进行了类型转换。此时编译器就认为pAn保存的就是Animal对象的地址。在func函数中调用breath时,当然就认为是Animal的breath();
当我们构造fish类的对象时,首先要调用animal类的构造函数去构造animal类的对象。然后才调用fish类的构造函数完成自身部分的构造,从而拼接出一个完整的fish对象。我们将fish类的对象转换为aniaml类型是,该对象就认为是原对象整个内存模型的上半部分,也就是图中animal的对象所占的内存,当我们利用类型转换后的 对象指针去调用它的方法时,自然就是
调用它所在内存的方法。
如果想在上面的代码中输出fish breath,只需改一句代码:
void breath();改为
virtual void breath()
virtual函数采用late binding技术,也就是编译时并不确定具体调用的函数,而是在运行时,依据对象的类型(在程序中,我们传递的fish类对象的地址)来确认调用的是 哪一个函数,这种能力就叫做c++的多态性。
纯虚函数
将breath函数定义为纯虚函数,在函数定义加上=0:
class Animal { public: Animal(int height,int weight) { cout<<"animal construct"<<endl; } ~Animal() { cout<<"animal destruct"<<endl; } virtual void breath()=0; };
含有纯虚函数的类叫做抽象类。抽象类不能构造对象。抽象类为派生类服务。在派生类中必须完全实现基类的纯虚函数,否则,派生类也就变成了抽象类,不能实例化对象。
函数的覆盖
class Animal { public: .. virtual void breath() { cout<<"animal breath"<<endl; } }; class Fish:public animal { public: void breath() { cout<<"fish breath:"<<endl; } };
fish类的breath函数和 animal类的breath函数完全一样,无论函数名,还是参数列表都是一样的,这称为函数的覆盖(override)。
构成函数覆盖的条件:
1.基类函数必须是虚函数(使用virtual声明)
2.发生覆盖的两个函数要分别位于派生类和基类中
3函数名称和参数列表必须完全相同
函数的隐藏
class Animal { public: .. void breath() { cout<<"animal breath"<<endl; } }; class Fish:public animal { public: void breath() { cout<<"fish breath:"<<endl; } };
派生类fish中的breath函数和基类animal中的breath函数也是完全一样的。不同的是breath函数不是虚函数。这种情况
称为函数的隐藏。所谓隐藏,是指派生类中具有与基类同名的函数(不考虑参数列表是否相同),从而在派生类中隐藏了基类的同名函数。
当发生隐藏时,想要在派生类中 使用基类的函数时:
BaseClass: 函数名(参数列表)