【问题标题】:How would one write object-oriented code in C? [closed]如何用 C 编写面向对象的代码? [关闭]
【发布时间】:2010-09-25 23:44:06
【问题描述】:

在 C 中编写面向对象的代码有哪些方法?尤其是在多态性方面。


另请参阅此 Stack Overflow 问题 Object-orientation in C

【问题讨论】:

  • ldeniau.web.cern.ch/ldeniau/html/oopc.html">Object 面向 C 的编程 by Laurent Deniau
  • @Camilo Martin:我故意问可以而不是应该。我实际上对在 C 中使用 OOP 并不感兴趣。但是,通过看到 C 中的 OO 解决方案,我/我们将更多地了解 C 的局限性和/或灵活性,以及​​实现和使用多态性的创造性方法。跨度>
  • OO 只是一种模式。检查这里,它甚至可以在 .bat 文件中完成: dirk.rave.org/chap9.txt(我认为,如果你足够感兴趣,任何模式都可以应用于任何编程语言)。不过,这是值得深思的好东西。将这些我们认为理所当然的模式应用到没有这些模式的语言上,或许可以学到很多东西。
  • GTK - '对不起,GObject - 实际上是 C 中 OOP (sorta) 的一个很好的例子。所以,回答@Camilo,C interpoliability。
  • 这个问题怎么会被关闭真是令人震惊。正如收到的分数所示,这是一个非常好的问题,令人大开眼界。它只是说明了 SO 是如何被驱动的......

标签: c oop object


【解决方案1】:

既然您在谈论多态性,那么是的,您可以,我们在 C++ 出现之前几年就在做这种事情。

基本上,您使用struct 来保存数据和函数指针列表以指向该数据的相关函数。

因此,在通信类中,您将有一个 open、read、write 和 close 调用,这些调用将作为结构中的四个函数指针以及对象的数据进行维护,例如:

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

当然,上面的那些代码段实际上是在一个“构造函数”中,例如rs232Init()

当您从该类“继承”时,您只需更改指针以指向您自己的函数。调用这些函数的每个人都会通过函数指针来完成,给你你的多态性:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

有点像手动 vtable。

您甚至可以通过将指针设置为 NULL 来创建虚拟类 - 行为与 C++ 略有不同(运行时的核心转储,而不是编译时的错误)。

这是一段演示它的示例代码。首先是顶层类结构:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

那么我们就有了 TCP 'subclass' 的函数:

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

还有 HTTP:

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

最后是一个测试程序来展示它的实际效果:

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.microsoft.com");

    return 0;
}

这会产生输出:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

因此您可以看到不同的函数被调用,具体取决于子类。

【讨论】:

  • 封装很容易,多态是可行的——但继承很棘手
  • lwn.net 最近发表了一篇题为Object Oriented design Patterns in the kernel 的文章,主题是与上述答案类似的结构——即包含函数指针的结构,或指向具有函数的结构的指针一个指向结构体的指针,我们正在使用的数据作为参数。
  • +1 好例子!虽然如果有人真的想走这条路,“实例”结构更适合有一个 single field 指向他们的“虚拟表”实例,包含该类型的所有虚拟函数在一处。 IE。您的tCommClass 将重命名为tCommVT,而tCommClass 结构将只有数据字段和一个指向“唯一”虚拟表的tCommVT vt 字段。在每个实例中携带所有指针会增加不必要的开销,并且更像是你在 JavaScript 中做事的方式,而不是 C++,恕我直言。
  • 所以这演示了单个接口的实现,但是实现多个接口呢?还是多重继承?
  • Weber,如果你想要 C++ 的所有功能,你可能应该使用 C++。这个问题专门询问了多态性,即对象采取不同“形式”的能力。你当然可以在 C 中做接口和多重继承,但这是相当多的额外工作,而且你必须自己管理智能而不是使用 C++ 内置的东西。
【解决方案2】:

是的,有可能。

这是纯 C,没有宏预处理。它具有继承、多态、数据封装(包括私有数据)。它没有等效的受保护限定符,这意味着私有数据在继承链中也是私有的。

#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

/*output:
6.56
13.12
*/

【讨论】:

【解决方案3】:

C stdio FILE 子库是一个很好的例子,说明了如何在纯 C 中创建抽象、封装和模块化。

