C++ 三大特性

封装,继承,多态可以说是 C++ 老生常谈的三大特性,简单总结一下基本概念,以及附带一些拓展的内容~

封装

面向对象 编程方法中,封装 是指,一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。
同时,它也是一种防止外界调用端,去访问对象内部实现细节的手段,这个手段是由编程语言本身来提供的。
封装被视为是面向对象的四项原则(抽象封装继承多态)之一。

适当的封装,可以将对象使用接口的程序实现部分隐藏起来,不让用户看到,同时确保用户无法任意更改对象内部的重要资料,若想接触资料只能通过公开接入方法的方式( 如:“get” 和 “set” )。它可以让代码更容易理解与维护,也加强了代码的安全性。

继承

继承 是面向对象软件技术当中的一个概念。
如果一个类 B 继承自 另一个类 A,就把这个 B 称为 A子类,而把 A 称为 B父类,也可以称 AB超类
继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。在令子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。
另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在运行期扩展。

继承方式

类成员的访问权限可以分为以下三种

  • public 成员可以通过对象来访问
  • private 成员不能通过对象访问。
  • protected 成员和 private 成员类似,也不能通过对象访问。但是当存在继承关系时,父类中的 protected 成员可以在子类中使用

列个表格,看一下子类不同的继承方式和父类成员在子类中的访问权限的关系。

父类成员属性(竖)\
继承方式(横)
public 继承 protected 继承 private 继承
public 成员 public protected private
protected 成员 protected protected private
private 成员 不可见 不可见 不可见

简单点说,也就是说,继承方式中的 publicprotectedprivate 是用来指明父类成员在子类中的 最高访问权限 的。

父类的 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 修饰成员函数,使其成为虚函数,主要用于实现多态,动态绑定。

  1. 普通函数(非类成员函数)不能是虚函数
  2. static 函数不能是虚函数 (static 函数不与任何 实例 相关,自然无法实现多态了。)
  3. 构造函数不能是虚函数 (因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)
  4. 内联函数不能是变现多态时的虚函数 (内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。)
  5. 模板类中可以使用虚函数
  6. 类(无论普通类或模板类)的成员模板(本身是模板的成员函数)不能是虚函数 (成员模板函数实例化出很多不同的版本,也就是可以实例化出很多不同版本的虚函数,编译器需要确定类的虚函数表的大小,这对于多文件的项目来说,代价是非常高的)

提到虚函数,绕不开的就是虚函数表,这一块的知识建议详细阅读下面这位大佬的文章,一定会茅塞顿开
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 等显式强制类型转换,或者一些默认类型转换 floatint 等等 运行期:子类泛型化。使用基类的指针指向派生类对象也算是强制多态的一种