Windows系統(tǒng)下使用C語言編寫單線程的文件備份程序
寫在最前方
源路徑:即 From-Path,你準備要備份的資料
目的路徑: 即 To-Path,你準備要存儲備份的資料的地方
稍微回想一下,上一次寫的代碼,本次的任務是遍歷目錄及其子目錄,那么這回要干的就是將上次遍歷過的數(shù)據(jù),挪一下窩,到我們想要他們?nèi)サ奈恢谩?br />
這涉及到兩個操作,遍歷 和 拷貝,前一個動作我們在上一回已經(jīng)實現(xiàn)了,只需做小小的改動,就能夠使用。后一個動作也是需要靠 Windows API來完成,至于哪些,稍后再提。
現(xiàn)在先讓我們完成一個魔法,3, 2, 1?。?/p>
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)默認目的路徑和當前使用的目的路徑。
新增了倒數(shù)第四行的查看目的路徑歷史紀錄的功能。
在main函數(shù)外頭需要 extern DEFAULT_TO_PATH;因為引用了setPath.c里的一個全局變量。
寫在中間
我們曾經(jīng)提到要讓函數(shù)的功能更加清晰,為了達到這個目的,應該把可能用到的一些原生庫函數(shù)包裹一下,讓可能發(fā)生的錯誤盡量掌握在我們自己的手里
安全函數(shù)
新建 safeFunc.h safeFunc.c
考慮一下我們需要包裹的函數(shù): malloc, free, fopen 三個庫函數(shù)。
為了不讓后方的多線程實現(xiàn)產(chǎn)生更多的以后,不單獨使用全局錯誤輸出。
讓我來將他們實現(xiàn)一下
我不會省略一些看似不必要的東西,例如注釋,而是完整的呈現(xiàn)出來,如果覺得篇幅過長,可以選擇跳躍的閱讀。
魔法來了,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ù)中使用了預處理的機制,如果在頭文件中定義了 `#define NOT_DEBUG_AT_ALL`,這個輸出將不在出現(xiàn)
安全函數(shù)已經(jīng)撰寫完成,接下來就是干正事了
setPath.h
我們首先要將程序里保存上默認的目的路徑,首先想到用常量#define ...
其次應該要確保當前目的路徑不被其他非法的渠道訪問,那就應該用一個static 字符數(shù)組存儲。
接下來就是要提供一個函數(shù)當作接口(這里用了接口這個術(shù)語不知道合不合適),來獲取當前實際在使用的目的路徑 get_backup_topath。
這里還需要將之前實現(xiàn)過的 repl_str ,再次實現(xiàn)一次,因為之前的顯示功能只是測試,并不會實際應用到程序當中。
完成這兩個功能函數(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 用于返回當前使用的目的路徑 */ const char * get_backup_topath(); /** * @version 1.0 2015/09/28 * @author wushengxin * @param src 外部傳入的,用于調(diào)整 * @function 用于替換路徑中的 / 為 \ 的 */ void repl_str(char * src);
對應的實現(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; }
有了上面的代碼,主界面就再次能夠無誤運行了,那么剩下的就是實現(xiàn),設(shè)置目的路徑,存儲目的路徑到本地,顯示目的路徑,分別對應主界面的2, 3。
怎么實現(xiàn)比較好,再開始之前,分析一下會遇到的情況:
我們在得到目的路徑之后,會將其拷貝給默認路徑 to_path_buf,并且將其存儲到本地緩存文件中,以便下次程序開始時可以直接使用上一次的路徑
還可以使用另一個文件存儲所有用過的歷史路徑,包含時間信息。
那么這就要求我們首先實現(xiàn)存儲目的路徑的功能,其次再實現(xiàn)設(shè)置目的路徑的功能,最后實現(xiàn)顯示目的路徑的功能
注:兩個看似無用的全局變量(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ù)的功能稍微復雜,大體來說便是 `讀取路徑輸入->檢查路徑有效性->讀取判斷數(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的解釋
這個函數(shù)對于大的內(nèi)存塊的初始化實際上是很慢的,當然我們這個30KB左右大概的內(nèi)存可能影響還沒有那么大,但是上兆以后,調(diào)用memset就是一種性能問題了,很多情況下,編譯器在開啟高優(yōu)化等級之后會自動幫你取消memset的隱式調(diào)用
什么隱式調(diào)用,例如 init_path的第二行代碼,聲明并且用花括號初始化這個數(shù)組的時候,就會調(diào)用隱式memset。
寫在中間
上面完成了界面的大部分功能,剩下的便是備份這個主要功能。
在完成備份之前,首先想想要如何構(gòu)造這個備份模型
既然是備份,如果不想擴展為多線程的形式,參考第一次寫的遍歷函數(shù)(show_structure)直接找到文件便調(diào)用Windows API(稍后介紹)進行復制即可,不需要講待備份的文件路徑保存下來。
如果要考慮多線程擴展,我們就需要從長計議。
對于一個備份模型,最好的莫過于使用一個隊列,依舊實行的是遍歷模式,但是將找到的文件路徑保存,并放入一個先進先出的隊列中,這樣我們就能夠保證在擴展成多線程的時候,可以有一個很清晰的模型參考。
那么現(xiàn)在的任務就是實現(xiàn)這個用于備份的隊列模型。
隊列模型
- 應該有一個容器空間:用于存放路徑
- 有隊首隊尾標志
- O(1)復雜度的檢查隊列是否為空的接口或標志
- O(1)復雜度的返回容器容量的接口或標志,容器容量應該固定不變
使用一些面向?qū)ο蟮暮谀Хǎ4嬉恍┎僮骱瘮?shù)防止代碼混亂。
- 初始化函數(shù)
- 釋放函數(shù)
- 彈出操作函數(shù)
- 壓入操作函數(shù)
- 隊列實體
考慮到要存儲的是字符串,并且由于Windows API的參數(shù)需求,對于一個文件,我們需要存儲的路徑有兩個<源路徑,目的路徑>,對此應該再使用一個路徑模型結(jié)構(gòu)體包裹他們,則空間的類型就相應改變一下
新建 Queue.h Queue.c
Queue.h
typedef struct _vector_queue queue; typedef struct _combine combine; | 返回值 | | 函數(shù)類型名 || 參數(shù)類型 | typedef int (*fpPushBack)(queue * __restrict, const char * __restrict, const char * __restrict); typedef const combine * (*fpPopFront)(queue *); typedef void (*fpDelete)(queue *);
五個typedef不知道有沒有眼前一懵。,希望能夠很好的理解
前兩個是結(jié)構(gòu)體的聲明,分別對應著 隊列模型 和 路徑模型。
后兩個是函數(shù)指針,作用是放在結(jié)構(gòu)體里,使C語言的結(jié)構(gòu)體也能夠擁有一些簡單的面向?qū)ο蠊δ?,例如成員函數(shù)功能,原理就是可以給這些函數(shù)指針類型的變量賦值。稍后例子更加明顯。試著解讀一下,很簡單的。
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; /* 析構(gòu)釋放整個隊列空間 */ }; /** * @version 1.0 2015/10/03 * @author wushengxin * @param object 外部傳入的對象指針,相當于 this * @function 初始化隊列模型,建立隊列實體,分配空間,以及設(shè)置屬性。 */ int newQueue(queue* object);
可以看到,上方的函數(shù)指針類型,被用在了結(jié)構(gòu)體內(nèi),此處少了一個初始化函數(shù),是因為不打算把他當作成員函數(shù)(借用面向?qū)ο笮g(shù)語)
在使用的時候可以直接obj_name.PushBack(..., ..., ...);
更詳細的可以看后面的實現(xiàn)部分。成為成員函數(shù)的三個函數(shù),將被實現(xiàn)為 static 函數(shù),不被外界訪問。
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; }
在初始化函數(shù)中,可以看到,設(shè)置了隊首隊尾以及容量,分配了容器空間,配置了成員函數(shù)。
最后三句配置函數(shù)的語句中,push_back, pop_front, del_queue在后方以static 函數(shù)實現(xiàn)。
但是由于沒有聲明,所以切記要將三個static函數(shù)的實現(xiàn)放在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); /* 用數(shù)組實現(xiàn)循環(huán)隊列的步驟 */ 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; }
一個一個的說這些函數(shù)
del_queue:釋放函數(shù),直接調(diào)用Free_s
push_back:壓入函數(shù),將外部傳入的兩個原始的沒有組成的路徑字符串,組合成一個combine,并壓入路徑,每次都判斷并置是否為空標志位,實際上這個函數(shù)中有累贅代碼的嫌疑,應該再分出一個函數(shù),專門用來分配三個空間,防止這個函數(shù)過長(接近40行)
pop_front:彈出函數(shù),將隊列的隊首combine彈出,用于復制,但是這里有一個隱患,就是要將釋放的工作交給外者,如果疏忽大意的話,隱患就是內(nèi)存泄漏。
沒有特地的提供一個接口,用來判斷是否為空,因為當編譯器一優(yōu)化,也會將這種接口給優(yōu)化成直接使用成員的形式,某種形式上的內(nèi)聯(lián)。
隊列模型設(shè)計完畢,可以開始設(shè)計備份模型
備份模型可以回想一下之前的遍歷函數(shù),大體的結(jié)構(gòu)一樣,只是此處為了擴展成多線程,需要添加一些多線程的調(diào)用函數(shù),以及為了規(guī)格化,需要添加一個二級界面
先設(shè)計一下二級界面
二級界面
思考一下,這個界面要做什么
選擇是否開始備份
并且源路徑需要在此處輸入
返回上一級
新建 backup.h backup.c 文件
在主界面選擇 1 以后就會調(diào)用二級界面的函數(shù)
列出二級界面的選項
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; }
這個函數(shù)只說幾點,首先是`switch`的`case 1`,之所以用**花括號**包裹起來的原因是,這樣才能在里面定義**本地變量**,直接在冒號后面定義是**編譯錯誤**,這個特性可能比較少用,這里提一下,前面也有說過。
寫在最后方
剩下的就是編寫主要的功能函數(shù)和線程調(diào)用函數(shù)了。
相關(guān)文章
c++結(jié)合opencv如何實現(xiàn)讀取多張圖片并顯示
這篇文章主要介紹了c++結(jié)合opencv如何實現(xiàn)讀取多張圖片并顯示問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11詳解C++中的vector容器及用迭代器訪問vector的方法
使用迭代器iterator可以更方便地解引用和訪問成員,當然也包括vector中的元素,本文就來詳解C++中的vector容器及用迭代器訪問vector的方法,需要的朋友可以參考下2016-05-05