「C++ のからくり」の小遣い帳プログラム ( 2 ) ～ C++ で書き直し

今回は、**コメントスタイル、関数のオーバーロード、参照型引数、インライン関数、変数の宣言場所、ストリーム I/O、const 変数** を利用して、[C版 小遣い帳](https://qiita.com/ntkiot/items/9df1af8152c035390716) を C++ で書き直していきます。プログラムの骨格は、ほとんど変わっていません。

## プログラムの修正箇所
(「C++ のからくり」から引用 )
- **コメントを新しいスタイルにした。**  
`/* */` スタイルから `//` に変更しました。`//` の後に続く同じ行はコメントとして見なされます。
- **当座預金口座と普通預金口座の類似性から、関数 init( ) と関数 process( ) をオーバーロード ( 多重定義 ) した。**( ソースコードの**注 1** )  
2 つの預金口座の違いは、引数の型の違いによって認識されます。
```
initChecking(Checking *)     → init(Checking *)
initSavings(Savings *)       → init(Savings *)
processsChecking(Checking *) → process(Checking *)
processSavings(Savings *)    → process(Savings *)
```
- **ポインタ型の使用を避けるため、関数の引数をポインタ型ではなく参照型にした。**  
ポインタ型と同じように、関数には引数のアドレスが渡されます。  
```
init(Checking *)    → init(Checking &)
init(Savings * )    → init(Savings &)
process(Checking *) → void process(Checking &)
process(Savings *)  → void process(Savings &)
```
- **関数 init( ) はサイズが小さいので、インライン関数にした。** ( ソースコードの**注 2** )  
インライン関数は、その関数が使用される部分に、インライン関数自身のコードを直接展開 ( 埋め込み ) します。  
```
void initCahecking(Checking *)   → inline void init(Checking &)
void initSavings(Savings *)      → inline void init(Savings &)
```
関数呼び出し時の引数は、以下のように変更します。( ソースコードの**注 3** )
```
initChecking(&chkAcnts[noChkAccounts])    → init(chkAcnts[noChkAccounts])
initSavings(&chkAcnts[noChkAccounts])     → init(svgAcnts[noSvgAccounts])
processChecking(&chkAcnts[noChkAccounts]) → process(chkAcnts[noChkAccounts])
processSavings(&svgAcnts[noSvgAccounts])  → process(svgAcnts[noSvgAccounts])
```
構造体のメンバー参照は、以下のように変更します。( ソースコードの**注 5** )
```
pChecking->balance      → checking.balance
pSavings->balance       → savings.balance
pSavings->noWithdrawals → savings.noWithdrawals
```
- **変数の宣言は、それを使う場所の近くで行うようにした。**  
C版 小遣い帳プログラムでは、メイン関数のブロックの先頭で宣言していた  
```
float    chkTotal;
float    svgTotal;
float    total;
int      i;
```
を、トータル計算をおこなうコードの近くに移動しました。( ソースコードの**注 4** )
- **ストリーム I/O に変更した。**  
printf( ) と scanf( ) を cout ( 標準出力 : ディスプレイ ) と cin ( 標準入力 : キーボード ) に変更しました。ストリーム I/O を使うには、<iostream> ヘッダーのインクルードが必要です。
```
#include <iostream>
```
ヘッダーでは、`cout` や `cin` は名前空間の `namespace std{ }` の中に置かれているので、`std::cout` あるいは `std::cin` のように書く必要がありますが、`using namespace std;` と書くことによって `std::` を省略することができます。
- **#define 文を const 変数と置き換えた。**  
```
#define MAXACCOUNTS 10 → const int maxAccounts = 10;
```
const 変数を宣言した後は、その値を変更できなくなります。

## 小遣い帳の機能
小遣い帳プログラムは、シンプルな当座預金口座 ( 小切手によってお金を引き出す口座 ) と普通預金口座の記録プログラムです。この機能は以下のとおりです。( 「C++ のからくり」から引用 )
1. 当座預金口座の処理、普通預金口座の処理、またはプログラムの終了。
2. 口座番号の登録。
3. 入金と引き出しの処理を受け付ける際に、0 が入力された場合はその処理を終了する。
4. ユーザが終了を選択後、最終的な一覧表として、すべての当座預金口座の残高集計と、すべての普通預金口座の残高集計、およびすべての口座の残高集計を表示する。

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

## ソースコード ( Budget2.cpp ) の構造体、const 変数、関数、配列
Visual Studio 2022 のソリューション・エクスプローラでは、以下のように表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549839/f68876f8-f033-118a-501f-55468eafef56.png)

