我一直在研究一个在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();; GCC的statement-expr扩展可能很有用.
查看GTK及其Gobject系统作为C的可爱对象系统的示例.
顺便说一下,你可以决定你有第一类方法选择器(可能是整数,或指向一些常见选择器类型的指针)并编写一个可变参数send 调度函数(所以你可以代码send(StopWatch,TICK_SEL)而不是你的梦想Stopwatch->tick())或宏.您可能会发现libffi很有用.旧Xview可能是鼓舞人心的.
最后,与许多花哨的对象层实现者一样,您可能会使用一些元编程并提供一些C代码生成工具(如mocQt中所示).您甚至可以考虑使用MELT自定义GCC编译器.或者从你喜欢的OOP方言到C(像VALA那样)翻译(见这个).或者使用外部预处理器(您自己的,或GPP等)预处理您的代码.m4
注意:已经有很多好的答案,这解释了为什么"方法调用"语法在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)
好的,我们定义:
stop_watch_functionsstop_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++实现的策略(使用虚拟表); 还有其他可用的策略,但这一策略可广泛应用.
这简直不是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设计的书籍,我个人认为它们不会更糟.
为什么我从不喜欢那本书,试图将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文件中键入一个结构,其中公共成员变量被声明为纯结构成员,而私有成员变量是通过不完整类型声明的.
| 归档时间: |
|
| 查看次数: |
4164 次 |
| 最近记录: |