第2章:課題の実装

はじめに

本章では、CPP Module 00の3つの演習を実装します。各演習の要件分析、設計アプローチ、そして実装のポイントを解説します。

---

1. ex00: Megaphone

1.1 課題の要件

  • コマンドライン引数を大文字に変換して出力する
  • 引数がない場合、デフォルトメッセージを出力する
  • 複数の引数はスペースなしで連結する

期待される動作:

___CODE_BLOCK_0___gt; ./megaphone "shhhhh... I think the students are asleep..."
SHHHHH... I THINK THE STUDENTS ARE ASLEEP...
___CODE_BLOCK_0___gt; ./megaphone Damnit " ! " "Sorry students, I thought this thing was off."
DAMNIT ! SORRY STUDENTS, I THOUGHT THIS THING WAS OFF.
___CODE_BLOCK_0___gt; ./megaphone
* LOUD AND UNBEARABLE FEEDBACK NOISE *

1.2 設計アプローチ

[main(argc, argv)]
      │
      ├── argc == 1 → デフォルトメッセージ出力
      │
      └── argc > 1 → 各引数を処理
                     │
                     └── 各文字を大文字変換して出力

1.3 実装

// megaphone.cpp
#include <iostream>
#include <cctype>

int main(int argc, char **argv) {
    if (argc == 1) {
        std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl;
        return 0;
    }

    for (int i = 1; i < argc; i++) {
        for (int j = 0; argv[i][j]; j++) {
            std::cout << static_cast<char>(std::toupper(argv[i][j]));
        }
    }
    std::cout << std::endl;

    return 0;
}

1.4 ポイント解説

std::toupper の使用:

#include <cctype>

char upper = std::toupper('a');  // 'A'

// 注意: std::toupperはintを返すので、charにキャストする
std::cout << static_cast<char>(std::toupper(c));

static_cast: C++では型変換にキャスト演算子を使用します:

// C言語スタイル(非推奨)
char c = (char)std::toupper('a');

// C++スタイル(推奨)
char c = static_cast<char>(std::toupper('a'));

---

2. ex01: PhoneBook

