C++中非常重要的概念,尤其是相对于C语言而言,也是其具有如此高的工程使用性的重要原因。
封装
所谓封装是将某些东西隐藏起来,让外界无法直接使用,而必须通过某些特定的方式才能访问。也即是,将抽象得到的数据和行为(类似于属性和方法)结合构成一个有机整体,将数据与操作数据的函数构成类,其中数据和函数都是类的成员。
其目的是将对象的使用者和设计者隔离开来,提高软件的可维护性和可修改性,使用者不必了解具体的实现细节而只是通过外部接口及特定的访问权限使用类成员,从而增强了安全性且简化了编程,也使得不同类之间的相互影响降到最低。
一个类的成员有三种访问权限可以选择
- public 所有人均可访问
- private 只有当前类中成员函数可以访问
- protected 只有当前类中和当前类的派生类中的成员函数可以访问
如果没有显示声明,class中的成员数据或成员函数默认访问权限是private,struct中默认访问权限是public
继承
指的是新类从已有的类中得到已有的特性。
继承使得子类具有父类的某些数据和函数,而不需要再次编写相同的代码,在继承的同时,子类也可以重新定义某些数据和函数并覆盖父类原有的数据和函数。
与类的成员访问权限相关,继承方式也分为三种:public,private,protected
一般分为单一继承和多重继承,常用的是单一继承,编程简单,可读性好。
需要注意的是class默认是private继承,而struct默认是public继承。
父类中的访问权限 | 继承方式 | 子类中的访问权限 |
public | public | public |
private | No access | |
protected | protected | |
public | private | private |
private | No access | |
protected | private | |
public | protected | protected |
private | No access | |
protected | protected |
子类会继承父类/基类除构造函数和析构函数以外的成员函数,一般析构函数定义为虚函数,否则的话析构时只会调用父类/基类中定义的析构函数而不会调用子类/派生类中的析构函数。
多态
父类成员函数前加virtual修饰就变成了虚函数。
纯虚函数就是父类只定义了虚函数而没有实现,则必须在子类中实现。如果子类中没有实现则子类仍为虚类,需要子类的子类去实现纯虚函数,即哪一层子类实现了纯虚函数,哪一层的子类才可以被初始化。纯虚函数相当于一个接口,子类必须实现这个接口才可以使用。
而多态性就是为了接口重用,无论传递过来的是哪个子类的对象,都能调用相应的子类函数。
多态是我们可以用相同的方式处理不同类型的对象,极大地提高了代码的可重复性。
多态性允许将子类类型的指针赋值给父类类型的指针,常通过虚函数来实现。虚函数就是允许子类重新定义的成员函数。
子类重写父类中的虚函数时,即使不用virtual声明,该函数也是虚函数,而父类中必须要有virtual声明。
一个接口,多种方法,多态主要有两种形式。
静态多态
在编译期将函数链接起来,此时即可确定调用哪个函数或模板,静态多态通过重载和模板实现。
在宏多态中,通过定义变量,编译时直接把变量替换,实现宏多态。
- 优点
- 泛型编程,STL,非常便利
- 编译期完成,提高了运行效率
- 具有很强的适配性和松耦合性
- 缺点
- 使得编程可读性降低,代码调试困难
- 无法实现木板的分离编译,工程量大时编译时间较长
- 无法处理异质对象集合
动态多态
在程序运行期间才能确定调用哪个函数或实现。父类指针或引用能够指向子类对象,调用子类的函数,所以在编译时无法确定调用哪个函数。
例如在父类中写一个虚函数,子类中进行重写,用一个指向父类的指针调用虚函数,实际上会调用在子类中重写的虚函数。
运行期多态的实现依赖于虚函数机制,当某个类声明了虚函数时,编译器将为该类对象安插一个虚函数表指针,并为该类设置一个唯一的虚函数表,表中存放该类虚函数的入口地址,运行期间通过虚函数表指针与虚函数表去确定该类虚函数的真正实现。
- 优点
- 面向对象设计中非常重要的特性,对客观世界直觉的认识。
- 可以处理同一继承体系下的异质类集合
vector<Animal*> anims; Animal *anim1 = new Dog; Animal *anim2 = new Cat; // 处理异质类集合实例 anims.push_back(anim1); anims.push_back(anim2);
- 缺点
- 运行期间进行虚函数绑定,增加了程序运行开销
- 庞大的类继承层次,对接口的修改容易影响类的继承层次
- 虚函数在运行期绑定,编译器无法对其进行优化