在my open-source plain C code中,我使用这个简单的结构来读取和解析字符串缓冲区中的数据:
typedef struct lts_LoadState
{
const unsigned char * pos;
size_t unread;
} lts_LoadState;
使用this simple API访问缓冲区:
/* Initialize buffer */
void ltsLS_init(lts_LoadState * ls,const unsigned char * data, size_t len);
/* Do we have something to read? (Actually a macro.) */
BOOL ltsLS_good(ls);
/* How much do we have to read? (Actually a macro.) */
size_t ltsLS_unread(ls);
/* Eat given number of characters, return pointer to beginning of eaten data */
const unsigned char * ltsLS_eat(lts_LoadState * ls, size_t len);
注意:ltsLS_unread
可能会被return (ltsLS_good(ls)) ? SIZE_MAX : 0
替换而不会破坏当前的实施。
此代码用于load some data in a custom format from a string buffer。 (This可能是一个更好的例子。)
现在我需要不是从字符串缓冲区加载数据,而是从FILE
指针加载数据。
我不想复制粘贴实现,而希望重用现有代码。 (当然,我可以重构/改编它。)
这是C ++中的教科书,但如何在普通C中执行此操作而不会产生运行时开销?
这是一个使用lts_LoadState
API并且不能进行复制粘贴的示例函数(当然,可以更改为支持字符串缓冲区和FILE *
):
static int ltsLS_readline(
lts_LoadState * ls,
const unsigned char ** dest,
size_t * len
)
{
const unsigned char * origin = ls->pos;
unsigned char last = 0;
size_t read = 0;
while (ltsLS_good(ls))
{
if (ltsLS_unread(ls) > 0)
{
unsigned char b = *ls->pos; /* OK, this should be ltsLS_eat_char macro. */
++ls->pos;
--ls->unread;
if (b == '\n')
{
*dest = origin;
*len = (last == '\r') ? read - 1 : read;
return LUATEXTS_ESUCCESS;
}
last = b;
++read;
}
else
{
ls->unread = 0;
ls->pos = NULL;
}
}
return LUATEXTS_ECLIPPED;
}
答案 0 :(得分:3)
听起来你想要函数变量,你可以将它作为参数传递。 C可以做到,但语法不是很漂亮。
可能有一点运行时开销,但不多。
答案 1 :(得分:2)
我讨厌打开这个,但这是我今天想到的事情,我不认为这有很好的答案。
我认为在C中实现鸭子打字是一个全球性的vtable。每个结构(对象)都应该将vtable作为结构中的第一个元素。基本上,只要有想要通过duck typing访问的行为,就可以将其添加到此全局vtable中;那么你可以调用它,无论将哪个对象传递给你的函数,你都能够将对象强制转换为表,查看行为的位置,检查它是否为空,以及叫它。
//Would be declared in some global.h or similar
struct global_v_table_t =
{
char* (*toString)(void);
//... other functions being accessed through duck typing go here
}
//--------------------
//In some other files:
//Then we create some objects:
struct bicycle_t
{
struct global_v_table;
void (*ride)(void);
};
//When we initialise a bicycle
bicycle_t * bicycyle_init(void)
{
bicycle_t * bike = malloc(sizeof(bicycle_t));
//Req'd basically for every object within the project:
//Either do this or call calloc() instead of malloc():
globalVtableInit((global_v_table_init)bike);//NULL the vtable
//Set the behaviours that this object exhibits:
bike->global_v_table.toString = BIKE_toString;
}
static char * bikeString = "I'm a bike!";
char * BIKE_toString(void)
{
return bikeString;
}
//----------------
//Now anyone can ask that an object provide it's toString:
//The example uses an error logging function:
void logError(void * obj)
{
char * (toStringMethod)(void) = ((global_v_table *)obj)->toString;
if (NULL != toStringMethod)
{//As long as the object implements the toString behaviour:
printf(toStringMethod()); //Print the object's toString.
}
}
//Will tidy this code up a bit later but this is what I'm thinking.
//Hopefully is at least partly understandable. The obvious drawback
//to this implementation is that for every object you get this massive
//v_table which is full of mostly NULL's for each object as it scales.
//If you want to make C behave like other languages though you have
//to expect some sort of penalty I guess...
答案 2 :(得分:0)
我的postscript解释器中有一个类似的需求,token
运算符无论是从FILE*
还是字符串读取都是一样的。您似乎已经完成了第一步,至少部分是通过get / unget对将解析逻辑与数据访问分开。如果您可以编写与库FILE*
函数的原型匹配的字符串版本,则可以简化实现。
对于我的,我有一个主入口点,它接受get / unget访问器的函数指针。
int toke (Xpost_Context *ctx,
Xpost_Object *src,
int (*next)(Xpost_Context *ctx, Xpost_Object *src),
void (*back)(Xpost_Context *ctx, int c, Xpost_Object *src),
Xpost_Object *retval);
正常的操作符执行根据类型调用适当的接口函数。因此,文件版本调用toke
并以较低级别的术语实现这两个操作。
/* file token token true
false
read token from file */
static
int Fnext(Xpost_Context *ctx,
Xpost_Object *F)
{
return xpost_file_getc(xpost_file_get_file_pointer(ctx->lo, *F));
}
static
void Fback(Xpost_Context *ctx,
int c,
Xpost_Object *F)
{
(void)ungetc(c, xpost_file_get_file_pointer(ctx->lo, *F));
}
static
int Ftoken (Xpost_Context *ctx,
Xpost_Object F)
{
Xpost_Object t;
int ret;
if (!xpost_file_get_status(ctx->lo, F))
return ioerror;
ret = toke(ctx, &F, Fnext, Fback, &t);
if (ret)
return ret;
if (xpost_object_get_type(t) != nulltype) {
xpost_stack_push(ctx->lo, ctx->os, t);
xpost_stack_push(ctx->lo, ctx->os, xpost_bool_cons(1));
} else {
xpost_stack_push(ctx->lo, ctx->os, xpost_bool_cons(0));
}
return 0;
}
字符串版本使用字符串实现来执行这两个操作。
/* string token substring token true
false
read token from string */
static
int Snext(Xpost_Context *ctx,
Xpost_Object *S)
{
int ret;
if (S->comp_.sz == 0) return EOF;
ret = xpost_string_get_pointer(ctx, *S)[0];
++S->comp_.off;
--S->comp_.sz;
return ret;
}
static
void Sback(Xpost_Context *ctx,
int c,
Xpost_Object *S)
{
--S->comp_.off;
++S->comp_.sz;
xpost_string_get_pointer(ctx, *S)[0] = c;
}
static
int Stoken (Xpost_Context *ctx,
Xpost_Object S)
{
Xpost_Object t;
int ret;
ret = toke(ctx, &S, Snext, Sback, &t);
if (ret)
return ret;
if (xpost_object_get_type(t) != nulltype) {
xpost_stack_push(ctx->lo, ctx->os, S);
xpost_stack_push(ctx->lo, ctx->os, t);
xpost_stack_push(ctx->lo, ctx->os, xpost_bool_cons(1));
} else {
xpost_stack_push(ctx->lo, ctx->os, xpost_bool_cons(0));
}
return 0;
}
这是来自文件src / lib / xpost_op_token.c中的xpost postscript interpreter。