C++老鸟日记001 继承
引言
-------------------------------------------------------------------------------------------------------
面向对象有三大特征:封装、继承、多态。我们今天来讨论一下继承。
正文
-------------------------------------------------------------------------------------------------------
继承是什么意思呢?在自然界中,我们会把哈士奇、吉娃娃、德国牧羊犬等狗狗都叫做犬类,而把鹦鹉、大雁、天鹅等称作鸟类,进行如此的归类是因为他们有一些相同点,当然他们也有不同点。具有相同点的这种特性,我们就可以称之为从某个基类继承得到的特性,也就是基类具备某种特性后,子类可以通过继承也得到这种特性。比如所有犬类都会“汪汪”叫,哈士奇、吉娃娃、德国牧羊犬就都具有这种特性。
定义类CAnimal(野兽),它提供接口run(跑)、接口roar(吼叫)和接口eatSomething(吃东西)。
//:animal.h
lass CAnimal
{
public:
CAnimal(){}
virtual ~CAnimal(){}
virtual void run()=0;
virtual void roar()=0;
void eatSomething(){}
};
- 务必注意,类定义处以";"(分号)结束。否则导致的编译错误,可能很难查找到原因。
- 接口前面的virtual表示该接口是虚接口,可以被派生类覆盖。
- 接口后的=0,表示该接口是纯虚接口,派生类必须提供具体实现,否则派生类将无法实例化(无法用来声明对象)
- CAnimal(){}
这种语法,表示直接将接口的实现编写在头文件中,
当然也可以写成CAniaml();然后在派生类中编写接口的实现。
请注意,如果在头文件中只写接口声明,那在接口声明后一定用";"(分号)结束,如果在头文件中直接写定义(就是写上花括号),那么就如上述代码即可,无需以分号结束。
- ~CAnimal() 析构函数的前面用virtual表示该类的派生类析构时,其派生类的析构函数一定会被调用,这种语法可以保证编译器做到。否则,有可能在对象析构时只调用了父类的析构函数,却忘记调用派生类的析构函数,就可能导致内存泄漏。
定义类CDog(狗狗), 从CAniaml派生
//: dog.h
class CDog : public CAniaml
{
public:
CDog(){}
~CDog(){}
virtual void run(){ std::cout << "我在奔跑." << std::endl;}
virtual void roar() { std::cout << "汪汪" << std::endl};
}
l CDog类从CAnimal派生,因为CAnimal已经提供了eatSomething()接口,因此CDog无需再提供实现,它即可从父类自动派生该接口,除非它的行为与父类不同。
l CDlog的run()和roar()接口前仍旧写了virtual限定符,表示从父类继承而来的接口,当然CDog的virtual限定符也可以省略。我个人的建议是保留。
结语
-------------------------------------------------------------------------------------------------------
我们可以把类的体系设计成很多层,然后每一层都可以提供一些不同的特性,以供派生类继承。我们也可以把其中某一层设计成纯虚类,这相当于设计一个纯虚接口类,这样使用者就可以用这个纯虚类定义一个指针指向派生类对象并调用接口,而无需关注这个对象到底是哪个子类。