79

有没有办法用C编程语言编写类似 OO 的代码?


也可以看看:

通过搜索“[c] oo”找到。

4

11 回答 11

65

第一个 C++ 编译器(“C with classes”)实际上会生成 C 代码,所以这绝对是可行的。

基本上,您的基类是一个结构;派生结构必须在第一个位置包含基结构,因此指向“派生”结构的指针也将是指向基结构的有效指针。

typedef struct {
   data member_x;
} base;

typedef struct {
   struct base;
   data member_y;
} derived;

void function_on_base(struct base * a); // here I can pass both pointers to derived and to base

void function_on_derived(struct derived * b); // here I must pass a pointer to the derived class

函数可以作为函数指针作为结构的一部分,因此像 p->call(p) 这样的语法成为可能,但您仍然必须显式地将指向结构的指针传递给函数本身。

于 2009-02-07T16:27:08.477 回答
55

常见的方法是使用指向函数的指针来定义结构。这定义了可以在任何类型上调用的“方法”。然后子类型在这个公共结构中设置自己的函数,并返回它。

例如,在linux内核中,有struct:

struct inode_operations {
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    struct dentry * (*lookup) (struct inode *,struct dentry *, 
                               struct nameidata *);
    ...
};

create然后,每种注册类型的文件系统都会为、lookup和其余功能注册自己的功能。其余代码可以使用通用 inode_operations:

struct inode_operations   *i_op;
i_op -> create(...);
于 2009-02-07T16:24:29.403 回答
32

C++ 与 C 相差不远。

类是具有隐藏指针的结构,该指针指向称为 VTable 的函数指针表。Vtable 本身是静态的。当类型指向具有相同结构但指针指向其他实现的 Vtable 时,您将获得多态性。

建议将调用逻辑封装在以结构体为参数的函数中,避免代码混乱。

您还应该在函数中封装结构实例化和初始化(这相当于 C++ 构造函数)和删除(C++ 中的析构函数)。无论如何,这些都是很好的做法。

typedef struct
{
   int (*SomeFunction)(TheClass* this, int i);
   void (*OtherFunction)(TheClass* this, char* c);
} VTable;

typedef struct
{
   VTable* pVTable;
   int member;

} TheClass;

调用方法:

int CallSomeFunction(TheClass* this, int i)
{
  (this->pVTable->SomeFunction)(this, i);
}
于 2009-02-07T16:35:58.270 回答
18

我查看了其他人的答案并想出了这个:

#include <stdio.h>

typedef struct
{
    int (*get)(void* this);
    void (*set)(void* this, int i);
    int member;

} TheClass;

int Get(void* this)
{
    TheClass* This = (TheClass*)this;
    return This->member;
}

void Set(void* this, int i)
{
    TheClass* This = (TheClass*)this;
    This->member = i;
}

void init(TheClass* this)
{
    this->get = &Get;
    this->set = &Set;
}

int main(int argc, char **argv)
{
    TheClass name;
    init(&name);
    (name.set)(&name, 10);
    printf("%d\n", (name.get)(&name));
    return 0;
}

我希望这能回答一些问题。

于 2012-09-24T09:05:23.043 回答
12

VPRI的 Ian Piumarta 和 Alessandro Warth的文章Open Reusable Object Models的附录 B 是GNU C 中对象模型的实现,大约 140 行代码。这是一本引人入胜的读物!

这是宏的未缓存版本,它使用 C 的 GNU 扩展(语句表达式)向对象发送消息:

struct object;

typedef struct object *oop; 
typedef oop *(*method_t)(oop receiver, ...);

//...

#define send(RCV, MSG, ARGS...) ({ \ 
    oop r = (oop)(RCV); \ 
    method_t method = _bind(r, (MSG)); \ 
    method(r, ##ARGS); \ 
}) 

在同一个文档中,查看object、和结构,以及和函数。vtablevtable_delegatedsymbol_bindvtable_lookup

干杯!

于 2009-02-07T22:00:15.107 回答
2

我通常喜欢做的是将结构包装在另一个包含有关包装类的元信息的结构中,然后构建访问者,如作用于通用结构的函数列表。这种方法的优点是您不需要修改现有结构,并且可以为任何结构子集创建访问者。

举个通常的例子:

typedef struct {
    char call[7] = "MIAO!\n";
} Cat;
    
typedef struct {
    char call[6] = "BAU!\n";
} Dog;

我们可以将这两个结构包装在这个新结构中:

typedef struct {
    const void * animal;
    AnimalType type;
} Animal;

类型可以是简单的 int 但我们不要偷懒:

typedef enum  {
    ANIMAL_CAT = 0,
    ANIMAL_DOG,
    ANIMAL_COUNT
} AnimalType;

有一些包装功能会很好:

Animal catAsAnimal(const Cat * c) {
    return (Animal){(void *)c, ANIMAL_CAT};
}

Animal dogAsAnimal(const Dog * d) {
    return (Animal){(void *)d, ANIMAL_DOG};
}

现在我们可以定义我们的“访问者”:

void catCall ( Animal a ) {
    Cat * c = (Cat *)a.animal;
    printf(c->call);
}

void dogCall ( Animal a ) {
    Dog * d = (Dog *)a.animal;
    printf(d->call);
}

