char *或char **可以伪装成FILE *吗?

时间:2016-01-19 22:16:08

标签: c file file-io io

在C中,我经常想要处理从文件读取的数据和以相同方式从字符串数组中读取的数据。通常从文件读取是用于生产,而字符串用于测试。我最终写了很多像这样的代码:

void handle_line(char *line, Things *things) {
    ...
}

Things *read_from_chars(char *lines[]) {
    Things *things = Things_new();

    for (int i = 0; lines[i] != NULL; i++) {
        handle_line(lines[i], things);
    }

    return things;
}

Things *read_from_input(FILE *input) {
    char *line = NULL;
    size_t linelen = 0;

    Things *things = Things_new();

    while (getline(&line, &linelen, input) > 0) {
        handle_line(line, things);
    }

    return things;
}

这是一种重复劳动。

有没有办法让一个字符串数组伪装成FILE *指针?或相反亦然?或者有更好的模式来处理这个问题吗?

对于奖励积分:解决方案应使char *char **可用于fgetsgetline等标准文件函数。

7 个答案:

答案 0 :(得分:6)

你可以使用一个包含FILE*的歧视联合和一个指向数组的指针,然后编写一个get_next函数,用它做正确的事。

typedef struct {
    enum { is_file, is_array } type;
    union {
        FILE *file;
        struct {
            int index;
            int size;
            char **lines;
        } array;
    } data;
} file_or_array;

char *get_next(file_or_array foa) {
    if (foa.type == is_file) {
        char *line = NULL;
        size_t linelen = 0;
        getline(&line, &linelen, foa.data.file);
        return line;
    } else {
        if (foa.data.array.index < foa.data.array.size) {
            return strdup(foa.data.array.lines[foa.data.array.index++]);
        } else {
            return NULL;
        }
    }
}

strdup()的调用对于使这项工作保持一致是必要的。由于getline()返回一个新分配的字符串,调用者需要释放该字符串,因此从数组返回字符串时也会执行相同的操作。然后调用者可以在两种情况下安全地释放它。

答案 1 :(得分:5)

有一个非标准函数fmemopen可以让你打开一个char []进行阅读或写作。我认为它可以在大多数GNU libc版本和大多数Linux版本中使用。

(这使您可以读取或写入单个字符串,而不是您询问的字符串数组。)

答案 2 :(得分:2)

处理此问题的最有效方法之一是通过流。我用它们来隐藏文件/字符串/串口等

我已经推出了自己的流库,主要用于嵌入式系统

一般的想法是: -

typedef struct stream_s stream_t;

struct stream_s
{
    BOOL (*write_n)(stream_t* stream,  char* s, WORD n);
    BOOL (*write_byte)(stream_t* stream,  BYTE b);
    BOOL (*can_write)(stream_t* stream);
    BOOL (*can_read)(stream_t* stream);
    BYTE (*read_byte)(stream_t* stream);
    void* context;
};

然后你制作了一大堆函数

BOOL stream_create(stream_t* stream);
BOOL stream_write_n(stream_t* stream, char* s, WORD n);
BOOL stream_can_read(stream_t* stream);
BYTE stream_read_byte(stream_t* stream);

使用那些基函数回调。

流结构中的上下文,用于指向串行,字符串,文件或任何您想要的结构。然后你会有file_create_stream(stream_t* stream, char* filename)这样的东西,它会在stream上填充与文件相关的函数的回调。然后对于字符串,你有类似的东西,但处理字符串

答案 3 :(得分:1)

  

有没有更好的模式来处理这个问题?

我建议的解决方案是执行函数重载。

提供所有可能的参数:

Things* readThings(FILE *f, char *l[])
{
    char *line = NULL;
    size_t linelen = 0;
    Things *things = Things_new();

    if (f)
    {
        while(getline(&line, &linelen, input) > 0)
            handle_line(line, things);
    }
    else
    {
        for(int i = 0; lines[i] != NULL; i++)
            handle_line(lines[i], things);
    }

    return things;
}

Things* readThingsChar(char *l[]){ return readThings(0, l); }

Things* readThingsFile(FILE *f){ return readThings(f, 0); }

如何使用

FILE *f;
char *l[100];

..

Things *a = readThings(f,0); // or readThingsFile(f)
Things *b = readThings(0,l); // or readThingsChar(l)

您可以将其嵌入数据中:

Things* readThings(char *l[])
{
    char *line = NULL;
    size_t linelen = 0;
    Things *things = Things_new();
    FILE *f = NULL;

    if (l[0][0]==UNIQUE_IDENTIFIER)
    {
        f = fopen(l[0]+1);

        while(getline(&line, &linelen, input) > 0)
            handle_line(line, things);

        fclose(f);
    }
    else
    {
        for(int i = 0; lines[i] != NULL; i++)
            handle_line(lines[i], things);
    }

    return things;
}

