Windows系统下使用C语言编写单线程的文件备份程序
写在最前方 源路径:即 From-Path,你准备要备份的资料 do{ puts("-------------------------------------------------"); fprintf(stdout,"The Default Path is : %s n",DEFAULT_TO_PATH); fprintf(stdout,"Now The Path is : %s n",get_backup_topath()); puts("-------------------------------------------------"); puts("That is a System Back Up Software for Windows! "); puts("List of the software function : "); puts("1. Back Up "); puts("2. Set Back Up TO-PATH "); puts("3. Show TO-PATH History"); puts("4. Read Me "); puts("5. Exit "); puts("-------------------------------------------------"); 对界面稍微有了一些改动。 新增了第三行和第四行的 系统默认目的路径和当前使用的目的路径。 新增了倒数第四行的查看目的路径历史纪录的功能。 在main函数外头需要 extern DEFAULT_TO_PATH;因为引用了setPath.c里的一个全局变量。 写在中间 我们曾经提到要让函数的功能更加清晰,为了达到这个目的,应该把可能用到的一些原生库函数包裹一下,让可能发生的错误尽量掌握在我们自己的手里 安全函数 新建 safeFunc.h safeFunc.c 为了不让后方的多线程实现产生更多的以后,不单独使用全局错误输出。
#include <stdio.h> /* size_t */ #include <stdlib.h> #include <setjmp.h> #define TRY_TIMES 3 typedef struct _input_para{ char * file; /* 待打开或创建的文件名 */ char * mode; /* 打开的模式 */ }params; jmp_buf malc_jmp; /*Malloc_s*/ jmp_buf fopn_jmp; /*Fopen*/ /** * @version 1.0 2015/10/01 * @author wushengixin * @param ... 参看结构体说明 可传入任意的个数的,形式为 .file = "xxx",.mode = "x" 的参数 * function 用于使用默认参数,并调用函数 Fopen 进行打开操作 */ #define Fopen_s(...) Fopen((params){.file = NULL,.mode = "r",__VA_ARGS__}) FILE* Fopen(const params file_open); /** * @version 1.0 2015/10/01 * @author wushengxin * param sizes 输入需要分配的大小 * function 用于隐藏一些对错误的处理,并调用malloc库函数分配空间 */ void * Malloc_s(size_t sizes); /** * @version 1.0 2015/10/01 * @author wushengxin * @param input 外部传入的等待释放的指针 * function 用于隐藏一些对错误的处理,并调用free库函数进行释放指针 */ void Free_s(void * input); 里面用到了一些新的特性,如果使用 GCC/Clang作为编译器的,记得要开启-std=c11 支持。 这几个函数就不再详细解释,而是简略说几个,接下来放上实现代码: FILE* Fopen(const params file_open) { int times = 0; FILE* ret_p = NULL; if (file_open.file == NULL) { fputs("The File Name is EMPTY! Comfirm it and Try Again",stderr); return ret_p; } setjmp(fopn_jmp); /* fopn_jmp To there */ ret_p = fopen(file_open.file,file_open.mode); if (ret_p == NULL) { if (times++ < TRY_TIMES) longjmp(fopn_jmp,0); /* fopn_jmp From here */ fprintf(stderr,"The File : %s Open with Mode (%s) Fail!n",file_open.file,file_open.mode); } return ret_p; } void * Malloc_s(size_t sizes) { int times = 0; void * ret_p = NULL; if (sizes == 0) return NULL; setjmp(malc_jmp); /* malc_jmp To There */ ret_p = malloc(sizes); if (ret_p == NULL) { if (times++ < TRY_TIMES) /* malc_jmp From Here */ longjmp(malc_jmp,0); fputs("Allocate Memory Fail!",stderr); } return ret_p; } void Free_s(void * input) { if (input == NULL) { #if !defined(NOT_DEBUG_AT_ALL) fputs("Sent A NULL pointer to the Free_s Function!", stderr); #endif return; } free(input); input = NULL; } 第一个函数是用外部定义的宏 `Fopen_s`启动它,这里没有实现隐藏它。 最后一个函数中使用了预处理的机制,如果在头文件中定义了 `#define NOT_DEBUG_AT_ALL`,这个输出将不在出现 setPath.h 我们首先要将程序里保存上默认的目的路径,首先想到用常量#define ... #include "safeFunc.h" #define SELF_LOAD_DEFAULT_PATH "C:/" #define MIN_PATH_NAME _MAX_PATH /* 最小的限制 */ #define LARGEST_PATH_NAME 32767 /* 路径的最大限制 */ /* * @version 1.0 2015/10/02 * @author wushengxin * @function 用于返回当前使用的目的路径 */ const char * get_backup_topath(); /** * @version 1.0 2015/09/28 * @author wushengxin * @param src 外部传入的,用于调整 * @function 用于替换路径中的 / 为 的 */ void repl_str(char * src); 对应的实现中,会定义一个静态的字符数组,且在头文件中能够看见,很多是在`showFiles`里定义过的。 定义过的函数,例如 `repl_str`需要把`showFiles.c`中的**实现**,使用`#if 0 ... #endif` 进行注释掉,不然会发生重定义的错误。 #include "setPath.h" static char to_path_buf[LARGEST_PATH_NAME] = SELF_LOAD_DEFAULT_PATH; const char * DEFAULT_TO_PATH = SELF_LOAD_DEFAULT_PATH; const int LARGEST_PATH = LARGEST_PATH_NAME; const char * get_backup_topath() { return to_path_buf; } void repl_str(char * src) { size_t length = strlen(src); for (size_t i = 0; i <= length; ++i) { if (src[i] == '/') src[i] = ''; } return; } 有了上面的代码,主界面就再次能够无误运行了,那么剩下的就是实现,设置目的路径,存储目的路径到本地,显示目的路径,分别对应主界面的2,3。 存储目的路径 store_hist_path setPath.h #include <time.h> /** * @version 1.0 2015/10/02 * @version wushengxin * @param path 需要存储的路径 * @function 用于存储路径到本地文件 "show_hist" 和 "use_hist" */ void store_hist_path(const char * path); setPath.c void store_hist_path(const char * path) { time_t ctimes; time(&ctimes); /* 获取时间 */ FILE* input_use = Fopen_s(.file = "LastPath.conf",.mode = "w"); /* 每次写入覆盖 */ FILE* input_show = Fopen_s(.file = "PathHistory.txt",.mode = "a"); if (!input_show || !input_use) { #if !defined(NOT_DEBUG_AT_ALL) fputs("Open/Create the File Fail!",stderr); #endif return; } fprintf(input_use,"%sn",path); /* 写入 */ fprintf(input_show,"%s %s",path,ctime(&ctimes)); fclose(input_show); fclose(input_use); return; } `time`和`ctime` 函数的使用网路上的介绍更加全面,这里不做解释。 完成了存储的函数之后,便是实现从键盘读取并且设置默认路径 在此处需要停下来在此思考一下,如果用户输入了错误的路径(无效路径或者恶意路径),也应该被读取吗?所以应该增加一个检查,用于确认路径的有效性。 #include <string.h> #include <io.h> /* _access */ enum {NOT_EXIST = 0,EXIST = 1}; /** * @version 1.0 2015/10/02 * @author wushengxin * @function 用于读取从键盘输入的路径并将之设置为默认路径,并存储。 */ void set_enter_path(); /** * @version 1.0 2015/10/02 * @author wushengxin * @param path 用于检查的路径 * @function 用于检查用户输入的路径是否是有效的 */ int is_valid_path(const char * path); setPath.c int is_valid_path(const char * path) {/* _access 后方有解释 */ if (_access(path,0) == 0) /* 是否存在 */ return EXIST; else return NOT_EXIST; } void set_enter_path() { int intJudge = 0; /* 用来判断是否决定完成输入 */ char tmpBuf[LARGEST_PATH_NAME]; /** 临时缓冲区 **/ while (1) { printf("Enter The Path You want!n"); fgets(tmpBuf,LARGEST_PATH_NAME*sizeof(char),stdin); /* 获取输入的路径 */ sscanf(tmpBuf,"%s",to_path_buf); if (is_valid_path(to_path_buf) == NOT_EXIST) { fprintf(stderr,"Your Enter is Empty,So Load the Default Pathn"); fprintf(stderr,"%s n",SELF_LOAD_DEFAULT_PATH); strcpy(to_path_buf,SELF_LOAD_DEFAULT_PATH); } fprintf(stdout,"Your Enter is " %s " ?(1 for yes,0 for no) n",to_path_buf); fgets(tmpBuf,stdin); sscanf(tmpBuf,"%d",&intJudge); /* 获取判断数的输入 */ if (intJudge != 0) { if (to_path_buf[strlen(to_path_buf) - 1] != '/') strcat(to_path_buf,"/");/* 如果最后一个字符不是'/',则添加,这里没考虑是否越界 */ store_hist_path(to_path_buf); break; } /* if(intJudge) */ }/* while (1) */ return; }/* set_enter_path */ 这一组函数的功能稍微复杂,大体来说便是 `读取路径输入->检查路径有效性->读取判断数->是否结束循环` 其中`_access` 函数有些渊源,因为这个函数被大家所熟知的是这个形式 `access`,但由于这个形式是 **POSIX** 标准,故 **Windows** 将其实现为`_access`,用法上还是一样的,就是名字不同而已。 setPath.h /** * @version 1.0 2015/10/02 * author wushengxin * function 用于在窗口显示所有的历史路径 */ void show_hist_path(); setPath.c void show_hist_path() { system("cls"); char outBufName[LARGEST_PATH_NAME] = {' '}; FILE* reading = Fopen_s(.file = "PathHistory.txt",.mode = "r"); if (!reading) return; for (int i = 1; i <= 10 && (!feof(reading)); ++i) { fgets(outBufName,reading); fprintf(stdout,"%2d. %s",i,outBufName); } fclose(reading); system("pause"); return; } 剩下最后一个收尾工作 初始化路径 setPath.h /** * @versions 1.0 2015/10/02 * @author wushengxin * @function 用于每次程序启动时初始化目的路径 */ void init_path(); setPath.c void init_path() { int len = 0; char last_path[LARGEST_PATH_NAME] = { ' ' }; FILE* hist_file = Fopen_s(.file = "LastPath.conf",.mode = "r"); if (!hist_file) /* 打开失败则不初始化 */ return; fgets(last_path,LARGEST_PATH_NAME,hist_file); len = strlen(last_path); if (len > 1) { last_path[len - 1] = ' '; /* 消除一个多余的 ‘n' */ strcpy(to_path_buf,last_path); } return; } 这样就大功告成了,对于这个函数中的后`8`行代码,没使用惯用的`fgets 配合 sscanf` 是因为如果这么干的话,需要搭配一个`memset`函数清零,后面会有解释。 对于memset的解释 写在中间 上面完成了界面的大部分功能,剩下的便是备份这个主要功能。 既然是备份,如果不想扩展为多线程的形式,参考第一次写的遍历函数(show_structure)直接找到文件便调用Windows API(稍后介绍)进行复制即可,不需要讲待备份的文件路径保存下来。
使用一些面向对象的黑魔法,保存一些操作函数防止代码混乱。
考虑到要存储的是字符串,并且由于Windows API的参数需求,对于一个文件,我们需要存储的路径有两个<源路径,目的路径>,对此应该再使用一个路径模型结构体包裹他们,则空间的类型就相应改变一下 Queue.h typedef struct _vector_queue queue; typedef struct _combine combine; | 返回值 | | 函数类型名 || 参数类型 | typedef int (*fpPushBack)(queue * __restrict,const char * __restrict,const char * __restrict); typedef const combine * (*fpPopFront)(queue *); typedef void (*fpDelete)(queue *); 五个typedef不知道有没有眼前一懵。,希望能够很好的理解 前两个是结构体的声明,分别对应着 队列模型 和 路径模型。 后两个是函数指针,作用是放在结构体里,使C语言的结构体也能够拥有一些简单的面向对象功能,例如成员函数功能,原理就是可以给这些函数指针类型的变量赋值。稍后例子更加明显。试着解读一下,很简单的。 struct _combine{ char * src_from_path; /* 源路径 */ char * dst_to_path; /* 目的路径 */ }; struct _vector_queue{ combine ** path_contain; /* 存储路径的容器主体 */ unsigned int rear; /* 队尾坐标 */ unsigned int front; /* 队首坐标 */ int empty; /* 是否为空 */ unsigned int capcity; /* 容器的容量 */ fpPushBack PushBack; /* 将元素压入队尾 */ fpPopFront PopFront; /* 将队首出队 */ fpDelete Delete; /* 析构释放整个队列空间 */ }; /** * @version 1.0 2015/10/03 * @author wushengxin * @param object 外部传入的对象指针,相当于 this * @function 初始化队列模型,建立队列实体,分配空间,以及设置属性。 */ int newQueue(queue* object); 可以看到,上方的函数指针类型,被用在了结构体内,此处少了一个初始化函数,是因为不打算把他当作成员函数(借用面向对象术语) 在使用的时候可以直接obj_name.PushBack(...,...,...); 更详细的可以看后面的实现部分。成为成员函数的三个函数,将被实现为 static 函数,不被外界访问。 queue.c int newQueue(queue * object) { queue* loc_que = object; combine** loc_arr = NULL; loc_arr = (combine**)Malloc_s(CAPCITY * sizeof(combine*)); if (!loc_arr) return 1; loc_que->capcity = CAPCITY; /* 容量 */ loc_que->front = 0; /* 队首 */ loc_que->rear = 0; /* 队尾 */ loc_que->path_contain = loc_arr; /* 将分配好的空间,放进对象中 */ loc_que->PushBack = push_back; loc_que->PopFront = pop_front; loc_que->Delete = del_queue; return 0; } 在初始化函数中,可以看到,设置了队首队尾以及容量,分配了容器空间,配置了成员函数。 最后三句配置函数的语句中,push_back,pop_front,del_queue在后方以static 函数实现。 但是由于没有声明,所以切记要将三个static函数的实现放在newQueue的前方 /** * @version 1.0 2015/10/03 * @author wushengxin * @param object 外部传入的对象指针 相当于 this * @function 释放整个队列实体的空间 */ static void del_queue(queue * object) { Free_s(object->path_contain); return; } /** * @version 1.0 2015/10/03 * @author wushengxin * @param object 外部传入的对象指针 相当于 this src 源路径 dst 目的路径 * @function 将外部传入的<源路径,目的路径> 存入队列中 */ static int push_back(queue * __restrict object,const char * __restrict src,const char * __restrict dst) { int times = 0; char* loc_src = NULL; /* 本地变量,尽量利用寄存器以及缓存 */ char* loc_dst = NULL; combine* loc_com = NULL; queue* loc_que = object; size_t len_src = strlen(src); /* 获取路径长度 */ size_t len_dst = strlen(dst); size_t rear = loc_que->rear; /*获取队尾*/ size_t front = loc_que->front; /*获取队首*/ loc_src = Malloc_s(len_src + 1); /* 分配空间 */ if (!loc_src) return 1; loc_dst = Malloc_s(len_dst + 1); if (!loc_dst) return 2; strcpy(loc_src,src); strcpy(loc_dst,dst); loc_com = Malloc_s(sizeof(combine)); if (!loc_com) return 3; loc_com->dst_to_path = loc_dst; loc_com->src_from_path = loc_src; loc_que->path_contain[rear++] = loc_com; /* 将本地路径加入实体 */ loc_que->rear = (rear % CAPCITY); /* 用数组实现循环队列的步骤 */ if (loc_que->rear == loc_que->front) loc_que->empty = 0; return 0; } /** * @version 1.0 2015/10/03 * @author wushengxin * @param object 外部传入的对象指针 */ static const combine * pop_front(queue* object) { size_t loc_front = object->front; /*获取当前队首*/ combine* loc_com = object->path_contain[loc_front]; /*获取当前文件名*/ object->path_contain[loc_front] = NULL; /*出队操作*/ object->front = ((object->front) + 1) % 20; /*完成出队*/ if (object->front == object->rear) object->empty = 1; else object->empty = 0; return loc_com; } 一个一个的说这些函数 del_queue:释放函数,直接调用Free_s push_back:压入函数,将外部传入的两个原始的没有组成的路径字符串,组合成一个combine,并压入路径,每次都判断并置是否为空标志位,实际上这个函数中有累赘代码的嫌疑,应该再分出一个函数,专门用来分配三个空间,防止这个函数过长(接近40行) pop_front:弹出函数,将队列的队首combine弹出,用于复制,但是这里有一个隐患,就是要将释放的工作交给外者,如果疏忽大意的话,隐患就是内存泄漏。 没有特地的提供一个接口,用来判断是否为空,因为当编译器一优化,也会将这种接口给优化成直接使用成员的形式,某种形式上的内联。 队列模型设计完毕,可以开始设计备份模型 二级界面 思考一下,这个界面要做什么 /** * @version 1.0 2015/10/03 * @author wushengxin * function 显示二级界面 */ void sec_main_windows(); backup.c void sec_main_windows() { char tmpBuf[256]; int selects; do{ setjmp(select_jmp); system("cls"); puts("-------------------1. Back Up------------------ "); puts(" For This Select,You can choose Two Options: "); puts(" 1. Start Back up (The Directory Path That You Enter LATER) "); puts(" 2. Back To last level "); puts("----------------------------------------------- "); fprintf(stdout,"Enter Your Selection: "); fgets(tmpBuf,256,stdin); sscanf(tmpBuf,&selects); if (selects != 1 && selects != 2 ) { fprintf(stdout,"n Your Select " %s " is Invalid!n Try Again n",tmpBuf); longjmp(select_jmp,1); } switch (selects) { jmp_buf enter_path_jmp; case 1: { char tmpBuf[LARGEST_PATH],tmpPath[LARGEST_PATH]; /* 使用栈分配空间,因为只用分配一次 */ setjmp(enter_path_jmp); /* enter jump to there */ puts(" Enter the Full Path You want to BackUp(e.g: C:/Programing/)"); fprintf(stdout," Or Enter q to back to selectnYour Enter : "); fgets(tmpBuf,LARGEST_PATH,tmpPath); if (_access(tmpPath,0) != 0) /*检查路径是否存在,有效*/ { if (tmpPath[0] == 'q' || tmpPath[0] == 'Q') longjmp(select_jmp,0); /* 回到可以选择返回的界面 */ fprintf(stderr,"The Path You Enter is Not Exit! n Try Again : "); longjmp(enter_path_jmp,0); /* enter jump from here */ } } break; case 2: return; default: break; }/* switch */ } while (1); return; } 这个函数只说几点,首先是`switch`的`case 1`,之所以用**花括号**包裹起来的原因是,这样才能在里面定义**本地变量**,直接在冒号后面定义是**编译错误**,这个特性可能比较少用,这里提一下,前面也有说过。 剩下的就是编写主要的功能函数和线程调用函数了。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- c# – 我可以将foreach和LINQ查询组合成一个吗?
- c# – protobuf和List – 如何序列化/反序列化?
- SQLite入门与分析(二)---设计与概念
- 正则表达式 – 正则表达式匹配两个单词之间的所有内容
- for-loop – Makefile:for循环和中断错误
- ruby-on-rails – Rails&Oauth:多个提供商
- c# – 如何在Dynamic Linq中编写String.Contains
- c# – 用于查询Active Directory的任务并行库(使用Director
- react-native – 在React Native中从父组件调用子函数
- 探讨6410的启动过程