继承和多态性——通常被认为对 OOP 至关重要的其他方面——并不一定能提供他们所承诺的生产力提升,reasonableargumentsbeen made,它们实际上会阻碍开发和思考问题域。

【讨论】:

  • stdio不是在内核层抽象出来的吗?如果我没记错的话,C 库将它们视为字符文件/设备,而内核驱动程序会完成这项工作,...
【解决方案4】:

我建立了一个小库,并在其中进行了尝试,对我来说它工作得非常好。所以我想我分享一下经验。

https://github.com/thomasfuhringer/oxygen

使用结构体可以很容易地实现单继承,并将其扩展到每个其他子类。对父结构的简单转换使得可以在所有后代上使用父方法。 只要您知道一个变量指向一个包含此类对象的结构,您就可以随时将其强制转换为根类并进行自省。

如前所述,虚方法有些棘手。但它们是可行的。为了简单起见,我只是在类描述结构中使用了一组函数,每个子类都复制并在需要时重新填充各个插槽。

多重继承实现起来会相当复杂,并且会对性能产生重大影响。所以我离开它。我确实认为在很多情况下清晰地模拟现实生活环境是可取和有用的,但在 90% 的情况下,单继承可能满足需求。而且单继承很简单,而且不花钱。

我也不关心类型安全。我认为你不应该依赖编译器来防止你编程错误。无论如何,它只能保护您免受一小部分错误的影响。

通常,在面向对象的环境中,您还希望实现引用计数以尽可能自动化内存管理。所以我还在“Object”根类中加入了一个引用计数,以及一些封装堆内存分配和释放的功能。

这一切都非常简单和精简,并为我提供了 OO 的基本要素,而无需强迫我处理 C++ 这个怪物。而且我保留了留在 C 领域的灵活性,这使得集成第三方库变得更加容易。

