「C++ のからくり」の小遣い帳プログラム ( 3 ) ～ クラスの導入

今回は、いよいよクラスを導入します。オブジェクト指向プログラミングと呼ばれているものです。前回の [C++ で書き直した小遣い帳](https://qiita.com/ntkiot/items/f8d4f9f1e5ea0876d23e) の当座預金口座と普通預金口座の構造体 ( **Checking** と **Savings** ) に、クラス特有の処理を取り込んで、クラスを作成します。

## プログラムの修正箇所
(「C++ のからくり」から引用 )
- **当座預金口座 ( Checking ) と普通預金口座 ( Savings ) のクラスを作った**   
**protected** メンバーは、そのクラスのサブクラスからアクセスできます。  
メンバー関数をクラスの外部で定義するときには、**クラス名 : : メンバー関数**とします。  
 **注 1～10** はソースコードを参照してください。

【**Checking** クラス】

アクセス|メンバー|説明||
:--|:--|:--|:--
**public:**|**Checking( )**|コンストラクタ。口座番号 ( **accountNumber** ) と 残高 ( **balance** ) の初期化|**注 1**
||**void init( )**|口座番号 ( **accountNumber** ) の入力|**注 2**
||**int account( )**|口座番号 ( **accountNumber** ) を取得する|**注 3**
||**float acntBalance( )**|外部から残高 ( **balance** ) を取得する|**注 4**
||**void deposit( float )**|預金の入金処理|**注 5**
||**void withdrawal( float )**|預金の引き出し処理 ( 外部定義 )|
||**void display( )**|口座番号 ( **accoumtNumber** ) と残高 ( **balance** ) の表示|**注 6**
**protected:**|**unsigned accountNumber**|口座番号|**注 7**
||**float balance**|残高||

【**Savings** クラス】

アクセス|メンバー|説明
:--|:--|:--
**public:**|**Savings( )**|コンストラクタ。口座番号 ( **accountNumber** ) と 残高 ( **balance** ) の初期化
||**void init( )**|口座番号 ( **accountNumber** ) の入力
||**int account( )**|口座番号の ( **accountNumber** ) の取得
||**float acntBalance( )**|残高 ( **balance** ) の取得
||**void deposit( float )**|預金の入金処理
||**void withdrawal( float )**|預金の引き出し処理 ( 外部定義 )
||**void display( )**|口座番号 ( **accountNumber** ) と残高 ( **balance** ) の表示
**protected:**|**unsigned accountNumber**|口座番号
||**float balance**|残高
||**int noWithdrawals**|引き出し回数

- **Checking と Savings のオブジェクトは、グローバルな配列として確保するので、main( ) の前で作成した。( 注 8 )**
```
// 当座預金口座と普通預金口座のオブジェクト
Checking chkAcnts[maxAccounts];
Savings svgAcnts[maxAccounts];
// main - 最初の入力と残高トータルの出力
int main()
```
- **メンバー関数 ( メソッド ) の呼び出し方法は、オブジェクト名.メンバー関数 のように書く。**  
【 **main( )** 関数】( **注 9** )   
```
chkAcnts[].init()
chkAconts[].display()
svgAcnts[].init()
svgAconts[].display()
```  
【 **process( Checking& checking )** 関数】
```
checking.deposit()    ==> chkAconts[].deposit() と同じ
checking.withdrawal() ==> chkAconts[].withdrawal() と同じ
```
【 **process(Savings& savings )** 関数】  
```
savings.deposit()    ==> svgAconts[].deposit() と同じ
savings.withdrawal() ==> svgAconts[].withdrawal() と同じ
```
【**データメンバーへのアクセス関数 ( 注 10 )** 】 
``` 
chkAconts[].acntBalance()
svgAconts[].acntBalance()
```

## ソースコード ( Budget3.cpp ) のクラス、const 変数、関数、配列
Visual Studio 2022 のソリューション・エクスプローラでは、以下のように表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549839/b5925367-ba32-3080-6cbb-153a224402ad.png)

