管理资源(文件、内存)是一件很麻烦的事,因为在分配资源后必须释放资源。例如用fopen打开了文件后必须用fclose关闭文件;使用malloc分配了内存后必须使用free释放掉内存。因此,处理资源的代码总是显得非常繁杂。
一、返回非int 值
1.file_reader.h
//1
typedef struct FileReaderContext{
const char * const pFname;
void(* const processor)(struct FileReaderContext * pThis,FILE * fp);
} FileReaderContext;
//继承 FileReaderContext
typedef struct{
FileReaderContext base;
int result;
} MyFileReaderContext;
int read_file(FileReaderContext *pCtx);
int range(const char * pFname);
1.file_reader.c
//
// file_reader.c
// TestXianDaiC
//
// Created by kangxg on 2018/2/7.
// Copyright © 2018年 zy. All rights reserved.
//
#include "file_reader.h"
#include
static void calc_range(FileReaderContext * p,FILE * fp);
int read_file(FileReaderContext *pCtx)
{
fprintf(stderr, "filename'%s\n",pCtx->pFname);
FILE * fp = fopen(pCtx->pFname, "r");
if (fp == NULL)
{
return -1;
}
pCtx->processor(pCtx,fp);
fclose(fp);
return 0;
}
int range(const char * pFname)
{
MyFileReaderContext ctx = {{pFname,calc_range},0};
if (read_file(&ctx.base)!=0)
{
fprintf(stderr, "Cannot open file'%s,.\n",pFname);
}
return ctx.result;
}
static int range_processor(FILE * fp)
{
int min = INT_MAX;
int max = INT_MIN;
char buf[256];
while ((fgets(buf, sizeof(buf), fp))!=NULL)
{
if (buf[0] == '\n')
{
return -1;
}
int value = atoi(buf);
min = min>value?value:min;
max = max>value?value:max;
}
return max-min;
}
static void calc_range(FileReaderContext * p,FILE * fp)
{
MyFileReaderContext * pCtx = (MyFileReaderContext *)p;
pCtx->result = range_processor(fp);
}
atoi函数
atoi (表示 alphanumeric to integer)是把字符串转换成整型数的一个函数。 函数原型:int atoi(const char *nptr); 参数 nptr为字符串指针 说明:
参数nptr字符串,如果第一个非空格字符存在,是数字或者正负号则开始做类型转换,之后检测到非数字(包括结束符 \0) 字符时停止转换,返回整型数。否则,返回零。 包含在头文件stdlib.h中 实例: #include#include int main(void) { int n; char *str = "12345.67"; n = atoi(str); printf("int=%d\n",n); return0; } 结果输出: int=12345
方法说明:
1.定义FileReaderContext 对象,其中有一个向模版方法processor的函数指针
2.然后通过继承,在实际保存的结果对象中增加result成员
3.之后将结果保存在result中
4.最后将result作为range函数的结果返回。
二、处理其他文件资源
不仅仅是文件资源,在使用其他需要分配和释放的资源时,都可以使用模版方法模式。例如如果在运行时才决定需要多少存储空间,如上面代码中将fopen 和fclose替换为 malloc 和free即可。
那么当需要使用多种资源时该如何处理呢?例如将一个文件中的数据读取至内存中,进行排序后再输出至另一个文件中的的情况。当然,也可以编写一个分配和释放三种资源(读取的文件、内存、写入的文件)的函数,但是这样的代码缺少通用性,在其他地方很难服用。而且想要正确地写出这样的函数,本身就并非那么简单。
static long file_size(FILE * fp); int process_file( const char * pInputFileName,const char * pOutputFileName,void(* sorter) (void * pBuf)) { FILE * fpInp = fopen(pInputFileName, "rb"); if (fpInp == NULL) { return FILE_OPEN_ERROR; } long size = file_size(fpInp); void * p = malloc(size); if (p == NULL) { return NO_MEMORY_ERROR;//资源泄漏 } fread(p, size, 1, fpInp); //... }这段代码中,程序打开文件后先分配足够容纳下该文件的内存空间,然后读入文件。如果没有足够的内存空间,则返回错误。但是正如注释的那样,如果在这里return的话,就无法关闭文件了,文件会一直处于打开状态。可以预想到的是,这之后还有写入文件的处理,整体代码结构肯定是相当复杂的。
此时,不应当将处理多个资源的代码整合起来,而应当将它们分开进行管理。这样不仅代码会更加简洁,也更容易复用。
整体的处理流程如下:
如上图 通过分析可以通过对模版代码整合为一个函数。
1.file.accessor.h
typedef struct FileAccessorContext{ const char * const pFname; const char * const pMode; void (* const processor)(struct FileAccessorContext * pThis,FILE * fp); }FileAccessorContext; bool access_file(FileAccessorContext * pCtx);
2.file.accessor.c
bool access_file(FileAccessorContext * pCtx) { FILE * fp =fopen(pCtx->pFname, pCtx->pMode); if (fp == NULL) { return false; } pCtx->processor(pCtx,fp); fclose(fp); return true; }
3.buffer.h
typedef struct BufferContext{ void * pBuf; size_t size; void (* processor)(struct BufferContext * p); }BufferContext; bool buffer(BufferContext * pThis);
4.buffer.c
bool buffer(BufferContext * pThis) { pThis->pBuf = malloc(pThis->size); if (pThis->pBuf == NULL) { return false; } pThis->processor(pThis); free(pThis->pBuf); return true; }
5.int_sorter.h
#include#include "buffer.h" typedef enum{ ERR_CAT_OK = 0, ETT_CAT_FILE, ETT_CAT_MEMORY } IntSorterError; typedef struct{ const char * const pFname; int errorCategory; } Context; typedef struct{ BufferContext base; Context * pAppCtx; } MybufferContext; IntSorterError int_sorter(const char * pFname);
6.int_sorter.c
#include "int_sorter.h" #include#include #include #include #include #include "file_accessor.h" typedef struct{ FileAccessorContext base; long size; } SizeGetterContext; typedef struct{ FileAccessorContext base; MybufferContext * pBufCtx; } MyFileAccessorContext; static void size_reader(FileAccessorContext * p,FILE * fp); static void file_error(Context * pCtx); static void reader(FileAccessorContext * p,FILE * fp); static void writer(FileAccessorContext * p,FILE * fp); int comparator(const void *a, const void *b) { return 0; } static void do_with_buffer(BufferContext * p) { MybufferContext * pBufCtx= (MybufferContext *)p; MyFileAccessorContext readFileCtx = {{pBufCtx->pAppCtx->pFname,"rb",reader},pBufCtx}; if (!access_file(&readFileCtx.base)) { file_error(pBufCtx->pAppCtx); return; } qsort(p->pBuf, p->size/sizeof(int), sizeof(int), comparator); MyFileAccessorContext writeFileCtx = {{pBufCtx->pAppCtx->pFname,"wb",writer},pBufCtx}; if (!access_file(&writeFileCtx.base)) { file_error(pBufCtx->pAppCtx); return; } } static void reader(FileAccessorContext * p,FILE * fp) { MyFileAccessorContext * pFileCtx = (MyFileAccessorContext *)p; MybufferContext * pBufCtx = pFileCtx->pBufCtx; if (pBufCtx->base.size!=fread(pBufCtx->base.pBuf, 1, pBufCtx->base.size, fp)) { file_error(pBufCtx->pAppCtx); } } static void writer(FileAccessorContext * p,FILE * fp) { MyFileAccessorContext * pFileCtx = (MyFileAccessorContext *)p; MybufferContext * pBufCtx = pFileCtx->pBufCtx; if (fwrite(pBufCtx->base.pBuf,1, pBufCtx->base.size, fp)!=pBufCtx->base.size) { file_error(pBufCtx->pAppCtx); } } static long file_size(const char * pFname) { SizeGetterContext ctx = {{pFname,"rb",size_reader},0}; if (!access_file(&ctx.base)) { return -1; } return ctx.size; } static void size_reader(FileAccessorContext * p,FILE * fp) { SizeGetterContext * pThis = (SizeGetterContext*)p; pThis->size = -1; if (fseek(fp, 0, SEEK_END)==0) { pThis->size = ftell(fp); } } IntSorterError int_sorter(const char * pFname) { Context ctx = {pFname,ERR_CAT_OK}; long size = file_size(pFname); if (size == -1) { file_error(&ctx); return ctx.errorCategory; } if (!buffer(&bufCtx.base)) { ctx.errorCategory = ETT_CAT_MEMORY; } return ctx.errorCategory; } static void file_error(Context * pCtx) { fprintf(stderr, "%s:%s\n",pCtx->pFname,strerror(errno)); pCtx->errorCategory = ETT_CAT_FILE; }
6 上下文
通过使用模版方法模式可以方便地改变一连串处理中的某一部分。前面的内容以资源分配于释放处理为例,但是最后的那个改善后的代码可能并非最优解决方案,例如一共有两次打开和关闭文件;首先打开文件计算文件大小,然后关闭文件;然后再次打开文件读取文件内容并关闭文件。
要想解决这个问题。只需要稍微改进下资源的访问方式即可
buffer函数在以函数指针方式调用用户自定义函数(processor的参数)之前分配了内存,在用户自定义函数结束之后释放内存。因此,在调用用户自定义函数前,buffer函数需要知道所分配内存的大小。但是如前所述,应用设计上有一个限制条件。因此,无法事先知道所需分配内存的大小,使得计算文件大小的处理无法从buffer函数中移动至用户自定义函数中。
现在重新修改一下设计,将获得所需内存的大小的处理推迟至调用用户自定义函数后。如下图展示了一种可能的设计流程。“上下文”这个词具有“语境”“环境”的意思,但是在程序中,“场所”这个解释可能更为确切。
根据上面的设计,代码修改如下:
6.1 buffer.h
typedef struct BufferContext{ void * pBuf; size_t size; bool(* processor)(struct BufferContext * p); }BufferContext;
6.2 buffer.c
#includebool buffer(BufferContext * pThis) { assert(pThis); bool ret = pThis->processor(pThis); free(pThis->pBuf); pThis->pBuf = malloc(pThis->size); return ret; } void * allocate_buffer(BufferContext * pThis,size_t size) { assert(pThis); assert(pThis->pBuf == NULL); pThis->pBuf = malloc(size); pThis->size = size; return pThis->pBuf; }
6.3 重写 access_file 函数
file_accessor.h typedef struct FileAccessorContext{ FILE * fp; const char * const pFname; const char * const pMode; bool (* const processor)(struct FileAccessorContext * pThis); }FileAccessorContext; bool access_file(FileAccessorContext * pThis); file_accessor.c #includebool access_file(FileAccessorContext * pThis) { assert(pThis); bool ret = pThis->processor; if (pThis->fp != NULL) { if (fclose(pThis->fp) != 0) { ret = false; } } return ret; } FILE * get_file_pointer(FileAccessorContext * pThis) { assert(pThis); if (pThis->fp == NULL) { pThis->fp = fopen(pThis->pFname, pThis->pMode); } return pThis->fp; }
6.4 修改int_sorter.c
#include "int_sorter.h" #include#include #include #include #include #include #include "file_accessor.h" //1 typedef struct{ FileAccessorContext base; long size; } SizeGetterContext; typedef struct{ FileAccessorContext base; MybufferContext * pBufCtx; } MyFileAccessorContext; static void size_reader(FileAccessorContext * p,FILE * fp); static void file_error(Context * pCtx); static long file_size(FileAccessorContext * pThis); static bool reader(FileAccessorContext * p); static bool writer(FileAccessorContext * p); static long file_current_pos(FileAccessorContext * pFileCtx); static int set_file_pos(FileAccessorContext * pFileCtx,long offset,int whence); int comparator(const void *a, const void *b) { return 0; } static bool do_with_buffer(BufferContext * p) { //3 MybufferContext * pBufCtx= (MybufferContext *)p; MyFileAccessorContext readFileCtx = {{NULL,pBufCtx->pAppCtx->pFname,"rb",reader},pBufCtx}; //4 if (!access_file(&readFileCtx.base)) { file_error(pBufCtx->pAppCtx); return false; } //11 qsort(p->pBuf, p->size/sizeof(int), sizeof(int), comparator); MyFileAccessorContext writeFileCtx = {{NULL,pBufCtx->pAppCtx->pFname,"wb",writer},pBufCtx}; if (!access_file(&writeFileCtx.base)) { file_error(pBufCtx->pAppCtx); return false; } return true; } static bool reader(FileAccessorContext * p) { //5 MyFileAccessorContext * pFileCtx = (MyFileAccessorContext *)p; MybufferContext * pBufCtx = pFileCtx->pBufCtx; //6 long size = file_size(p); if (size == -1) { file_error(pBufCtx->pAppCtx); return false; } //9 if (!allocate_buffer(&pBufCtx->base, size)) { pBufCtx->pAppCtx->errorCategory = ETT_CAT_MEMORY; return false; } FILE * fp = get_file_pointer(p); //10 if (pBufCtx->base.size != fread(pBufCtx->base.pBuf, 1, pBufCtx->base.size, fp)) { file_error(pBufCtx->pAppCtx); return false; } return true; } static bool writer(FileAccessorContext * p) { //12 MyFileAccessorContext * pFileCtx = (MyFileAccessorContext *)p; MybufferContext * pBufCtx = pFileCtx->pBufCtx; FILE * fp = get_file_pointer(p); if (fwrite(pBufCtx->base.pBuf,1, pBufCtx->base.size, fp)!=pBufCtx->base.size) { file_error(pBufCtx->pAppCtx); return false; } return true; } static long file_size(FileAccessorContext * pThis) { //7 long save = file_current_pos(pThis); if(save<0) { return -1; } if (set_file_pos(pThis,save,SEEK_END)!=0) { return -1; } long size = file_current_pos(pThis); if (set_file_pos(pThis,save,SEEK_SET)!=0) { return -1; } return size; } static long file_current_pos(FileAccessorContext * pFileCtx) { assert(pFileCtx); //8 FILE * fp = get_file_pointer(pFileCtx); if (fp == NULL) { return -1; } return ftell(fp); } static int set_file_pos(FileAccessorContext * pFileCtx,long offset,int whence) { assert(pFileCtx); //8 FILE * fp = get_file_pointer(pFileCtx); if (fp == NULL) { return -1; } return fseek(fp, offset, whence); } static void size_reader(FileAccessorContext * p,FILE * fp) { SizeGetterContext * pThis = (SizeGetterContext*)p; pThis->size = -1; if (fseek(fp, 0, SEEK_END)==0) { pThis->size = ftell(fp); } } //1 IntSorterError int_sorter(const char * pFname) { Context ctx = {pFname,ERR_CAT_OK}; MybufferContext bufCtx = {{NULL,0,do_with_buffer},&ctx}; buffer(&bufCtx.base); return ctx.errorCategory; } static void file_error(Context * pCtx) { fprintf(stderr, "%s:%s\n",pCtx->pFname,strerror(errno)); pCtx->errorCategory = ETT_CAT_FILE; }