好吧,如果硬要說(shuō)的官方一點(diǎn)那就是:
創(chuàng)建一個(gè)入口源文件Entery.c,用于放置main函數(shù)
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
system("pause"); /* 因?yàn)閃indows的命令行命令對(duì)大小寫(xiě)不敏感,所以命令無(wú)所謂大小寫(xiě),從環(huán)境變量的配置就能知道這一點(diǎn) */
return 0;
}
因?yàn)榈纫幌滦枰獦?gòu)造一個(gè)字符顯示界面模擬GUI的功能,故添加`#include <stdlib.h>`
當(dāng)然并不是只因?yàn)檫@個(gè)原因。但是現(xiàn)在是為了使用其中的`system()`函數(shù)調(diào)用系統(tǒng)命令。
等一下,要直接寫(xiě)程序了嗎?程序功能呢?程序結(jié)構(gòu)呢?從哪里開(kāi)始寫(xiě)呢?
所以,停下來(lái)我們好好構(gòu)思構(gòu)思!
回答:
問(wèn)題:
題外話(huà)
首先我們需要實(shí)現(xiàn)一個(gè)功能,就是遍歷輸出路徑下的所有文件夾和文件
在這之前,我們先設(shè)計(jì)一下界面,稍后...3, 2, 1,出現(xiàn)!
while(1)
{
system("cls"); /* 系統(tǒng)的清屏命令 */
do{
puts("-------------------------------------------------");
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. Read Me ");
puts("4. Exit ");
puts("-------------------------------------------------");
puts("Your Select: ");
fscanf(stdin, "%d", &select);
getchar(); /* 讀取上方 fscanf 留在流里面的換行符 '\n' */
}while ((select < 1) || (select > 4)); /* 如果選擇無(wú)效 */
system("cls");
switch(select)
{
case 1 :
break;
case 2 :
break;
case 3 :
break;
case 4 :
exit(0); /* 退出程序 */
default :
break;
}/** switch(select) **/
}/** while(1) **/
system("pause");
return 0;
突然出現(xiàn)的這一段代碼就是設(shè)計(jì)的界面,其實(shí)很簡(jiǎn)單,看看就懂了,不再多說(shuō)。
英文莫怪。
緊接著,我們來(lái)實(shí)現(xiàn)第一個(gè)功能,顯示結(jié)構(gòu),讓我們吧這個(gè)功能函數(shù)叫做show_structure
新建頭文件showFiles.h
/*頭文件包裹一定要切記*/
#ifndef INCLUDE_SHOWFILES_H
#define INCLUDE_SHOWFILES_H
/* 代碼寫(xiě)在里面,這樣就不會(huì)發(fā)生重定義,也能節(jié)省資源 */
#endif
新建源文件showFiles.c
#include <showFiles.h>showFiles.h 稍后進(jìn)行解釋
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h> /* Windows API */
#include <string.h> /* 字符串函數(shù) */
#include <tchar.h> /* _ftprintf */
#include <setjmp.h> /* setjmp, longjmp */
#define TRY_TIMES 3 /* 重新嘗試獲取的最大次數(shù) */
#define MIN_PATH_NAME _MAX_PATH /* 最小的限制 */
#define LARGEST_PATH_NAME 32767 /* 路徑的最大限制 */
/* 我們需要在這里面包含函數(shù)的聲明 */
/** 加上文檔注釋?zhuān)惶矚g死板的硬套,選擇自己覺(jué)得重要的信息記錄吧
* @version 1.0 2015/09/28
* @author wushengxin
* @param from_dir_name 源目錄,即用于掃描的目錄
depth 遞歸的深度,用于在屏幕上格式化輸出,每次增加 4
* @function 用于輸出顯示目錄結(jié)構(gòu)的,可以改變輸出流
*/
void show_structure(const char * from_dir_name, int depth);
題外話(huà),在Visual Studio中,會(huì)強(qiáng)制要求你使用他們編寫(xiě)的安全函數(shù),例如fscanf_s,如果你不想用的話(huà),那就將它關(guān)閉吧,具體怎么操作,就當(dāng)是一個(gè)小問(wèn)題留給你自己。
showFiles.c 先不寫(xiě)太多,這里比較重要的是寫(xiě)法
/* 首先是需要的一系列變量 */
int times = 0; /* 用來(lái)配合 setjmp和longjmp重新獲取句柄HANDLE的 */
/** 操作時(shí)獲取文件夾,文件信息的的必要變量 **/
HANDLE file_handle;
WIN32_FIND_DATAA file_data;
LARGE_INTEGER file_size;
`file_handle` : 文件的句柄,后期操作的主要對(duì)象
`file_data` : 文件的信息,各種屬性
`file_size` : 文件的大小有可能非常大,需要使用特定的結(jié)構(gòu)體保存
到這里我們停下來(lái),因?yàn)橄乱徊轿覀円?shí)現(xiàn)獲取路徑的操作。
回答:
2~3GB的分配空間(4GT機(jī)制),64位就更為可怕了(Windows8 最大有512GB, Windows7 最大有 192GB,服務(wù)器系列大概是1~2TB)。速度較慢對(duì)于微軟API處理自己的Windows路徑,一般要求末尾以/或者\結(jié)尾,前者在C語(yǔ)言中不是轉(zhuǎn)義,所以比較好存儲(chǔ),如果需要使用后者,可以選擇如此\\就行了。
char dir_path_1[PATH_MAX] = "../";
char dir_path_2[PATH_MAX] = "..\\";
/* 兩種效果一致,且占的空間也相同 */
windef.h中定義了一個(gè)常量PATH_MAX的值為260,也就是說(shuō)最大的路徑長(zhǎng)路為260字節(jié),但是如果我們的路徑名超過(guò)了這個(gè)長(zhǎng)度怎么辦呢?\\?\,這樣長(zhǎng)度限制就增加到了32767了解決了上一個(gè)關(guān)于路徑的問(wèn)題,我們就需要考慮一下如何設(shè)計(jì)實(shí)現(xiàn)這個(gè)功能,首先要達(dá)到模塊化的目的,即盡量減少每個(gè)函數(shù)的功能。
showFiles.h 變個(gè)魔術(shù) 3, 2, 1,添加了如下代碼
/**
* @version 1.0 2015/09/28
* @author wushengxin
* @param src 外部傳進(jìn)來(lái)的,用于向所分配空間中填充的路徑
* @function 用于在堆上為存儲(chǔ)的路徑分配空間。
*/
char * make_path(const char * src);
/*
* @version 1.0 2015/09/28
* @author wushengxin
* @param src 外部傳進(jìn)來(lái)的,是由 make_path 所分配的空間,將其釋放
* @function 用于釋放 make_path 分配的內(nèi)存空間
*/
void rele_path(char * src);
/*
* @version 1.0 2015/09/28
* @author wushengxin
* @param src_path 用于 FindFirstFile()
src_file 用于添加找到的目錄名,形成新的目錄路徑
* @function 用于調(diào)整用戶(hù)從鍵盤(pán)輸入的路徑字符串,使他們變得一致,便于處理
*/
void adjust_path(char * __restrict src_path, char * __restrict src_file);
/*
* @version 1.0 2015/09/28
* @author wushengxin
* @param src 外部傳入的,用于調(diào)整
* @function 用于替換路徑中的 / 為 \ 的
*/
void repl_str(char * src);
具體功能在文檔里已經(jīng)寫(xiě)的很清楚了,唯一要解釋的就是最后兩個(gè)函數(shù),本來(lái)是一體的,后來(lái)被我拆開(kāi)成了兩個(gè)函數(shù),為了也是功能更加清晰
倒數(shù)第二個(gè)函數(shù) adjust_path 的作用是將路徑處理成符合 Windows 函數(shù) FindFirstFile 的要求可以具體看看。
showFiles.c 繼續(xù) show_structure 的實(shí)現(xiàn)
shows_tructure
size_t length = strlen(from_dir_name);
char * dir_buf = make_path(from_dir_name); //路徑
char * dir_file_buf = make_path(from_dir_name); //文件
if (dir_buf == NULL || dir_file_buf == NULL)
return; /* 如果分配失敗就結(jié)束函數(shù) */
adjust_path(dir_buf, dir_file_buf); /* 調(diào)整路徑和文件格式到標(biāo)準(zhǔn)格式 */
repl_str(dir_buf);
repl_str(dir_file_buf);
這是調(diào)用 `WINDOWS API` 之前的所有操作,來(lái)一一實(shí)現(xiàn)他們
首先是分配空間給路徑
make_path : 24 lines·
對(duì)于這個(gè)函數(shù)的功能便是,為需要存儲(chǔ)的路徑分配空間。
int times = 0;
size_t len_of_src = strlen(src); /* 需要分配的長(zhǎng)度 */
size_t len_of_dst = MIN_PATH_NAME;
if (len_of_src > MIN_PATH_NAME - 10) /* \\?\ //* 8個(gè)字符 */
{ /* 這里用了10這個(gè)神奇的垃圾數(shù),所以必須做一點(diǎn)注釋?zhuān)苑劳?*/
len_of_dst = LARGEST_PATH_NAME;
if (len_of_src > LARGEST_PATH_NAME - 10)
{
fprintf(stderr, "The Path Name is larger than 32767, Which is not Support!\n%s", src);
return NULL;
}
}
setjmp(alloc_jmp); /* alloc_jmp to here */
char * loc_buf = malloc(len_of_dst + 1);
if (loc_buf == NULL)
{
fprintf(stderr, "ERROR OCCUR When malloc the memory at %s\n Try the %d th times", __LINE__, times+1);
if (times++ < TRY_TIMES)
longjmp(alloc_jmp, 0); /* alloc_jmp from here */
return NULL;
}
//sprintf(loc_buf, "\\\\?\\%s", src); /* 作為日后的擴(kuò)展 */
strcpy(loc_buf, src);
return loc_buf;
對(duì)于 `10` 這個(gè)數(shù)的考慮是,至少留出 `8` 個(gè)空位給所說(shuō)的字符,加 `2` 湊整。
對(duì)于函數(shù) `malloc` ,在這里沒(méi)有進(jìn)行**包裹**,是因?yàn)檫@只是一個(gè)預(yù)熱的功能,后期在實(shí)現(xiàn)備份的時(shí)候,會(huì)對(duì)它進(jìn)行包裝,也使得錯(cuò)誤處理的代碼隱藏,讓函數(shù)功能更加清晰。
adjust_path : 16 lines
其次是調(diào)整路徑的函數(shù),功能就是調(diào)整路徑
size_t length = strlen(src_path); /* 兩個(gè)參數(shù)的長(zhǎng)度在此函數(shù)調(diào)用之前必定一致 */
if (length == 1) /* 處理情況為,當(dāng)用戶(hù)輸入的是根目錄的情況 例如: C */
{
strcat(src_file, ":/");
}
else if (src_path[length - 1] != '\\' && src_path[length - 1] != '/')
{
strcat(src_file, "/");
}
else
{
src_path[length - 1] = '/';
}
strcpy(src_path, src_file);
strcat(src_path, "/*");
return;
當(dāng)用戶(hù)輸入的是一個(gè)字符的根目錄,我們要將其處理為 `C:/` 這樣的形式
當(dāng)用戶(hù)輸入的是不帶`/`結(jié)尾的,我們需要將其添加上 `/`
當(dāng)用戶(hù)輸入以 `\` 結(jié)尾的路徑時(shí),將其替換為 `/`,雖然后方又全部換成了 `\`
將目錄處理為帶 `/*` 結(jié)尾的,以達(dá)到 **API** 的要求
`src_file` 用于將目錄下的子目錄名連接。生成新的目錄。
`src_path` 用于遞交給 **API** 掃描目錄下的所有文件和文件夾。
repl_str : 7 lines
size_t length = strlen(src);
for (size_t i = 0; i <= length; ++i)
{
if (src[i] == '/')
src[i] = '\\';
}
return;
不再贅述這個(gè)函數(shù)的功能
到此處,所有在第一個(gè) **Windows API** 之前調(diào)用的函數(shù)都實(shí)現(xiàn)了,接下來(lái)要做什么?
當(dāng)然是調(diào)用API函數(shù)啦
show_structure
/* 開(kāi)始調(diào)用 Windows API 獲取路徑下的文件和文件夾信息 */
setjmp(get_hd_jmp);
fileHandle = FindFirstFileA(dir_buf, &fileData);
if (fileHandle == INVALID_HANDLE_VALUE) /* 如果無(wú)法獲取句柄超過(guò)上限次數(shù),就退出 */
{
fprintf(stderr, "The Handle getting Failure! \n");
if (times++ < TRY_TIMES)
longjmp(get_hd_jmp, 0);
return;
}
對(duì)于這一段代碼的解釋?zhuān)鋵?shí)核心就是第二句代碼,其中的函數(shù) `FindFirstFileA`需要解釋一下。
在 **Windows API** 文檔 **MSDN** 中介紹的是 `FindFirstFile` ,但是**某些情況下**(定義了UNICODE宏,不知道有沒(méi)有記錯(cuò)),這個(gè)官方提供的接口會(huì)被定義(`#define`)成 `FindFirstFileW`,如果使用 `char *` 的 **ANSI** 字符串當(dāng)成參數(shù)的話(huà)是會(huì)獲取句柄失敗的!并且另一個(gè)參數(shù)使用的 `file_data` 類(lèi)型也是 **ANSI** 的 `WIN32_FIND_DATAA`
所以這里**顯式地選擇調(diào)用** `FindFirstFileA` 而不是讓 **Windows** 幫我們選擇。
**接下來(lái)我們要做的事情就是,遍歷這個(gè)目錄下的所有文件和文件夾,提取出來(lái)他們的信息:**
do{
char * tmp_dir_file_buf = make_path(dir_file_buf);
if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{ /* 如果是文件夾 */
fprintf(stderr, "%*s%s\t<DIR>\t\n", depth, "", fileData.cFileName);
if (strcmp(fileData.cFileName, ".") == 0 || /* . 和 .. 便是當(dāng)前文件夾和上一級(jí)文件夾, 世界上所有系統(tǒng)都一樣 */
strcmp(fileData.cFileName, "..") == 0)
continue;
strcat(tmp_dir_file_buf, fileData.cFileName); /* 將文件名連接到當(dāng)前文件夾路徑之后,形成文件路徑 */
show_structure(tmp_dir_file_buf, depth + 4);
}
else
{
fileSize.LowPart = fileData.nFileSizeLow; /* 輸出大小 */
fileSize.HighPart = fileData.nFileSizeHigh;
fprintf(stderr, "%*s%s \t%ld bytes\t\n", depth, "", fileData.cFileName,
fileSize.QuadPart);
}
rele_path(tmp_dir_file_buf); /* 這個(gè)的實(shí)現(xiàn)稍后放出 */
} while (FindNextFileA(fileHandle, &fileData));
代碼是仿照 **MSDN** 提供的 [官方例子](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364418(v=vs.85).aspx)改寫(xiě)的。
其中第五句代碼,用到了前方提到過(guò)的格式化輸出的一個(gè)不常用的技巧,占位,忘記的可以回去看看在**第一章**
采用的方法是遞歸,循環(huán)的方法留給看者自己實(shí)現(xiàn),思路很簡(jiǎn)單,用一個(gè)隊(duì)列或者棧存放所有找到的目錄和文件,依次取出直到棧或者隊(duì)列為空。
以及最后一段的代碼,用于收尾:
FindClose(fileHandle);
rele_path(dir_buf);
rele_path(dir_file_buf);
return;
rele_path : 4 lines
free(src);
src = NULL;
return;
最后在 Entry.c 的 main 函數(shù)中,在 switch 的 case 1 標(biāo)簽范圍內(nèi),加上一些獲取和處理輸入的函數(shù) : (因?yàn)檫@里只會(huì)使用一次,故采用的是系統(tǒng)棧而不是在堆上分配)
char tmp[_MAX_PATH];
...
case 1 :
scanf("%s", tmp);
printf("Enter : %s\n", tmp);
getchar(); /* 前方提到過(guò),作用是清理標(biāo)準(zhǔn)輸入流 */
show_structure(tmp, 0);
system("pause");
break;
260字符的路徑?jīng)]有測(cè)試,大概能猜到是不行的。但是解決方案上方也有提到。