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

时间:2015-09-02 08:13:46

标签: c oop

我一直在研究一个在C中学习OOP的例子。目前我已经提出了这个有效的代码,但是我有兴趣让这些方法隐含地通过自我作为一个参数。

#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);    
}

我已经尝试过阅读并关注Object Oriented Programming with ANSI-C,但无法解决如何构建我的&#34;对象&#34;所以而不是

Stopwatch->tick(Stopwatch);

我可以写

Stopwatch->tick();

4 个答案:

答案 0 :(得分:14)

  

我不能围绕如何构建我的“对象”,而不是

     

Stopwatch->tick(Stopwatch);

     

我可以写Stopwatch->tick();

这在标准C中是不可能的。您需要将接收器作为显式形式参数传递给C函数(与将this作为的C ++相比较隐含正式)。

然而:

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

  • 你可以有一些宏(或者内联函数)来避免两次Stopwatch;你仍然会写TICK(Stopwatch)而不是Stopwatch->tick();; statement-exprGCC扩展名可能很有用。

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

顺便说一句,你可以决定你有第一类方法选择器(可能是整数,或指向一些常见选择器类型的指针)并编写一个可变send dispatching函数(所以你要编码{{ 1}}而不是您梦寐以求的send(StopWatch,TICK_SEL))或宏。您可能会发现libffi很有用。旧Xview可能是鼓舞人心的。

最后,与许多花哨的对象层实现者一样,您可能会使用一些元编程并提供一些C代码生成工具(如Qt中的Stopwatch->tick())。出于此类目的,您甚至可以考虑使用GCC自定义MELT编译器。或者从你喜欢的OOP方言到C {translatorthis(见VALA)。或者使用外部预处理器(您自己的,或m4GPP等)预处理您的代码。)。

答案 1 :(得分:8)

注意:已经有很多好的答案,这解释了为什么“方法调用”语法在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);

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

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

晚期绑定

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

它也可以在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);

好的,我们定义:

  • 一个v-table:stop_watch_functions
  • 要保留在该v-table上的结构: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);
}

非常简单,对吧?

最后,让我们转到具体的秒表实施:

// 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);

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

// 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));
}

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

答案 2 :(得分:6)

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

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

 stoplib_tick(Stopwatch);

而不是

 Stopwatch->tick(Stopwatch);

另外,在结构的每个实例中携带一堆始终相同的函数指针是个坏主意;这只是浪费空间,可能是错误的原因。如果需要,可以使一个结构包含所有类型的函数指针,并从该表中调用它们。基本上,这就是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设计的书籍,我个人认为它们不会更糟。

答案 3 :(得分:6)

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

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

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

这允许您将秒表实现为不完整类型(也称为“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!

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)
{
}

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

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