現(xiàn)在先讓我們完成一個魔法,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("-------------------------------------------------");
對界面稍微有了一些改動。
新增了第三行和第四行的 系統(tǒng)默認目的路徑和當(dāng)前使用的目的路徑。
新增了倒數(shù)第四行的查看目的路徑歷史紀錄的功能。
在main函數(shù)外頭需要 extern DEFAULT_TO_PATH;因為引用了setPath.c里的一個全局變量。
前一次我們曾經(jīng)提到要讓函數(shù)的功能更加清晰,為了達到這個目的,應(yīng)該把可能用到的一些原生庫函數(shù)包裹一下,讓可能發(fā)生的錯誤盡量掌握在我們自己的手里
安全函數(shù)
safeFunc.h safeFunc.c考慮一下我們需要包裹的函數(shù): malloc, free, fopen 三個庫函數(shù)。
魔法來了,3, 2, 1!
#include <stdio.h> /* size_t */
#include <stdlib.h>
#include <setjmp.h>
#define TRY_TIMES 3
typedef struct _input_para{
char * file; /* 待打開或創(chuàng)建的文件名 */
char * mode; /* 打開的模式 */
}params;
jmp_buf malc_jmp; /*Malloc_s*/
jmp_buf fopn_jmp; /*Fopen*/
/**
* @version 1.0 2015/10/01
* @author wushengixin
* @param ... 參看結(jié)構(gòu)體說明
可傳入任意的個數(shù)的,形式為 .file = "xxx", .mode = "x" 的參數(shù)
* function 用于使用默認參數(shù),并調(diào)用函數(shù) 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 用于隱藏一些對錯誤的處理,并調(diào)用malloc庫函數(shù)分配空間
*/
void * Malloc_s(size_t sizes);
/**
* @version 1.0 2015/10/01
* @author wushengxin
* @param input 外部傳入的等待釋放的指針
* function 用于隱藏一些對錯誤的處理,并調(diào)用free庫函數(shù)進行釋放指針
*/
void Free_s(void * input);
里面用到了一些新的特性,如果使用 GCC/Clang作為編譯器的,記得要開啟-std=c11 支持。
這幾個函數(shù)就不再詳細解釋,而是簡略說幾個,接下來放上實現(xiàn)代碼:
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;
}
第一個函數(shù)是用外部定義的宏 `Fopen_s`啟動它,這里沒有實現(xiàn)隱藏它。
最后一個函數(shù)中使用了預(yù)處理的機制,如果在頭文件中定義了 `#define NOT_DEBUG_AT_ALL`,這個輸出將不在出現(xiàn)
安全函數(shù)已經(jīng)撰寫完成,接下來就是干正事了
setPath.h
#define ...static 字符數(shù)組存儲。get_backup_topath。repl_str ,再次實現(xiàn)一次,因為之前的顯示功能只是測試,并不會實際應(yīng)用到程序當(dāng)中。完成這兩個功能函數(shù)以后,再去考慮實現(xiàn)怎么樣設(shè)置路徑,存儲路徑,以及使用文件流操作來緩存歷史目的路徑
#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 用于返回當(dāng)前使用的目的路徑
*/
const char * get_backup_topath();
/**
* @version 1.0 2015/09/28
* @author wushengxin
* @param src 外部傳入的,用于調(diào)整
* @function 用于替換路徑中的 / 為 \ 的
*/
void repl_str(char * src);
對應(yīng)的實現(xiàn)中,會定義一個靜態(tài)的字符數(shù)組,且在頭文件中能夠看見,很多是在`showFiles`里定義過的。
定義過的函數(shù),例如 `repl_str`需要把`showFiles.c`中的**實現(xiàn)**,使用`#if 0 ... #endif` 進行注釋掉,不然會發(fā)生重定義的錯誤。
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)是為了其他文件的可見性而設(shè)立的,且相對于#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` 函數(shù)的使用網(wǎng)路上的介紹更加全面,這里不做解釋。
完成了存儲的函數(shù)之后,便是實現(xiàn)從鍵盤讀取并且設(shè)置默認路徑
設(shè)置目的路徑 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 用于讀取從鍵盤輸入的路徑并將之設(shè)置為默認路徑,并存儲。
*/
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]; /** 臨時緩沖區(qū) **/
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); /* 獲取判斷數(shù)的輸入 */
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 */
這一組函數(shù)的功能稍微復(fù)雜,大體來說便是 `讀取路徑輸入->檢查路徑有效性->讀取判斷數(shù)->是否結(jié)束循環(huán)`
其中`_access` 函數(shù)有些淵源,因為這個函數(shù)被大家所熟知的是這個形式 `access`,但由于這個形式是 **POSIX** 標準,故 **Windows** 將其實現(xiàn)為`_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;
}
這樣就大功告成了,對于這個函數(shù)中的后`8`行代碼,沒使用慣用的`fgets 配合 sscanf` 是因為如果這么干的話,需要搭配一個`memset`函數(shù)清零,后面會有解釋。
memset的解釋
30KB左右大概的內(nèi)存可能影響還沒有那么大,但是上兆以后,調(diào)用memset就是一種性能問題了,很多情況下,編譯器在開啟高優(yōu)化等級之后會自動幫你取消memset的隱式調(diào)用init_path的第二行代碼,聲明并且用花括號初始化這個數(shù)組的時候,就會調(diào)用隱式memset。