|構造体、const 変数、関数、配列|説明|
|:--|:--|
|**const int maxAccounts = 10**|開ける口座の最大数を 10 に設定する。|
|**struct Checking**|当座預金の構造体。メンバーは口座番号<br>( **accountNumber** ) と残高 ( **balance** )。|
|**chkAcnts[ ]**|**Checking** 型の配列。|
|**struct Savings**|普通預金の構造体。メンバーは口座番号<br>( **accountNumber** ) 、残高 ( **balance** )、<br>引き出し回数 ( **noWithdrawals** )。|
|**svgAcnts[ ]**|**Savings** 型の配列。|
|**init(Checking &)**|当座預金口座を初期化する関数。|
|**init(Savings &)**|普通預金口座を初期化する関数。|
|**process(Checking &)**|当座預金口座のデータ入力関数。|
|**process(Savings &)**|普通預金口座のデータ入力関数。|

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

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

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

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

【最終結果処理】
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++
// BUDGET2.CPP - 「Cの改善版（ベターC）としてのC++」版
//                小遣い帳プログラム

#include <iostream>
using namespace std;

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

// Checking - 当座預金口座の構造体
struct Checking
{
    unsigned accountNumber;
    float    balance;
} chkAcnts[maxAccounts];

// Savings - 普通預金口座の構造体（何をしているかわかるよね）
struct Savings
{
    unsigned accountNumber;
    float    balance;
    int      noWithdrawals;
} svgAcnts[maxAccounts];

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

// インライン関数
// 初期化（Checking）－当座預金口座の初期化
inline void init(Checking& checking)   // 注2
{
    cout << "口座番号の入力：";
    cin >> checking.accountNumber;
    checking.balance = 0.0;
}

// 初期化（Savings）－普通預金口座の初期化
inline void init(Savings& savings)
{
    cout << "口座番号の入力：";
    cin >> savings.accountNumber;
    savings.balance = 0.0;
    savings.noWithdrawals = 0;
}

// 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)
            {
                init(chkAcnts[noChkAccounts]);      // 注3
                process(chkAcnts[noChkAccounts]);   // 注3
                noChkAccounts++;
            }
            else
            {
                cout << "これ以上口座を開くことはできません。\n";
            }
            break;

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

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

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

    // ここで、トータル残高を表示する
    float chkTotal = 0;                 // 注4 当座預金口座の残高トータル
    cout << "当座預金口座：\n";
    int i;                              // 注4
    for (i = 0; i < noChkAccounts; i++) 
    {
        cout << "口座 " << chkAcnts[i].accountNumber
            << " = " << chkAcnts[i].balance
            << "\n";
        chkTotal += chkAcnts[i].balance;
    }
    float svgTotal = 0;                // 注4 普通預金口座の残高トータル
    cout << "普通預金口座：\n";
    for (i = 0; i < noSvgAccounts; i++)
    {
        cout << "口座 " << svgAcnts[i].accountNumber
            << " = " << svgAcnts[i].balance
            << "(引き出し回数 = " << svgAcnts[i].noWithdrawals
            << ")\n";
        svgTotal += svgAcnts[i].balance;
    }

    float total = chkTotal + svgTotal;  // 注4
    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)
        {
            checking.balance += transaction;    // 注5
        }

        // それとも、引き出しか？
        if (transaction < 0)
        {
            // 引き出し
            transaction = -transaction;
            if (checking.balance < transaction)
            {
                cout << "残高が足りません： 残高"
                    << checking.balance
                    << ", 引き出し額 "
                    << transaction
                    << "\n";
            }
            else
            {
                checking.balance -= transaction;

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

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

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

        // これは、入金か？
        if (transaction > 0)
        {
            savings.balance += transaction;     // 注5
        }

        // それとも、引き出しか？
        if (transaction < 0)
        {
            transaction = -transaction;
            if (savings.balance < transaction)
            {
                cout << "残高が足りません： 残高"
                    << savings.balance
                    << ", 引き出し額 "
                    << transaction
                    << "\n";
            }
            else
            {
                if (++savings.noWithdrawals > 1)    // 注5
                {
                    savings.balance -= 5.00F;
                }
                savings.balance -= transaction;
            }
        }
    } while (transaction != 0);
}

```