2.1 課題の要件

  • 電話帳アプリケーションを作成する
  • 最大8件の連絡先を管理する
  • ADD、SEARCH、EXITコマンドを実装する
  • 古い連絡先は新しいもので上書きされる
  • 2.2 Contactクラスの設計

    // Contact.hpp
    #ifndef CONTACT_HPP
    #define CONTACT_HPP
    
    #include <string>
    
    class Contact {
    private:
        std::string firstName;
        std::string lastName;
        std::string nickname;
        std::string phoneNumber;
        std::string darkestSecret;
    
    public:
        // Orthodox Canonical Form
        Contact();
        Contact(const Contact& other);
        Contact& operator=(const Contact& other);
        ~Contact();
    
        // ゲッター
        std::string getFirstName() const;
        std::string getLastName() const;
        std::string getNickname() const;
        std::string getPhoneNumber() const;
        std::string getDarkestSecret() const;
    
        // セッター
        void setFirstName(const std::string& value);
        void setLastName(const std::string& value);
        void setNickname(const std::string& value);
        void setPhoneNumber(const std::string& value);
        void setDarkestSecret(const std::string& value);
    
        // 表示
        void displayShort(int index) const;
        void displayFull() const;
    
        // 検証
        bool isEmpty() const;
    };
    
    #endif
    

    2.3 Contactクラスの実装

    // Contact.cpp
    #include "Contact.hpp"
    #include <iostream>
    #include <iomanip>
    
    // コンストラクタ
    Contact::Contact() {}
    
    Contact::Contact(const Contact& other) {
        *this = other;
    }
    
    Contact& Contact::operator=(const Contact& other) {
        if (this != &other) {
            firstName = other.firstName;
            lastName = other.lastName;
            nickname = other.nickname;
            phoneNumber = other.phoneNumber;
            darkestSecret = other.darkestSecret;
        }
        return *this;
    }
    
    Contact::~Contact() {}
    
    // ゲッター
    std::string Contact::getFirstName() const { return firstName; }
    std::string Contact::getLastName() const { return lastName; }
    std::string Contact::getNickname() const { return nickname; }
    std::string Contact::getPhoneNumber() const { return phoneNumber; }
    std::string Contact::getDarkestSecret() const { return darkestSecret; }
    
    // セッター
    void Contact::setFirstName(const std::string& value) { firstName = value; }
    void Contact::setLastName(const std::string& value) { lastName = value; }
    void Contact::setNickname(const std::string& value) { nickname = value; }
    void Contact::setPhoneNumber(const std::string& value) { phoneNumber = value; }
    void Contact::setDarkestSecret(const std::string& value) { darkestSecret = value; }
    
    // 10文字に切り詰めるヘルパー関数
    static std::string truncate(const std::string& str) {
        if (str.length() > 10) {
            return str.substr(0, 9) + ".";
        }
        return str;
    }
    
    // 短縮表示(一覧用)
    void Contact::displayShort(int index) const {
        std::cout << std::setw(10) << std::right << index << "|";
        std::cout << std::setw(10) << std::right << truncate(firstName) << "|";
        std::cout << std::setw(10) << std::right << truncate(lastName) << "|";
        std::cout << std::setw(10) << std::right << truncate(nickname) << std::endl;
    }
    
    // 詳細表示
    void Contact::displayFull() const {
        std::cout << "First Name: " << firstName << std::endl;
        std::cout << "Last Name: " << lastName << std::endl;
        std::cout << "Nickname: " << nickname << std::endl;
        std::cout << "Phone Number: " << phoneNumber << std::endl;
        std::cout << "Darkest Secret: " << darkestSecret << std::endl;
    }
    
    bool Contact::isEmpty() const {
        return firstName.empty();
    }
    

    2.4 PhoneBookクラスの設計

    // PhoneBook.hpp
    #ifndef PHONEBOOK_HPP
    #define PHONEBOOK_HPP
    
    #include "Contact.hpp"
    
    class PhoneBook {
    private:
        Contact contacts[8];
        int count;
        int oldestIndex;
    
        std::string getInput(const std::string& prompt) const;
        void displayHeader() const;
    
    public:
        PhoneBook();
        PhoneBook(const PhoneBook& other);
        PhoneBook& operator=(const PhoneBook& other);
        ~PhoneBook();
    
        void add();
        void search() const;
    };
    
    #endif
    

    2.5 PhoneBookクラスの実装

    // PhoneBook.cpp
    #include "PhoneBook.hpp"
    #include <iostream>
    #include <iomanip>
    #include <limits>
    #include <cstdlib>
    
    PhoneBook::PhoneBook() : count(0), oldestIndex(0) {}
    
    PhoneBook::PhoneBook(const PhoneBook& other) {
        *this = other;
    }
    
    PhoneBook& PhoneBook::operator=(const PhoneBook& other) {
        if (this != &other) {
            for (int i = 0; i < 8; i++) {
                contacts[i] = other.contacts[i];
            }
            count = other.count;
            oldestIndex = other.oldestIndex;
        }
        return *this;
    }
    
    PhoneBook::~PhoneBook() {}
    
    std::string PhoneBook::getInput(const std::string& prompt) const {
        std::string input;
    
        while (true) {
            std::cout << prompt;
            if (!std::getline(std::cin, input)) {
                // EOFの処理
                std::cout << std::endl;
                std::exit(0);
            }
            if (!input.empty()) {
                break;
            }
            std::cout << "Input cannot be empty." << std::endl;
        }
        return input;
    }
    
    void PhoneBook::add() {
        Contact newContact;
    
        newContact.setFirstName(getInput("First Name: "));
        newContact.setLastName(getInput("Last Name: "));
        newContact.setNickname(getInput("Nickname: "));
        newContact.setPhoneNumber(getInput("Phone Number: "));
        newContact.setDarkestSecret(getInput("Darkest Secret: "));
    
        if (count < 8) {
            contacts[count] = newContact;
            count++;
        } else {
            contacts[oldestIndex] = newContact;
            oldestIndex = (oldestIndex + 1) % 8;
        }
    
        std::cout << "Contact added successfully!" << std::endl;
    }
    
    void PhoneBook::displayHeader() const {
        std::cout << std::setw(10) << std::right << "Index" << "|";
        std::cout << std::setw(10) << std::right << "First Name" << "|";
        std::cout << std::setw(10) << std::right << "Last Name" << "|";
        std::cout << std::setw(10) << std::right << "Nickname" << std::endl;
        std::cout << std::string(44, '-') << std::endl;
    }
    
    void PhoneBook::search() const {
        if (count == 0) {
            std::cout << "PhoneBook is empty." << std::endl;
            return;
        }
    
        displayHeader();
        for (int i = 0; i < count; i++) {
            contacts[i].displayShort(i);
        }
    
        std::cout << "Enter index to view details: ";
        std::string input;
        if (!std::getline(std::cin, input)) {
            std::cout << std::endl;
            return;
        }
    
        // 入力の検証
        if (input.empty() || input.find_first_not_of("0123456789") != std::string::npos) {
            std::cout << "Invalid index." << std::endl;
            return;
        }
    
        int index = std::atoi(input.c_str());
        if (index < 0 || index >= count) {
            std::cout << "Index out of range." << std::endl;
            return;
        }
    
        contacts[index].displayFull();
    }
    

    2.6 メインプログラム

    // main.cpp
    #include "PhoneBook.hpp"
    #include <iostream>
    
    int main() {
        PhoneBook phoneBook;
        std::string command;
    
        while (true) {
            std::cout << "Enter command (ADD, SEARCH, EXIT): ";
            if (!std::getline(std::cin, command)) {
                std::cout << std::endl;
                break;
            }
    
            if (command == "ADD") {
                phoneBook.add();
            } else if (command == "SEARCH") {
                phoneBook.search();
            } else if (command == "EXIT") {
                break;
            } else {
                std::cout << "Unknown command. Use ADD, SEARCH, or EXIT." << std::endl;
            }
        }
    
        return 0;
    }
    

    ---

    3. ex02: Account

    3.1 課題の要件

  • 提供されたヘッダファイルからクラスを実装する
  • ログ出力が期待される形式と一致する必要がある
  • タイムスタンプを正しく生成する