【讨论】:

    【解决方案5】:

    似乎人们正在尝试使用 C 来模拟 C++ 风格。我的看法是,进行面向对象编程 C 确实是在进行面向结构的编程。但是,您可以实现后期绑定、封装和继承等功能。对于继承,您在子结构中显式定义指向基本结构的指针,这显然是多重继承的一种形式。您还需要确定您的

    //private_class.h
    struct private_class;
    extern struct private_class * new_private_class();
    extern int ret_a_value(struct private_class *, int a, int b);
    extern void delete_private_class(struct private_class *);
    void (*late_bind_function)(struct private_class *p);
    
    //private_class.c
    struct inherited_class_1;
    struct inherited_class_2;
    
    struct private_class {
      int a;
      int b;
      struct inherited_class_1 *p1;
      struct inherited_class_2 *p2;
    };
    
    struct inherited_class_1 * new_inherited_class_1();
    struct inherited_class_2 * new_inherited_class_2();
    
    struct private_class * new_private_class() {
      struct private_class *p;
      p = (struct private_class*) malloc(sizeof(struct private_class));
      p->a = 0;
      p->b = 0;
      p->p1 = new_inherited_class_1();
      p->p2 = new_inherited_class_2();
      return p;
    }
    
        int ret_a_value(struct private_class *p, int a, int b) {
          return p->a + p->b + a + b;
        }
    
        void delete_private_class(struct private_class *p) {
          //release any resources
          //call delete methods for inherited classes
          free(p);
        }
        //main.c
        struct private_class *p;
        p = new_private_class();
        late_bind_function = &implementation_function;
        delete_private_class(p);
    

    c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj编译。

    所以建议是坚持纯 C 风格,不要试图强行进入 C++ 风格。此外,这种方式也适用于构建 API 的非常简洁的方式。

    【讨论】:

    • 对于继承,通常基类或实例结构嵌入派生结构中,而不是单独分配并使用指针引用。这样,最顶层的基总是在其任何派生类型结构的开头,因此它们可以轻松地相互转换,而对于可能位于任何偏移量的指针,这是无法做到的。
    【解决方案6】:

    问题的答案是“是的,你可以”。

    面向对象的 C (OOC) 套件适用于那些想要以面向对象的方式编程,但也坚持使用良好的旧 C 语言的人。 OOC 实现类、单继承和多继承、异常处理。

    特点

    • 仅使用 C 宏和函数,无需语言扩展! (ANSI-C)

    • 易于阅读的应用程序源代码。尽可能让事情变得简单。

    • 类的单一继承

    • 接口和 mixin 的多重继承(从 1.3 版开始)

    • 实现异常(在纯 C 中!)

    • 类的虚函数

    • 便于类实现的外部工具

    更多详情,请访问http://ooc-coding.sourceforge.net/

    【讨论】:

      【解决方案7】:

      我已经研究了一年了:

      由于 GObject 系统很难与纯 C 一起使用,我尝试编写一些不错的宏来简化 C 的 OO 风格。

      #include "OOStd.h"
      
      CLASS(Animal) {
          char *name;
          STATIC(Animal);
          vFn talk;
      };
      static int Animal_load(Animal *THIS,void *name) {
          THIS->name = name;
          return 0;
      }
      ASM(Animal, Animal_load, NULL, NULL, NULL)
      
      CLASS_EX(Cat,Animal) {
          STATIC_EX(Cat, Animal);
      };
      static void Meow(Animal *THIS){
          printf("Meow!My name is %s!\n", THIS->name);
      }
      
      static int Cat_loadSt(StAnimal *THIS, void *PARAM){
          THIS->talk = (void *)Meow;
          return 0;
      }
      ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)
      
      
      CLASS_EX(Dog,Animal){
          STATIC_EX(Dog, Animal);
      };
      
      static void Woof(Animal *THIS){
          printf("Woof!My name is %s!\n", THIS->name);
      }
      
      static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
          THIS->talk = (void *)Woof;
          return 0;
      }
      ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)
      
      int main(){
          Animal *animals[4000];
          StAnimal *f;
          int i = 0;
          for (i=0; i<4000; i++)
          {
              if(i%2==0)
                  animals[i] = NEW(Dog,"Jack");
              else
                  animals[i] = NEW(Cat,"Lily");
          };
          f = ST(animals[0]);
          for(i=0; i<4000; ++i) {
              f->talk(animals[i]);
          }
          for (i=0; i<4000; ++i) {
              DELETE0(animals[i]);
          }
          return 0;
      }
      

      这是我的项目站点(我没有足够的时间来写en.doc,但是中文的文档要好得多)。

      OOC-GCC

      【讨论】:

      • CLASS STATIC ASM NEW DELETE ST ... 是 OOC-GCC 中的宏
      【解决方案8】:

      添加一点OOC代码:

      #include <stdio.h>
      
      struct Node {
          int somevar;
      };
      
      void print() {
          printf("Hello from an object-oriented C method!");
      };
      
      struct Tree {
          struct Node * NIL;
          void (*FPprint)(void);
          struct Node *root;
          struct Node NIL_t;
      } TreeA = {&TreeA.NIL_t,print};
      
      int main()
      {
          struct Tree TreeB;
          TreeB = TreeA;
          TreeB.FPprint();
          return 0;
      }
      

      【讨论】:

        【解决方案9】:

        这读起来很有趣。我自己也一直在思考同样的问题,思考它的好处是:

        • 尝试想象如何在非 OOP 语言中实现 OOP 概念有助于我理解 OOp 语言(在我的例子中是 C++)的优势。这有助于让我更好地判断是否将 C 或 C++ 用于给定类型的应用程序——在这种应用程序中,一个比另一个更重要。

        • 在浏览 Web 以获取有关这方面的信息和意见时,我发现一位正在为嵌入式处理器编写代码并且只有 C 编译器可用的作者: http://www.eetimes.com/discussion/other/4024626/Object-Oriented-C-Creating-Foundation-Classes-Part-1

        在他的案例中,用纯 C 语言分析和调整 OOP 概念是一项有效的追求。看来他愿意牺牲一些 OOP 概念,因为尝试在 C 中实现它们会导致性能开销损失。

        我吸取的教训是,是的,它可以在一定程度上做到,是的,有一些很好的理由去尝试。

        最后,机器正在旋转堆栈指针位,使程序计数器跳来跳去并计算内存访问操作。从效率的角度来看,您的程序执行的这些计算越少越好……但有时我们必须简单地支付这笔税,以便我们可以以一种最不易受人为错误影响的方式组织我们的程序。 OOP 语言编译器努力优化这两个方面。程序员必须更加小心地在 C 等语言中实现这些概念。

        【讨论】:

          【解决方案10】:

          您可能想做的一件事是查看Xt 工具包的X Window 实现。当然,它正在变得越来越长,但是使用的许多结构被设计为在传统 C 中以 OO 方式工作。通常这意味着在这里和那里添加一个额外的间接层,并设计结构以相互叠加。

          你真的可以用这种方式在 C 中以 OO 的方式做很多事情,即使有时感觉像这样,OO 概念并没有完全从 #include&lt;favorite_OO_Guru.h&gt; 的脑海中形成。它们确实构成了当时许多既定的最佳实践。 OO 语言和系统只是提炼和扩展了当时编程时代精神的一部分。

          【讨论】:

            【解决方案11】:

            命名空间通常通过以下方式完成:

            stack_push(thing *)
            

            而不是

            stack::push(thing *)
            

            要将C 结构变成类似C++ 类的东西,您可以转:

            class stack {
                 public:
                    stack();
                    void push(thing *);
                    thing * pop();
                    static int this_is_here_as_an_example_only;
                 private:
                    ...
            };
            

            进入

            struct stack {
                 struct stack_type * my_type;
                 // Put the stuff that you put after private: here
            };
            struct stack_type {
                 void (* construct)(struct stack * this); // This takes uninitialized memory
                 struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
                 void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
                 thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
                 int this_is_here_as_an_example_only;
            }Stack = {
                .construct = stack_construct,
                .operator_new = stack_operator_new,
                .push = stack_push,
                .pop = stack_pop
            };
            // All of these functions are assumed to be defined somewhere else
            

            然后做:

            struct stack * st = Stack.operator_new(); // Make a new stack
            if (!st) {
               // Do something about it
            } else {
               // You can use the stack
               stack_push(st, thing0); // This is a non-virtual call
               Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
               st->my_type.push(st, thing2); // This is a virtual call
            }
            

            我没有做析构函数或删除,但它遵循相同的模式。

            this_is_here_as_an_example_only 就像一个静态类变量——在一个类型的所有实例之间共享。所有方法实际上都是静态的,除了有些采用 this *

            【讨论】:

            • @nategoose - st-&gt;my_type-&gt;push(st, thing2); 而不是 st-&gt;my_type.push(st, thing2);
            • @nategoose: OR struct stack_type my_type; 而不是 struct stack_type * my_type;
            • 我喜欢为类设置结构的概念。但是通用的Class 结构呢?这将使 OO C 比 C++更加动态。那个怎么样?顺便说一句,+1。
            【解决方案12】:

            有几种技术可以使用。最重要的是更多如何拆分项目。我们在项目中使用一个在 .h 文件中声明的接口,并在 .c 文件中使用该对象的实现。重要的部分是包含 .h 文件的所有模块仅将对象视为 void *,而 .c 文件是唯一知道结构内部的模块。

            以我们命名为 FOO 的类为例:

            在.h文件中

            #ifndef FOO_H_
            #define FOO_H_
            
            ...
             typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */
            
            /* Declaration of accessors, functions */
            FOO_type *FOO_new(void);
            void FOO_free(FOO_type *this);
            ...
            void FOO_dosomething(FOO_type *this, param ...):
            char *FOO_getName(FOO_type *this, etc);
            #endif
            

            C 实现文件将是这样的。

            #include <stdlib.h>
            ...
            #include "FOO.h"
            
            struct FOO_type {
                whatever...
            };
            
            
            FOO_type *FOO_new(void)
            {
                FOO_type *this = calloc(1, sizeof (FOO_type));
            
                ...
                FOO_dosomething(this, );
                return this;
            }
            

            因此,我将指向该模块每个函数的对象的指针显式地赋予。 C++ 编译器会隐式执行它,而在 C 中我们会显式地写出来。

            我确实在我的程序中使用了this,以确保我的程序不会在 C++ 中编译,并且它具有在我的语法高亮编辑器中呈现另一种颜色的优良属性。

            FOO_struct 的字段可以在一个模块中修改,而另一个模块甚至不需要重新编译即可使用。

            通过这种风格,我已经掌握了 OOP(数据封装)的大部分优势。通过使用函数指针,甚至可以很容易地实现继承之类的东西,但老实说,它真的很少有用。

            【讨论】:

            • 如果你在标题中使用 typedef struct FOO_type FOO_type 而不是 typedef 来 void,你可以获得类型检查的额外好处,同时仍然不会暴露你的结构。
            【解决方案13】:

            当然,它不会像使用具有内置支持的语言那么漂亮。我什至写过“面向对象的汇编程序”。

            【讨论】:

              【解决方案14】:

              动物和狗的简单示例:您反映了 C++ 的 vtable 机制(基本上无论如何)。您还将分配和实例化(Animal_Alloc,Animal_New)分开,因此我们不会多次调用 malloc()。我们还必须明确地传递this 指针。

              如果您要执行非虚拟功能,那就太简单了。您只是不将它们添加到 vtable 和静态函数不需要 this 指针。多重继承通常需要多个 vtable 来解决歧义。

              此外,您应该能够使用 setjmp/longjmp 进行异常处理。

              struct Animal_Vtable{
                  typedef void (*Walk_Fun)(struct Animal *a_This);
                  typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);
              
                  Walk_Fun Walk;
                  Dtor_Fun Dtor;
              };
              
              struct Animal{
                  Animal_Vtable vtable;
              
                  char *Name;
              };
              
              struct Dog{
                  Animal_Vtable vtable;
              
                  char *Name; // Mirror member variables for easy access
                  char *Type;
              };
              
              void Animal_Walk(struct Animal *a_This){
                  printf("Animal (%s) walking\n", a_This->Name);
              }
              
              struct Animal* Animal_Dtor(struct Animal *a_This){
                  printf("animal::dtor\n");
                  return a_This;
              }
              
              Animal *Animal_Alloc(){
                  return (Animal*)malloc(sizeof(Animal));
              }
              
              Animal *Animal_New(Animal *a_Animal){
                  a_Animal->vtable.Walk = Animal_Walk;
                  a_Animal->vtable.Dtor = Animal_Dtor;
                  a_Animal->Name = "Anonymous";
                  return a_Animal;
              }
              
              void Animal_Free(Animal *a_This){
                  a_This->vtable.Dtor(a_This);
              
                  free(a_This);
              }
              
              void Dog_Walk(struct Dog *a_This){
                  printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
              }
              
              Dog* Dog_Dtor(struct Dog *a_This){
                  // Explicit call to parent destructor
                  Animal_Dtor((Animal*)a_This);
              
                  printf("dog::dtor\n");
              
                  return a_This;
              }
              
              Dog *Dog_Alloc(){
                  return (Dog*)malloc(sizeof(Dog));
              }
              
              Dog *Dog_New(Dog *a_Dog){
                  // Explict call to parent constructor
                  Animal_New((Animal*)a_Dog);
              
                  a_Dog->Type = "Dog type";
                  a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
                  a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;
              
                  return a_Dog;
              }
              
              int main(int argc, char **argv){
                  /*
                    Base class:
              
                      Animal *a_Animal = Animal_New(Animal_Alloc());
                  */
                  Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());
              
                  a_Animal->vtable.Walk(a_Animal);
              
                  Animal_Free(a_Animal);
              }
              

              附言。这是在 C++ 编译器上测试的,但它应该很容易在 C 编译器上工作。

              【讨论】:

              • typedefstruct 中在 C 中是不可能的。
              【解决方案15】:

              是的,你可以。在 C++ 或 Objective-C 出现之前,人们正在编写面向对象的 C。 C++ 和 Objective-C 都在某种程度上尝试采用 C 中使用的一些 OO 概念并将它们形式化为语言的一部分。

              这是一个非常简单的程序,它展示了如何制作看起来像/是方法调用的东西(有更好的方法可以做到这一点。这只是证明语言支持这些概念):

              #include<stdio.h>
              
              struct foobarbaz{
                  int one;
                  int two;
                  int three;
                  int (*exampleMethod)(int, int);
              };
              
              int addTwoNumbers(int a, int b){
                  return a+b;
              }
              
              int main()
              {
                  // Define the function pointer
                  int (*pointerToFunction)(int, int) = addTwoNumbers;
              
                  // Let's make sure we can call the pointer
                  int test = (*pointerToFunction)(12,12);
                  printf ("test: %u \n",  test);
              
                  // Now, define an instance of our struct
                  // and add some default values.
                  struct foobarbaz fbb;
                  fbb.one   = 1;
                  fbb.two   = 2;
                  fbb.three = 3;
              
                  // Now add a "method"
                  fbb.exampleMethod = addTwoNumbers;
              
                  // Try calling the method
                  int test2 = fbb.exampleMethod(13,36);
                  printf ("test2: %u \n",  test2);
              
                  printf("\nDone\n");
                  return 0;
              }
              

              【讨论】:

                【解决方案16】:

                当然可以。这就是GObject,所有GTK+GNOME 所基于的框架所做的。

                【讨论】:

                • 这种方法的优缺点是什么? IE。使用 C++ 编写它要容易得多。
                • @kravemir 好吧,C++ 的可移植性不如 C,而且将 C++ 链接到可能由不同 C++ 编译器编译的代码有点困难。但是,是的,用 C++ 编写类更容易,尽管 GObject 也不是那么难(假设你不介意一些样板)。
                【解决方案17】:

                是的。事实上,Axel Schreiner 免费提供了his book“ANSI-C 中的面向对象编程”,其中涵盖了相当全面的主题。

                【讨论】:

                • 虽然本书中的概念是固定的,但你会失去类型安全性。
                • 在我们所知的设计模式之前,是被称为“面向对象”的设计模式;与垃圾收集等相同。它们现在已经根深蒂固,我们往往会忘记,当它们最初被设计时,它与我们今天所认为的设计模式大致相同
                • 您可以直接从作者的网站:cs.rit.edu/~ats/books/ooc.pdf 其他同作者的论文:cs.rit.edu/~ats/books/index.html
                • 正确的集合(书籍 + 源代码示例)可从此 rit.edu 索引 Object oriented programming with ANSI-C 获得
                • 这本书是否经过同行评审?第一页第一段第一句有错别字。
                【解决方案18】:

                我参加聚会有点晚了,但我想分享一下我在这个话题上的经验:这些天我使用嵌入式东西,我唯一(可靠的)编译器是 C,所以我想申请我用 C 编写的嵌入式项目中的面向对象方法。

                到目前为止,我看到的大多数解决方案都大量使用类型转换,因此我们失去了类型安全性:如果您犯了错误,编译器将无济于事。这是完全不能接受的。

                我的要求:

                • 尽可能避免类型转换,这样我们就不会失去类型安全性;
                • 多态性:我们应该能够使用虚拟方法,并且类的用户不应该知道某些特定方法是否是虚拟的;
                • 多重继承:我不经常使用它,但有时我真的希望某个类实现多个接口(或扩展多个超类)。

                我在这篇文章中详细解释了我的方法:Object-oriented programming in C;此外,还有一个用于为基类和派生类自动生成样板代码的实用程序。

                【讨论】:

                  【解决方案19】:

                  请参阅http://slkpg.byethost7.com/instance.html 以了解 C 中 OOP 的另一个转折点。它强调仅使用本机 C 的可重入实例数据。多重继承是使用函数包装器手动完成的。保持类型安全。这是一个小样本:

                  typedef struct _peeker
                  {
                      log_t     *log;
                      symbols_t *sym;
                      scanner_t  scan;            // inherited instance
                      peek_t     pk;
                      int        trace;
                  
                      void    (*push) ( SELF *d, symbol_t *symbol );
                      short   (*peek) ( SELF *d, int level );
                      short   (*get)  ( SELF *d );
                      int     (*get_line_number) ( SELF *d );
                  
                  } peeker_t, SlkToken;
                  
                  #define push(self,a)            (*self).push(self, a)
                  #define peek(self,a)            (*self).peek(self, a)
                  #define get(self)               (*self).get(self)
                  #define get_line_number(self)   (*self).get_line_number(self)
                  
                  INSTANCE_METHOD
                  int
                  (get_line_number) ( peeker_t *d )
                  {
                      return  d->scan.line_number;
                  }
                  
                  PUBLIC
                  void
                  InitializePeeker ( peeker_t  *peeker,
                                     int        trace,
                                     symbols_t *symbols,
                                     log_t     *log,
                                     list_t    *list )
                  {
                      InitializeScanner ( &peeker->scan, trace, symbols, log, list );
                      peeker->log = log;
                      peeker->sym = symbols;
                      peeker->pk.current = peeker->pk.buffer;
                      peeker->pk.count = 0;
                      peeker->trace = trace;
                  
                      peeker->get_line_number = get_line_number;
                      peeker->push = push;
                      peeker->get = get;
                      peeker->peek = peek;
                  }
                  

                  【讨论】:

                    【解决方案20】:

                    OOP 只是一种将数据置于程序中比代码更重要的范式。 OOP 不是一种语言。所以,就像纯 C 是一门简单的语言一样,纯 C 中的 OOP 也很简单。

                    【讨论】:

                      【解决方案21】:

                      我建议使用 Objective-C,它是 C 的超集。

                      虽然 Objective-C 已有 30 年的历史,但它允许编写优雅的代码。

                      http://en.wikipedia.org/wiki/Objective-C

                      【讨论】:

                      • 在这种情况下,我会推荐 C++,因为它实际上是面向对象的......
                      • 这不是答案。但无论如何,@YoYoYonnY:我不使用Objective-C,而是使用C++,但是没有基础的cmets没有用处,你也没有提供。为什么你声称 Objective-C 没有“真正面向对象......”?为什么 C++ 在 Objective-C 失败的地方会成功?有趣的是,Objective-C 的名字中确实有 Object 这个词,而 C++ 将自己定位为一种多范式语言,而不是 OOP 语言(即主要不是 OOP,在一些相当极端的人的观点根本不是 OOP)......所以你确定你没有把这些名字弄错吗?
                      【解决方案22】:

                      哪些文章或书籍适合在 C 中使用 OOP 概念?

                      Dave Hanson 的C Interfaces and Implementations 在封装和命名方面非常出色,并且非常擅长使用函数指针。 Dave 不会尝试模拟继承。

                      【讨论】:

                        【解决方案23】:

                        我相信,除了本身有用之外,在 C 中实现 OOP 也是学习 OOP 并了解其内部工作原理的绝佳方式。许多程序员的经验表明,要有效和自信地使用一项技术,程序员必须了解底层概念最终是如何实现的。在 C 中模拟类、继承和多态性正是在教导这一点。

                        为了回答最初的问题,这里有一些资源可以教你如何在 C 中进行 OOP:

                        EmbeddedGurus.com 博客文章“C 中基于对象的编程”展示了如何在可移植 C 中实现类和单一继承: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c/

                        应用笔记““C+”—C 中的面向对象编程”展示了如何使用预处理器宏在 C 中实现类、单继承和后期绑定(多态性): http://www.state-machine.com/resources/cplus_3.0_manual.pdf,示例代码可从http://www.state-machine.com/resources/cplus_3.0.zip获得

                        【讨论】:

                        【解决方案24】:

                        我认为首先要说的是(至少恕我直言)C 的函数指针实现真的很难使用。我会跳过一大堆的箍来避免函数指针......

                        就是说,我觉得别人说的还不错。你有结构,你有模块,而不是foo-&gt;method(a,b,c),你最终得到method(foo,a,b,c) 如果你有多个类型的“方法”方法,那么你可以在它前面加上类型,所以FOO_method(foo,a,b,c),如其他人说...通过充分利用 .h 文件,您可以获得私有和公共等。

                        现在,这项技术无法为您提供一些东西。它不会为您提供私有数据字段。我认为,这与意志力和良好的编码卫生有关......而且,没有一种简单的方法可以用它进行继承。

                        至少这些是容易的部分……其余的,我认为是 90/10 的情况。 10%的收益需要90%的工作……

                        【讨论】:

                        • 单继承(虽然没有多态性)也可以很容易地用这种技术实现。您需要做的就是将超类作为子类的第一个成员嵌入。根据 C 标准,整个结构必须与第一个成员对齐,因此为 foo 设计的任何方法 (method(foo, a, b, c)) 将在传递 bar 指针时起作用(bar 是富)。这是继承。
                        • @miro。哇。那是......这是一个严重的kludge在那里......
                        • 不是真的......它被广泛使用...... glib 几乎是建立在这个想法上的,Linux 内核(广泛地),它本质上与使用对象时发生的事情相同面向语言,编译器使用与上面描述的相同的对象布局(尽管多重继承使这稍微复杂化,其中一个超类必须与对象开始有偏移)
                        • 您可以通过将指针传递给已声明但未在实现细节之外的任何地方定义的事物来获取私有数据。所以foo 参数是struct foo *,没有人需要知道其中的真正含义。
                        • @MiroSamek 当然,它会工作,但指针必须显式转换为函数期望的类型。 C 不正式识别继承关系,因此不会像 C++ 或其他语言那样隐式转换指针/引用。
                        【解决方案25】:

                        您可能会发现查看 Apple 的核心基础 API 文档很有帮助。它是一个纯 C API,但许多类型都桥接到了 Objective-C 对象等价物。

                        您可能还会发现了解 Objective-C 本身的设计很有帮助。它与 C++ 有点不同,因为对象系统是根据 C 函数定义的,例如objc_msg_send 调用对象的方法。编译器将方括号语法转换为这些函数调用,因此您不必知道它,但考虑到您的问题,您可能会发现了解它的底层工作原理很有用。

                        【讨论】:

                          【解决方案26】:

                          查看GObject。它意味着在 C 中是面向对象的,并且是您正在寻找的一种实现。但是,如果您真的想要 OO,请使用 C++ 或其他一些 OOP 语言。如果您习惯于处理 OO 语言,有时 GObject 可能真的很难使用,但像任何东西一样,您会习惯约定和流程。

                          【讨论】:

                            【解决方案27】:

                            如果您确信 OOP 方法更适合您要解决的问题,为什么还要尝试使用非 OOP 语言来解决它?似乎您使用了错误的工具来完成这项工作。使用 C++ 或其他一些面向对象的 C 变体语言。

                            如果您是因为开始在一个已经存在的用 C 编写的大型项目上编写代码而询问,那么您不应该尝试将您自己(或其他任何人)的 OOP 范例强加到项目的基础架构中。遵循项目中已经存在的指导方针。一般来说,干净的 API 和独立的库和模块将大大有助于实现干净的 OOP-ish 设计。

                            如果,在这一切之后,您真的打算使用 OOP C,请阅读 this (PDF)。

                            【讨论】:

                            • 没有真正回答这个问题...
                            • @Brian,PDF 的链接似乎可以直接回答这个问题,尽管我没有时间自己检查。
                            • 指向 PDF 的链接似乎是关于该主题的整本教科书......一个漂亮的证明,但它不适合页边距......
                            • 是的,回答问题。询问如何以特定方式使用语言是完全有效的。没有要求就其他语言发表意见....
                            • @Brian & Tim Ring:这个问题是针对某个主题提出的书籍推荐;我给了他一个链接,指向一本专门讨论这个主题的。我还就为什么解决问题的方法可能不是最优的问题发表了自己的看法(我认为这里的许多人似乎都同意,基于投票和其他 cmets/answers)。您对改进我的答案有什么建议吗?
                            【解决方案28】:

                            在 Jim Larson 的 1996 年演讲中,Section 312 Programming Lunchtime Seminar 上有一个使用 C 进行继承的示例:High and Low-Level C

                            【讨论】:

                              【解决方案29】:

                              面向对象的 C,可以做到,我在韩国的生产环境中看到过这种类型的代码,这是我多年来见过的最可怕的怪物(就像去年(2007 年)我看到的代码)。 所以是的,它可以做到,是的,人们以前做过,即使在这个时代仍然这样做。但我推荐 C++ 或 Objective-C,它们都是从 C 诞生的语言,目的是为面向对象提供不同的范式。

                              【讨论】:

                                【解决方案30】:

                                是的,但我从未见过有人尝试用 C 实现任何类型的多态性。

                                【讨论】:

                                • 你需要多看看 :) 例如,微软的 Direct X 有一个多态 C 接口。
                                • 例如查看 linux 内核实现。这是 C 语言中非常普遍和广泛使用的做法。
                                • glib 也是多态的,或者可以以允许多态的方式使用(就像 C++ 你必须明确说明哪些调用是虚拟的)
                                • 多态性在 C 中并不罕见,另一方面,多重继承是。
                                猜你喜欢
                                • 2011-01-25
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2015-04-28
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多