如何在C中实现类?

时间:2022-09-02 09:31:32

Assuming I have to use C (no C++ or object oriented compilers) and I don't have dynamic memory allocation, what are some techniques I can use to implement a class, or a good approximation of a class? Is it always a good idea to isolate the "class" to a separate file? Assume that we can preallocate the memory by assuming a fixed number of instances, or even defining the reference to each object as a constant before compile time. Feel free to make assumptions about which OOP concept I will need to implement (it will vary) and suggest the best method for each.

假设我必须使用C(没有c++或面向对象的编译器),而且我没有动态内存分配,那么我可以使用什么技术来实现一个类,或者很好地近似一个类?将“类”隔离在一个单独的文件中是否总是一个好主意?假设我们可以预先分配内存,假设有固定数量的实例,或者甚至在编译之前将每个对象的引用定义为常量。请随意假设我需要实现哪个OOP概念(它会有所不同),并为每个概念提出最佳的方法。

Restrictions:

限制:

  • I have to use C and not an OOP because I'm writing code for an embedded system, and the compiler and preexisting code base is in C.
  • 我必须使用C而不是OOP,因为我正在为嵌入式系统编写代码,而编译器和现有的代码库是在C中。
  • There is no dynamic memory allocation because we don't have enough memory to reasonably assume we won't run out if we start dynamically allocating it.
  • 没有动态内存分配,因为我们没有足够的内存来合理地假设,如果我们开始动态分配内存,就不会用完。
  • The compilers we work with have no problems with function pointers
  • 我们使用的编译器在函数指针上没有问题

19 个解决方案

#1


69  

That depends on the exact "object-oriented" feature-set you want to have. If you need stuff like overloading and/or virtual methods, you probably need to include function pointers in structures:

这取决于您想要的“面向对象”特性集。如果您需要重载和/或虚拟方法之类的东西,您可能需要在结构中包含函数指针:

typedef struct {
  float (*computeArea)(const ShapeClass *shape);
} ShapeClass;

float shape_computeArea(const ShapeClass *shape)
{
  return shape->computeArea(shape);
}

This would let you implement a class, by "inheriting" the base class, and implementing a suitable function:

通过“继承”基类并实现合适的函数,可以实现类:

typedef struct {
  ShapeClass shape;
  float width, height;
} RectangleClass;

static float rectangle_computeArea(const ShapeClass *shape)
{
  const RectangleClass *rect = (const RectangleClass *) shape;
  return rect->width * rect->height;
}

This of course requires you to also implement a constructor, that makes sure the function pointer is properly set up. Normally you'd dynamically allocate memory for the instance, but you can let the caller do that, too:

当然,这也需要您实现一个构造函数,以确保函数指针被正确设置。通常你会动态地为实例分配内存,但是你也可以让调用者这样做:

void rectangle_new(RectangleClass *rect)
{
  rect->width = rect->height = 0.f;
  rect->shape.computeArea = rectangle_computeArea;
}

If you want several different constructors, you will have to "decorate" the function names, you can't have more than one rectangle_new() function:

如果你想要几个不同的构造函数,你必须“修饰”函数名,你不能有一个以上的rectangle_new()函数:

void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
  rectangle_new(rect);
  rect->width = width;
  rect->height = height;
}

Here's a basic example showing usage:

下面是一个展示用法的基本示例:

int main(void)
{
  RectangleClass r1;

  rectangle_new_with_lengths(&r1, 4.f, 5.f);
  printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1));
  return 0;
}

I hope this gives you some ideas, at least. For a successful and rich object-oriented framework in C, look into glib's GObject library.

我希望这至少能给你一些建议。要获得C语言中成功而丰富的面向对象框架,请查看glib的GObject库。

Also note that there's no explicit "class" being modelled above, each object has its own method pointers which is a bit more flexible than you'd typically find in C++. Also, it costs memory. You could get away from that by stuffing the method pointers in a class structure, and invent a way for each object instance to reference a class.

还要注意的是,上面没有显式的“类”,每个对象都有它自己的方法指针,它比c++中通常发现的更灵活一点。同时,成本的记忆。您可以通过在类结构中填充方法指针来摆脱它,并为每个对象实例发明一种方法来引用类。

#2


21  

I had to do it once too for a homework. I followed this approach:

我也得做一次作业。我跟着这个方法:

  1. Define your data members in a struct.
  2. 在结构中定义数据成员。
  3. Define your function members that take a pointer to your struct as first argument.
  4. 定义以指向结构体的指针作为第一个参数的函数成员。
  5. Do these in one header & one c. Header for struct definition & function declarations, c for implementations.
  6. 在一个header和一个c. header中完成这些,用于结构定义和函数声明,c用于实现。

