C中的OOP,隐式传递self作为参数

Car*_*ing 16 c oop

我一直在研究一个在C中学习OOP的例子.目前我已经提出了这个有效的代码,但是我有兴趣让这些方法隐式地将self作为参数传递.

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
//#include "Stopwatch.h"


typedef struct stopwatch_s
{
    unsigned int milliseconds;
    unsigned int seconds;
    unsigned int minutes;
    unsigned int hours;

    bool is_enabled;

    void ( *tick )      ( struct stopwatch_s* );
    void ( *start )     ( struct stopwatch_s* );
    void ( *stop )      ( struct stopwatch_s* );
    void ( *reset )     ( struct stopwatch_s* );
} stopwatch_t;

static void tick (stopwatch_t* _self)
{
    stopwatch_t * self = _self;
    if (self->is_enabled)
    {
        self->milliseconds++;
        if (self->milliseconds >= 1000)
        {
            self->milliseconds = 0;
            self->seconds++;

            if (self->seconds >= 60)
            {
                self->seconds = 0;
                self->minutes++;

                if (self->minutes >= 60)
                {
                    self->minutes = 0;
                    self->hours++;
                }
            }
        }
    }
}

static void start (stopwatch_t* _self)
{
    stopwatch_t * self = _self;
    self->is_enabled = true;
}

static void stop (stopwatch_t* _self)
{
    stopwatch_t * self = _self;
    self->is_enabled = false;
}

static void reset (stopwatch_t* _self)
{
    stopwatch_t * self = _self;
    self->is_enabled = false;
    self->milliseconds = 0;
    self->seconds = 0;
    self->minutes = 0;
    self->hours = 0;
}

void * new_stopwatch()
{
    stopwatch_t * newInstance = (stopwatch_t *)calloc(1, sizeof(stopwatch_t));
    newInstance->is_enabled = false;
    newInstance->milliseconds = 0;
    newInstance->seconds = 0;
    newInstance->minutes = 0;
    newInstance->hours = 0;
    newInstance->tick = &tick;
    newInstance->start = &start;
    newInstance->stop = &stop;
    newInstance->reset = &reset;

    return newInstance;
}

