Windows體系下應用C說話編寫單線程的文件備份法式。本站提示廣大學習愛好者:(Windows體系下應用C說話編寫單線程的文件備份法式)文章只能為提供參考,不一定能成為您想要的結果。以下是Windows體系下應用C說話編寫單線程的文件備份法式正文
寫在最後方
源途徑:即 From-Path,你預備要備份的材料
目標途徑: 即 To-Path,你預備要存貯備份的材料的處所
略微回憶一下,上一次寫的代碼,本次的義務是遍歷目次及其子目次,那末這回要干的就是將前次遍歷過的數據,挪一下窩,到我們想要他們去的地位。
這觸及到兩個操作,遍歷 和 拷貝,前一個舉措我們在上一回曾經完成了,只需做小小的修改,就可以夠應用。後一個舉措也是須要靠 Windows API來完成,至於哪些,稍後再提。
如今先讓我們完成一個魔法,3, 2, 1!:
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
斟酌一下我們須要包裹的函數: malloc, free, fopen 三個庫函數。
為了不讓前方的多線程完成發生更多的今後,不零丁應用全局毛病輸入。
讓我來將他們完成一下
我不會省略一些看似不用要的器械,例如正文,而是完全的出現出來,假如認為篇幅太長,可以選擇騰躍的浏覽。
魔法來了,3, 2, 1!
#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 ...
其次應當要確保以後目標途徑不被其他不法的渠道拜訪,那就應當用一個static 字符數組存儲。
接上去就是要供給一個函數看成接口(這裡用了接口這個術語不曉得合不適合),來獲得以後現實在應用的目標途徑 get_backup_topath。
這裡還須要將之前完成過的 repl_str ,再次完成一次,由於之前的顯示功效只是測試,其實不會現實運用到法式傍邊。
完成這兩個功效函數今後,再去斟酌完成怎樣樣設置途徑,存儲途徑,和應用文件流操作來緩存汗青目標途徑
#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` 停止正文失落,否則會產生重界說的毛病。
setPath.c
#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。
怎樣完成比擬好,再開端之前,剖析一下會碰到的情形:
我們在獲得目標途徑以後,會將其拷貝給默許途徑 to_path_buf,而且將其存儲到當地緩存文件中,以便下次法式開端時可以直接應用上一次的途徑
還可使用另外一個文件存儲一切用過的汗青途徑,包括時光信息。
那末這就請求我們起首完成存儲目標途徑的功效,其次再完成設置目標途徑的功效,最初完成顯示目標途徑的功效
注:兩個看似無用的全局變量(const)是為了其他文件的可見性而設立的,且絕對於#define可以或許省一些舉足輕重的空間。
存儲目標途徑 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, "%s\n", path); /* 寫入 */ fprintf(input_show, "%s %s", path, ctime(&ctimes)); fclose(input_show); fclose(input_use); return; }
`time`和`ctime` 函數的應用網路上的引見加倍周全,這裡不做說明。
完成了存儲的函數以後,就是完成從鍵盤讀取而且設置默許途徑
設置目標途徑 set_enter_path
在此處須要停上去在此思慮一下,假如用戶輸出了毛病的途徑(有效途徑或許歹意途徑),也應當被讀取嗎?所以應當增長一個檢討,用於確認途徑的有用性。
setPath.h
#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 Path\n"); 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, LARGEST_PATH_NAME*sizeof(char), 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`,用法上照樣一樣的,就是名字分歧罷了。
顯示汗青途徑 show_hist_path
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] = {'\0'}; FILE* reading = Fopen_s(.file = "PathHistory.txt", .mode = "r"); if (!reading) return; for (int i = 1; i <= 10 && (!feof(reading)); ++i) { fgets(outBufName, LARGEST_PATH_NAME*sizeof(char), reading); fprintf(stdout, "%2d. %s", i, outBufName); } fclose(reading); system("pause"); return; }
剩下最初一個掃尾任務
初始化途徑
每次法式啟動的時刻,我們都邑讀取當地文件,獲得上一次法式應用的最初一個途徑,作為以後應用的目標途徑
初始化目標途徑 init_path
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] = { '\0' }; 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] = '\0'; /* 清除一個過剩的 ‘\n' */ strcpy(to_path_buf, last_path); } return; }
如許就年夜功樂成了,關於這個函數中的後`8`行代碼,沒應用習用的`fgets 合營 sscanf` 是由於假如這麼干的話,須要搭配一個`memset`函數清零,前面會有說明。
關於memset的說明
這個函數關於年夜的內存塊的初始化現實上是很慢的,固然我們這個30KB閣下年夜概的內存能夠影響還沒有那末年夜,然則上兆今後,挪用memset就是一種機能成績了,許多情形下,編譯器在開啟高優化品級以後會主動幫你撤消memset的隱式挪用
甚麼隱式挪用,例如 init_path的第二行代碼,聲明而且用花括號初始化這個數組的時刻,就會挪用隱式memset。
寫在中央
下面完成了界面的年夜部門功效,剩下的就是備份這個重要功效。
在完成備份之前,起首想一想要若何結構這個備份模子
既然是備份,假如不想擴大為多線程的情勢,參考第一次寫的遍歷函數(show_structure)直接找到文件便挪用Windows API(稍後引見)停止復制便可,不須要講待備份的文件途徑保留上去。
假如要斟酌多線程擴大,我們就須要從長計議。
關於一個備份模子,最好的莫過於應用一個隊列,照舊實施的是遍歷形式,然則將找到的文件途徑保留,並放入一個先輩先出的隊列中,如許我們就可以夠包管在擴大成多線程的時刻,可以有一個很清楚的模子參考。
那末如今的義務就是完成這個用於備份的隊列模子。
隊列模子
應用一些面向對象的黑魔法,保留一些操作函數避免代碼凌亂。
斟酌到要存儲的是字符串,而且因為Windows API的參數需求,關於一個文件,我們須要存儲的途徑有兩個<源途徑,目標途徑>,對此應當再應用一個途徑模子構造體包裹他們,則空間的類型就響應轉變一下
新建 Queue.h Queue.c
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彈出,用於復制,然則這裡有一個隱患,就是要將釋放的任務交給外者,假如忽視年夜意的話,隱患就是內存洩露。
沒有專程的供給一個接口,用來斷定能否為空,由於當編譯器一優化,也會將這類接口給優化成直接應用成員的情勢,某種情勢上的內聯。
隊列模子設計終了,可以開端設計備份模子
備份模子可以回憶一下之前的遍歷函數,年夜體的構造一樣,只是此處為了擴大成多線程,須要添加一些多線程的挪用函數,和為了規格化,須要添加一個二級界面
先設計一下二級界面
二級界面
思慮一下,這個界面要做甚麼
選擇能否開端備份
而且源途徑須要在此處輸出
前往上一級
新建 backup.h backup.c 文件
在主界面選擇 1 今後就會挪用二級界面的函數
列出二級界面的選項
1 Start Back up
2 Back To last level
backup.h
/** * @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, "%d", &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 select\nYour Enter : "); fgets(tmpBuf, LARGEST_PATH, stdin); sscanf(tmpBuf, "%s", 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`,之所以用**花括號**包裹起來的緣由是,如許能力在外面界說**當地變量**,直接在冒號前面界說是**編譯毛病**,這個特征能夠比擬罕用,這裡提一下,後面也有說過。
寫在最初方
剩下的就是編寫重要的功效函數和線程挪用函數了。