---
# System prepended metadata

title: Week06-產生身份證字號與檢察

---

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