C语言学习笔记—面向对象思维

0、简述

        关于面向对象编程(OOP),有许多有名的书籍和文章。但是这些文章中没有多少会使用像C这样非OOP语言来解决OOP的问题。这怎么可能呢?我们怎么能够用不支持OOP的语言编写出OOP程序呢?确切的是说,可以用C语言写出一个面向对象的程序吗?

        答案是:可以的!

        我们看看现在几乎所有成熟的C代码库(如开源内核),HTTPD、Postfix、nfsd、ftpd等服务的实现,以及许多其他C库(如OpenSSL和OpenCV),它们都是以面向对象的方式编写的。这种现象并不意味着C是面向对象的,但是这些项目所采用的内部结构组织方法都来自面向对象的思维方式。这意味着,如果我们掌握这种思维方式,它会使我们像那些设计代码库的工程师一样思考和设计,其次,它也将有助于阅读这些库的源代码。

        C语言在语法上不支持如类、继承和虚函数之类的面向对象概念。然而,它确实以一种间接的方式支持面向对象的概念。事实上,在Smalltalk、C++、Java出现之前,历史上几乎所有的计算机语言本质上都支持OOP。这是因为每一种通用编程语言都必须有一种方法来扩展其数据类型,这是面向对象OOP的第一步。

        C语言在语法上不能也不应该支持面向对象的特性。这不是因为年代的原因,而是有其他很充分的理由,我们将在下述讨论。简而言之,可以使用C语言编写面向对象的程序,但是需要一些额外的努力来解决复杂性的问题。

        人们常说,OOP是与过程式、函数式相并列的另一种编程范式。但OOP远非如此。OOP更像是一种思考和分析问题的方式。它是一种对宇宙和其中对象层次的认识,是人们在理解和分析周围物理实体和抽象实体时,采用的古老、本质和延续至今的方法的组成部分,是理解自然的基础。

一、面向对象思维

        用面向对象方法思考,就是我们日常分解和分析周围事物的方法。

        当我们看着桌子上的花瓶时,不需要仔细分析,也能明白花瓶和桌子是分开的对象。在不知不觉中,我们意识到它们之间有一条界线,我们明白,可以改变花瓶的颜色而不会影响到桌子的颜色。

        这些观察说明,我们是以面向对象的角度来看待环境的。换句话说,我们在头脑中创建了一个对周围现实的反射,这个现实就是面向对象的。我们在电脑游戏、3D建模软件和工程软件中也经常看到这种情况,所有这些都涉及许多相互作用的对象。

        OOP把面向对象的思想引入到软件设计和开发中,面相对象的思维是我们处理环境的默认方式,这就是OOP已经成为编写软件最常用的范式的原因。

1.1 思维概念

        如果一个人写一个程序,它自然是面向对象的。这一点在变量命名中很明显。看到下面的例子1,它声明了能保存10个学生信息的变量:

        上述中,三个以student_为前缀的数组,根据命名约定,被设计用于保存10个学生的信息,其声明展示了如何使用变量名将一些变量分组到同一概念下,在本例中,这些变量就是学生。必须这么做,否则,会被哪些对面向对象思维没有任何意义的临时名称弄糊涂。

        变量命名非常重要,因为名称会提醒我们注意其对应的概念以及数据和这些概念之间的关系。如果使用哪些没有任何意义的临时性的命名,代码就会失去这些概念以及概念与数据之间的关系。这可能不会对计算机造成问题,但它会使程序员分析和排除故障变得复杂,并增加了犯错误的可能性。

        在当前的语境中,需进一步澄清一下我们所说的概念是什么意思。概念是作为思想或观念而存在于头脑中的精神或抽象形象。概念可以通过对现实世界实体的感知而形成,也可以完全是想象和抽象的。当你看到一颗树或想到一辆车时,它们对应的图像会在脑海中形成两个不同的概念。

        当用于与技术相关的主题时,“概念”一词只是指和理解该主题相关的原则。概念对于面向对象的思维很重要,因为如果你不能在头脑中形成并保持对对象的理解,你就无法提取它们所代表的以及与之相关的细节,也无法理解它们的相互关系。

        所以,面相对象的思维从概念和概念之间的关系出发来思考问题。因此,如果想要编写一个合适的面向对象的程序,我们需要在头脑中对所有相关的对象、与它们相对应的概念以及它们之间的关系有一个合适的理解。

        当团队协作完成一项任务时,在头脑中形成的面向对象的思维图可能包含了许多概念和相互的关系,它们不太容易向他人表达清除。除此之外,这些思维概念并不稳定、难以琢磨,很容易忘记。这也就说明,我们需要模型和其他表达工具,来将思维图转换为可传达的想法

