第1章:メモリ割り当てと参照

はじめに

CPP Module 01では、C++のメモリ管理と参照について学びます。C言語のmalloc/freeからC++のnew/deleteへの移行、そして参照という新しい概念を理解することがこのモジュールの目標です。

---

1. new/delete演算子

1.1 mallocからnewへ

C言語のメモリ割り当て:

/* C言語 */
int* ptr = (int*)malloc(sizeof(int));
if (ptr == NULL) {
    /* エラー処理 */
}
*ptr = 42;
free(ptr);

C++のメモリ割り当て:

// C++
int* ptr = new int;
*ptr = 42;
delete ptr;

// または初期化と同時に
int* ptr = new int(42);
delete ptr;

1.2 newの特徴

| 観点 | malloc | new | |------|--------|-----| | 型 | voidを返す(キャスト必要) | 正しい型を返す | | サイズ | sizeof()が必要 | 自動計算 | | コンストラクタ | 呼ばれない | 呼ばれる | | 失敗時 | NULLを返す | 例外を投げる |

1.3 オブジェクトの動的生成

class Zombie {
private:
    std::string name;

public:
    Zombie(const std::string& name) : name(name) {
        std::cout << name << " is born" << std::endl;
    }

    ~Zombie() {
        std::cout << name << " is destroyed" << std::endl;
    }

    void announce() const {
        std::cout << name << ": BraiiiiiiinnnzzzZ..." << std::endl;
    }
};

int main() {
    // スタック上に生成
    Zombie stackZombie("StackZombie");
    stackZombie.announce();

    // ヒープ上に生成
    Zombie* heapZombie = new Zombie("HeapZombie");
    heapZombie->announce();
    delete heapZombie;

    return 0;
}  // stackZombieはここで自動的に破棄

1.4 配列の動的割り当て

// オブジェクト配列の生成
Zombie* horde = new Zombie[5];

// 使用
for (int i = 0; i < 5; i++) {
    horde[i].announce();
}

// 解放(delete[]を使う!)
delete[] horde;

注意: new[]で確保したメモリは必ずdelete[]で解放する。

---

2. 参照(References)

2.1 参照とは

参照は変数の別名(alias)です:

int original = 42;
int& ref = original;  // refはoriginalの別名

std::cout << original << std::endl;  // 42
std::cout << ref << std::endl;       // 42

ref = 100;
std::cout << original << std::endl;  // 100

2.2 ポインタとの違い

int value = 42;

// ポインタ
int* ptr = &value;
*ptr = 100;         // 間接参照が必要
ptr = nullptr;      // 再代入可能

// 参照
int& ref = value;
ref = 100;          // 直接使用
// ref = other;     // 別の変数を参照することはできない

| 観点 | ポインタ | 参照 | |------|---------|------| | 初期化 | 後から可能 | 宣言時に必須 | | null | nullptrを指せる | 必ず有効な変数を参照 | | 再代入 | 可能 | 不可能 | | 構文 | &が必要 | 通常の変数と同じ |

2.3 関数パラメータとしての参照

// 値渡し(コピーが作られる)
void byValue(int x) {
    x = 100;  // 元の変数には影響しない
}

// ポインタ渡し
void byPointer(int* x) {
    *x = 100;  // 元の変数が変更される
}

// 参照渡し
void byReference(int& x) {
    x = 100;  // 元の変数が変更される
}

int main() {
    int a = 42;

    byValue(a);
    std::cout << a << std::endl;     // 42

    byPointer(&a);
    std::cout << a << std::endl;     // 100

    a = 42;
    byReference(a);
    std::cout << a << std::endl;     // 100

    return 0;
}

2.4 const参照

void printValue(const std::string& str) {
    std::cout << str << std::endl;
    // str = "modified";  // エラー:変更不可
}

int main() {
    std::string message = "Hello";
    printValue(message);
    return 0;
}

const参照の利点:

  • コピーを避けて効率的
  • 誤って変更することを防ぐ
  • 一時オブジェクトを受け取れる

---

3. ファイルストリーム

3.1 ファイル入出力の基本

#include <fstream>
#include <iostream>

