01 //p7ok.c -- 我拆我拆..我拆拆拆! @CopyLeft by tsaiwn@cs.nctu.edu.tw 02 /// 第七週查出所有錯誤以及產生合法ID, 03 /// 可指定第一碼(出生地), 也可指定男或女 04 // 身分證號碼檢查器與產生器 05 // 測試時, 請故意輸入 999 或 gen 或 a2 或 a3 或 92 看看會怎樣? 06 /// 學號: 07 /// 姓名: 08 /// Last modified date and time: 2010/11/05 18:00 Friday 09 /// Original creation date and time: 2010/10/17 11:15 Sunday 10 /// Original author: tsaiwn@csie.nctu.edu.tw 11 ////// ### rand( )在 ; is???, to??? 在 12 #include 13 #include 14 #include 15 #include 16 /// 用 macro 定義一些符號常數(Symbolic constant) 備用且方便改! 17 // 比較好的方式是把這些巨集定義都搬去一個header file 再 #include 它 18 #define HELP "HELP" 19 #define QUIT1 "QUIT" 20 #define QUIT2 "-1" 21 #define GEN1 "999" 22 #define GEN2 "GEN" 23 #define GEN3 "GENERATE" 24 #define ERR_ABC 1 25 #define ERR_SEX 2 26 #define ERR_TOO_SHORT 4 27 #define ERR_TOO_LONG 8 28 #define ERR_NON_DIGIT 16 29 #define ERR_CHECKSUM 32 30 int errNUM[ ] = {ERR_ABC, ERR_SEX, ERR_TOO_SHORT 31 ,ERR_TOO_LONG, ERR_NON_DIGIT, ERR_CHECKSUM 32 };;; 33 const int N_ERR = (sizeof errNUM/sizeof errNUM[0]); 34 /// 寫一些小工具函數 (utility functions)幫忙處理字串 35 void chop(char*); // remove tail NewLine; 去掉最尾巴的 '\n' 36 void squeeze(char*); // remove white space; 去掉所有空白 37 void toUpper(char*); // pre_process it so that easy to handle 38 /// ^^^ 包括 call squeeze 擠掉空白, 再將各字母轉為大寫 39 /// /// declare functions that will be used 40 int findErrCode(char*id); // error code of id, 0 means no error 41 void pErrorMSG(char*, int); // print out error message for id 42 char* genID(char*); // generate a valid ID 43 int isHelp(char*), isQuit(char*), isGen(char*); // boolean function 44 void pHelp( ); // Help message 45 int main( ) { // 大多數 main program 都這樣開頭 46 int er; // for error code 47 static char id[99]; // 不會打那麼多字吧 ! 48 static char tmps[99]; // temporary string for id 49 srand( time(0) ); // randomize 以便產生真的亂數序 50 for( ;; ) { // (注意) Loop forever 51 printf("ID checker, type ID: "); 52 fgets(id, sizeof(id), stdin); 53 if(feof(stdin)) break; // EOF encounted! say byebye 54 chop(id); // 去掉最尾巴的 '\n' 55 strcpy(tmps, id); // copy id into tmps; 注意是右邊copy到左邊 56 squeeze(tmps); // 擠掉空白; squeeze out all white space 57 toUpper(tmps); // to upper case 58 if(isQuit(tmps)) break; 59 if(isHelp(tmps)) {pHelp( ); continue;} 60 if(isGen(tmps)) { genID(tmps); continue; } 61 er = findErrCode(tmps); // find error code of the id in tmps 62 pErrorMSG(tmps, er); // the modified input, error code 63 } // for(;; 64 printf("Bye bye!\nHit RETURN key ..."); 65 getchar( ); // 企圖讀取一個 char, 讓程式停著等 User 按 RETURN 鍵 66 return 0; // 告知作業系統(OS)表示我們這主程式正常結束 67 }// main( 68 ///////////////////////////////////////// 69 void chop(char*p) { // remove tail '\n' ; by tsaiwn@csie.nctu 70 if(p[0] == 0) return; // NULL string 71 while(p[0] != 0) ++p; // 到字串結束的 NULL char; p[0]就是 *p 72 --p; // move back to the last char 73 if(*p == '\n') *p = 0; // remove the newLine if it is 74 }// chop( 75 void squeeze(char* p) { // 去掉所有空白; by tsaiwn@csie.nctu 76 char* p2 = p; // p2 points to first char 77 if(p[0] == 0) return; // NULL string 78 while(*p != 0) { // Loop till end of the string 79 if( ! isspace(*p) ) { *p2 = *p; ++p2; } //保留非空白字 80 ++p; // move to next char for check 81 }// while( 82 *p2 = 0; // 使字串結束 ! Important !!! 83 }// squeeze( 84 void toUpper(char*s) { 85 int i; 86 for(i=0; i < strlen(s); ++i) s[i] = toupper(s[i]); // library 87 // do NOT forget to include 88 }// toUpper 89 int isHelp(char*p) { 90 if(p[0] == '?') return 1; // question mark for help 91 if( strcmp(p, HELP) == 0) return 1; //在 92 return 0; // 注意 strcmp 傳回 0 表示兩字串相等 93 } 94 int isQuit(char*p) { 95 if( strcmp(p, QUIT1) == 0) return 1; // "quit" 96 if( strcmp(p, QUIT2) == 0) return 1; // "-1" 97 return 0; 98 } 99 int isGen(char*p) { // check to see if he want to generate a ID 100 if( strcmp(p, GEN1) == 0) return 1; 101 if( strcmp(p, GEN2) == 0) return 1; 102 if( strcmp(p, GEN3) == 0 ) return 1; 103 if( strlen(p) <= 2) return 1; // 只打 0, 1, 2 字都當作要生ID 104 return 0; 105 } // isGen( 106 /// 107 void pErrorMSG(char*id, int e) { // the array msg[][]也可寫外面 108 static char msg[ ][66] ={ // Note ! 注意與之前只印一種error不同! 109 "Letter?第一字須字母", // 1 means the first char error 110 "Sex性別錯誤", // 2 sex code error 111 "too short 太短了!", // 4 too short error 112 "too long 太長了!怎會有那麼多碼?", // 8 means too long 113 "nonDigit? 怎會有非數字?", //16 contains non-digit 114 "check code? 神秘檢查碼有問題啦!" }; //32,注意尾巴要有分號 ; 115 int i; 116 if(e == 0) { // no error 117 printf(" %s : OK 正確! 讚喔 !\n", id); return; 118 }// if 119 for(i=0; i < N_ERR; i++) { 120 if(e & errNUM[i]) // 注意是bitwise and &, 不是 && 121 printf(" %s : %s\n", id, msg[i]); // 注意不是 msg[e] 喔! 122 }//for i 123 //若嫌太簡單, 改為可印出所有查出的錯誤 .. 124 // 可利用各 bit 代表不同 error(1, 2, 4, 8, 16, 32) 125 // 每種錯誤用 e & (2的次方) 查看是否該印, 126 // 當然, 這樣那個 errCode( ) 也要改; 可用 | bitwise or 127 // 但因各 bit 獨立, 所以算 e 的時候也可用 + 取代 | 128 }// pErrorMSG 129 void pHelp( ) { // print Help message 130 printf(" %s, %s, %s, %s, %s, -1\n", 131 HELP, GEN1, GEN2, GEN3, QUIT1, QUIT2); 132 }// pHelp 133 //////////////// 134 /// === a table for looking up from 'A'==> 10,.. == /// /// 135 int yy[26]={10, 11, 12, 13, 14, 15, 16, 17, // ABCDEFGH 136 34, 18, 19, 20, 21, 22, 35 // I JKLMN O 137 ,23, 24, 25, 26, 27, 28, 29 //PQRSTUV 138 ,32, 30, 31, 33};;; // W(金門縣) X Y Z(連江縣) 139 int a2nn(int x) { // 'A' ==> 10, 'B' ==> 11, ... 140 int i, ans = 0; // this function 負責把字母轉為兩位數 141 x = toupper(x); // ensure it is in Uppercase 'A'..'Z' 142 // Table look up ! 143 x = x - 'A'; // 0..25 144 if(x<0 || x > 25) return 99; // Error, impossible !(防呆) 145 ans = yy[x]; // 取得對應該字母的 10..35 146 return ans; 147 } 148 int findCheckSum(char*s) { // compute the check sum 149 int i, ans=0; 150 ans = a2nn(s[0]); // 'A' ==> 10, 'B' ==> 11, ... 151 ans = ans/10 + 9*(ans%10); // weight: 1, 9, 8, 7, .., 1 152 // use for loop; //todo ! 153 for(i=1; i <= 8; ++i) ans += (s[i] - '0') * (9-i); 154 ans = ans % 10; 155 ans = 10 - ans; 156 if(ans == 10) ans = 0; 157 // 注意 '0' 就是 48, '1' 就是 49 (ASCII code) 158 // 注意這算出的是 0..9; 不是 '0'..'9' 159 // 若要用 '0'..'9'也可, 但使用此函數者要搭配改 160 return ans; // 回傳 check sum 161 } 162 int findErrCode(char*id) { // return error code of the id 163 int i, len = strlen(id); // length of id 164 int ans = 0; 165 if( !isalpha(id[0]) ) ans+= ERR_ABC; // 1st char 字母Letter? 166 if( id[1] != '1' && id[1] != '2') ans+= ERR_SEX; // error Sex 167 if(len < 10) ans += ERR_TOO_SHORT; // too short 168 if(len > 10) ans += ERR_TOO_LONG; // too Long 169 for(i=2; i< len; ++i) { 170 if( !isdigit(id[i]) ) {ans += ERR_NON_DIGIT; break; } 171 } // 注意 !!! 只要查到一個 non-digit 就要 break! WHY ? Why? why? 172 /// 只有剩下檢查碼不知對否? now do check the checksum 173 if(len == 10) { 174 i = findCheckSum(id); // 注意這算出的是 0..9; 不是 '0'..'9' 175 if( i != (id[9]-'0') ) ans += ERR_CHECKSUM; 176 } // check the checksum ONLY when length of ID is 10 :-) 177 return ans; // 也可能是 0 喔 178 } // findErrCode 179 char* genID(char*p) { // 依據 p[ ] 指示來產生合法 ID 180 int i, k, c; 181 static char newID[33] = "...not yet"; // 生好的 ID 放這 182 // default 也要前面兩個 char (出生地 + Sex code) 183 newID[0] = 'A' + rand( )%26; // 'A'..'Z' 184 newID[1] = '1' + rand( )%2; // '1' .. '2' 185 c = p[0]; // 看看 第一字(出生地)要不要指定? 但要注意合法! 186 if(strlen(p)==1) { 187 if(isalpha(c)) newID[0] = toupper(c); // 要防呆 ! 188 }else if(strlen(p)==2) { 189 if(isalpha(c)) newID[0] = toupper(c); // 要防呆 ! 190 c = p[1]; // obtain the sex code 191 if(c=='1' || c=='2') newID[1] = c; // 防呆! 正確才放入! 192 } // if 193 newID[2] = '0' + rand( )%10; // '0' .. '9' 194 for(i=2; i<=8; ++i) { // 用 rand() 生出流水號共 7 碼 195 /// generate newID[2] .. newID[8] // todo ! 196 newID[i] = '0' + rand( )%10; // '0' .. '9' 197 }//for(i 198 k = findCheckSum(newID); // find weighted check sum 199 newID[9] = '0' + k; // 檢查碼 ; 200 printf(" Valid ID for you: %s\n", newID); 201 return newID; // 回傳該 id, 用不用隨你 :-) 202 } 203 //////////////////////////////////////////////////////////// 204 /****** 205 D:\testc>path c:\Dev-Cpp\bin;%path% 206 D:\testc>gcc p7ok.c 207 D:\testc>a.exe 208 ID checker, type ID: a 123 456 789 209 A123456789 : OK 正確! 讚喔 ! 210 ID checker, type ID: a 1234 5678 9 211 A123456789 : OK 正確! 讚喔 ! 212 ID checker, type ID: a123 456 788 213 A123456788 : check code? 神秘檢查碼有問題啦! 214 ID checker, type ID: a123 456 798 215 A123456798 : OK 正確! 讚喔 ! 216 ID checker, type ID: 9999999 217 9999999 : Letter?第一字須字母 218 9999999 : Sex性別錯誤 219 9999999 : too short 太短了! 220 ID checker, type ID: 221 Valid ID for you: L124620433 222 ID checker, type ID: a2 223 Valid ID for you: A210967944 224 ID checker, type ID: p1 225 Valid ID for you: P199751035 226 ID checker, type ID: p 227 Valid ID for you: P287153227 228 ID checker, type ID: s 229 Valid ID for you: S266502910 230 ID checker, type ID: bbbb 231 BBBB : Sex性別錯誤 232 BBBB : too short 太短了! 233 BBBB : nonDigit? 怎會有非數字? 234 ID checker, type ID: a123456 235 A123456 : too short 太短了! 236 ID checker, type ID: aaaaaaaaaaaaaaaaaaaaa 237 AAAAAAAAAAAAAAAAAAAAA : Sex性別錯誤 238 AAAAAAAAAAAAAAAAAAAAA : too long 太長了!怎會有那麼多碼? 239 AAAAAAAAAAAAAAAAAAAAA : nonDigit? 怎會有非數字? 240 ID checker, type ID: gen 241 Valid ID for you: G132023641 242 ID checker, type ID: 999 243 Valid ID for you: Q214060378 244 ID checker, type ID: g9 245 Valid ID for you: G187141863 246 ID checker, type ID: g2 247 Valid ID for you: G258950574 248 ID checker, type ID: generate 249 Valid ID for you: M128526106 250 ID checker, type ID: help 251 HELP, 999, GEN, GENERATE, QUIT, -1 252 ID checker, type ID: -1 253 Bye bye! 254 Hit RETURN key ... 255 256 D:\testc> 257 258 **********************/