以C++为例,充分理解面向对象的三大特性
一:封装
封装是指将数据(属性)和操作这些数据的方法(函数或过程)包裹在一起形成一个“类”(Class),类的实例就是对象。封装可以隐藏对象内部的复杂性,并只通过接口与外界交互,这样可以保证了内部实现的独立性和安全性。
封装有以下好处:(看看就行)
-
数据隐私和安全性:封装可以隐藏对象的内部状态,只通过对象的方法来访问和修改。这样可以防止外部直接访问对象的内部状态,从而提高数据的安全性。
-
易于维护和修改:封装后的对象,其内部实现可以独立于其接口进行修改,而不会影响到其他使用该对象的代码。这使得代码更易于维护和修改。
-
模块化:通过封装,可以将复杂的系统分解为独立的、可重用的模块。每个模块都有明确的职责,可以独立开发和测试,从而提高开发效率。
-
抽象:封装提供了一种抽象机制,可以将复杂的实现细节隐藏起来,只提供简单的接口给外部使用。这使得代码更易于理解和使用。
-
代码复用:通过封装,可以创建通用的、可重用的组件。这些组件可以在多个地方使用,从而提高代码的复用性。
-
降低耦合度:封装有助于降低系统中各个模块之间的耦合度。因为每个模块都通过接口与外部通信,而不直接访问其他模块的内部状态,这使得模块之间的依赖关系更清晰,更容易管理。
二:继承
继承允许我们定义基于另一个已存在类(父类或基类)的新类(子类)。子类继承了父类所有公共属性和方法,并可以添加新功能或者重写父类已有功能。这样做可以减少代码重复,并且增强代码间关系。
继承方式:(共同特性:父类中的Private成员在子类中是不可访问的状态)
Public继承:父类成员访问权限在子类中保持不变;
Protected继承:所有成员在子类中的访问权限变为Protected;
Private继承:所有成员在子类中的访问权限变为Private;
除此之外还有Virtual继承,可以用来解决菱形继承的问题。
继承有以下好处:(看看就行)
-
代码复用:子类可以继承父类的属性和方法,这意味着你可以在子类中复用父类的代码,而不需要重复编写相同的代码。
-
提供抽象:父类可以定义一个通用的接口,子类可以提供具体的实现。这使得你可以使用父类作为一个抽象的数据类型,而不需要关心具体的实现细节。
-
多态:如果子类重写了父类的方法,那么你可以使用父类的引用或者指针来调用子类的方法。这是一种称为多态的特性,它使得你可以写出更灵活,更通用的代码。
-
组织和理解代码:继承可以帮助你更好地组织和理解代码。通过使用继承,你可以创建一个类的层次结构,这可以帮助你理解代码的结构和功能。
-
易于维护和扩展:使用继承,你可以在不修改父类的情况下,通过添加新的子类或者修改现有的子类来扩展程序的功能。这使得代码更易于维护和扩展。
三:多态
多态可以分为静态多态和动态多态
1:静态多态(编译时期):又可分为 函数重载 和 模板
(1)函数重载:指的是处在相同作用域下,名称相同而参数列表(包含参数个数和参数类型)不同的函数。在调用这样的同名函数时,编译器会根据我们传入的参数来选择最佳的函数。
注:仅仅是返回值不同的两个函数是重定义,而不是构成重载。
理由一:函数签名只由两部分组成:函数的名称以及函数的参数,不包含函数的返回值。
理由二:如果只有返回值不同的两个函数可以构成重载,由于函数的返回值可以不被显式处理,即我们可能用不到函数的返回值,也就不会用变量去接受这个返回值,此时就会出现二义性问题:编译器不知道该调用哪一个函数。
(2)模板:包括函数模板和类模板
在C++中,模板是一种用于编写通用的,可重用的代码的功能。它们是类型无关的,这意味着你可以使用任何类型的数据,只要这个类型支持你在模板中使用的操作。
模板的原理:在所有使用到模板的源文件中,根据使用的模板参数来生成具体的实现(即直接生成相应的代码),由于C++是分离式编译(每个源文件都会被独立地编译成目标文件),因此模板的声明和实现一般要放在一起。
模板还有全特化和偏特化的概念:
-
全特化:对于给定参数列表或条件,提供了完全专门化版本。全特化覆盖了泛型定义,并提供了针对具体类型或条件进行优化、自定义行为等更具体化版本。
-
偏特化:对于给定参数列表或条件中某些参数进行专门化处理,而其他参数保持泛型状态。偏特化允许根据某些属性来进一步限制泛型行为,并提供更多灵活性。
模板更多知识参考链接:TODO
静态多态有以下好处:
-
效率:由于函数调用在编译时就已经确定,因此静态多态的性能通常比动态多态更高。
-
灵活性:模板可以用于实现类型无关的代码,使得我们可以编写出对多种数据类型都适用的函数或类。
-
类型安全:静态多态在编译时进行类型检查,这有助于提前发现代码中的错误。
2:动态多态(运行时期):主要是虚函数
C++的虚函数是实现动态多态性的一种机制。 在C++中,通过基类指针或引用调用一个成员函数时,通常会调用基的版本,而不是指针实际指向类型的版本。 但如果我们将这个成员函数声明为虚函数(virtual),那么就可以根据实际类型来决定调用哪个版本。
编译器在实现虚函数重写时主要完成了以下几个步骤:
-
虚函数表(vtable)的创建:编译器会为每一个含有虚函数的类创建一个虚函数表。这个表是一个函数指针数组,其中包含了这个类的所有虚函数的地址。
-
虚函数表指针(vptr)的添加:编译器会在每一个类对象中添加一个虚函数表指针,这个指针指向该对象所属类的虚函数表。当这个对象调用虚函数时,编译器会通过这个指针找到虚函数表,然后在虚函数表中查找对应的函数地址。
-
虚函数的调用代码生成:当编译器遇到虚函数的调用时,它会生成一段代码,这段代码会通过对象的虚函数表指针找到虚函数表,然后在虚函数表中查找对应的函数地址,最后通过这个地址调用函数。
-
虚函数表的更新:当一个类重写了基类的虚函数时,编译器会在这个类的虚函数表中更新对应的函数地址。这样,当这个类的对象调用这个虚函数时,就会调用到这个类自己的版本,而不是基类的版本。
虚函数更多知识参考链接:C++:虚函数补充-CSDN博客
动态多态有以下好处:
-
灵活性:动态多态允许我们在运行时决定调用哪个函数,这使得我们可以编写出更灵活的代码。
-
代码简洁:通过使用父类指针或引用来操作子类对象,我们可以简化代码,减少代码冗余。
-
易于扩展:当我们需要添加新的子类时,不需要修改已有的代码,只需要添加新的子类并实现相应的虚函数即可。