# Week06-產生身份證字號與檢察 程式碼皆使用教授提供的程式碼做修正,查找縣市的方式使用Look Up Table。 <br> > [color=#FFD700] > ## 目標 > * 以C語言作答 > * 印出說明如何輸入才會產生ID:輸入3個字以內就產生ID,輸入QUIT或EXIT則離開程式 > * 產生ID後印出ID,並印出"這是 縣市 的身分證號碼" > * 最後檢查ID是否符合規則 <br> ## 參考程式 :::spoiler main.c ```cPP= #include<stdio.h> #include<stdlib.h> #include<string.h> #include<ctype.h> #include "utility.h" #include "ID.h" #define and && // 這樣程式比較有可讀性 (Readable) #define or || // 注意這 && 和 || 以及 ! 都是邏輯的運算(Logical operation) #define not ! char cmd[123]; // 用來讀入使用者輸入的一列資料, 可能是 ID, 可能 quit / exit //注意上面這句"定義"了變數 cmd, 所以不可以放入 .h 檔, 就和主程式放一起即可! void hello( ); // print message, 也要讓 user 知道輸入啥會結束程式 int main( ) // 主程式以及只有主程式會用到的放獨立檔案例如 main.c (可任意名稱) { hello( ); // 印出說明讓使用者知道如何輸入以及輸入怎樣才會產生想要的 ID while (1) { printf("Enter your choice:\n"); fgets(cmd, 123, stdin);//印出提示並讀入一列到 cmd; // 建議用 fgets( ) chop(cmd); //照抄; 去掉 cmd 尾巴的 '\n' (if any), 它影響 strlen( ) cmdToUpper(cmd); //照抄; 若有小寫字母就轉為大寫, 其它不更動 //使用 strcmp( ) 看看如果 cmd 是 "QUIT" 或者 "EXIT" 則 break; 離開 Loop srand(time(0)); if(strcmp(cmd, "QUIT") == 0 || strcmp(cmd, "EXIT")==0 ) break; //幫你寫好啦 if( strlen(cmd) <= 3) { generateID(cmd); //打入三字內算要產生 ID :-) printf("%s\n", cmd); printf("這是 %s 的身分證號碼\n", cityName(cmd) ); } checkID(cmd); clearCharArr(cmd); } // while( return 0; // 規定 main( ) 須回傳一個整數 } // main( // 主程式其實超短的, 因為工作都分配出去啦 ///以下寫一些讓大家參考; /// 依據題目規定, 該些須放在另一個檔案 (最開始用建立新專案存在自己建立目錄內 ) void hello() { printf("Please follow the instruction: Type any word in 3 charators to generate ID, 'QUIT' or 'EXIT' to leave the program.\n"); } ``` ::: :::spoiler utility.h ```cpp= #include<stdio.h> #include<stdlib.h> #include<string.h> #include<ctype.h> void cmdToUpper(char*p); char* chop(char*p); void clearCharArr(char*p); ``` ::: :::spoiler utility.c ```cpp= #define and && // 這樣程式比較有可讀性 (Readable) #define or || // 注意這 && 和 || 以及 ! 都是邏輯的運算(Logical operation) #define not ! void cmdToUpper(char*p) // 簡單工具程式, 若含有小寫字母就轉成大寫字母 { while(*p) // 只要 *p 不是 0 就進入 Loop { if( (*p >='a') && (*p <='z') ) *p = *p -'a'+'A'; //轉成大寫字母 ++p; /* p 前進一個元素 */ }//while(*p } // cmdToUpper( // 把整個字串中的小寫字母都轉成大寫字母 char* chop(char*p) // 照抄去用即可 { if(p==0 || *p==0) return 0; // NULL char* pOLD = p; // 記住真正字串開始處 while(p[0] != 0) // p[0] 就是 *p; 此處 意思是還沒到字串結束 { if(*p == '\n') *p = 0; // chop it 咬掉它; 因 fgets 讀入可能有 newLine ++p; // 前進一個 char } // while(p[ return pOLD; // 故意的, 因函數 head 頭部寫 char* 做函數型別 } // chop( void clearCharArr(char*p) { if(p==0 || *p==0) return 0; // NULL char* pOLD = p; // 記住真正字串開始處 while(p[0] != 0) // p[0] 就是 *p; 此處 意思是還沒到字串結束 { *p = 0; // chop it 咬掉它; 因 fgets 讀入可能有 newLine ++p; // 前進一個 char } return pOLD; // 故意的, 因函數 head 頭部寫 char* 做函數型別 } ``` ::: :::spoiler ID.h ```cpp= #include<stdio.h> #include<stdlib.h> #include<string.h> #include<ctype.h> #include "utility.h" void generateID(char* id); void printErrMSG(int err); int checkSumOK(char*id); void checkID(char*id); ``` ::: :::spoiler ID.c ```cpp= #define and && // 這樣程式比較有可讀性 (Readable) #define or || // 注意這 && 和 || 以及 ! 都是邏輯的運算(Logical operation) #define not ! #define MAX_A 35 #define MIN_A 10 #define MAX_N 9 #define MIN_N 0 int LookUpTable_A2N[26] = {10, 11, 12, 13, 14, 15, 16, 17, 34, 18, 19, 20, 21, 22, 35, 23, 24, 25, 26, 27, 28, 29, 32, 30, 31, 33}; char LookUpTable_N2A[26] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'W', 'Z', 'I', 'O'}; char City[26][15] = {"台北市", "台中市", "基隆市", "台南市", "高雄市", "台北縣", "宜蘭縣", "桃園縣", "嘉義市", "新竹縣", "苗栗縣", "台中縣", "南投縣", "彰化縣", "新竹市", "雲林縣", "嘉義縣", "台南縣", "高雄縣", "屏東縣", "花蓮縣", "台東縣", "金門縣", "澎湖縣", "陽明山特區", "連江縣"}; char* cityName(char* id) { return City[id[0]-'A']; } void generateID(char* id) { clearCharArr(id); int tmp = rand()%(MAX_A - MIN_A + 1) + MIN_A; id[0] = LookUpTable_N2A[tmp-10]; id[1] = (rand()%(2-1+1) + 1) + '0'; int i; for (i = 2; i < 9; i++) { id [i] = (rand()%(MAX_N - MIN_N + 1) + MIN_N) + '0'; } int sum = tmp/10 + (tmp%10)*9; for (i = 1; i < 9; i++){ sum += (id[i]-'0')*(9-i); } sum = sum%10; if (sum == 0) id[9] = sum + '0'; else id[9] = (10 - sum) + '0'; } // generateID void printErrMSG(int err) // 列出各種錯誤信息, 這給檢察ID的函數使用 { // 使用 & bit-wise AND 查看各 Error bit, 印出錯誤訊息; bit 比重 1, 2, 4, 8, 16 if(err & 1) printf(" ID 太短!\n"); //寫五六個 if 還 OK; 也不算丟臉 :-) if(err & 2) printf(" ID 太長啦 !\n"); // .. 如果有五六十個那就要考慮 table Lookup //其他都用 if 沒關係, 其實就剩 檢查 4, 8 兩 bit 兩句錯誤訊息 if(err & 16) printf(" 第一個字以外必須都是數字!\n"); } // printErrMSG int checkSumOK(char*id) { int ggyy= 10; // 先假設 'A' 開頭 ID ; 這樣可以測 A123456789 和 A123456798 ggyy = LookUpTable_A2N[id[0]-'A'];// 用 Table Look up 方式查出字母對應的整數值 int ggyy = 10 到 35 ; // 把 ggyy 拆開成兩位: gg = ggyy/10; yy= ggyy%10; // 仔細想想 ! int gg = ggyy/10; // 注意 5/2 得到答案 2 不是 2.5 因為C/C++規定整數除以整數得到整數 int yy = ggyy % 10; // Remainder 餘數; 例如 3388 % 10 得到餘數 8 int sum = gg+ 9* yy; //依據規定比重 1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1 // 關於ID編碼規則參看 https://iottalk.vip/idcheck/ int i; for(i=1; i< 9; ++i) sum += (9-i) * (id[i] - '0') ;// 比重 8, 7, 6, 5, 4, 3, 2, 1 sum += id[9] - '0' ; // 檢查碼比重是 1 ; 這樣設計很不好 ! WHY ? if(sum%10 != 0) return 0; // 表示 Error ; sum %10 餘數必須是 0 才正確 return 1; // 不是 0 表示ID正確 OK;; 注意因我們取名稱 checkSumOK 所以用 不是 0 表示 OK } // checkSumOK void checkID(char*id) // 注意傳入的 id 尾巴不要有 newline '\n' 否則長度判斷會錯誤 { int err=0; // 假設 id 沒錯誤 (沒表面上一眼就可看出的錯誤) if( strlen(id) < 10) err += 1; // too short if(strlen(id) > 10) err += 2; // too LONG if( not isupper( toupper(*id)) ) err += 4; // 第一個 char 不是字母 // 其實 id 傳入之前已經轉大寫, 所以 toupper(*id) 是多轉換了, 但 OK if( (*(id+1) != '1') and (id[1] != '2') ) err += 8; // id[1] 就是 *(id+1) 性別錯誤 int gg=0; // 注意上述的 and 在前面有 #define and && //邏輯的運算 int i; for(i=1; i < 10; ++i) if( not isdigit(id[i]) ) gg=16; // non digit err += gg; // gg maby 0 or 16 if(err != 0) // 至少一種錯; 思考為何這用 0 表示沒錯誤? { printErrMSG(err); return; // 有錯不必再看檢查碼啦 ! 所以就 return 回去囉 ! } // if(err // check weighted check sum --- 驗證檢查碼 // 因為這大概要十句左右, 弄成另一個函數 checkSumOK(id) 來幫忙驗證, 傳回來不是 0 表示OK if( checkSumOK(id) )printf("? 正確的 ID: %s\n", id); else printf("? ID 的檢查碼錯誤: %s\n", id); }// checkID( ``` ::: <br> ## 相關參考網頁 * [ASCII code](https://www.vlsifacts.com/wp-content/uploads/2023/02/ASCII-Code.png) * [fgets的用法](https://c.biancheng.net/view/235.html) * [printf的用法](https://blog.csdn.net/jisuanji198509/article/details/80466546) * [strlen與其他字串長度計算的方式](https://blog.csdn.net/z_qifa/article/details/77744482) * [strcpy與相關字串處理](https://skylinelimit.blogspot.com/2018/02/c-2.html) * [strcmp用法](https://www.runoob.com/cprogramming/c-function-strcmp.html) * [random的作法](https://blog.gtwang.org/programming/c-cpp-rand-random-number-generation-tutorial-examples/)