C++ 与 Python 的 OOP 比较
1. OOP 三特性
封装 :将相关数据和操作数据的方法打包成一个类。不同的类相互隔离,也可以自由组合。
继承 :从一个父类衍生出子类,子类可以自然地拥有与父类的相同的属性和行为。
多态 :子类与父类或者兄弟类在某一种行为上有所区别,即同一函数不同实现。
个人理解,继承保持了类之间的共性,多态使得这些具有共性的类之间有各自的特性。
2. 封装
类是一组数据以及操这组数据的函数(方法)的集合。类是对象的抽象模板,对象是类的具体实例,给类的数据取不同的值,同一个类就产成了不同的对象。
数据
于是数据应该有两种:一种是与 类级别 的,同一个类取值都一样,与实例无关;另一种是 实例级别 的,同一个类的不同实例取值各不相同。
数据类别 | C++ | Python |
---|---|---|
类级别 | 静态数据成员 | 类变量 |
实例级别 | 非静态数据成员 | 实例变量 |
方法
既然数据有两种,方法至少也应该有两种,一种是类级别的,一种是实例级别的。类级别的数据在实例化之前就存在,在实例化之前操作类级别的数据,是一种方法。实例化之后产生了实例级别的数据,这时候的方法可以同时操作两类数据,是另一种方法。
可操作数据 | C++ | Python |
---|---|---|
类级别 | 静态成员函数 | 类方法 |
类级别和实例级别 | 非静态成员函数 | 实例方法 |
C++ 中还有一种重要方法是 虚函数,使用虚函数可以实现 C++ 中的多态。
Python 中还有一种方法是静态方法。在 Python 中可以认为,实例方法传入了实例对象的指针,类方法传入了类的指针,而静态方法既不需要传入实例,也不需要传入类。
3. 继承
子类继承父类,使子类拥有父类的数据和方法。
单一继承
这种情况下,python 和 C++ 的最大区别应该在于继承方式。C++ 继承分为 public、private、protected 三种,Python 都是 public。
多重继承
没有虚函数的情况下,区别主要有两点:
(1) 假设函数名为 fun,当多个父类中定义方法 fun,而子类没有定义方法 fun,通过子类调用方法 fun,C++ 会 报错 ,Python 会使用MRO 来确定调用哪个父类的 fun。
(2) 对菱形继承的情况,C++ 要使用 虚继承,Python 要使用super 结合 MRO。
4. 多态
多态在代码上的表现为一个方法多个实现。C++ 的多态必须建立在继承基础上,现有继承,后有多态。Python 的多态没有继承关系的限制,只要实现了同名方法即可。
C++ 多态
前文介绍了 C++ 对象的内存模型,这里只说最简单的单一继承情况。C++ 通过父类的指针或引用调用虚函数,在编译期间无法确定调用的是父类的实现还是子类的实现,只有在执行期间访问内存模型中的虚函数表才能确定。
假设 Derived 类继承 Base 类,Base 类中定义了虚函数 method,Derived 类重写了虚函数 method,此时 Base 类和 Derived 类的对象模型如图:
执行如下代码:
1 | Base *ptr = new Derived(); |
编译器看到 ptr 是 Base 类型,如果 method 不是虚函数,那么执行的应该是 Base::method。现在 method 是虚函数,执行期调用 Base::method 还是 Derived::method,要看赋给 ptr 的是 Base 对象地址还是 Derived 对象地址。上面的代码是创建了一个 Derived 对象,并把地址传给 Base 类指针,但是内存模型中的 vptr 指向的仍然是 Derived 类实现的虚函数,所以最后调用的是 Derived::method。
Python 多态
相比 C++ 复杂的内存模型,Python 的鸭子类型让多态更灵活(Python 内存模型跟多态的关系好像不大)。
1 | class Cat: |
这里 Cat 和 Dog 没有继承关系,say 也不是虚函数,调用 func(Cat)和 func(Dog)都能正确执行。要是再定义个 Person 类,只要定义了方法 say,就可以把 Person 传给 func 完成调用。
5. 写在最后
虽然都是从 C 语言发展出来的 OOP 语言,C++ 和 Python 的区别还是挺大的,特别是多态的处理,所以对相同逻辑的多态执行结果也是有区别的:
C++ 示例
1 |
|
Python 示例
1 | class Base: |