我一直在研究一个在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();
答案 0 :(得分:14)
我不能围绕如何构建我的“对象”,而不是
Stopwatch->tick(Stopwatch);
我可以写
Stopwatch->tick();
这在标准C中是不可能的。您需要将接收器作为显式形式参数传递给C函数(与将this
作为的C ++相比较隐含正式)。
然而:
您通常希望将所有方法函数打包在一个具有多个函数成员的struct
中(并且每个实例都以指向该struct
的指针开头)。阅读vtable - s。
你可以有一些宏(或者内联函数)来避免两次Stopwatch
;你仍然会写TICK(Stopwatch)
而不是Stopwatch->tick();
; statement-expr的GCC扩展名可能很有用。
查看GTK及其Gobject system作为C的可爱对象系统的示例。
顺便说一句,你可以决定你有第一类方法选择器(可能是整数,或指向一些常见选择器类型的指针)并编写一个可变send
dispatching函数(所以你要编码{{ 1}}而不是您梦寐以求的send(StopWatch,TICK_SEL)
)或宏。您可能会发现libffi很有用。旧Xview可能是鼓舞人心的。
最后,与许多花哨的对象层实现者一样,您可能会使用一些元编程并提供一些C代码生成工具(如Qt中的Stopwatch->tick()
)。出于此类目的,您甚至可以考虑使用GCC自定义MELT编译器。或者从你喜欢的OOP方言到C {translator做this(见VALA)。或者使用外部预处理器(您自己的,或m4
或GPP等)预处理您的代码。)。
答案 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);
好的,我们定义:
stop_watch_functions
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文件中键入一个结构,其中公共成员变量被声明为纯结构成员,而私有成员变量则通过不完整的类型声明。