如何使用

char *f[1] = { "_file.txt" };
char *l[100] = { "first line", .. "last line" };

f[0][0] = UNIQUE_IDENTIFIER;

Things *a = readThings(f);
Things *b = readThings(l);

答案 4 :(得分:1)

对这个特定的猫进行换肤的方法不止一种,但一般来说,解决方法是将公共接口的实现隐藏在间接后面,这允许您注入单独的“实现”。

(这个问题的化身也与确保代码版本之间的ABI兼容性有些不同。)

要在C中解决这个问题,你可以这样做,类似于C ++中的pimpl with-inheritance(受保护而不是私有d指针,带有被覆盖的受保护构造函数):

您创建了一个不透明的'reader'/'stream'对象(指向C中转发声明的struct w / typedef的指针)和适当命名的构造函数,以实例化注入所需实现的opaque对象。

让我们勾勒出示例头文件,让您了解函数如何组合在一起。让我们从胆量开始,d指针/ p-impl对象的定义(N.B。:我省略了一些类似标题保护的样板文件):

<强>读取器 - private.h

/* probably should be in its proper C file, but here for clarification */
struct FileReaderPrivateData {
   FILE * fp;
};

/* probably should be in its proper C file, but here for clarification */
struct StringReaderPrivateData {
   size_t nlines;
   size_t cursor;
   char ** lines;
};

/* in C we don't have inheritance, but we can 'fix' it using callbacks */
struct ReaderPrivate {
   int (* close)(void* pData); /* impl callback */
   ssize_t (* readLine)(void* pData, char** into); /* impl callback */
   /* impl-specific data object, callbacks can type cast safely */
   void * data;
};

/* works like a plain p-impl/d-pointer, delegates to the callbacks */
struct Reader {
    struct ReaderPrivate * dPtr;
}

<强> reader.h

typedef struct Reader* Reader;
/* N.B.: buf would be a pointer to set to a newly allocated line buffer. */
ssize_t readLine(Reader r, char ** buf); 
int close(Reader r);

文件-reader.h

#include "reader.h"
Reader createFileReader(FILE * fp);
Reader createFileReader(const char* path);

<强>绳reader.h

#include "reader.h"
Reader createStringReader(const char**, size_t nlines);

这是在C中使用继承执行pimpl / d-pointer的一般模式,因此您可以抽象通过不透明指针访问的公共接口后面的实现内容。此机制通常用于保证公共接口的各种实现之间的API和ABI兼容性,并实现简单的继承模式。

答案 5 :(得分:1)

这是使用fcookieopen的实现[IIRC,BSD有类似的东西]:

// control for string list
struct cookie {
    char **cook_list;                       // list of strings
    int cook_maxcount;                      // maximum number of strings

    int cook_curidx;                        // current index into cook_list
    int cook_curoff;                        // current offset within item
};

int cookie_close(void *vp);
ssize_t cookie_read(void *vp,char *buf,size_t size);

cookie_io_functions_t cook_funcs = {
    .read = cookie_open;
    .close = cookie_close;
};

// cookie_open -- open stream
FILE *
cookie_open(char **strlist,int count,const char *mode)
// strlist -- list of strings
// count -- number of elements in strlist
// mode -- file open mode
{
    cookie *cook;
    FILE *stream;

    cook = calloc(1,sizeof(cookie));
    cook->cook_list = strlist;
    cook->cook_maxcount = count;

    stream = fopencookie(cook,mode,&cook_funcs);

    return stream;
}

// cookie_close -- close stream
int
cookie_close(void *vp)
{

    free(vp);

    return 0;
}

// cookie_read -- read stream
ssize_t
cookie_read(void *vp,char *buf,size_t size)
{
    cookie *cook = vp;
    char *base;
    ssize_t totcnt;

    totcnt = 0;

    while (size > 0) {
        // bug out if all strings exhausted
        if (cook->cook_curidx >= cook->cook_maxcount)
            break;

        base = cook->cook_list[cook->cook_curidx];
        base += cook->cook_curoff;

        // if at end of current string, start on the next one
        if (*base == 0) {
            cook->cook_curidx += 1;
            cook->cook_curoff = 0;
            continue;
        }

        // store character and bump buffer and count
        *buf++ = *base;
        size -= 1;
        totcnt += 1;

        cook->cook_curoff += 1;
    }

    return totcnt;
}

答案 6 :(得分:1)

如果您只需要调试此功能,请将fopen_strings(char *list[])函数写入:

  • 创建临时文件
  • 使用模式fopen
  • "r+"打开
  • 将所有字符串写入其中
  • 删除文件(FILE *仍可对其进行操作,直到它在程序结束时显式或隐式关闭。您可能需要在某些操作系统上跳过此步骤,以防止删除打开的文件。
  • rewind
  • 返回流,让程序像使用常规文件一样使用它。