为什么C不被视为“面向对象”语言?


92

C似乎有自己的准对象,例如“结构”,可以将其视为对象(以我们通常认为的高级方式)。

而且,C文件本身基本上是单独的“模块”,对吗?那模块不是也像“对象”吗?我对为什么C(看上去与C ++如此相似)为何被视为低级“过程”语言感到困惑,而C ++却是高级“面向对象”语言。

*编辑:(澄清)为什么和在哪里,画线,什么是“对象”,什么不是?


40
全部-为什么要投反对票?这是一个基本问题,但不是一个坏问题。
乔恩·霍普金斯

7
您可以在C语言中有效地使用OO原理(通常编写出色的C代码的人也可以使用),但是这种语言并没有像其他许多新语言那样使它变得容易。
asveikau 2011年

5
C只是有一种不同的,更简单的(至少老实说)更好地定义了(至少在开源社区中)数据抽象方法。C ++往往是一个抽象的功能强大的平台,它允许许多伟大的事情,但要付出一定的代价,那就是必须了解如何[不]正确使用它们,而这在常见程序中常常是十分缺乏的。请参阅我的完整答案以获取更多详细信息。
Yam Marcovic

1
不久:结构不能有方法。(指向一个函数的指针并不能完全消除它)。
SF。

1
使用C进行基于对象的编程可能会有些困难。但这并不能使其面向对象

Answers:


101

C似乎有自己的准对象,例如“结构”,可以将其视为对象

让我们和您一起通读有关面向对象编程的Wikipedia页面,并检查与传统上认为是面向对象的样式相对应的C样式结构的功能:

(OOP)是一种使用“对象”的编程范例-由数据字段和方法及其相互作用组成的数据结构

C结构是否由字段和方法以及它们的相互作用组成?没有。

编程技术可以包括诸如数据抽象,封装,消息传递,模块化,多态性和继承之类的功能。

C结构是否以“一流”的方式来做这些事情?不会。语言在您使用的所有步骤中都会对您不利。

面向对象的方法鼓励程序员将数据放置在程序其余部分无法直接访问的位置

C结构会这样做吗?没有。

面向对象的程序通常将包含不同类型的对象,每种类型对应于要管理的特定类型的复杂数据,或者可能对应于实际对象或概念

C结构会这样做吗?是。

可以将对象视为将其数据包装在旨在确保适当使用数据的一组函数中

没有。

每个对象都能够接收消息,处理数据以及向其他对象发送消息

结构本身可以发送和接收消息吗?否。可以处理数据吗?没有。

OOP数据结构倾向于“随身携带他们自己的运算符”

这会在C中发生吗?没有。

动态调度...封装...子类型多态性...对象继承...打开递归...对象类...类的实例...作用于附加对象的方法...消息传递... 。抽象

C结构有这些功能吗?没有。

您确切地认为结构的哪些特征是“面向对象的”?因为我找不到任何比一个事实,即结构定义其他类型

现在,您当然可以使结构的字段成为函数的指针。您可以使结构具有字段,这些字段是指向函数指针数组的指针,与虚拟方法表相对应。等等。当然,您可以在C中模拟 C ++。您最好只使用C ++。

而且,C文件本身基本上是单独的“模块”,对吗?那模块不是也像“对象”吗?

同样,您认为模块的哪些特征使它们像对象一样起作用?模块是否支持抽象,封装,消息传递,模块化,多态和继承?

抽象和封装非常薄弱。显然,模块是模块化的。这就是为什么它们被称为模块。消息传递?仅在方法调用是消息的意义上,模块可以包含方法。多态性?不。遗产?不。模块是“对象”的较弱候选者。


15
我不同意“模拟C ++”(您的术语)不是惯用的C。一个反例是OS内核通常如何实现文件操作-以Linux为例,其中您的结构充满了功能指针。它们可能是某种“特定于域”的习惯用法(例如,仅限于在* nix上编写驱动程序),但是它们是可以识别的,并且可以为某些目的进行足够干净的工作。还有一些用户模式库的例子很好地表达了一些面向对象的概念。使用Gtk +及其依赖的库。称其为hacky,您可能是对的,但是使用它们并不可怕
asveikau 2011年

