封装,继承,多态可以说是 C++ 老生常谈的三大特性,简单总结一下基本概念,以及附带一些拓展的内容~
封装
在 面向对象 编程方法中,封装 是指,一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。
同时,它也是一种防止外界调用端,去访问对象内部实现细节的手段,这个手段是由编程语言本身来提供的。
封装被视为是面向对象的四项原则(抽象
、封装
、继承
、多态
)之一。
适当的封装,可以将对象使用接口的程序实现部分隐藏起来,不让用户看到,同时确保用户无法任意更改对象内部的重要资料,若想接触资料只能通过公开接入方法的方式( 如:“get” 和 “set” )。它可以让代码更容易理解与维护,也加强了代码的安全性。
继承
继承 是面向对象软件技术当中的一个概念。
如果一个类 B
继承自 另一个类 A
,就把这个 B
称为 A
的 子类,而把 A
称为 B
的 父类,也可以称 A
是 B
的 超类。
继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。在令子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。
另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在运行期扩展。
继承方式
类成员的访问权限可以分为以下三种
public
成员可以通过对象来访问private
成员不能通过对象访问。protected
成员和private
成员类似,也不能通过对象访问。但是当存在继承关系时,父类中的protected
成员可以在子类中使用
列个表格,看一下子类不同的继承方式和父类成员在子类中的访问权限的关系。
父类成员属性(竖)\ 继承方式(横) | public 继承 |
protected 继承 |
private 继承 |
---|---|---|---|
public 成员 |
public |
protected |
private |
protected 成员 |
protected |
protected |
private |
private 成员 |
不可见 | 不可见 | 不可见 |
简单点说,也就是说,继承方式中的 public
、protected
、private
是用来指明父类成员在子类中的 最高访问权限 的。
父类的 private
成员不能在子类中使用,不是说父类的 private
成员不能被继承。实际上,父类的 private
成员是能够被继承的,并且(成员变量)会占用子类对象的内存,它只是在子类中不可见,导致无法使用。private
成员的这种特性,能够很好的对子类隐藏父类的实现,以体现 面向对象的封装性。
多继承
这一块我平时接触的比较少,建议看一下下面几篇大佬的文章,介绍的都很详细
多继承,虚继承的内存环境
https://www.oschina.net/translate/cpp-virtual-inheritance
https://blog.csdn.net/chlele0105/article/details/22654869
C++多继承中重写不同基类中相同原型的虚函数
https://blog.csdn.net/starlee/article/details/2825522
多态
多态指为不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型
C++ 多态分类及实现:
- 重载多态(Ad-hoc Polymorphism,编译期):函数重载、运算符重载
- 子类型多态(Subtype Polymorphism,运行期):虚函数
- 参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
- 强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换
具体分类的描述可以参考一下这篇文章:
https://catonmat.net/cpp-polymorphism
重载多态
函数重载 的实现原理: name mangling
(命名倾轧)
https://blog.csdn.net/apollon_krj/article/details/60760586
运算符重载 的实质也还是函数重载或函数多态。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
子类型多态
用 virtual
修饰成员函数,使其成为虚函数,主要用于实现多态,动态绑定。
- 普通函数(非类成员函数)不能是虚函数
static
函数不能是虚函数 (static
函数不与任何 实例 相关,自然无法实现多态了。)- 构造函数不能是虚函数 (因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)
- 内联函数不能是变现多态时的虚函数 (内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。)
- 模板类中可以使用虚函数
- 类(无论普通类或模板类)的成员模板(本身是模板的成员函数)不能是虚函数 (成员模板函数实例化出很多不同的版本,也就是可以实例化出很多不同版本的虚函数,编译器需要确定类的虚函数表的大小,这对于多文件的项目来说,代价是非常高的)
提到虚函数,绕不开的就是虚函数表,这一块的知识建议详细阅读下面这位大佬的文章,一定会茅塞顿开
https://blog.twofei.com/496/
参数多态化
参数多态性为任何类型提供了执行相同代码的手段。在C++参数多态性通过 模板 实现。
#include <iostream>
#include <string>
template <class T>
T max(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << ::max(9, 5) << std::endl; // 9
std::string foo("foo"), bar("bar");
std::cout << ::max(foo, bar) << std::endl; // "foo"
}
强制多态
通过语义操作,把操作对象的类型 强行 加以变换,以符合函数或操作符的要求。
程序设计语言中基本类型的大多数操作符,在发生不同类型的数据进行混合运算时。编译程序一般都会进行强制多态。程序员也可以显示地进行强制多态的操作(Casting)。
编译期:使用 dynamic_cast
等显式强制类型转换,或者一些默认类型转换 float
转 int
等等
运行期:子类泛型化。使用基类的指针指向派生类对象也算是强制多态的一种