3.2 ヘッダファイルの分析

// Account.hpp(提供される)
class Account {
public:
    typedef Account t;

    static int getNbAccounts(void);
    static int getTotalAmount(void);
    static int getNbDeposits(void);
    static int getNbWithdrawals(void);
    static void displayAccountsInfos(void);

    Account(int initial_deposit);
    ~Account(void);

    void makeDeposit(int deposit);
    bool makeWithdrawal(int withdrawal);
    int checkAmount(void) const;
    void displayStatus(void) const;

private:
    static int _nbAccounts;
    static int _totalAmount;
    static int _totalNbDeposits;
    static int _totalNbWithdrawals;

    static void _displayTimestamp(void);

    int _accountIndex;
    int _amount;
    int _nbDeposits;
    int _nbWithdrawals;

    Account(void);
};

3.3 実装

// Account.cpp
#include "Account.hpp"
#include <iostream>
#include <iomanip>
#include <ctime>

// 静的メンバ変数の初期化
int Account::_nbAccounts = 0;
int Account::_totalAmount = 0;
int Account::_totalNbDeposits = 0;
int Account::_totalNbWithdrawals = 0;

// 静的メンバ関数
int Account::getNbAccounts(void) {
    return _nbAccounts;
}

int Account::getTotalAmount(void) {
    return _totalAmount;
}

int Account::getNbDeposits(void) {
    return _totalNbDeposits;
}

int Account::getNbWithdrawals(void) {
    return _totalNbWithdrawals;
}