5
@asveikau:习俗是不寻常的(即使不是闻所未闻的)和“骇人听闻”的想法,从定义上说,这不是惯用的吗?
亚当·罗宾逊

19
@asveikau:当然,您可以做一些事情来使C程序更具风格。但是正如您所说,这需要纪律处分。语言功能本身并不能自然地将您引向封装,多态性,类继承等。相反,开发人员可以将这种模式强加给语言。从逻辑上讲,这并不意味着结构与对象是同一件事。哎呀,您也可以在Lisp中以OO风格编程,但这并不意味着con单元格是对象。
埃里克·利珀特

3
@asveikau,我是否可以建议您将(成语)对“成语”的解释理解为“属于自己”?:)
Benjol 2011年

8
@BillK:结构不能包含C语言中的代码。结构可以包含函数指针,即data。函数指针不是代码代码是程序员创建的文本工件。
埃里克·利珀特

46

关键字是“面向”而不是“对象”。即使使用对象但像结构一样使用对象的C ++代码也不是面向对象

C和C ++都可以进行OOP(C中没有访问控制),但是C中的语法不方便(至少可以这样说),而C ++中的语法则非常吸引人。尽管在这方面核心功能几乎相同,但C面向过程,而C ++面向对象。

使用对象来实现只能用对象完成设计的代码(通常意味着利用多态性)是面向对象的代码。使用对象仅比数据包多一点的代码,甚至使用面向对象语言的继承,实际上只是过程代码,它比需要的复杂。C语言中使用的函数指针在运行时随数据变化的结构而变化,这有点像是多态的,甚至可以说是面向对象的,即使是面向过程的语言。


2
+1对于指向我们的C可以是OO。这不方便,但是可以做到。
Dietbuddha 2011年

在VBA中创建对象比较容易,但是我也不认为它是面向对象的。
JeffO 2011年

VBA我称之为“基于对象”。它使用对象,但不是多态的,并且在我完成的工作中,我不确定无论尝试哪种代码杂技,您都可以做到多态。它基本上是一种带有封装的过程语言。
kylben 2011年

2
大多数语言都可以充当OO语言,功能性语言或几乎任何风格的语言。区别在于“定向”,我之前从未听说过。我希望我可以将它投票3次并接受,这是正确的答案。
Bill K

1
@kylben将SQL代码存储在表中然后提取并执行(使表成为您的对象)并不是一件容易的事,这将是一个有趣的实验。其实有点诱人……
Bill K

19

基于最高级别的负责人:

对象是一种以相互链接的方式封装的数据和行为,这样它们就可以作为一个整体进行操作,并且可以实例化多次,并且如果您知道外部接口,则可以作为黑盒工作。

结构包含数据但没有任何行为,因此不能视为对象。

模块既包含行为也包含数据,但没有以相互关联的方式进行封装,当然不能多次实例化。

那是在您进行继承和多态之前...


模块中的行为和数据如何无关?成员基本上不是对模块内部存在的数据的“行为”起作用吗?
黑暗圣堂武士

4
问题在于它们没有被封装,而不是不相关。
伊恩·卡洛尔

实际上,对象主要围绕行为而不是数据。数据是/应该是OOP中的二等公民。相反,结构以数据为中心,没有任何行为。
Sklivvz 2011年

1
@黑暗圣堂武士-伊恩说的...这是关系的本质。我可以看到您来自哪里-您肯定可以在模块中使用Structs,它可以模拟OO的一些非常基本的元素,但是您要做的很多事情都将依赖于程序员坚持使用set自我强加的规则,而不是由语言来强制执行它。那你为什么要打扰?您没有获得OO的好处-例如没有继承-并且您在限制自己。
乔恩·霍普金斯,

这比标记为正确的答案更好。
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

6

“结构”仅是数据。通常,对“面向对象”的快速而肮脏的测试是:“是否存在一种允许将代码和数据封装为单个单元的结构?”。C失败了,因此是程序上的。C ++通过了该测试。