A simple example would be this:

一个简单的例子就是:

/// Queue.h
struct Queue
{
    /// members
}
typedef struct Queue Queue;

void push(Queue* q, int element);
void pop(Queue* q);
// etc.
/// 

#3


11  

If you only want one class, use an array of structs as the "objects" data and pass pointers to them to the "member" functions. You can use typedef struct _whatever Whatever before declaring struct _whatever to hide the implementation from client code. There's no difference between such an "object" and the C standard library FILE object.

如果您只想要一个类,请使用一个结构体数组作为“对象”数据,并将指针传递给“成员”函数。在声明struct _whatever之前,您可以使用typedef struct _whatever在客户机代码中隐藏实现。这样的“对象”和C标准库文件对象之间没有区别。

If you want more than one class with inheritance and virtual functions, then it's common to have pointers to the functions as members of the struct, or a shared pointer to a table of virtual functions. The GObject library uses both this and the typedef trick, and is widely used.

如果您希望有多个具有继承和虚函数的类,那么通常会有指向函数的指针作为结构体的成员,或者指向虚函数表的共享指针。GObject库同时使用这个技巧和typedef技巧,并被广泛使用。

There's also a book on techniques for this available online - Object Oriented Programming with ANSI C.

还有一本书是关于如何使用ANSI C进行面向对象的在线编程。

#4


7  

you can take a look at GOBject. it's an OS library that give you a verbose way to do an object.

你可以看看GOBject。它是一个操作系统库,提供了一个详细的方法来处理对象。

http://library.gnome.org/devel/gobject/stable/

http://library.gnome.org/devel/gobject/stable/

#5


6  

Miro Samek developed an object-oriented C framework for his state machine framework: http://sourceforge.net/projects/qpc/. And he also wrote a book about it: http://www.state-machine.com/psicc2/.

Miro Samek为他的状态机框架开发了一个面向对象的C框架:http://sourceforge.net/projects/qpc/。他还为此写了一本书:http://www.state-machine.com/psicc2/。

#6


6  

C Interfaces and Implementations: Techniques for Creating Reusable Software, David R. Hanson

C接口和实现:创建可重用软件的技术,David R. Hanson

http://www.informit.com/store/product.aspx?isbn=0201498413

http://www.informit.com/store/product.aspx?isbn=0201498413

This book does an excellent job of covering your question. It's in the Addison Wesley Professional Computing series.

这本书很好地介绍了你的问题。这是Addison Wesley职业计算丛书。

The basic paradigm is something like this:

基本的范例是这样的:

/* for data structure foo */

FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);

#7


4  

Use a struct to simulate the data members of a class. In terms of method scope you can simulate private methods by placing the private function prototypes in the .c file and the public functions in the .h file.

使用struct来模拟类的数据成员。就方法范围而言,您可以通过在.c文件中放置私有函数原型和在.h文件中放置公共函数来模拟私有方法。

#8


4  

I will give a simple example of how OOP should be done in C. I realise this thead is from 2009 but would like to add this anyway.

我将给出一个如何在c中执行OOP的简单示例。

/// Object.h
typedef struct Object {
    uuid_t uuid;
} Object;

int Object_init(Object *self);
uuid_t Object_get_uuid(Object *self);
int Object_clean(Object *self);

/// Person.h
typedef struct Person {
    Object obj;
    char *name;
} Person;

int Person_init(Person *self, char *name);
int Person_greet(Person *self);
int Person_clean(Person *self);

/// Object.c
#include "object.h"

int Object_init(Object *self)
{
    self->uuid = uuid_new();

    return 0;
}
uuid_t Object_get_uuid(Object *self)
{ // Don't actually create getters in C...
    return self->uuid;
}
int Object_clean(Object *self)
{
    uuid_free(self->uuid);

    return 0;
}

/// Person.c
#include "person.h"

int Person_init(Person *self, char *name)
{
    Object_init(&self->obj); // Or just Object_init(&self);
    self->name = strdup(name);

    return 0;
}
int Person_greet(Person *self)
{
    printf("Hello, %s", self->name);

    return 0;
}
int Person_clean(Person *self)
{
    free(self->name);
    Object_clean(self);

    return 0;
}

/// main.c
int main(void)
{
    Person p;

    Person_init(&p, "John");
    Person_greet(&p);
    Object_get_uuid(&p); // Inherited function
    Person_clean(&p);

    return 0;
}

The basic concept involves placing the 'inherited class' at the top of the struct. This way, accessing the first 4 bytes in the struct also accesses the first 4 bytes in the 'inherited class' (Asuming non-crazy optimalisations). Now, when the pointer of the struct is cast to the 'inherited class', the 'inherited class' can access the 'inherited values' in the same way it would access it's members normally.

