8

我的开源纯 C 代码中,我使用这个简单的结构从字符串缓冲区读取和解析数据:

typedef struct lts_LoadState
{
  const unsigned char * pos;
  size_t unread;
} lts_LoadState;

使用这个简单的 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

此代码用于从字符串缓冲区以自定义格式加载一些数据。(可能是一个更好的说明。)


现在我需要不是从字符串缓冲区加载数据,而是从FILE指针加载数据。

我不想复制粘贴实现,而是想重用现有代码。(当然,我可以重构/调整它。)

这是 C++ 中的教科书内容,但是如何在纯 C 中做到这一点而不会产生运行时开销?


这是一个使用lts_LoadStateAPI 且不可复制粘贴的示例函数(当然,可以更改以同时支持字符串缓冲区和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;
}
4

3 回答 3

3

听起来您想要函数变量,您可以将其作为参数传递。C 可以做到,但语法不是很漂亮。

可能有一点运行时开销,但不多。

如何在 C 中将函数作为参数传递?

于 2012-04-03T21:57:33.750 回答
2

我讨厌打开这个备份,但这是我今天正在考虑的事情,我认为这还没有一个很好的答案。

我认为在 C 中实现鸭式输入你所追求的是一个全局 vtable。每个结构(对象)都应该将 vtable 作为结构中的第一个元素。基本上,只要您想通过鸭子类型访问某个行为,您就可以将其添加到这个全局 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...
于 2015-04-11T09:26:31.550 回答
0

我在我的 postscript 解释器中也有类似的需求,让操作员无论是从 a还是从字符串中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 解释器。

于 2015-05-24T00:46:51.893 回答