1.2 思维导图和对象模型

        假设我们用文本来描述一个场景。由于描述的是向受众传达相关的具体概念,因此可以想象:正在描述的人在脑海中绘制了一张导图,图中列出了各种概念以及它们之间的联接关系。他们的目的是将这张思维导图传递给受众。不论是看绘画作品、听音乐,或是读小说时,这种思维图的传递都在发生。

        面向对象的程序依据对象来模拟概念,当我们在大脑中为一个问题创建思维导图时,程序在内存中创建一个对象模型。换句话说,如果我们将人类与面向对象的程序进行比较的话,

        概念、大脑、思维导图 这三个术语分别等同于 对象、内存、对象模型

        OOP实际上并不是关于创建对象的。它创建了一组指令,当程序运行时,这组指令可以产生一个完全动态的对象模型。一旦编译和运行后,面向对象的代码应该能够创建、修改、关联甚至删除对象。

        因此,编写面向对象的代码是一项棘手的任务。你需要在对象存在之前就想象它们及其关系。这正是OOP复杂的原因,也是我们需要一种支持面向对象的编程语言的原因。

        想象还没有创造出来的东西,描述或设计它的各种细节的艺术通常被称为设计,因此在面向对象编程中,这个过程通常被称为面向对象的设计(OOD)。

1.3 对象属性

        任何头脑中的每一个概念都有一些与之相关的属性。假设在一个教室中,我们有一把椅子,叫做chair1,它是棕色的。换句话说,每个chair对象都有一个名为color的属性,而chair1对象的属性是棕色。教室里还有另外四把椅子,它们的颜色属性可以有不同的值。

        一个对象可以有多个属性或一组属性。我们把赋给这些属性的值统称为对象的状态。状态可以被简单地看作是一组值的列表,每个值都属于一个对象中某个特定的属性。对象在其生命周期内可以被修改,这样的对象被称为可变的。这仅仅意味着状态在其生命周期内可以更改。对象也可以是无状态的,这意味着它们不携带任何状态或任何属性。对象也可以是不可变的,这意味着,其不能被改变,它在构造时就确定了,之后就不能被修改。

        注意,不可变对象特别重要,尤其在需要多线程环境中共享它们时,它们的状态不能更改是一个优势。

1.4 领域

        每个为解决特定问题而编写的程序,即使是非常小的程序,都有一个定义良好的领域。领域是软件工程文献中广泛使用的另一个重要术语。领域定义了软件展示其功能的边界。他还定义了软件应该处理的需求。

        一个领域使用特定的和预先确定的术语来表达它的任务,并可以让工程师在领域的边界内建模。每个参与软件项目的人,都应该知道它们的项目定义在哪个领域中。

        例如,银行软件通常是为定义非常明确的领域而构建的。他有一套总所周知的术语,包括“账户”、“信用”、“余额”等等。领域的定义可以通过其术语表中的术语来明确,例如,在银行领域,不会找到“病人”、“药物”等术语。

1.5 对象之间的关系

        对象可以是相互关联的,它们可以互相引用来表示关系。例如,在教室描述中,对象student4可能与对象chair3相关,关系名为“坐在”。也就是,student4坐在chair3上。通过这种方式,系统中的所有对象相互引用,并形成了一个被称为对象模式的对象网络。我们之前说过,对象模型就是我们在头脑中形成的思维导图的对应。

        当两个对象相关时,一个对象状态的改变可能会影响另一个对象的状态。如果两个对象之间形成了关系,那么这些对象的状态(或与它们的属性相对应的值的列表)就会改变。因此,对象之间的关系可以通过向它们添加新属性来创建,所以,这个关系就成为对象状态的一部分。

        注意,定义对象状态和不可变性的属性子集可以从一个域更改为另一个域,而且它不一定包含所有的属性。在一个域中,要表示对象的状态,我们可能只使用非引用属性,而在另一个域中,则可能将它们与引用属性组合在一起使用。