基本概念包括将“继承类”放在结构体的顶部。通过这种方式,访问结构中的前4个字节也可以访问“继承类”中的前4个字节(非疯狂的优化)。现在,当结构体的指针被转换为“继承类”时,“继承类”可以访问“继承值”,其方式与它通常访问成员的方式相同。

This and some naming conventions for constructors, destructors, allocation and deallocarion functions (I recommend init, clean, new, free) will get you a long way.

对于构造函数、析构函数、分配函数和deallocarion函数(我推荐init、clean、new、free),这种命名约定和一些命名约定将使您受益匪浅。

As for Virtual functions, use function pointers in the struct, possibly with Class_func(...); wrapper too. As for (simple) templates, add a size_t parameter to determine size, require a void* pointer, or require a 'class' type with just the functionality you care about. (e.g. int GetUUID(Object *self); GetUUID(&p);)

对于虚函数,可以使用struct中的函数指针,可能是Class_func(…);包装器。至于(简单的)模板,添加size_t参数来确定大小,需要一个void*指针,或者需要一个“类”类型,只有您关心的功能。(例如int GetUUID(对象*自我);GetUUID(拟定);)

#9


3  

In your case the good approximation of the class could be the an ADT. But still it won't be the same.

在你的例子中,类的好近似可以是ADT。但还是不一样。

#10


3  

My strategy is:

