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

而且,C文件本身就是基本上是分开的“模块”,对不对?那模块不是也像“对象”吗?我对为什么C(看上去与C ++如此相似)为何被认为是一种低级的“过程”语言感到困惑,而C ++却是一种高级的“面向对象”语言。澄清)为什么和在哪里,画线的目的是什么,不是?

评论

全部-为什么要投反对票?这是一个基本问题,但不是一个坏问题。

您可以在C语言中有效地使用OO原理(通常编写出色的C代码的人也可以使用),但是这种语言并不是像最近的许多语言那样围绕着它变得容易的。

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

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

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

#1 楼


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


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


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


C结构是否由字段和方法及其相互作用组成?不。


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


C结构是否以“一流”的方式来做这些事情?否。该语言在您的每一个步骤中都不利于您。


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


C结构可以做到这一点吗?否。


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


C结构可以做到这一点吗?是的。


对象可以被认为是将它们的数据包装在一组旨在确保适当使用数据的函数中。


否。


每个对象都能够接收消息,处理数据并将消息发送给其他对象


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


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


这是否在C中发生?


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


这些功能中的任何一个C结构?否。

您确切地认为结构的哪些特征是“面向对象的”?因为除了结构定义类型的事实外,我找不到其他任何东西。

现在,您当然可以制作具有字段的指针,这些字段是指向函数的指针。您可以使结构体具有指向函数指针数组的指针的字段,这些字段对应于虚拟方法表。等等。您当然可以在C中模拟C ++。但这是使用C进行编程的一种非常惯用的方式;这是一种非常习惯的方法。您最好只使用C ++。


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


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

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

评论


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

–asveikau
2011年10月10日23:16

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

–亚当·罗宾逊(Adam Robinson)
2011年10月11日,下午1:50

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

–埃里克·利珀特
2011年10月11日,下午5:48

@asveikau,我是否可以建议您将“成语”解释为“属于自己的”是有点...特质? :)

– Benjol
2011年10月11日5:57



@BillK:结构体不能包含C语言中的代码。结构体可以包含函数指针,即数据。函数指针不是代码。代码是程序员创建的文本工件。

–埃里克·利珀特
2011年10月11日21:36

#2 楼

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

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

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

评论


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

–dietbuddha
2011年10月11日,下午4:04

在VBA中创建对象更容易,但是我也不认为它是面向对象的。

– JeffO
2011年10月11日12:09

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

– kylben
11-10-11在12:17

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

– Bill K
2011年10月11日15:34

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

– Bill K
2011年10月11日在20:07

#3 楼

基于最高级别的原则:

对象是以相互关联的方式封装数据和行为的,因此它们可以作为一个整体进行操作,并且可以多次实例化,并且可以作为黑盒进行处理。您知道外部接口。

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

模块既包含行为又包含数据,但没有以这种方式封装两者是相关的,并且肯定不能多次实例化。

那是在继承和多态之前...

评论


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

–黑暗圣堂武士
2011年10月10日21:05

问题在于它们没有被封装,而不是不相关。

– Ein卡洛尔
2011年10月10日21:08

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

–Sklivvz
2011年10月11日下午0:27

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

–乔恩·霍普金斯(Jon Hopkins)
2011年10月11日,下午6:20

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

–zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
11年2月2日在18:34

#4 楼

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

评论


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

–编码器
2011年10月10日20:50

@Brian Knoblauch:但是C文件本身不是数据和代码的封装吗?

–黑暗圣堂武士
2011年10月10日20:51

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

–user7043
2011-10-10 20:53

翻译单位? >。<

–黑暗圣堂武士
2011年10月10日20:56

@Dark Templar:翻译单元是源文件,加上#包含在其中的所有内容,并且减去由于没有有条件地包含在内而删除的所有内容(#if,#ifdef等)。

– David Thornley
2011年10月11日13:45

#5 楼