int main() {
    // ファイルへの書き込み
    std::ofstream outFile("output.txt");
    if (!outFile) {
        std::cerr << "Failed to open file" << std::endl;
        return 1;
    }
    outFile << "Hello, File!" << std::endl;
    outFile.close();

    // ファイルからの読み込み
    std::ifstream inFile("output.txt");
    if (!inFile) {
        std::cerr << "Failed to open file" << std::endl;
        return 1;
    }
    std::string line;
    while (std::getline(inFile, line)) {
        std::cout << line << std::endl;
    }
    inFile.close();

    return 0;
}

3.2 ファイルストリームクラス

| クラス | 用途 | |--------|------| | std::ifstream | 入力(読み込み) | | std::ofstream | 出力(書き込み) | | std::fstream | 入出力両方 |

3.3 文字列の置換

#include <fstream>
#include <string>

void replaceInFile(const std::string& filename,
                   const std::string& s1,
                   const std::string& s2) {
    std::ifstream inFile(filename.c_str());
    if (!inFile) {
        std::cerr << "Cannot open: " << filename << std::endl;
        return;
    }

    std::string content;
    std::string line;
    while (std::getline(inFile, line)) {
        if (!content.empty()) {
            content += '\n';
        }
        content += line;
    }
    inFile.close();

    // 置換
    size_t pos = 0;
    while ((pos = content.find(s1, pos)) != std::string::npos) {
        content = content.substr(0, pos) + s2 + content.substr(pos + s1.length());
        pos += s2.length();
    }

    // 新しいファイルに書き込み
    std::ofstream outFile((filename + ".replace").c_str());
    outFile << content;
    outFile.close();
}

---

4. ポインタToメンバ関数

4.1 基本構文

class Harl {
public:
    void debug() { std::cout << "DEBUG" << std::endl; }
    void info() { std::cout << "INFO" << std::endl; }
    void warning() { std::cout << "WARNING" << std::endl; }
    void error() { std::cout << "ERROR" << std::endl; }
};

int main() {
    Harl harl;

    // メンバ関数へのポインタ
    void (Harl::*funcPtr)() = &Harl::debug;

    // 呼び出し
    (harl.*funcPtr)();

    return 0;
}

4.2 配列による関数選択

class Harl {
public:
    void debug() { std::cout << "DEBUG" << std::endl; }
    void info() { std::cout << "INFO" << std::endl; }
    void warning() { std::cout << "WARNING" << std::endl; }
    void error() { std::cout << "ERROR" << std::endl; }

    void complain(std::string level) {
        void (Harl::*functions[])() = {
            &Harl::debug,
            &Harl::info,
            &Harl::warning,
            &Harl::error
        };

        std::string levels[] = {"DEBUG", "INFO", "WARNING", "ERROR"};

        for (int i = 0; i < 4; i++) {
            if (levels[i] == level) {
                (this->*functions[i])();
                return;
            }
        }
    }
};

---

5. switch文

5.1 基本構文

void filterLevel(const std::string& level) {
    int startLevel = -1;

    if (level == "DEBUG") startLevel = 0;
    else if (level == "INFO") startLevel = 1;
    else if (level == "WARNING") startLevel = 2;
    else if (level == "ERROR") startLevel = 3;

    switch (startLevel) {
        case 0:
            std::cout << "[ DEBUG ]" << std::endl;
            // fall through
        case 1:
            std::cout << "[ INFO ]" << std::endl;
            // fall through
        case 2:
            std::cout << "[ WARNING ]" << std::endl;
            // fall through
        case 3:
            std::cout << "[ ERROR ]" << std::endl;
            break;
        default:
            std::cout << "[ Probably complaining about insignificant problems ]" << std::endl;
    }
}

5.2 fall-through

breakがない場合、次のcaseに「落ちる」:

switch (value) {
    case 1:
        std::cout << "One" << std::endl;
        // breakなし - 次のcaseに続く
    case 2:
        std::cout << "Two" << std::endl;
        break;
}

// value = 1 の場合: "One" と "Two" が出力される

---

まとめ

本章で学んだこと:

  • new/delete: C++でのメモリ動的割り当て
  • 参照: 変数の別名、ポインタとの違い
  • ファイルストリーム: ファイルの読み書き
  • ポインタToメンバ関数: 関数を動的に選択
  • switch文: 複数条件での分岐

次章では、各演習の詳細な実装を解説します。