我的策略是:

  • Define all code for the class in a separate file
  • 在单独的文件中定义类的所有代码
  • Define all interfaces for the class in a separate header file
  • 在单独的头文件中定义类的所有接口
  • All member functions take a "ClassHandle" which stands in for the instance name (instead of o.foo(), call foo(oHandle)
  • 所有成员函数都有一个代表实例名的“ClassHandle”(而不是o.foo(),调用foo(oHandle)
  • The constructor is replaced with a function void ClassInit(ClassHandle h, int x, int y,...) OR ClassHandle ClassInit(int x, int y,...) depending on the memory allocation strategy
  • 构造函数被替换为一个函数void ClassInit(ClassHandle h, int x, int y,…)或ClassHandle ClassInit(int x, int y,…)取决于内存分配策略
  • All member variables are store as a member of a static struct in the class file, encapsulating it in the file, preventing outside files from accessing it
  • 所有成员变量都作为类文件中的静态结构的成员存储,并将其封装在文件中,防止外部文件访问它
  • The objects are stored in an array of the static struct above, with predefined handles (visible in the interface) or a fixed limit of objects that can be instantiated
  • 对象存储在上面静态结构的数组中,具有预定义的句柄(在接口中可见)或可实例化的对象的固定限制
  • If useful, the class can contain public functions that will loop through the array and call the functions of all the instantiated objects (RunAll() calls each Run(oHandle)
  • 如果有用,类可以包含公共函数,这些函数将循环遍历数组并调用所有实例化对象的函数(RunAll()调用每次运行(oHandle)
  • A Deinit(ClassHandle h) function frees the allocated memory (array index) in the dynamic allocation strategy
  • 函数的作用是:释放动态分配策略中分配的内存(数组索引)

Does anyone see any problems, holes, potential pitfalls or hidden benefits/drawbacks to either variation of this approach? If I am reinventing a design method (and I assume I must be), can you point me to the name of it?

有没有人发现这种方法的变化有任何问题、漏洞、潜在的隐患或潜在的好处/缺点?如果我正在重新发明一种设计方法(我想我肯定是),你能告诉我它的名字吗?

#11


3  

There's a very extensive book on the subject, which might be worth checking out:

关于这个主题有一本非常广泛的书,可能值得一读:

Object Oriented Programming in ANSI-C

面向对象的ANSI-C编程

#12


3  

Also see this answer and this one

也看这个答案和这个。

It is possible. It always seems like a good idea at the time but afterwards it becomes a maintenance nightmare. Your code become littered with pieces of code tying everything together. A new programmer will have lots of problems reading and understanding the code if you use function pointers since it will not be obvious what functions is called.

这是可能的。这在当时似乎是一个好主意,但后来却变成了维护的噩梦。您的代码将把所有东西都绑定在一起。如果使用函数指针,新的程序员将在读取和理解代码时遇到很多问题,因为函数的调用并不明显。

Data hiding with get/set functions is easy to implement in C but stop there. I have seen multiple attempts at this in the embedded environment and in the end it is always a maintenance problem.

带有get/set函数的数据隐藏在C语言中很容易实现,但仅限于此。我已经在嵌入式环境中看到过多次尝试,最终它总是一个维护问题。

Since you all ready have maintenance issues I would steer clear.

既然大家都准备好了维护问题,我就不赘述了。

#13


3  

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <uchar.h>

/**
 * Define Shape class
 */
typedef struct Shape Shape;
struct Shape {
    /**
     * Variables header...
     */
    double width, height;

    /**
     * Functions header...
     */
    double (*area)(Shape *shape);
};

/**
 * Functions
 */
double calc(Shape *shape) {
        return shape->width * shape->height;
}

/**
 * Constructor
 */
Shape _Shape() {
    Shape s;

    s.width = 1;
    s.height = 1;

    s.area = calc;

    return s;
}

/********************************************/

int main() {
    Shape s1 = _Shape();
    s1.width = 5.35;
    s1.height = 12.5462;

    printf("Hello World\n\n");

    printf("User.width = %f\n", s1.width);
    printf("User.height = %f\n", s1.height);
    printf("User.area = %f\n\n", s1.area(&s1));

    printf("Made with \xe2\x99\xa5 \n");

    return 0;
};

#14


3  

May be this online book could help you to solve your problem.

也许这本在线书可以帮助你解决你的问题。

#15


2  

My approach would be to move the struct and all primarily-associated functions to a separate source file(s) so that it can be used "portably".

我的方法是将struct和所有主相关的函数移动到一个单独的源文件中,这样就可以使用“portable”。

Depending on your compiler, you might be able to include functions into the struct, but that's a very compiler-specific extension, and has nothing to do with the last version of the standard I routinely used :)

根据编译器的不同,您可能可以将函数包含到结构体中,但这是一个非常特定于编译器的扩展,与我通常使用的标准的最后一个版本没有关系:)

#16


2  

The first c++ compiler actually was a preprocessor which translated the C++ code into C.

第一个c++编译器实际上是一个预处理器,它将c++代码翻译成c。

So it's very possible to have classes in C. You might try and dig up an old C++ preprocessor and see what kind of solutions it creates.

因此,在C语言中有类是很可能的,您可以尝试挖掘一个旧的c++预处理器,看看它创建了什么样的解决方案。

#17


1  

Do you want virtual methods?

你想要虚拟方法吗?

If not then you just define a set of function pointers in the struct itself. If you assign all the function pointers to standard C functions then you will be able to call functions from C in very similar syntax to how you would under C++.

如果不是,那么您只需在结构体本身中定义一组函数指针。如果你把所有的函数指针都赋给了标准的C函数,那么你就可以从C中调用函数,其语法与在c++下调用函数的语法非常相似。

If you want to have virtual methods it gets more complicated. Basically you will need to implement your own VTable to each struct and assign function pointers to the VTable depending on which function is called. You would then need a set of function pointers in the struct itself that in turn call the function pointer in the VTable. This is, essentially, what C++ does.

如果你想要虚拟方法,就会变得更复杂。基本上,您需要为每个结构实现自己的VTable,并根据调用哪个函数为VTable分配函数指针。然后需要结构本身的一组函数指针,这些指针反过来调用VTable中的函数指针。本质上,这就是c++所做的。

TBH though ... if you want the latter then you are probably better off just finding a C++ compiler you can use and re-compiling the project. I have never understood the obsession with C++ not being usable in embedded. I've used it many a time and it works is fast and doesn't have memory problems. Sure you have to be a bit more careful about what you do but its really not that complicated.

《虽然……如果您想要后者,那么您最好找到一个c++编译器,您可以使用它并重新编译项目。我从来都不理解c++在嵌入式中不能使用的困扰。我用过很多次,它运行得很快,没有内存问题。当然,你必须对你所做的事情更加小心,但这真的没有那么复杂。

#18


1  

GTK is built entirely on C and it uses many OOP concepts. I have read through the source code of GTK and it is pretty impressive, and definitely easier to read. The basic concept is that each "class" is simply a struct, and associated static functions. The static functions all accept the "instance" struct as a parameter, do whatever then need, and return results if necessary. For Example, you may have a function "GetPosition(CircleStruct obj)". The function would simply dig through the struct, extract the position numbers, probably build a new PositionStruct object, stick the x and y in the new PositionStruct, and return it. GTK even implements inheritance this way by embedding structs inside structs. pretty clever.

GTK完全构建在C之上,它使用了许多OOP概念。我已经阅读了GTK的源代码,它给人留下了深刻的印象,并且肯定更容易阅读。基本概念是,每个“类”都是一个结构体和相关的静态函数。静态函数都接受“实例”结构作为参数,然后执行所需的操作,并在必要时返回结果。例如,您可能有一个函数“GetPosition(CircleStruct obj)”。函数只需挖掘结构体,提取位置号,可能构建一个新的PositionStruct对象,在新的PositionStruct中插入x和y,然后返回它。GTK甚至通过在结构体中嵌入结构体来实现继承。很聪明。

#19


0  

C isn't an OOP language, as your rightly point out, so there's no built-in way to write a true class. You're best bet is to look at structs, and function pointers, these will let you build an approximation of a class. However, as C is procedural you might want to consider writing more C-like code (i.e. without trying to use classes).

C不是一种OOP语言,正如您正确地指出的那样,因此没有编写真正类的内置方法。最好的办法是看结构体和函数指针,它们可以让你建立类的近似值。但是,由于C是程序性的,您可能需要考虑编写更类似C的代码(即不尝试使用类)。

Also, if you can use C, you can probally use C++ and get classes.

另外,如果您可以使用C语言,您可以使用c++来获得类。

#1


69  

That depends on the exact "object-oriented" feature-set you want to have. If you need stuff like overloading and/or virtual methods, you probably need to include function pointers in structures:

这取决于您想要的“面向对象”特性集。如果您需要重载和/或虚拟方法之类的东西,您可能需要在结构中包含函数指针:

typedef struct {
  float (*computeArea)(const ShapeClass *shape);
} ShapeClass;

float shape_computeArea(const ShapeClass *shape)
{
  return shape->computeArea(shape);
}

This would let you implement a class, by "inheriting" the base class, and implementing a suitable function:

通过“继承”基类并实现合适的函数,可以实现类:

typedef struct {
  ShapeClass shape;
  float width, height;
} RectangleClass;

static float rectangle_computeArea(const ShapeClass *shape)
{
  const RectangleClass *rect = (const RectangleClass *) shape;
  return rect->width * rect->height;
}

This of course requires you to also implement a constructor, that makes sure the function pointer is properly set up. Normally you'd dynamically allocate memory for the instance, but you can let the caller do that, too:

当然,这也需要您实现一个构造函数,以确保函数指针被正确设置。通常你会动态地为实例分配内存,但是你也可以让调用者这样做:

void rectangle_new(RectangleClass *rect)
{
  rect->width = rect->height = 0.f;
  rect->shape.computeArea = rectangle_computeArea;
}

If you want several different constructors, you will have to "decorate" the function names, you can't have more than one rectangle_new() function:

如果你想要几个不同的构造函数,你必须“修饰”函数名,你不能有一个以上的rectangle_new()函数:

void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
  rectangle_new(rect);
  rect->width = width;
  rect->height = height;
}

Here's a basic example showing usage:

下面是一个展示用法的基本示例:

int main(void)
{
  RectangleClass r1;

  rectangle_new_with_lengths(&r1, 4.f, 5.f);
  printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1));
  return 0;
}

