Makefile
系列的构造工具,而是使用集成开发环境直接一站式的完成所有的工作,而我们只需要专注于编写有用的代码即可。上回完成了界面的大部分功能,剩下的便是备份这个主要功能。
在完成备份之前,首先想想要如何构造这个备份模型
队列模型
队列实体
新建 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
文件
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
,之所以用花括号包裹起来的原因是,这样才能在里面定义本地变量,直接在冒号后面定义是编译错误,这个特性可能比较少用,这里提一下,前面也有说过。