6
有解决方法,结构可以具有指向成员函数的静态函数指针。它们将需要显式this指针,但是在那种情况下,数据和处理数据的方法将封装在结构内部。
编码器

@Brian Knoblauch:但是C文件本身不是数据和代码的封装吗?
黑暗圣堂武士

@DarkTemplar从某种意义上讲,是的,但是除非您设法创建翻译单元,将其编译并在运行时将它们加载到正在运行的进程中,否则这样做没有什么用。

翻译单位?>。<
黑暗圣堂武士

@Dark Templar:翻译单元是一个源文件,加上其中的所有#included,减去未有条件地包含在内的所有内容(#if#ifdef等)。
David Thornley,

5

C与C ++一样,具有提供Data Abstraction的能力,这是之前存在的面向对象编程范例的一种习语。

  • C结构可以具有数据(这是它们的主要目的)
  • C结构也可以将函数指针定义为数据
  • 就像方法一样,C结构可以并经常具有一组与之关联的函数,只有this指针不会隐式传递,但是您必须显式地将其指定为设计用于处理指定结构的每个方法的第一个参数。无论您定义还是调用类/结构方法,C ++都会自动为您完成。

C ++中的OOP扩展了抽象数据的方法。有人说它是有害的,而另一些人则认为正确使用它是一个好工具。

  • C ++ 通过不要求用户将其传递到“类/结构的方法”中来隐含指针,只要可以(至少部分地)标识类型即可。
  • C ++允许您限制对某些方法(类函数)的访问,因此允许进行更多的“防御性编程”或“防白痴”。
  • C ++通过引入更强的类型安全性来鼓励抽象
    1. 新的操盘手的malloc +投
    2. 模板代替空指针
    3. 内联函数接收类型值而不是
    4. 内置的不需要您自己实现的多态性,可让您创建抽象层次结构合同专业化

但是,您会发现许多C的“黑客”都在宣讲C如何完全有能力进行适当数量的抽象,以及C ++产生的开销只会分散他们解决实际问题的注意力。

效率低下的抽象编程模型已经过去两年了,您会注意到某种抽象不是很有效,但是现在您的所有代码都依赖于周围所有漂亮的对象模型,并且如果不重写应用程序就无法修复它。莱纳斯·托瓦尔兹(Linus Torvalds)

其他人则倾向于以更加平衡的方式看待它,既接受优点也接受缺点。

C可以使您轻松射击自己的脚;C ++使它变得更难,但是当您这样做时,它会使您全神贯注。-比尼亚(Bjarne Stroustrup)


2

您需要看看硬币的另一面:C ++。

在OOP中,我们想到一个抽象对象(并相应地设计程序),例如可以停下,加速,向左或向右转的汽车等。具有大量功能的结构根本不符合该概念。

例如,对于“真实”对象,我们需要隐藏成员,或者我们也可以通过真实的“是”关系进行继承,等等。

阅读下面的评论之后: 没错,(几乎)一切都可以用C来完成(这始终是对的),但是乍一看,我认为将c与c ++分开的是设计程序时的思维方式。

唯一真正改变的是编译器强加策略。即纯虚函数,等等。但是这个答案仅涉及技术问题,但是我认为主要区别(如上所述)是您在编写代码时的原始思维方式,因为C ++为您提供了一种更好的内置语法来执行此类操作,而不是在在C中有点笨拙


不太确定为什么带有“函数捆绑”的结构不适合该概念吗?+1虽然是答案
黑暗圣堂武士

1
除了访问控制(私有/受保护/公共)外,其他所有操作都可以通过结构来完成。
编码器

1
@DarkTemplar:嗯,您可以尝试用C模拟OOP(人们实际上已经编写了有关该主题的电子书,并且在现实世界中进行,尽管很少),就像您可以尝试使用Java模拟函数式编程一样。但是C为它提供了零帮助,因此C语言不是面向对象

1

你自己说了。尽管C的东西有点像对象,但它们仍然不是对象,这就是为什么C不被视为OOP语言的原因。


1
好吧,我想问题是:为什么和什么不是对象画线?
黑暗圣堂武士

1

