对于线程的概念以及意义,我说一些
好吧,如果硬要说的官方一点那就是:
看完上面的解释,就准备开始着手写程序了
创建一个入口源文件Entery.c
,用于放置main
函数
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
system("pause"); /* 因为Windows的命令行命令对大小写不敏感,所以命令无所谓大小写,从环境变量的配置就能知道这一点 */
return 0;
}
因为等一下需要构造一个字符显示界面模拟GUI的功能,故添加`#include <stdlib.h>`
当然并不是只因为这个原因。但是现在是为了使用其中的`system()`函数调用系统命令。
好,那么接下来...
**等一下,要直接写程序了吗?**程序功能呢?程序结构呢?从哪里开始写呢?
所以,停下来我们好好构思构思!
问题:
回答:
问题:
回答:
题外话
首先我们需要实现一个功能,就是遍历输出路径下的所有文件夹和文件
在这之前,我们先设计一下界面,稍后...3, 2, 1
,出现!
while(1)
{
system("cls"); /* 系统的清屏命令 */
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)); /* 如果选择无效 */
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;
突然出现的这一段代码就是设计的界面,其实很简单,看看就懂了,不再多说。
英文莫怪。
紧接着,我们来实现第一个功能,显示结构,让我们吧这个功能函数叫做show_structure
新建头文件showFiles.h
/*头文件包裹一定要切记*/
#ifndef INCLUDE_SHOWFILES_H
#define INCLUDE_SHOWFILES_H
/* 代码写在里面,这样就不会发生重定义,也能节省资源 */
#endif
新建源文件showFiles.c
- 记得包含头文件#include <showFiles.h>
showFiles.h
稍后进行解释
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h> /* Windows API */
#include <string.h> /* 字符串函数 */
#include <tchar.h> /* _ftprintf */
#include <setjmp.h> /* setjmp, longjmp */
#define TRY_TIMES 3 /* 重新尝试获取的最大次数 */
#define MIN_PATH_NAME _MAX_PATH /* 最小的限制 */
#define LARGEST_PATH_NAME 32767 /* 路径的最大限制 */
/* 我们需要在这里面包含函数的声明 */
/** 加上文档注释,不太喜欢死板的硬套,选择自己觉得重要的信息记录吧
* @version 1.0 2015/09/28
* @author wushengxin
* @param from_dir_name 源目录,即用于扫描的目录
depth 递归的深度,用于在屏幕上格式化输出,每次增加 4
* @function 用于输出显示目录结构的,可以改变输出流
*/
void show_structure(const char * from_dir_name, int depth);
题外话,在Visual Studio中,会强制要求你使用他们编写的安全函数,例如fscanf_s
,如果你不想用的话,那就将它关闭吧,具体怎么操作,就当是一个小问题留给你自己。
showFiles.c
先不写太多,这里比较重要的是写法
/* 首先是需要的一系列变量 */
int times = 0; /* 用来配合 setjmp和longjmp重新获取句柄HANDLE的 */
/** 操作时获取文件夹,文件信息的的必要变量 **/
HANDLE file_handle;
WIN32_FIND_DATAA file_data;
LARGE_INTEGER file_size;
file_handle
: 文件的句柄,后期操作的主要对象
file_data
: 文件的信息,各种属性
file_size
: 文件的大小有可能非常大,需要使用特定的结构体保存
到这里我们停下来,因为下一步我们要去实现获取路径的操作。
有两个路径:备份的来源路径,备份的目的路径。前者用键盘输入,后者在程序内部首先指定一个。
这里有两种方案:用系统栈存,用堆存
2~3GB
的分配空间(4GT机制),64位就更为可怕了(Windows8 最大有512GB
, Windows7 最大有 192GB
,服务器系列大概是1~2TB
)。速度较慢对于微软API处理自己的Windows路径,一般要求末尾以/
或者\
结尾,前者在C语言中不是转义,所以比较好存储,如果需要使用后者,可以选择如此\\
就行了。
char dir_path_1[PATH_MAX] = "../";
char dir_path_2[PATH_MAX] = "..\\";
/* 两种效果一致,且占的空间也相同 */
windef.h
中定义了一个常量PATH_MAX
的值为260,也就是说最大的路径长路为260字节
,但是如果我们的路径名超过了这个长度怎么办呢?\\?\
,这样长度限制就增加到了32767
了解决了上一个关于路径的问题,我们就需要考虑一下如何设计实现这个功能,首先要达到模块化的目的,即尽量减少每个函数的功能。
showFiles.h
变个魔术 3, 2, 1
,添加了如下代码
/**
* @version 1.0 2015/09/28
* @author wushengxin
* @param src 外部传进来的,用于向所分配空间中填充的路径
* @function 用于在堆上为存储的路径分配空间。
*/
char * make_path(const char * src);
/*
* @version 1.0 2015/09/28
* @author wushengxin
* @param src 外部传进来的,是由 make_path 所分配的空间,将其释放
* @function 用于释放 make_path 分配的内存空间
*/
void rele_path(char * src);
/*
* @version 1.0 2015/09/28
* @author wushengxin
* @param src_path 用于 FindFirstFile()
src_file 用于添加找到的目录名,形成新的目录路径
* @function 用于调整用户从键盘输入的路径字符串,使他们变得一致,便于处理
*/
void adjust_path(char * __restrict src_path, char * __restrict src_file);
/*
* @version 1.0 2015/09/28
* @author wushengxin
* @param src 外部传入的,用于调整
* @function 用于替换路径中的 / 为 \ 的
*/
void repl_str(char * src);
具体功能在文档里已经写的很清楚了,唯一要解释的就是最后两个函数,本来是一体的,后来被我拆开成了两个函数,为了也是功能更加清晰
倒数第二个函数 adjust_path
的作用是将路径处理成符合 Windows 函数 FindFirstFile
的要求可以具体看看。
showFiles.c
继续 show_structure
的实现
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; /* 如果分配失败就结束函数 */
adjust_path(dir_buf, dir_file_buf); /* 调整路径和文件格式到标准格式 */
repl_str(dir_buf);
repl_str(dir_file_buf);
这是调用 WINDOWS API
之前的所有操作,来一一实现他们
首先是分配空间给路径
make_path
: 24 lines
·
对于这个函数的功能便是,为需要存储的路径分配空间。
int times = 0;
size_t len_of_src = strlen(src); /* 需要分配的长度 */
size_t len_of_dst = MIN_PATH_NAME;
if (len_of_src > MIN_PATH_NAME - 10) /* \\?\ //* 8个字符 */
{ /* 这里用了10这个神奇的垃圾数,所以必须做一点注释,以防忘记 */
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); /* 作为日后的扩展 */
strcpy(loc_buf, src);
return loc_buf;
对于 10
这个数的考虑是,至少留出 8
个空位给所说的字符,加 2
凑整。
对于函数 malloc
,在这里没有进行包裹,是因为这只是一个预热的功能,后期在实现备份的时候,会对它进行包装,也使得错误处理的代码隐藏,让函数功能更加清晰。
adjust_path
: 16 lines
其次是调整路径的函数,功能就是调整路径
size_t length = strlen(src_path); /* 两个参数的长度在此函数调用之前必定一致 */
if (length == 1) /* 处理情况为,当用户输入的是根目录的情况 例如: 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;
当用户输入的是一个字符的根目录,我们要将其处理为 C:/
这样的形式
当用户输入的是不带/
结尾的,我们需要将其添加上 /
当用户输入以 \
结尾的路径时,将其替换为 /
,虽然后方又全部换成了 \
将目录处理为带 /*
结尾的,以达到 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;
不再赘述这个函数的功能
到此处,所有在第一个 Windows API 之前调用的函数都实现了,接下来要做什么?
当然是调用API函数啦
show_structure
/* 开始调用 Windows API 获取路径下的文件和文件夹信息 */
setjmp(get_hd_jmp);
fileHandle = FindFirstFileA(dir_buf, &fileData);
if (fileHandle == INVALID_HANDLE_VALUE) /* 如果无法获取句柄超过上限次数,就退出 */
{
fprintf(stderr, "The Handle getting Failure! \n");
if (times++ < TRY_TIMES)
longjmp(get_hd_jmp, 0);
return;
}
对于这一段代码的解释,其实核心就是第二句代码,其中的函数 FindFirstFileA
需要解释一下。
在 Windows API 文档 MSDN 中介绍的是 FindFirstFile
,但是某些情况下(定义了UNICODE宏,不知道有没有记错),这个官方提供的接口会被定义(#define
)成 FindFirstFileW
,如果使用 char *
的 ANSI 字符串当成参数的话是会获取句柄失败的!并且另一个参数使用的 file_data
类型也是 ANSI 的 WIN32_FIND_DATAA
所以这里显式地选择调用 FindFirstFileA
而不是让 Windows 帮我们选择。
接下来我们要做的事情就是,遍历这个目录下的所有文件和文件夹,提取出来他们的信息:
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 || /* . 和 .. 便是当前文件夹和上一级文件夹, 世界上所有系统都一样 */
strcmp(fileData.cFileName, "..") == 0)
continue;
strcat(tmp_dir_file_buf, fileData.cFileName); /* 将文件名连接到当前文件夹路径之后,形成文件路径 */
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); /* 这个的实现稍后放出 */
} while (FindNextFileA(fileHandle, &fileData));
代码是仿照 MSDN 提供的 官方例子改写的。
其中第五句代码,用到了前方提到过的格式化输出的一个不常用的技巧,占位,忘记的可以回去看看在第一章
采用的方法是递归,循环的方法留给看者自己实现,思路很简单,用一个队列或者栈存放所有找到的目录和文件,依次取出直到栈或者队列为空。
以及最后一段的代码,用于收尾:
FindClose(fileHandle);
rele_path(dir_buf);
rele_path(dir_file_buf);
return;
rele_path
: 4 lines
free(src);
src = NULL;
return;
最后在 Entry.c
的 main
函数中,在 switch
的 case 1
标签范围内,加上一些获取和处理输入的函数 : (因为这里只会使用一次,故采用的是系统栈而不是在堆上分配)
char tmp[_MAX_PATH];
...
case 1 :
scanf("%s", tmp);
printf("Enter : %s\n", tmp);
getchar(); /* 前方提到过,作用是清理标准输入流 */
show_structure(tmp, 0);
system("pause");
break;
260
字符的路径没有测试,大概能猜到是不行的。但是解决方案上方也有提到。