面向对象程序设计的核心思想是数据抽象,继承和动态绑定。
OOP概述
派生类通过使用派生类列表指明从哪个基类派生,派生类必须在其内部对所有重新定义的虚函数进行声明。
在C++语言中,当我们使用一个基类的引用或指针调用一个虚函数时将发生动态绑定,动态绑定会在运行时选择需要的函数版本,因此也叫运行时绑定。
定义基类和派生类
继承结构中根节点的类通常定义虚析构函数。
关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的定义。基类中某个成员函数是虚函数,则派生类中该函数隐式的也是虚函数。
派生类能访问基类的公有成员,不能访问基类的私有成员。
派生类必须将那些继承而来的需要覆盖的成员重新声明。
派生类经常覆盖它继承的虚函数,但不是必须的。
因为在派生类对象中包含有与其基类对应的组成部分,因此能把派生类的对象当成基类来使用。而且也能将基类的指针或引用绑定到派生类对象上。
首先初始化基类部分,然后按声明的顺序初始化派生类的成员。派生类不继承基类的构造函数,每个类控制自己的数据成员的初始化。
派生类的作用域嵌套在基类的作用域之内。
无论有多少继承,静态成员只存在唯一的实例。
基类必须已经定义才能作为基类使用,因此一个类不能派生自它自身。
在类名后跟关键字final可防止该类被当做基类使用。
我们可以将基类的指针或引用绑定到派生类的对象上,智能指针也接受派生类对象。该特性是面向对象的根本。
静态类型在编译时是已知的,动态类型直到运行时才已知。如果表达式即不是引用也不是指针,而动态类型永远和静态类型一致。
不存在从基类向派生类型的隐式转换,但存在从派生类向基类类型的转换,因为派生类包含基类的所有成员。
在对象之间不存在类型转换,派生类向基类的自动转换只对指针类型或引用类型有效。当我们给基类构造函数或给基类赋值派生类对象时,调用的都是基类的构造函数或拷贝赋值运算符,因此只能处理基类的成员。
当我们用一个派生类对象为基类对象初始化或赋值时,只有该派生类对象的基类部分会被拷贝,移动或赋值,它的派生类部分会被忽略。
虚函数
动态绑定只有在通过指针或引用调用虚函数时才会发生。
引用或指针的静态类型与动态类型不同正是C++支持多态性的根本所在。
一旦某个函数被声明成虚函数,则在所有派生类中它都是虚函数,在派生类中可以使用virtual关键字也可以不使用。
使用override关键字可以显示标明派生类中的某个函数覆盖了基类中的虚函数,如果因为参数或返回类型错误而没能覆盖基类虚函数,则编译器将报错,这给调试错误提供更过信息。如果不希望基类的函数被覆盖,可以给基类的函数添加final关键字,这样派生类将无法覆盖该函数。final和override写在参数列表和const和尾置返回类型后面。
如果调用虚函数时使用默认实参,则默认实参的值由调用的静态类型决定,因此基类和派生类中的默认实参最好一致。
使用域运算符可以禁用虚函数的动态绑定,如baseP->Base::fun()。通常情况下,如果一个派生类虚函数中需要调用基类版本,需要使用域运算符指定虚函数版本,如果未指定在派生类的虚函数中调用虚函数将会形成无线递归。
抽象基类
含有纯虚函数的类或未覆盖基类的纯虚函数的派生类是抽象基类。抽象基类负责定义接口,我们不能直接创建一个抽象基类的对象。纯虚函数无需定义,在函数声明的分号前面加=0声明纯虚函数。=0只能出现在类内部的虚函数声明语句处。我们也可以为纯虚函数提供定义,不过函数体必须定义在类的外部。
访问控制与继承
protected用来保护基类的成员不被类用户访问但可以被派生类访问。
关系比较复杂,暂时不研究。。。。。。。。。。。。。。。
继承中的类作用域
每个类定义一个自己的作用域,当存在继承关系时,派生类的作用域嵌套在基类的作用域中。
在编译时进行名字查找,即使对象是引用类型或指针类型,对象的那些成员可见由对象的静态类型决定。
派生类的成员将隐藏同名的基类成员。通过作用域运算符可以访问被隐藏的基类成员。除了覆盖继承而来的虚函数之外,派生类中最好不要定义同名的基类成员。
内层作用域的函数并不会重载外层作用域的函数,只会覆盖。因此派生类中的函数成员不会重载基类中的函数,即使名字相同,参数不同,这样只会隐藏基类中的同名函数。
派生类中同名的函数(参数列表不同)并不会覆盖基类的虚函数,在派生类的派生类中仍然可以访问该虚函数。
可以通过基类调用隐藏的虚函数,前提是使用指针类型或引用类型,这时调用虚函数时会进行运行时绑定。调用非虚函数时进行静态绑定。
覆盖重载的函数。。。。。。。。。。。