1.6 面向对象操作

        OOP语言允许我们规划对象构造、析构,以及改变即将运行的程序中的对象的状态。那么,先从对象构造开始。

        有两种方法来规划对象的构造:

        第一种方法:是构造一个空对象,即状态中没有任何属性或者构造一个具有最少属性的对象。当代码运行时,将确定并添加更多的属性。以这种方式,根据运行环境的变化,同一对象在同一程序的两次不同执行中可以具有不用的属性。每个对象都被视为一个单独的实体。在程序继续运行期间,任何两个对象,即使由于它们拥有一个公共属性列表而看起来属于同一个组(或类),他们各自状态中的属性也不同。

        属性在它们的内部数据结构中作为映射(或散列)保存,这些数据结构在运行时很容易更改。这种技术通常称为基于原型的OOP。

        第二种方法:可以构造一个对象,他的属性是预先确定的,并且在执行过程中不会改变。在运行时,不允许向这样的对象添加更多的属性,该对象将始终保持其结构。只允许修改属性的值,而且只有当对象是可变时才可能更改。要应用这种方法,程序员应该创建一个预先设计好的对象模板或类,以便在运行时始终保持需要在对象中呈现出的所有属性。在运行时,编译该模板并将其输入到面向对象的语言中。

        在很多编程语言中,对象模板被称为类,这个技术通常称为基于类的OOP。Python既支持基于原型的OOP,也支持基于类的OOP。

        请注意,对象和实例是相同的东西,它们可以互换使用。然而,在一些文章中,它们之间可能会有一些细微的差异。还有一个术语:“引用”。术语“对象”或“实例”用于表明内存中为该对象的值分配的实际位置,而“引用”类似于指向该对象的指针。所以,可以有很多引用指向同一个对象,一般来说,对象通常没有名字,但引用有名字。

        像对象构造一样,对象析构也在运行时发生,我们只能通过代码来规划它。一个对象在其它生命周期内分配的所有资源都应该在其被析构时被释放掉。当对象被析构时,所有其它相关的对象都应该被更改,从而使得它们不再引用被析构的对象。对象不应该具有引用不存在的对象的属性,否则将失去对象模型中的引用完整性。失去完整性可能导致运行时错误,如内存损坏或分段错误,以及像错误计算之类的逻辑错误。

        修改对象(或改变对象的状态)有两种不同的方式,可以直接更改现有的属性值,也可以在该对象的属性集中添加或删除属性。后者只有在基于原型的方法来构建对象时才会发生。记住,改变不可变对象的状态是被禁止的。

1.7 对象的行为

        每个对象,连同它的属性,都有一个它可以执行的特定功能列表。例如,一个汽车能够加速、减速、转弯。在OOP中,这些功能总是和域的需求相匹配。例如,在银行对象模型中,客户可以开设一个新账户,但不能进食。当然,客户是人,肯定能吃东西,但只要吃东西的功能与银行领域无关,那么它就不是客户对象的必要功能。

        每个功能都可以通过改变对象的属性的值来改变对象的状态。比如,汽车对象可以加速,那么加速就是汽车对象的一个功能,通过加速,汽车的速度(它的属性之一)会发生变化。总之,对象就是一组属性和功能。

二、C不是面向对象的,为什么呢?

        C不是面向对象的,并不是因为它的历史悠久,如果年龄是原因的话,科学家们现在就可以找到一种方法使他面向对象。但是可以看到在C语言最新标准C18中,并没有试图使C语言成为一个面向对象的语言。

        人的思维方式是面向对象的,但CPU执行的机器指令是程序性的。CPU只是一个接一个地执行一组指令,它必须不停地从内存中的不同地址跳转、获取和执行其他指令,这与使用C这样的过程性编程语言编写的程序中的函数调用非常相似。

        C不能是面相对象的,因为它位于面向对象和过程性编程之间的边界上。面向对象是人类对问题的理解,过程执行时CPU所能做的。因此,我们需要一些东西位于这个位置上,形成这个边界。否则。通常以面向对象的方式编写的高级程序无法直接转换成送入CPU的过程指令。我们需要一种机制来将其高级逻辑转换成低级过程性指令。

  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值