void Account::displayAccountsInfos(void) {
    _displayTimestamp();
    std::cout << "accounts:" << _nbAccounts
              << ";total:" << _totalAmount
              << ";deposits:" << _totalNbDeposits
              << ";withdrawals:" << _totalNbWithdrawals
              << std::endl;
}

void Account::_displayTimestamp(void) {
    std::time_t t = std::time(NULL);
    std::tm* now = std::localtime(&t);

    std::cout << "[" << (now->tm_year + 1900)
              << std::setfill('0')
              << std::setw(2) << (now->tm_mon + 1)
              << std::setw(2) << now->tm_mday
              << "_"
              << std::setw(2) << now->tm_hour
              << std::setw(2) << now->tm_min
              << std::setw(2) << now->tm_sec
              << "] ";
}

// コンストラクタ
Account::Account(int initial_deposit)
    : _accountIndex(_nbAccounts), _amount(initial_deposit),
      _nbDeposits(0), _nbWithdrawals(0) {
    _nbAccounts++;
    _totalAmount += initial_deposit;

    _displayTimestamp();
    std::cout << "index:" << _accountIndex
              << ";amount:" << _amount
              << ";created" << std::endl;
}

// デストラクタ
Account::~Account(void) {
    _displayTimestamp();
    std::cout << "index:" << _accountIndex
              << ";amount:" << _amount
              << ";closed" << std::endl;
}

// 入金
void Account::makeDeposit(int deposit) {
    _displayTimestamp();
    std::cout << "index:" << _accountIndex
              << ";p_amount:" << _amount
              << ";deposit:" << deposit;

    _amount += deposit;
    _nbDeposits++;
    _totalAmount += deposit;
    _totalNbDeposits++;

    std::cout << ";amount:" << _amount
              << ";nb_deposits:" << _nbDeposits
              << std::endl;
}

// 出金
bool Account::makeWithdrawal(int withdrawal) {
    _displayTimestamp();
    std::cout << "index:" << _accountIndex
              << ";p_amount:" << _amount
              << ";withdrawal:";

    if (withdrawal > _amount) {
        std::cout << "refused" << std::endl;
        return false;
    }

    _amount -= withdrawal;
    _nbWithdrawals++;
    _totalAmount -= withdrawal;
    _totalNbWithdrawals++;

    std::cout << withdrawal
              << ";amount:" << _amount
              << ";nb_withdrawals:" << _nbWithdrawals
              << std::endl;
    return true;
}

int Account::checkAmount(void) const {
    return _amount;
}

void Account::displayStatus(void) const {
    _displayTimestamp();
    std::cout << "index:" << _accountIndex
              << ";amount:" << _amount
              << ";deposits:" << _nbDeposits
              << ";withdrawals:" << _nbWithdrawals
              << std::endl;
}

3.4 ポイント解説

タイムスタンプの生成:

#include <ctime>

std::time_t t = std::time(NULL);
std::tm* now = std::localtime(&t);

// フォーマット: [YYYYMMDD_HHMMSS]
std::cout << "[" << (now->tm_year + 1900)
          << std::setfill('0')
          << std::setw(2) << (now->tm_mon + 1)  // 月は0-11
          << std::setw(2) << now->tm_mday
          << "_"
          << std::setw(2) << now->tm_hour
          << std::setw(2) << now->tm_min
          << std::setw(2) << now->tm_sec
          << "] ";

静的メンバの初期化:

// ヘッダファイル
class Account {
    static int _nbAccounts;
    // ...
};

// ソースファイル(クラス外で必ず定義)
int Account::_nbAccounts = 0;

---

まとめ

本章で実装した内容:

  • ex00: Megaphone
- コマンドライン引数の処理 - 文字列の大文字変換 - C++ I/Oストリーム

  • ex01: PhoneBook
- クラス設計とカプセル化 - Orthodox Canonical Form - ユーザー入力の処理 - フォーマット出力

  • ex02: Account
- ヘッダファイルからの実装 - 静的メンバ変数と関数 - タイムスタンプ処理

次章では、これらの実装のテストとデバッグ方法を学びます。