面向对象既指架构模式(甚至是元模式),也指具有帮助使用该模式实现或强制执行的功能的语言。

您可以实现“ OO”设计(Gnome桌面也许是用纯C语言完成OO的最佳示例),我什至已经看到了用COBOL做到这一点!

但是能够实现OO设计并不能使语言成为OO。纯粹主义者认为Java和C ++并不是真正的面向对象,因为您不能覆盖或继承基本的“类型”,例如“ int”和“ char”,并且Java不支持多重继承。但是,由于它们是使用最广泛的OO语言,并且支持大多数范例,因此大多数获得酬劳以产生工作代码的“真正”程序员都将它们视为OO语言。

另一方面,C仅支持结构(COBOL,Pascal和许多其他过程语言也是如此),您可以争辩说支持多重继承,因为您可以对任何数据使用任何函数,但是大多数人将其视为错误而不是比功能。


0

让我们看一下OO的定义:

  • 讯息传递
  • 封装形式
  • 后期绑定

C不提供这三个。特别是,它不提供Messaging,这是最重要的消息


1
我想我不得不不同意这一点。您当然可以在C中使用消息传递-与C中的Objective C API一起使用,您会发现它很深入-并且后期绑定可以通过函数指针完成,但是您并没有太多的语法糖可以隐藏它。(在C语言中封装实际上很容易;指向不完整结构类型的指针可以完美地完成工作。)
Donal Fellows

继承在OO中也被认为很重要,而C则不这样做。在C中最接近封装的是可以具有内部(static)函数和数据成员的单独文件。
David Thornley,

@DonalFellows,在Objective-C中传递的实际消息可以很容易地实现为C99宏。编译器唯一要做的就是从几行高级代码中静态生成所有必要的结构。大多数(如果不是全部)功能都可以从C API获得。
2011年

0

OO有很多关键因素,但重要的是大多数代码不知道对象内部是什么(他们看到表面接口,而不是实现),对象的状态是受管单元(即,当对象不再是对象时,其状态也是如此),并且当某些代码调用对象上的操作时,他们这样做时并不确切知道该操作是什么或所涉及的(它们所做的只是遵循一种模式来抛出在墙上的“消息”)。

C做封装就好了;无法看到结构定义的代码无法(合法地)窥视结构内部。您需要做的就是将这样的定义放在头文件中:

struct FooImpl;
typedef struct FooImpl *Foo;

当然,将需要一个构建Foos 的函数(即一个工厂),该函数应将一些工作委托给分配的对象本身(即通过“构造函数”方法),并且还需要一种方法再次处理该对象(同时通过其“析构函数”方法对其进行清理)的细节。

方法分配(即消息传递)也可以通过以下约定完成:结构的第一个元素实际上是指向具有功能指针的结构的指针,并且这些功能指针中的每个必须以a Foo作为其第一个参数。然后,调度就变成了查找该函数并使用正确的参数重写对其进行调用的问题,而使用和一点狡猾就不那么难了。(该函数表是使用C ++之类的类的真正核心。)

此外,这也给您带来了后期绑定:所有调度代码都知道,它正在将特定偏移量调用到对象所指向的表中。只需在对象的分配和初始化期间进行设置。可以使用更复杂的调度方案,这些方案可以为您带来更多的运行时动态性(以速度为代价),但是它们是基本机制之上的樱桃。

但这并不意味着C是一种OO语言。关键是C让您自己完成编写约定和调度机制的所有棘手工作(或使用第三方库)。这是很多工作。它还不提供语法或语义支持,因此实现完整的类系统(如继承)将不必要地痛苦;如果您要处理一个由OO模型很好描述的复杂问题,则OO语言对于编写解决方案将非常有用。额外的复杂性是合理的。


0

我认为C是完全正常和体面实现面向对象的概念,耸肩。从我实际的角度来看,我认为面向对象的语言的公分母子集之间的大多数差异本质上都是次要的和句法上的。

让我们从信息隐藏开始。在C语言中,我们可以通过简单地隐藏结构的定义并通过不透明的指针使用它来实现。当我们使用类时,这有效地建模了数据字段的publicvs. private区别。它很容易做到,而且几乎没有惯用法,因为标准C库在很大程度上依赖于此来实现信息隐藏。