クラス、const 変数、関数、配列|説明
:--|:--
**const int maxAccounts =10**|開ける口座の最大数を 10 に設定する。
**class Checking**|「プログラムの修正箇所」参照
**chkAcnts[ ]**|**Checking** 型のオブジェクトの配列
**class Savings**|「プログラムの修正箇所」参照
**svgAcnts[ ]**|**Saveings** 型のオブジェクトの配列
**process(Checking &)**|当座預金口座のデータ入力関数
**process(Savings &)**|普通預金口座のデータ入力関数
## 小遣い帳の機能
小遣い帳プログラムは、シンプルな当座預金口座 ( 小切手によってお金を引き出す口座 ) と普通預金口座の記録プログラムです。この機能は以下のとおりです。( 「C++ のからくり」から引用 )
1. 当座預金口座の処理、普通預金口座の処理、またはプログラムの終了。
2. 口座番号の登録。
3. 入金と引き出しの処理を受け付ける際に、0 が入力された場合はその処理を終了する。
4. ユーザが終了を選択後、最終的な一覧表として、すべての当座預金口座の残高集計と、すべての普通預金口座の残高集計、およびすべての口座の残高集計を表示する。

## 処理を行う際の金融ルール
このプログラムでは、処理を行う際に下記のような金融ルールを適用します。(「C++ のからくり」から引用 )
- 出金時に、口座に入っている残高がマイナスになってはならない。
- 入金する際は手数料はかからない。
- 当座預金口座の残高が 500.00 ドル未満であった場合、小切手 1 枚につき 0.20 ドル ( 20 セント ) の手数料がかかる。それ以外の場合は、手数料がかからない。
- 最初の引き出し以外は、普通預金口座から引き出すのに 5.00 ドルの手数料がかかる。
- 開くことのできる当座預金と普通預金の最大口座数 ( **maxAccounts** ) は、それぞれ 10。

## プログラムの動作
**Budget3.cpp** は以下のように動作します。(「C++ のからくり」から引用 )

【プログラムの構造】
1. **Checking** と **Savings** の 2 つのクラスが、当座預金口座と普通預金口座に必要な情報と関数を持つ。
2. これらの口座のための記憶領域は、**Checking** 型のオブジェクトの配列 **chkAcnts[ ]** と **Savings** 型のオブジェクトの配列 **svgAcnts[ ]** に割り当てられている。
3. オブジェクト生成時に、コンストラクタによってデータメンバーが初期化される。
4. メイン・プログラム ( **int main( )** ) は 2 つの部分に分かれている。1 つはデータを蓄積する部分、もう 1 つは最終的な結果を表示する部分である。
5. データを蓄積する部分では、**S**、**C** または **X** の入力を促すので、文字を選択入力する。

【当座預金処理】
1. ユーザが **C** を入力すると、当座預金処理に移る。
2. この時、当座預金口座の数 ( **noChkAccounts** )が最大口座数 ( **maxAccounts** ) に達していなければ、新たに 1 つの当座預金口座が追加され ( **chkAcnts[ ].init( )** ) 、処理される ( **process(chkAcnts[ ])** )。
3. 最大口座数に達してしまった場合は、口座の追加は拒否され、エラーメッセージが出力される。
4. 関数 **chkAcnts[ ].init( )** の処理は、口座の登録番号( **accoutNumber** ) を登録する。
5. 関数 **process(chkAcnts[ ])** の処理では、入金には正の数、引き出しには負の数、そして 0 を入力すると終了する。
6. 入金はそのまま受け付けるが ( **chkAconts[ ].deposit( float )** ) 、引き出すには、口座に十分な残高が残っているかの確認と当座預金引き出し手数料の計算 ( **金融ルール**参照 ) をおこなう ( **chkAcnt[ ].withdrawal( float )** )。  
※ **checking.～** は、**chkAcnts[ ].～** と同じ。

【普通預金処理】
1. ユーザが **S** を入力すると、当座預金処理に移る。
2. この時、普通預金口座の数 ( **noSvgAccounts** )が最大口座数 ( **maxAccounts** ) に達していなければ、新たに 1 つの当座預金口座が追加され ( **svgAcnts[ ].init( )** ) 、処理される ( **process(svgAcnts[ ])** )。
3. 最大口座数に達してしまった場合は、口座の追加は拒否され、エラーメッセージが出力される。
4. 関数 **svgAcnts[ ].init( )** の処理は、口座の登録番号( **accoutNumber** ) を登録する。
5. 関数 **process(svgAcnts[ ])** の処理では、入金には正の数、引き出しには負の数、そして 0 を入力すると終了する。
6. 入金はそのまま受け付けるが ( **svgAcnts[ ].deposit( float )** )、引き出すには、口座に十分な残高が残っているかの確認と普通預金引き出し手数料の計算 ( **金融ルール**参照 ) をおこなう ( **svgAcnts[ ].withdrawal( float )** )。  
※ **savings.～** は、**svgAcnts[ ].～**と同じ。