I hope this gives you some ideas, at least. For a successful and rich object-oriented framework in C, look into glib's GObject library.

我希望这至少能给你一些建议。要获得C语言中成功而丰富的面向对象框架,请查看glib的GObject库。

Also note that there's no explicit "class" being modelled above, each object has its own method pointers which is a bit more flexible than you'd typically find in C++. Also, it costs memory. You could get away from that by stuffing the method pointers in a class structure, and invent a way for each object instance to reference a class.

还要注意的是,上面没有显式的“类”,每个对象都有它自己的方法指针,它比c++中通常发现的更灵活一点。同时,成本的记忆。您可以通过在类结构中填充方法指针来摆脱它,并为每个对象实例发明一种方法来引用类。

#2


21  

I had to do it once too for a homework. I followed this approach:

我也得做一次作业。我跟着这个方法:

  1. Define your data members in a struct.
  2. 在结构中定义数据成员。
  3. Define your function members that take a pointer to your struct as first argument.
  4. 定义以指向结构体的指针作为第一个参数的函数成员。
  5. Do these in one header & one c. Header for struct definition & function declarations, c for implementations.
  6. 在一个header和一个c. header中完成这些,用于结构定义和函数声明,c用于实现。

A simple example would be this:

一个简单的例子就是:

/// Queue.h
struct Queue
{
    /// members
}
typedef struct Queue Queue;

void push(Queue* q, int element);
void pop(Queue* q);
// etc.
/// 

#3


11  

If you only want one class, use an array of structs as the "objects" data and pass pointers to them to the "member" functions. You can use typedef struct _whatever Whatever before declaring struct _whatever to hide the implementation from client code. There's no difference between such an "object" and the C standard library FILE object.

如果您只想要一个类,请使用一个结构体数组作为“对象”数据,并将指针传递给“成员”函数。在声明struct _whatever之前,您可以使用typedef struct _whatever在客户机代码中隐藏实现。这样的“对象”和C标准库文件对象之间没有区别。