当然,您会失去使用不透明类型轻松精确地控制结构在内存中分配位置的能力,但这只是C与C ++之间的一个值得注意的区别。C ++在比较它在C上仍然可以保持对内存布局的控制能力时,比较它在C上面向对象的概念的能力时,绝对是一种出色的工具,但这并不一定意味着Java或C#在这方面要优于C,因为这两个使您完全失去了控制对象在内存中分配位置的能力。

而且我们确实必须使用类似的语法fopen(file, ...); fclose(file);,而不是file.open(...); file.close();大声疾呼。谁真正在乎?也许只是那些非常依赖IDE中自动完成功能的人。我确实承认,从实际的角度来看,这可能是一项非常有用的功能,但可能没有必要讨论一种语言是否适合OOP。

我们确实缺乏有效实施protected领域的能力。我将完全在那里提交。但是我认为没有一条具体的规则说:“ 所有的OO语言都应该具有允许子类访问基本类的成员的功能,而普通客户仍然不能访问这些基类的成员。” 此外,我很少看到受保护成员的用例,这些用例至少一点也不怀疑会成为维护障碍。

当然,我们以“模仿” OO多态性与函数指针表和指向他们多一点的样板动态分配,以初始化这些类比vtablesvptrs,但样板的一点点从来没有给我带来太多的悲痛。

继承的方式大致相同。我们可以通过组合轻松地对其进行建模,并且在编译器的内部工作中可以归结为同一件事。当然,如果我们要向下转换,我们会失去类型安全性,并且我想说的是,如果您完全想要向下转换,请不要使用C,因为人们在C中所做的模仿向下转换的事情可能会因类型而变得可怕安全的观点,但我希望人们一点也不沮丧。类型安全性是您很容易在C语言中错过的东西,因为编译器提供了太多的空间来将其仅解释为位和字节,从而牺牲了在编译时捕获潜在错误的能力,但是某些语言被认为是面向对象的甚至不是静态输入的。

所以不知道,我认为很好。当然,我不会使用C尝试创建符合SOLID原则的大规模代码库,但这不一定是由于其在面向对象方面的缺点。如果我尝试使用C来实现此目的,我会遗漏的许多功能都与语言功能有关,这些语言功能没有直接被视为OOP的先决条件,例如强类型安全性,在对象超出范围时会自动调用的析构函数,运算符重载,模板/泛型和异常处理。就在我缺少C ++的那些辅助功能时。


1
我不确定如何将构造函数视为OOP的先决条件。封装的基本本质的一部分是确保对象不变的能力。这要求能够在这些不变式内正确初始化对象。这需要一个构造函数:一个或多个函数,以使您不能说该对象还不存在,直到至少调用了其中一个。
Nicol Bolas's

当我们用不透明的类型隐藏信息时,实例化它们,复制/克隆并销毁它们的唯一方法是通过类比的等效函数来充当构造函数和析构函数,并具有维护这些不变式的能力。它只是没有直接建模到语言和可能类似于形式foo_createfoo_destroyfoo_clone,如

struct Foo在这种情况下,我们需要保持不变的是确保外界无法访问和修改其数据成员,而这种不透明类型(已声明但未定义给外部世界)会立即给出。可以说,它是一种更强大的信息隐藏形式,与pimplC ++ 中的a形式相当。

是的,我知道人们如何用C实现OOP,谢谢。我的观点是,您关于构造函数“不直接视为OOP的先决条件”的说法是错误的。
Nicol Bolas's

我看到,lemme纠正了……但是在那种情况下,我们可以说C确实为析构函数和构造函数提供了(足够的?)支持吗?

-1

这不是一个坏问题。

如果要将c称为OO语言,则也几乎需要将所有过程语言都称为OO。因此,它将使该术语变得毫无意义。c没有面向对象的语言支持。如果具有结构,但是结构types不是类。

实际上,与大多数语言相比,c根本没有很多功能。它主要是因为它的速度,简单性,受欢迎程度以及包括大量库在内的支持。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.