C和C ++一样,具有提供数据抽象的能力,这是它之前存在的面向对象编程范例的一种习语。


C结构可以具有数据(并且这是它们的主要目的)
C结构还可以将函数指针定义为数据
C结构可以并经常具有与它们关联的一组函数,就像方法一样,仅this指针不是隐式传递的,而是您必须将其显式指定为设计用于处理指定结构的每个方法的第一个参数。无论是在定义和调用类/结构方法时,C ++都会自动为您执行此操作。

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


C ++通过不要求用户将其传递给“类/结构方法”来隐式显示此指针。只要可以(至少部分地)识别类型。
C ++允许您限制对某些方法(类函数)的访问,因此可以进行更多的“防御性编程”或“防白痴”。 /> C ++通过引入

新的运算符而不是malloc + cast

模板而不是void指针来鼓励抽象,从而鼓励抽象

内联函数接收类型值而不是宏

内置不需要实现自己的多态,可以创建抽象层次结构,协定和特殊化。



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


两年后,效率低下的抽象编程模型
您注意到有些抽象不是很有效,但现在所有
您的代码取决于周围所有漂亮的对象模型,而您
不重写应用程序就无法修复它。 -Linus Torvalds


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


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


#6 楼

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

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

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

阅读后下面的评论:没错,(几乎)所有事情都可以用C完成(总是如此),但是乍一看,我认为将c与c ++分开的是设计程序时的思维方式。

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

评论


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

–黑暗圣堂武士
2011-10-10 20:55

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

–编码器
2011-10-10 20:58

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

–user7043
2011-10-10 20:58



#7 楼

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

评论


好吧,我想问题是:为什么和什么不是对象画线?

–黑暗圣堂武士
2011年10月10日20:49

#8 楼

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

您可以实现“ OO”设计( Gnome桌面也许是用纯C语言完成OO的最好例子,我什至已经看到了用COBOL完成的事情!

但是能够实现OO设计并不能使OO语言成为现实。纯粹主义者认为Java和C ++并不是真正的面向对象,因为您不能覆盖或继承基本的“类型”,例如“ int”和“ char”,并且Java不支持多重继承。但是,由于它们是使用最广泛的OO语言,并且支持大多数范例,因此获得报酬来生产工作代码的大多数“实际”程序员都将它们视为OO语言。另一方面,C仅支持结构(如COBOL,Pascal和许多其他过程语言一样),您可以说支持多重继承,因为您可以对任何数据使用任何函数,但大多数人会将此视为错误而不是功能。

#9 楼

让我们看一下OO的定义:


消息传递
封装
后期绑定

C不提供这三个对象。特别是,它不提供最重要的消息传递。

评论


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

–研究员
2011年10月11日在8:03

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

– David Thornley
2011-10-11 13:39

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

–user2582
2011年10月11日15:22

#10 楼

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

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

 struct FooImpl;
typedef struct FooImpl *Foo;
 


当然,将需要一个构建Foo的函数(即工厂),该函数应将一些工作委托给分配的对象本身(即,通过“构造函数”方法),以及可以再次处理该对象(同时通过其“析构函数”方法对其进行清理),但是这很详细。

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

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

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

#11 楼

我认为C对于实现面向对象的概念来说是完美的,也不错。从我实际的角度来看,我认为面向对象的语言的公分母之间的大多数差异本质上都是微小的和句法上的。

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

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

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

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

当然,我们必须使用函数指针和指向它们的动态分配指针,需要更多样板来初始化类似的vtablesvptrs,但是一点样板却从未使我感到悲伤。

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

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

评论


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

–尼科尔·波拉斯(Nicol Bolas)
17年12月19日在1:30

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

–user204677
17/12/19在1:52



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

–user204677
17/12/19在1:54



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

–尼科尔·波拉斯(Nicol Bolas)
17年12月19日在1:57

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

–user204677
17/12/19在1:57



#12 楼

这不是一个坏问题。

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

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