面向对象程序设计(OOP)¶
- OOP中含有下面三个重要思想:封装 继承 多态
封装¶
为什么要进行封装¶
在C语言的编程中,可以通过链表或数组实现一个线性表,并对其编写增删改查等函数。尽管通过两种方式编写的是同一种抽象数据类型 (ADT, Abstract Data Type),他们的操作函数却不能同名,影响代码编写的效率。
例如,我们想在一个代码中同时使用对两种线性表的操作函数,我们需要手动将其命名为llist_create()
和 alist_create()
,如果调整部分代码使用的线性表类型,这些相关的函数也要进行修改。
struct 的扩展¶
为了解决上面提到的问题,我们将C语言中的struct进行扩展。
在C语言中,struct只能在其中使用 成员变量 ,即只能包含 数据 ,而在OOP的思想中,struct还可以包含 成员函数 ,即将 数据 和 操作 绑定到了一起。
这里是C++中的一个链表的定义
通过这样绑定,我们可以使用list.add(1)
而非llist_add(list,1)
alist_add(list,1)
操作一个线性表。list.add(1)
中,add函数的具体操作取决于list在create时的类型,这样我们的程序就能根据数据类型来自动选择操作类型。比如当list是由链表构成的,list.add()
, 执行的就是 linkedlist::add
, ::
前指示其所在类,后指示其函数。
对象和类¶
上面提到的绑定了数据和操作的数据类型,被称作类(Class)。
由类创建的一个实例,被称作对象或实例。对象包含他的状态和行为,状态即类中变量的具体值,行为即他进行的操作。
类和对象的关系可以类比为人类和烁烁(或是其他具体的人):人类都有如年龄,身高等数据,而烁烁在一个时刻里年龄和身高只有一个具体的值;人类可以进行多样的行为,而烁烁可以进行夜跑这一具体的行为。
访问控制¶
对于类中的一些数据或者函数,我们有时不希望他被外部代码访问或调用,比如下面这个类:
我们可以通过printf("%s", user1.password);
轻松获取密码,或者通过其他方式修改这些值。
为了避免这些问题,C++提供了 public
, private
和 protected
三种访问权限,public
表示其下的成员可以被外部调用,private
表示其下的成员只能在类的成员函数中调用。
修改后的代码如下:
此时,我们就无法从外部查看password的值。同时增加两个函数,用于修改和查看age这个保密需求低的值。
值得注意的是,通过private保护的数据不一定是安全的,他更像是一种带功能的标签,用于避免代码在编写过程中对其中成员的越界使用。
下面这段使用了private
进行了保护的类仍然是极其不安全的。
在编译器不进行保护的情况下,name和password在内存中是紧挨着的,当我们调用setname时,如果输入了一个长度超过10的str,会导致name的结尾没有 '\0' ,而puts输出时会输出到 '\0' 为止,此时puts(name)
就会将password一同输出。
1 |
|
在C++中,class默认是private的,struct默认是public的,这是其唯一区别。
小结¶
综上所述,封装 就是将数据和操作数据的函数绑定在一起,并给予必要的访问控制。通过封装的思想构建数据结构,有利于我们对其数据和相关的函数进行操作,即围绕对象进行操作。
继承¶
假设我们需要写一个画图软件,我们可以使用上面学到的类,每一个图形使用一个类,得到如下代码:
我们发现两种图形中包含了draw()
center
以及准备和重置等相同的工作。这时我们想要提取这些类的共同点,组成一个“类的交集”。然后在创建一个图形的类的时候,使用这个“交集”,从而减少代码中的重复内容,便于维护。
继承 指的就是我们克隆已有的类,再增加或修改部分内容得到一个新的类。被继承的类叫基类/父类,产生的新类叫派生类/子类
对于上面的问题,我们可以定义基类
然后我们可以继承出不同图形的类,并针对其图形特征添加内容,以及定义do_draw()
此时,我们可以更加简易地添加新的图形,也可以通过修改基类,一次性给所有的图形绘图添加如笔画粗细、颜色等其他特征。
多态¶
对于前面提到的 add()
或者是 do_draw()
函数,其具体内容是由调用它的对象决定的,这就是OOP中的多态。