void main()
{
    struct stopwatch_s * Stopwatch = new_stopwatch();
    printf ("Initial: %d\n", Stopwatch->milliseconds);
    Stopwatch->start (Stopwatch);
    Stopwatch->tick (Stopwatch);
    Stopwatch->tick (Stopwatch);
    Stopwatch->tick (Stopwatch);
    printf ("Started: %d\n", Stopwatch->milliseconds);
    Stopwatch->stop (Stopwatch);
    Stopwatch->tick (Stopwatch);
    printf ("Stopped: %d\n", Stopwatch->milliseconds);
    Stopwatch->reset (Stopwatch);
    printf ("Reset: %d\n", Stopwatch->milliseconds);    
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试过阅读并使用ANSI-C进行面向对象编程,但无法解决如何构建我的"对象"而不是

Stopwatch->tick(Stopwatch);
Run Code Online (Sandbox Code Playgroud)

我可以写

Stopwatch->tick();
Run Code Online (Sandbox Code Playgroud)

Bas*_*tch 15

我无法绕过如何构建我的"对象"而不是

Stopwatch->tick(Stopwatch);

我可以写 Stopwatch->tick();

这不是标准下可能需要将接收器作为传递明确的正式参数到C函数(与C++具有对比度this形式).

然而:

  • 您通常希望将所有方法函数打包在一个单独的struct函数成员中(并且每个实例都以指向该函数的指针开头struct).阅读vtable -s.

  • 你可以有一些宏(或者内联函数)来避免给出Stopwatch两次; 你仍然会写TICK(Stopwatch)Stopwatch->tick();; GCCstatement-expr扩展可能很有用.

查看GTK及其Gobject系统作为C的可爱对象系统的示例.

顺便说一下,你可以决定你有第一类方法选择器(可能是整数,或指向一些常见选择器类型的指针)并编写一个可变参数send 调度函数(所以你可以代码send(StopWatch,TICK_SEL)而不是你的梦想Stopwatch->tick())或宏.您可能会发现libffi很有用.旧Xview可能是鼓舞人心的.

最后,与许多花哨的对象层实现者一样,您可能会使用一些元编程并提供一些C代码生成工具(如mocQt中所示).您甚至可以考虑使用MELT自定义GCC编译器.或者从你喜欢的OOP方言到C(像VALA那样)翻译(见这个).或者使用外部预处理器(您自己的,或GPP等)预处理您的代码.m4


Mat*_* M. 9

注意:已经有很多好的答案,这解释了为什么"方法调用"语法在C中不可用,但是它们没有解释做什么而只是指向资源.C中的基本OO实际上相对简单,所以这里有一个快速如何.

本HOW TO分为两部分:

  • 第一部分展示了如何实现封装
  • 第二部分显示了如何在顶部进行后期绑定

封装

通常,OO实际上用于表示封装.封装的想法是获得一个模块化设计,在程序状态下具有良好定义的接口,以期更容易维护不变量.

在C中,传统上通过不透明指针实现:

// stop_watch.h
typedef struct stop_swatch_ stop_watch;

stop_watch* stop_watch_create();
stop_watch* stop_watch_clone(stop_watch const* sw);
void stop_watch_dispose(stop_watch* sw);

void stop_watch_tick(stop_watch* sw);
void stop_watch_start(stop_watch* sw);
void stop_watch_stop(stop_watch* sw);
void stop_watch_reset(stop_watch* sw);
Run Code Online (Sandbox Code Playgroud)

此标头是用户唯一看到的内容,因此无法命名该标头的内部struct stop_watch_.当然,这是C,用户仍然可以搞砸它们,但至少我们对它们有点困难.

注意:这.c是留给读者的练习; 毕竟它是无聊的C代码.

后期绑定

Late Binding在运行时决定调用的函数; 例如,它可以通过virtualC++,Java中的方法来实现......

它也可以在C中完成,也相对容易.你不会受益于所有的糖.

// stop_watch.h
typedef struct stop_watch_functions_ stop_watch_functions;

typedef struct {
    stop_watch_functions const* functions;
} stop_watch;

struct stop_watch_functions_ {
    void (*clone)(stop_watch const*);
    void (*dispose)(stop_watch*);

    void (*tick)(stop_watch*);
    void (*start)(stop_watch*);
    void (*stop)(stop_watch*);
    void (*reset)(stop_watch*);
};

stop_watch* stop_watch_clone(stop_watch const* sw);
void stop_watch_dispose(stop_watch* sw);

void stop_watch_tick(stop_watch* sw);
void stop_watch_start(stop_watch* sw);
void stop_watch_stop(stop_watch* sw);
void stop_watch_reset(stop_watch* sw);
Run Code Online (Sandbox Code Playgroud)

好的,我们定义:

  • 一个v表: stop_watch_functions
  • 一个结构守住该v表:stop_watch; 它应该是具体秒表实例的一部分.

让我们继续讨论实现:

// stop_watch.c
stop_watch* stop_watch_clone(stop_watch const* sw) {
    return (*sw->functions->clone)(sw);
}

void stop_watch_dispose(stop_watch* sw) {
    return (*sw->functions->dispose)(sw);
}

void stop_watch_tick(stop_watch* sw) {
    return (*sw->functions->tick)(sw);
}

void stop_watch_start(stop_watch* sw) {
    return (*sw->functions->start)(sw);
}

void stop_watch_stop(stop_watch* sw)  {
    return (*sw->functions->stop)(sw);
}

void stop_watch_reset(stop_watch* sw) {
    return (*sw->functions->reset)(sw);
}
Run Code Online (Sandbox Code Playgroud)

很简单,对吧?

最后,让我们继续进行具体的秒表实施:

// my_stop_watch.h
#include "stop_watch.h"

typedef struct my_stop_watch_ my_stop_watch;

my_stop_watch* my_stop_watch_create();

stop_watch* my_stop_watch_upcast(my_stop_watch* msw);
my_stop_watch* my_stop_watch_downcast(stop_watch* sw);
Run Code Online (Sandbox Code Playgroud)

好吧,标题很无聊; 所有隐藏的好东西毕竟是:

// my_stop_watch.c
#include "my_stop_watch.h"

struct my_stop_watch_ {
    stop_watch base;

    unsigned int milliseconds;
    unsigned int seconds;
    unsigned int minutes;
    unsigned int hours;

    bool is_enabled;
};

static stop_watch* my_stop_watch_clone(stop_watch const* sw) {
    my_stop_watch* new = malloc(sizeof(my_stop_watch));
    memset(new, (my_stop_watch const*)sw, sizeof(my_stop_watch));
}

static void my_stop_watch_dispose(stop_watch* sw) {
    free(sw);
}

static void my_stop_watch_tick(stop_watch* sw) {
    my_stop_watch* msw = (my_stop_watch*)sw;
    /* do something */
}

static void my_stop_watch_start(stop_watch* sw) {
    my_stop_watch* msw = (my_stop_watch*)sw;
    /* do something */
}

static void my_stop_watch_stop(stop_watch* sw) {
    my_stop_watch* msw = (my_stop_watch*)sw;
    /* do something */
}

static void my_stop_watch_reset(stop_watch* sw) {
    my_stop_watch* msw = (my_stop_watch*)sw;
    /* do something */
}

static stop_watch_functions const my_stop_watch_table = {
    &my_stop_watch_clone,
    &my_stop_watch_dispose,

    &my_stop_watch_tick,
    &my_stop_watch_start,
    &my_stop_watch_stop,
    &my_stop_watch_reset
};

my_stop_watch* my_stop_watch_create() {
    my_stop_watch* msw = malloc(sizeof(my_stop_watch*));

    msw->base = &my_stop_watch_table;

    /* do something */

    return msw;
}

stop_watch* my_stop_watch_upcast(my_stop_watch* msw) {
    return &msw->base;
}

my_stop_watch* my_stop_watch_downcast(stop_watch* sw) {
    if (sw->functions != &my_stop_watch_table) {
        return NULL;
    }

    return (my_stop_watch*)((char*)sw - offsetof(my_stop_watch, base));
}
Run Code Online (Sandbox Code Playgroud)

在这里,我使用了大多数C++实现的策略(使用虚拟表); 还有其他可用的策略,但这一策略可广泛应用.


Mar*_*ler 6

这简直不是C的语言特征,这可能是发明C++的动机之一,所以单独使用C语言,这是不可能的.

在C中有很多库,基本上我所知道的都使用与你相同的方法,使用结构来存储状态(在你的情况下stopwatch_t),但只要在可能的情况下跳过结构中的函数指针; 这不是因为C程序员不喜欢OOP,而是因为它不那么多余

 stoplib_tick(Stopwatch);
Run Code Online (Sandbox Code Playgroud)

代替

 Stopwatch->tick(Stopwatch);
Run Code Online (Sandbox Code Playgroud)

另外,在结构的每个实例中携带一堆总是相同的函数指针是一个坏主意; 这只是浪费空间,可能是错误的原因.如果需要,可以创建一个包含所有类型函数指针的结构,并从该表中调用它们.基本上,这就是C++中的VTables.

因此,没有C程序员会这样做; 除非您的函数指针实际上是可能发生变化的东西,否则您只是不保留可以对该结构中的结构执行的操作.

我不知道你所指的那本书,但如果它鼓吹这样做,我不喜欢它.

说真的,如果你想要C面向对象,那就去C++; 除了编写几个内核之外,你用C做的C++几乎没有什么用处,而且它实际上是半个月前发明的,它将面向对象引向C程序员.不要是20世纪60年代.

编辑开始阅读你链接到的PDF - 严重的是,谁现在会使用ANSI-C?特别是如果你想舒适地使用结构等,你不应该使用比C99更旧的东西(考虑到它已经很老了......),因此,这本书绝对过时,除非你想出一个非常重要的系统,遗产("嗨,我从20世纪80年代开始研究核武器控制系统,我需要解决这个问题和那个问题"),我想我不能想到这样一个例子可以理解的情况; 显然,"我正在学习从头开始在C中进行OOP"不应该基于十多年来已经过时的事情.

编辑:你评论

我来自的地方是嵌入式环境,坚持使用ANSI-C,事情永远都会有效.

我有点勉强同意.在某些平台上缺乏C99支持,但是:大多数编译器都支持绝大多数C99功能.ANSI-C(应该更准确地称为C89,因为C99也是ANSI标准)现在已经超过25年了,并且在不知情的情况下,您的代码甚至可能不符合C89.在这本书肯定是地狱的代码是不是有效的ANSI-C,无论是作者的权利要求.例如,ANSI-C没有//注释; 这只是一个小错误,我猜所有编译器,除非设置为迂腐模式不会抱怨,但仍然,这不是一个好景象.

所以:帮自己一个忙,不要依赖一本有选择地使用难以使用的语言状态的书,并尝试使用你的编译器支持的任何东西.

另外:我读的书越多,(现代)OOP中的好运动就越少(第3页,PDF页面9):

通用指针void *始终使用.一方面它使得无法发现一个集合的样子,但另一方面它允许我们几乎将任何东西传递给add()其他函数.

是的,因为类型安全不是对C的成功至关重要的概念,并且因为通过void*该方法的第一行然后将其转换为所需的指针类型是个好主意.AARGH!如果你想要可怕的错误,那就是你得到它们的方式.

看看像CPython这样的东西:Python是一种OO语言,但解释器/编译器是用C语言编写的.Python使用PyObject结构进行C-OOP,作为一个主要特征,它有一个类型引用,所以为了避免这些盲目管型.你不应该传递一个const char[]你期望指向你的一个对象的指针的地方,并且多态性的全部意义在于你可以使用你的类型的子类型,但不是完全不同的类型,带有一个函数.这本书真的不对OOP有任何好处.读一些别的东西.我很确定有关于CPython设计的书籍,我个人认为它们不会更糟.


Lun*_*din 6

为什么我从不喜欢那本书,试图将C变成C++.每个人都必须首先意识到C++编程不一定与面向对象编程相同.OOP是一种进行程序设计的方法,它与语言语法完全无关.C++只是让它更容易,更漂亮,就是这样.但是,仅仅因为C++具有在某些情况下使代码更漂亮的功能,它并不一定意味着该功能与OOP完全相关(例如采用运算符重载).

所以不要试图将C转换为C++.接受C有不同的语法,这可能不是那么漂亮.C实际上有很多可用的功能,可以实现OOP设计.私有/公共变量或函数的真正封装在C中是100%可实现的.

由于C不是C++,因此您不希望结构中包含成员函数.您需要的唯一函数指针是特殊情况,例如回调函数和类似函数.因此Stopwatch->tick(&Stopwatch),最好不要使用函数指针,而是直接调用成员函数:sw_tick(&Stopwatch).sw秒表模块的唯一前缀在哪里.

这允许您将Stopwatch实现为不完整类型的对象(也称为"opaque类型"),这是C中OOP的核心.不完整类型允许您将结构的内容隐藏到调用者.

然后重写整个秒表"类"(称之为类或ADT或其他),如下所示:

stopwatch.h

typedef struct stopwatch_t stopwatch_t; // incomplete type

stopwatch_t* sw_new (void);             // "constructor"

void sw_delete (stopwatch_t* sw);       // "destructor"

void sw_tick (const stopwatch_t* sw);   // public member function
// any number of public functions here
// mind const correctness!
Run Code Online (Sandbox Code Playgroud)

stopwatch.c

struct stopwatch_t        // implementation
{
  // true private variables:

  unsigned int milliseconds;
  unsigned int seconds;
  unsigned int minutes;
  unsigned int hours;
  bool is_enabled;
};

stopwatch_t* sw_new (void)
{
  // same as what you already have
}

// the module is responsible for cleaning up its own mess, NOT THE CALLER
void sw_delete (stopwatch_t* sw)
{
  free(sw);
}

// any number of public member functions:
void sw_tick (const stopwatch_t* sw)
{
  // here sw is the "self"/"this" pointer
}

// any number of private member functions:
static void sw_do_stuff (stopwatch_t* sw)
{
}
Run Code Online (Sandbox Code Playgroud)

调用者只能声明指向对象的指针,但绝不会声明它们的实例.这没什么大不了的,很多C和C++库都是这样工作的.指向不完整类型的指针有点类似于指向C++中抽象基类的指针.您也无法声明这些实例.

如果需要混合私有和公共成员变量,则可以在h文件中键入一个结构,其中公共成员变量被声明为纯结构成员,而私有成员变量是通过不完整类型声明的.