If you want more than one class with inheritance and virtual functions, then it's common to have pointers to the functions as members of the struct, or a shared pointer to a table of virtual functions. The GObject library uses both this and the typedef trick, and is widely used.

如果您希望有多个具有继承和虚函数的类,那么通常会有指向函数的指针作为结构体的成员,或者指向虚函数表的共享指针。GObject库同时使用这个技巧和typedef技巧,并被广泛使用。

There's also a book on techniques for this available online - Object Oriented Programming with ANSI C.

还有一本书是关于如何使用ANSI C进行面向对象的在线编程。

#4


7  

you can take a look at GOBject. it's an OS library that give you a verbose way to do an object.

你可以看看GOBject。它是一个操作系统库,提供了一个详细的方法来处理对象。

http://library.gnome.org/devel/gobject/stable/

http://library.gnome.org/devel/gobject/stable/

#5


6  

Miro Samek developed an object-oriented C framework for his state machine framework: http://sourceforge.net/projects/qpc/. And he also wrote a book about it: http://www.state-machine.com/psicc2/.

Miro Samek为他的状态机框架开发了一个面向对象的C框架:http://sourceforge.net/projects/qpc/。他还为此写了一本书:http://www.state-machine.com/psicc2/。

#6


6  

C Interfaces and Implementations: Techniques for Creating Reusable Software, David R. Hanson

C接口和实现:创建可重用软件的技术,David R. Hanson

http://www.informit.com/store/product.aspx?isbn=0201498413

http://www.informit.com/store/product.aspx?isbn=0201498413

This book does an excellent job of covering your question. It's in the Addison Wesley Professional Computing series.

这本书很好地介绍了你的问题。这是Addison Wesley职业计算丛书。

The basic paradigm is something like this:

基本的范例是这样的:

/* for data structure foo */

FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);

#7


4  

Use a struct to simulate the data members of a class. In terms of method scope you can simulate private methods by placing the private function prototypes in the .c file and the public functions in the .h file.

使用struct来模拟类的数据成员。就方法范围而言,您可以通过在.c文件中放置私有函数原型和在.h文件中放置公共函数来模拟私有方法。

#8


4  

I will give a simple example of how OOP should be done in C. I realise this thead is from 2009 but would like to add this anyway.

我将给出一个如何在c中执行OOP的简单示例。

/// Object.h
typedef struct Object {
    uuid_t uuid;
} Object;

int Object_init(Object *self);
uuid_t Object_get_uuid(Object *self);
int Object_clean(Object *self);

/// Person.h
typedef struct Person {
    Object obj;
    char *name;
} Person;

int Person_init(Person *self, char *name);
int Person_greet(Person *self);
int Person_clean(Person *self);

/// Object.c
#include "object.h"

int Object_init(Object *self)
{
    self->uuid = uuid_new();

    return 0;
}
uuid_t Object_get_uuid(Object *self)
{ // Don't actually create getters in C...
    return self->uuid;
}
int Object_clean(Object *self)
{
    uuid_free(self->uuid);

    return 0;
}

/// Person.c
#include "person.h"

int Person_init(Person *self, char *name)
{
    Object_init(&self->obj); // Or just Object_init(&self);
    self->name = strdup(name);

    return 0;
}
int Person_greet(Person *self)
{
    printf("Hello, %s", self->name);

    return 0;
}
int Person_clean(Person *self)
{
    free(self->name);
    Object_clean(self);

    return 0;
}

/// main.c
int main(void)
{
    Person p;

    Person_init(&p, "John");
    Person_greet(&p);
    Object_get_uuid(&p); // Inherited function
    Person_clean(&p);

    return 0;
}

The basic concept involves placing the 'inherited class' at the top of the struct. This way, accessing the first 4 bytes in the struct also accesses the first 4 bytes in the 'inherited class' (Asuming non-crazy optimalisations). Now, when the pointer of the struct is cast to the 'inherited class', the 'inherited class' can access the 'inherited values' in the same way it would access it's members normally.

基本概念包括将“继承类”放在结构体的顶部。通过这种方式,访问结构中的前4个字节也可以访问“继承类”中的前4个字节(非疯狂的优化)。现在,当结构体的指针被转换为“继承类”时,“继承类”可以访问“继承值”,其方式与它通常访问成员的方式相同。

This and some naming conventions for constructors, destructors, allocation and deallocarion functions (I recommend init, clean, new, free) will get you a long way.

对于构造函数、析构函数、分配函数和deallocarion函数(我推荐init、clean、new、free),这种命名约定和一些命名约定将使您受益匪浅。