【最終結果処理】
1. ユーザが **X** を入力すると、ループ処理を脱して最終的な結果を示す処理に移る。
2. 当座預金口座と普通預金口座のそれぞれの口座に登録されているすべての記録を吐き出し、当座預金の残高トータル ( **chkTotal** )、普通預金の残高トータル ( **svgTotal** )、全預金の残高トータル ( **total** ) を表示する。

※**このプログラムでは、引き出す前に必ず入金がなされていることを前提としている。**

## 実行例
下記に実行例を示しますが、数値を入力するところに文字を入力すると暴走するので注意してください。
```
入力 S（当座預金口座）, C （普通預金口座）, X （終了）
s
口座番号の入力：123
入力：入金の場合（正の数）
引き出しの場合（負の数）入力を終了する場合（0）:200
:-50
:-50
:0
入力 S（当座預金口座）, C （普通預金口座）, X （終了）
c
口座番号の入力：234
入力：入金の場合（正の数）
引き出しの場合（負の数）入力を終了する場合（0）:200
:-25
:-20
:0
入力 S（当座預金口座）, C （普通預金口座）, X （終了）
x
当座預金口座：
口座 234 = 154.6
普通預金口座：
口座 123 = 95(引き出し回数 = 2)
当座預金口座の残高トータル = 154.6
普通預金口座の残高トータル = 95
全預金口座の残高トータル   = 249.6
```

