第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文: 複数条件での分岐
次章では、各演習の詳細な実装を解説します。