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`,之所以用**花括號**包裹起來的緣由是,如許能力在外面界說**當地變量**,直接在冒號前面界說是**編譯毛病**,這個特征能夠比擬罕用,這裡提一下,後面也有說過。
寫在最初方
剩下的就是編寫重要的功效函數和線程挪用函數了。