void (*animalCalls[ANIMAL_COUNT])(Animal)={&catCall, &dogCall};

那么实际使用情况将是:

Cat cat;
Dog dog;

Animal animals[2];
animals[0] = catAsAnimal(&cat);
animals[1] = dogAsAnimal(&dog);

for (int i = 0; i < 2; i++) {
    Animal a = animals[i];
    animalCalls[a.type](a);
}

这种方法的缺点是每次要将结构用作泛型类型时都必须包装结构。

于 2020-08-18T17:59:56.553 回答
1

文件函数 fopen、fclose、fread 是 C 中 OO 代码的示例。它们不是类中的私有数据,而是用于封装数据的 FILE 结构,C 函数充当成员类函数。 http://www.amazon.com/File-Structures-Object-Oriented-Approach-C/dp/0201874016

于 2009-02-07T16:44:13.090 回答
1
#include <stdio.h>

typedef struct {
    int  x;
    int z;
} base;

typedef struct {
    base;
    int y;
    int x;
} derived;

void function_on_base( base * a) // here I can pass both pointers to derived and to base
{
    printf("Class base [%d]\n",a->x);
    printf("Class base [%d]\n",a->z);
}
void function_on_derived( derived * b) // here I must pass a pointer to the derived class
{
    printf("Class derived [%d]\n",b->y);
    printf("Class derived [%d]\n",b->x);
}

int main()
{
    derived d;
    base b;
    printf("Teste de poliformismo\n");

    b.x = 2;
    d.y = 1;
    b.z = 3;
    d.x = 4;
    function_on_base(&b);
    function_on_base(&d);
    function_on_derived(&b);
    function_on_derived(&d);
    return 0;
}

输出是:

Class base [3]
Class base [1]
Class base [4]
Class derived [2]
Class derived [3]
Class derived [1]
Class derived [4]

所以它有效,它是一个多态代码。

UncleZeiv 一开始就解释过。

于 2016-04-08T22:36:15.007 回答
0

来自维基百科:在编程语言和类型论中,多态性(来自希腊语 πολύς,polys,“many, much”和 μορφή,morphē,“form, shape”)是为不同类型的实体提供单一接口。

所以我想说在 C 中实现它的唯一方法是使用可变参数和一些(半)自动类型信息管理。例如,在 C++ 中,您可以编写(对不起,琐碎):

void add( int& result, int a1, int a2 );
void add( float& result, float a1, float a2 );
void add( double& result, double a1, double a2 );

在 C 中,在其他解决方案中,您能做的最好的事情是这样的:

int int_add( int a1, int a2 );
float float_add( float a1, fload a2 );
double double_add( double a1, double a2 );

void add( int typeinfo, void* result, ... );

然后你需要:

  1. 用枚举/宏实现“typeinfo”
  2. 用 stdarg.h 东西实现后一个功能
  3. 告别 C 静态类型检查

我几乎可以肯定,多态性的任何其他实现都应该看起来很像这个。相反,上面的答案似乎试图解决继承而不是多态!

于 2014-10-21T20:32:06.390 回答
0

为了在 C 中构建 OO 功能,您可以查看以前的答案。

但是,(正如在其他问题中已经提出的那样)如果您想通过 C 语言中的示例来了解多态性是什么。也许我错了,但我想不出像 C 指针算术这样容易理解的东西。在我看来,指针算术在 C 中本质上是多态的。在以下示例中,相同的函数(OO 中的方法),即加法 (+),将根据输入结构的属性产生不同的行为。

例子:

double a*;
char str*;

a=(double*)malloc(2*sizeof(double));
str=(char*)malloc(2*sizeof(char)); 

a=a+2; // make the pointer a, point 2*8 bytes ahead.

str=str+2; // make the pointer str, point 2*1 bytes ahead.

免责声明:我是 C 的新手,非常期待得到纠正并从其他用户的评论中学习,甚至完全删除这个答案,如果它是错误的。非常感谢,

于 2017-03-12T17:19:20.853 回答
0

简单函数重载的一个非常粗略的例子,可以使用可变参数宏来实现。

#include <stdio.h>
#include <stdlib.h>

#define SCOPE_EXIT(X) __attribute__((cleanup (X)))

struct A
{
  int a;
};

struct B
{
 int a, b;
};

typedef struct A * A_id;
typedef struct B * B_id;


A_id make_A()
{
   return (A_id)malloc(sizeof(struct A));
}

void destroy_A(A_id * ptr)
{
   free(*ptr);
   *ptr = 0;
}

B_id make_B()
{
  return (B_id)malloc(sizeof(struct B));
}

void destroy_B(B_id * ptr)
{
  free(*ptr);
  *ptr = 0;
}

void print_a(A_id ptr)
{
  printf("print_a\n"); 
}
void print_b(B_id ptr)
{
  printf("print_b\n"); 
}

#define print(X) _Generic((X),\
          A_id : print_a, \
          B_id : print_b\
)(X)

int main()
{
  A_id aa SCOPE_EXIT(destroy_A) = make_A();
  print(aa);

  B_id bb SCOPE_EXIT(destroy_B) = make_B();
  print(bb);
  return 0;
}
于 2021-08-27T09:47:12.727 回答