As for Virtual functions, use function pointers in the struct, possibly with Class_func(...); wrapper too. As for (simple) templates, add a size_t parameter to determine size, require a void* pointer, or require a 'class' type with just the functionality you care about. (e.g. int GetUUID(Object *self); GetUUID(&p);)

对于虚函数,可以使用struct中的函数指针,可能是Class_func(…);包装器。至于(简单的)模板,添加size_t参数来确定大小,需要一个void*指针,或者需要一个“类”类型,只有您关心的功能。(例如int GetUUID(对象*自我);GetUUID(拟定);)

#9


3  

In your case the good approximation of the class could be the an ADT. But still it won't be the same.

在你的例子中,类的好近似可以是ADT。但还是不一样。

#10


3  

My strategy is:

我的策略是:

  • Define all code for the class in a separate file
  • 在单独的文件中定义类的所有代码
  • Define all interfaces for the class in a separate header file
  • 在单独的头文件中定义类的所有接口
  • All member functions take a "ClassHandle" which stands in for the instance name (instead of o.foo(), call foo(oHandle)
  • 所有成员函数都有一个代表实例名的“ClassHandle”(而不是o.foo(),调用foo(oHandle)
  • The constructor is replaced with a function void ClassInit(ClassHandle h, int x, int y,...) OR ClassHandle ClassInit(int x, int y,...) depending on the memory allocation strategy
  • 构造函数被替换为一个函数void ClassInit(ClassHandle h, int x, int y,…)或ClassHandle ClassInit(int x, int y,…)取决于内存分配策略
  • All member variables are store as a member of a static struct in the class file, encapsulating it in the file, preventing outside files from accessing it
  • 所有成员变量都作为类文件中的静态结构的成员存储,并将其封装在文件中,防止外部文件访问它
  • The objects are stored in an array of the static struct above, with predefined handles (visible in the interface) or a fixed limit of objects that can be instantiated
  • 对象存储在上面静态结构的数组中,具有预定义的句柄(在接口中可见)或可实例化的对象的固定限制
  • If useful, the class can contain public functions that will loop through the array and call the functions of all the instantiated objects (RunAll() calls each Run(oHandle)
  • 如果有用,类可以包含公共函数,这些函数将循环遍历数组并调用所有实例化对象的函数(RunAll()调用每次运行(oHandle)
  • A Deinit(ClassHandle h) function frees the allocated memory (array index) in the dynamic allocation strategy
  • 函数的作用是:释放动态分配策略中分配的内存(数组索引)

Does anyone see any problems, holes, potential pitfalls or hidden benefits/drawbacks to either variation of this approach? If I am reinventing a design method (and I assume I must be), can you point me to the name of it?

有没有人发现这种方法的变化有任何问题、漏洞、潜在的隐患或潜在的好处/缺点?如果我正在重新发明一种设计方法(我想我肯定是),你能告诉我它的名字吗?

#11


3  

There's a very extensive book on the subject, which might be worth checking out:

关于这个主题有一本非常广泛的书,可能值得一读:

Object Oriented Programming in ANSI-C

面向对象的ANSI-C编程

#12


3  

Also see this answer and this one

也看这个答案和这个。

It is possible. It always seems like a good idea at the time but afterwards it becomes a maintenance nightmare. Your code become littered with pieces of code tying everything together. A new programmer will have lots of problems reading and understanding the code if you use function pointers since it will not be obvious what functions is called.

这是可能的。这在当时似乎是一个好主意,但后来却变成了维护的噩梦。您的代码将把所有东西都绑定在一起。如果使用函数指针,新的程序员将在读取和理解代码时遇到很多问题,因为函数的调用并不明显。

Data hiding with get/set functions is easy to implement in C but stop there. I have seen multiple attempts at this in the embedded environment and in the end it is always a maintenance problem.

带有get/set函数的数据隐藏在C语言中很容易实现,但仅限于此。我已经在嵌入式环境中看到过多次尝试,最终它总是一个维护问题。

Since you all ready have maintenance issues I would steer clear.

既然大家都准备好了维护问题,我就不赘述了。

#13


3  

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <uchar.h>

/**
 * Define Shape class
 */
typedef struct Shape Shape;
struct Shape {
    /**
     * Variables header...
     */
    double width, height;

    /**
     * Functions header...
     */
    double (*area)(Shape *shape);
};

/**
 * Functions
 */
double calc(Shape *shape) {
        return shape->width * shape->height;
}

/**
 * Constructor
 */
Shape _Shape() {
    Shape s;

    s.width = 1;
    s.height = 1;

    s.area = calc;

    return s;
}

/********************************************/

int main() {
    Shape s1 = _Shape();
    s1.width = 5.35;
    s1.height = 12.5462;

    printf("Hello World\n\n");

    printf("User.width = %f\n", s1.width);
    printf("User.height = %f\n", s1.height);
    printf("User.area = %f\n\n", s1.area(&s1));

    printf("Made with \xe2\x99\xa5 \n");

    return 0;
};

#14


3  

May be this online book could help you to solve your problem.

也许这本在线书可以帮助你解决你的问题。

#15


2  

My approach would be to move the struct and all primarily-associated functions to a separate source file(s) so that it can be used "portably".

我的方法是将struct和所有主相关的函数移动到一个单独的源文件中,这样就可以使用“portable”。

Depending on your compiler, you might be able to include functions into the struct, but that's a very compiler-specific extension, and has nothing to do with the last version of the standard I routinely used :)

根据编译器的不同,您可能可以将函数包含到结构体中,但这是一个非常特定于编译器的扩展,与我通常使用的标准的最后一个版本没有关系:)

#16


2  

The first c++ compiler actually was a preprocessor which translated the C++ code into C.

第一个c++编译器实际上是一个预处理器,它将c++代码翻译成c。

So it's very possible to have classes in C. You might try and dig up an old C++ preprocessor and see what kind of solutions it creates.

因此,在C语言中有类是很可能的,您可以尝试挖掘一个旧的c++预处理器,看看它创建了什么样的解决方案。

#17


1  

Do you want virtual methods?

你想要虚拟方法吗?

If not then you just define a set of function pointers in the struct itself. If you assign all the function pointers to standard C functions then you will be able to call functions from C in very similar syntax to how you would under C++.

如果不是,那么您只需在结构体本身中定义一组函数指针。如果你把所有的函数指针都赋给了标准的C函数,那么你就可以从C中调用函数,其语法与在c++下调用函数的语法非常相似。

If you want to have virtual methods it gets more complicated. Basically you will need to implement your own VTable to each struct and assign function pointers to the VTable depending on which function is called. You would then need a set of function pointers in the struct itself that in turn call the function pointer in the VTable. This is, essentially, what C++ does.

如果你想要虚拟方法,就会变得更复杂。基本上,您需要为每个结构实现自己的VTable,并根据调用哪个函数为VTable分配函数指针。然后需要结构本身的一组函数指针,这些指针反过来调用VTable中的函数指针。本质上,这就是c++所做的。

TBH though ... if you want the latter then you are probably better off just finding a C++ compiler you can use and re-compiling the project. I have never understood the obsession with C++ not being usable in embedded. I've used it many a time and it works is fast and doesn't have memory problems. Sure you have to be a bit more careful about what you do but its really not that complicated.

《虽然……如果您想要后者,那么您最好找到一个c++编译器,您可以使用它并重新编译项目。我从来都不理解c++在嵌入式中不能使用的困扰。我用过很多次,它运行得很快,没有内存问题。当然,你必须对你所做的事情更加小心,但这真的没有那么复杂。

#18


1  

GTK is built entirely on C and it uses many OOP concepts. I have read through the source code of GTK and it is pretty impressive, and definitely easier to read. The basic concept is that each "class" is simply a struct, and associated static functions. The static functions all accept the "instance" struct as a parameter, do whatever then need, and return results if necessary. For Example, you may have a function "GetPosition(CircleStruct obj)". The function would simply dig through the struct, extract the position numbers, probably build a new PositionStruct object, stick the x and y in the new PositionStruct, and return it. GTK even implements inheritance this way by embedding structs inside structs. pretty clever.

GTK完全构建在C之上,它使用了许多OOP概念。我已经阅读了GTK的源代码,它给人留下了深刻的印象,并且肯定更容易阅读。基本概念是,每个“类”都是一个结构体和相关的静态函数。静态函数都接受“实例”结构作为参数,然后执行所需的操作,并在必要时返回结果。例如,您可能有一个函数“GetPosition(CircleStruct obj)”。函数只需挖掘结构体,提取位置号,可能构建一个新的PositionStruct对象,在新的PositionStruct中插入x和y,然后返回它。GTK甚至通过在结构体中嵌入结构体来实现继承。很聪明。

#19


0  

C isn't an OOP language, as your rightly point out, so there's no built-in way to write a true class. You're best bet is to look at structs, and function pointers, these will let you build an approximation of a class. However, as C is procedural you might want to consider writing more C-like code (i.e. without trying to use classes).

C不是一种OOP语言,正如您正确地指出的那样,因此没有编写真正类的内置方法。最好的办法是看结构体和函数指针,它们可以让你建立类的近似值。但是,由于C是程序性的,您可能需要考虑编写更类似C的代码(即不尝试使用类)。

Also, if you can use C, you can probally use C++ and get classes.

另外,如果您可以使用C语言,您可以使用c++来获得类。