## ソースコード
【注意点】
- '\\' は Windows では '￥' と表示されます。
```c++
// BUDGET3.CPP －       本物のクラスを初めて使った
//                      小遣い帳プログラム。
//                      Cの構造体をクラスに変更した。
//                      なんだか立派なものになってきたゾ。

#include <iostream>
using namespace std;

// 開くことのできる、最高口座数
const int maxAccounts = 10;

// Checking - 当座預金口座を描写している。
class Checking                         // 注1
{
public:
    Checking()                          // 注2
    {
        accountNumber = 0;
        balance = 0.0F;
    }
    void init()                         // 注3
    {
        cout << "口座番号の入力：";
        cin >> accountNumber;
    }

    // アクセス関数                     //注4
    int accountNo()
    {
        return accountNumber;
    }
    float acntBalance()
    {
        return balance;
    }


    // 手続き処理関数                    // 注5
    void deposit(float amount)
    {
        balance += amount;
    }
    void withdrawal(float amount);

    // 'cout'への自己表示関数
    void display()                       // 注6
    {
        cout << "口座 " << accountNumber
            << " = " << balance
            << "\n";
    }

protected:                           // 注7
    unsigned accountNumber;
    float    balance;
};
// 引き出し － このメンバ関数はインライン関数
//             にするには大きすぎる。
void Checking::withdrawal(float amount)
{
    if (balance < amount)
    {
        cout << "残高が足りません： 残高" << balance
            << ", 引き出し額 " << amount
            << "\n";
    }
    else
    {
        balance -= amount;

        // 残高が少なくなってしまった場合は、手数料がかかる。
        if (balance < 500.00F)
        {
            balance -= 0.20F;
        }
    }
}

// Savings - 何をしてるかわかるよネ
class Savings
{
public:
    Savings()
    {
        accountNumber = 0;
        balance = 0.0F;
        noWithdrawals = 0;
    }
    void init()
    {
        cout << "口座番号の入力：";
        cin >> accountNumber;
    }

    // アクセス関数
    int accountNo()
    {
        return accountNumber;
    }
    float acntBalance()
    {
        return balance;
    }

    // 手続き処理関数
    void deposit(float amount)
    {
        balance += amount;
    }
    void withdrawal(float amount);

    // 'cout'への自己表示関数
    void display()
    {
        cout << "口座 " << accountNumber
            << " = " << balance
            << "(引き出し回数 = " << noWithdrawals
            << ")\n";
    }

protected:
    unsigned accountNumber;
    float    balance;
    int      noWithdrawals;
};
void Savings::withdrawal(float amount)
{
    if (balance < amount)
    {
        cout << "残高が足りません： 残高" << balance
            << ", 引き出し額 " << amount
            << "\n";
    }
    else
    {
        if (++noWithdrawals > 1)
        {
            balance -= 5.00F;
        }
        balance -= amount;
    }
}

// プロトタイプ宣言
void process(Checking& checking);
void process(Savings& savings);

// 当座預金口座と普通預金口座のオブジェクト
Checking chkAcnts[maxAccounts];            //注 8
Savings svgAcnts[maxAccounts];

// main - 最初の入力と残高トータルの出力
int main()
{
    /* Xまたはxが入力されるまで繰り返す */
    int noChkAccounts = 0;    // 当座預金口座の口座数
    int noSvgAccounts = 0;    // 普通預金口座の口座数
    char     accountType;     // SまたはC


    unsigned keepLooping = 1;
    while (keepLooping)
    {
        cout << "入力 S（当座預金口座）, "
            "C （普通預金口座）, X （終了）\n";
        cin >> accountType;

        switch (accountType)
        {
        case 'c':
        case 'C':
            if (noChkAccounts < maxAccounts)
            {
                chkAcnts[noChkAccounts].init();  // 注9
                process(chkAcnts[noChkAccounts]);
                noChkAccounts++;
            }
            else
            {
                cout << "当座預金口座をこれ以上開くことはできません。\n";
            }
            break;

        case 's':
        case 'S':
            if (noSvgAccounts < maxAccounts)
            {
                svgAcnts[noSvgAccounts].init(); // 注9
                process(svgAcnts[noSvgAccounts]);
                noSvgAccounts++;
            }
            else
            {
                cout << "普通預金口座をこれ以上開くことはできません。\n";
            }
            break;

        case 'x':
        case 'X':
            keepLooping = 0;
            break;

        default:
            cout << "理解できません。XかCかSを選択してください。\n";
        }
    }

    // ここで、残高トータルを表示する
    float chkTotal = 0.0F;              // 当座預金口座の残高トータル

    cout << "当座預金口座：\n";
    int i;
    for (i = 0; i < noChkAccounts; i++)
    {
        chkAcnts[i].display();           // 注9
        chkTotal += chkAcnts[i].acntBalance();  // 注10
    }
    float svgTotal = 0.0F;              // 普通預金口座の残高トータル
    cout << "普通預金口座：\n";
    for (i = 0; i < noSvgAccounts; i++)
    {
        svgAcnts[i].display();          // 注9
        svgTotal += svgAcnts[i].acntBalance();  // 注10
    }

    float total = chkTotal + svgTotal;
    cout << "当座預金口座の残高トータル = " << chkTotal << "\n";
    cout << "普通預金口座の残高トータル = " << svgTotal << "\n";
    cout << "全預金口座の残高トータル   = " << total << "\n";
    return 0;
}

// process(当座預金口座の処理) - 当座預金口座のデータ入力
void process(Checking& checking)
{
    cout << "入力：入金の場合（正の数）\n"
        << "引き出しの場合（負の数）入力を終了する場合（0）";

    float transaction;

    do
    {
        cout << ":";
        cin >> transaction;

        // 貯金
        if (transaction > 0.0F)
        {
            checking.deposit(transaction);
        }

        // 引き出し
        if (transaction < 0.0F)
        {
            checking.withdrawal(-transaction);
        }
    } while (transaction != 0.0F);
}
// process(普通預金口座の処理) - 普通預金口座のデータ入力
void process(Savings& savings)
{
    cout << "入力：入金の場合（正の数）\n"
        << "引き出しの場合（負の数）入力を終了する場合（0）";

    float transaction;
    do
    {
        cout << ":";
        cin >> transaction;

        // 貯金
        if (transaction > 0.0F)
        {
            savings.deposit(transaction);
        }

        // 引き出し
        if (transaction < 0.0F)
        {
            savings.withdrawal(-transaction);
        }
    } while (transaction != 0.0F);
}

```