# 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/)