C 字符*或字符**可以伪装成文件*?
在C语言中,我通常希望以相同的方式处理从文件读取的数据和从字符串数组读取的数据。通常,从文件中读取用于生产,从字符串中读取用于测试。我最终写了很多这样的代码:C 字符*或字符**可以伪装成文件*?,c,file,file-io,io,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] != NUL
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;
}
这是重复的努力
有没有办法让字符串数组伪装成文件*
指针?反之亦然?还是有更好的模式来处理这个问题
奖励点:解决方案应使
char*
或char**
与标准文件函数(如fgets
和getline
)一起使用,其中有一个非标准函数fmemopen
),允许您打开char[]进行读写。我认为,它在大多数版本的GNULIBC和大多数版本的Linux中都可用
(这使您可以读取或写入单个字符串,而不是您询问的字符串数组。)您可以使用一个包含
文件*
和指向该数组的指针的有区别的并集,然后编写一个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;
}
}
}
typedef结构{
枚举{is_文件,is_数组}类型;
联合{
文件*文件;
结构{
整数指数;
整数大小;
字符**行;
}阵列;
}数据;
}文件\或\数组;
char*get\u next(文件或数组foa){
if(foa.type==is_文件){
char*line=NULL;
尺寸线长度=0;
getline(&line,&linelen,foa.data.file);
回流线;
}否则{
if(foa.data.array.index
调用
strdup()
是使此工作一致的必要步骤。由于getline()
返回一个新分配的字符串,调用者需要释放该字符串,因此它在从数组返回字符串时也会执行相同的操作。然后调用者可以在这两种情况下安全地释放它。处理这一问题的最有效方法之一是通过流。我用它们来隐藏文件/字符串/串行端口等
我推出了自己的流库,主要用于嵌入式系统
总的想法是:-
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\u create\u stream(stream\u t*stream,char*filename)
之类的功能,它将使用与文件相关的函数填充stream
上的回调。然后对于字符串,你有一些类似的东西,但处理字符串
有没有更好的模式来处理这个问题
我建议的解决方案是进行函数重载
提供所有可能的参数:
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)
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);
您可以将其嵌入到数据中:
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;
}
如何使用
FILE *f;
char *l[100];
..
Things *a = readThings(f,0); // or readThingsFile(f)
Things *b = readThings(0,l); // or readThingsChar(l)
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);
有不止一种方法可以剥除这只特定的猫的皮肤,但一般来说,解决方案是将公共接口的实现隐藏在一个间接寻址后面,该间接寻址允许您注入单独的“实现” (这一问题的具体体现也与确保代码版本之间ABI兼容性的不同问题密切相关。)
在C中解决此问题,可以类似于在C++中继承继承的PIPML(受保护而不是私有D指针,用重写的保护构造函数):
创建一个不透明的“reader”/“stream”对象(指向C中带有类型定义的前向声明结构的指针)和适当命名的构造函数,以实例化注入所需实现的不透明对象 让我们勾勒出示例头文件,让您了解函数是如何组合在一起的。让我们从guts开始,d-pointer/p-impl对象的定义(注意:我省略了一些类似样板的标题保护): 读卡器专用.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;
}
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);
阅读器.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;
}
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);
文件读取器.h
#include "reader.h"
Reader createFileReader(FILE * fp);
Reader createFileReader(const char* path);
#include "reader.h"
Reader createStringReader(const char**, size_t nlines);
字符串读取器.h
#include "reader.h"
Reader createFileReader(FILE * fp);
Reader createFileReader(const char* path);
#include "reader.h"
Reader createStringReader(const char**, size_t nlines);
这是在C中执行带继承的pimpl/d-pointer的通用模式,因此您可以在通过不透明指针访问的公共接口后面抽象实现。此机制通常有助于保证公共接口的各种实现之间的API和ABI兼容性,并实现简单的继承模式。下面是一个使用
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;
}
如果只是为了调试而需要此功能,请将
fopen_strings(char*list[])
函数写入:
- 创建一个临时文件
- 用
以fopen
模式“r+”
- 把你所有的字符串都写进去
- 删除文件(在程序结束时显式或隐式关闭文件之前,文件*仍可对其进行操作。在某些阻止删除打开文件的操作系统上,可能需要跳过此步骤
流倒带流
- 返回流,让您的程序像使用常规文件一样使用它
handle\u line
,两个函数read\u from\u chars
和read\u from\u input
执行两个不同的任务(实际上,您需要支持的两个任务)。这是解决问题的正常方法。更复杂的方法