はじめに
【C++言語】 は、C言語を拡張して1980年代に登場したプログラミング言語です。
特徴としては、C言語の「速さ・低レベル制御」を残しつつ、「オブジェクト指向」「例外処理」「標準ライブラリの拡充」などが加わりました。
第1章:プログラミングとは何か
1.1 プログラムとは何か
プログラムとは、コンピュータに対して「何をどうするか」を順番に書いた命令の集合です。
コンピュータは基本的に以下の3つしか理解できません:
・数値
・メモリアドレス
・機械命令
つまり、私たちが書くC++のコードは、最終的には
・0 と 1 の命令列
に変換されます。
1.2 なぜC++なのか
C++はメモリを直接扱える高水準(クラスなど)も使える
低水準(ポインタなど)も使えるという「両方を持つ言語」です。
つまり、コンピュータの内部構造を理解しながら
安全な設計も学べる言語です。
1.3 コンパイルの流れ
C++はコンパイル言語です。
流れ
① ソースコード (.cpp)
↓
② プリプロセッサ処理
↓
③ コンパイル(機械語へ変換)
↓
④ リンク
↓
⑤ 実行ファイル
1.3.1 プリプロセッサとは
C++は実際にコンパイルされる前に
から始まる命令
が処理されます。
例:
#include
これは
iostreamの中身をここにコピーする
という意味です。
1.4 プログラムの最小構成
#include <iostream>
int main() {
std::cout << "Hello World" << std::endl;
return 0;
}
各部分の詳細説明
①
#include <iostream>
標準入出力ライブラリを読み込む。
・std::cout
・std::cin
・std::endl
が使えるようになります。
② int main()
C++のエントリーポイント。
・プログラムは必ずここから始まります。
③ return 0;
・終了コード。
値:意味
0:正常終了
1以上:エラー
1.5 main関数の引数
int main(int argc, char* argv[])
argc
引数の数。
argv
引数の配列。
1.6 プログラムは何をしているか
プログラムの本質は
入力 → 処理 → 出力
です。
例:
int x;
std::cin >> x;
std::cout << x * 2;
第2章:変数とメモリ
2.1 メモリとは何か
メモリは巨大な連続した番号付き棚です。
0x1000
0x1001
0x1002
...
変数はこの棚のどこかに置かれます。
2.2 変数の正体
int age = 20;
内部では:
メモリ番地 0x1000 に 20 を保存
2.3 型とは何か
型とは:
・何バイト使うか
・どう解釈するか
のルールです。
2.4 基本型のサイズ
std::cout << sizeof(int);
■ 一般的な64bit環境でのサイズ例
| 型 | サイズ |
|---|---|
| char | 1byte |
| short | 2byte |
| int | 4byte |
| long | 8byte(Linux系) |
| long long | 8byte |
| float | 4byte |
| double | 8byte |
※これはあくまで一般例です。
■ 環境依存について
C++の基本型サイズは、次の要素に依存します。
OS
コンパイラ
CPUアーキテクチャ
特に long は注意が必要です。
| 環境 | long のサイズ |
|---|---|
| 64bit Linux / macOS | 8byte |
| 64bit Windows | 4byte |
つまり、long は必ず8byteとは限りません。
■ 型サイズの規格上の定義
C++規格では、整数型のサイズは「最低保証サイズ」で定義されています。
short は少なくとも16bit以上
・int は short 以上
・long は int 以上
・long long は少なくとも64bit以上
そのため、処理系ごとにサイズが異なることがあります。
■ サイズと表現できる数値の関係
ビット数が増えると、表現できる数値の「個数」が増えます。
・16bit → 2^16 = 65,536通り
・32bit → 2^32 = 約42億通り
・64bit → 2^64 = 約1844京通り
符号付き整数は通常「2の補数方式」で表されます。
例:32bitの int の場合
・-2,147,483,648 ~ 2,147,483,647
■ 浮動小数点型のサイズ
| 型 | サイズ | long のサイズ |
|---|---|---|
| float / macOS | 4byte | 約7桁 |
| double | 8byte | 約15桁 |
これらはほとんどの環境で IEEE 754 規格 に従っています。
2.5 符号付きと符号なし
signed int:負数を扱える。
unsigned int:負数なし。
範囲
32bitの場合:
signed: -2^31 ~ 2^31-1
unsigned: 0 ~ 2^32-1
2.6 int32_t と int64_t
#include <cstdint>
int32_t a;
int64_t b;
これは
・ビット幅が保証されている型
です。
なぜ必要か?
環境によって:
int = 4byte の場合もあれば違う場合もある
だから明示的に書く。
2.7 ビット操作
整数は2進数で保存されます。
例:
5 の2進数:
00000101
AND
x & y
両方1のとき1。
OR
x | y
どちらか1で1。
XOR
x ^ y
違うとき1。
NOT
~x
全ビット反転。
シフト
x << 1
x >> 1
2.8 2の補数
負数は「2の補数」で保存されます。
-5 を8bitで計算
+5:
00000101
反転:
11111010
+1:
11111011
これが -5。
証明コード
#include <iostream>
#include <cstdint>
int main() {
int8_t x = -5;
std::cout << std::hex << (int)(uint8_t)x;
}
出力:
fb
なぜ2の補数を使うのか?
理由:
加算回路だけで減算ができるから
例:
5 + (-5) = 0
2進数でも同じ。
オーバーフロー
int8_t x = 127;
x++; // -128
なぜ?
01111111 + 00000001 = 10000000
これは -128。
第3章:関数
3.1 関数とは何か
関数とは:
・処理をまとめた再利用可能な部品
です。
3.2 基本構文
戻り値の型 関数名(引数) {
処理
return 値;
}
例
int add(int a, int b) {
return a + b;
}
3.3 メモリ上の動き
関数が呼ばれると:
スタックメモリに引数がコピーされる
関数内のローカル変数が生成される
3.4 値渡し
void foo(int x) {
x = 100;
}
👉 呼び出し元は変わらない。
文字列を 丸ごとコピー
関数終了後に破棄
💥 大きい文字列だと無駄が大きい
3.5 参照渡し(C++の重要概念)
void foo(int& x) {
x = 100;
}
👉 コピーではなく、元の変数を直接操作。
3.6 const参照
void print(const std::string& s);
/* 定数になり、コピーせず、安全に渡せる。 */
「変更しない引数は const参照で受ける」
🟦 なぜこれが重要?
C++では
std::string
std::vector
など、大きなクラス
はコピーコストが高い
3.7 関数のオーバーロード
int add(int a, int b);
double add(double a, double b);
👉 同じ関数名でも、引数の型が違えば別の関数として定義できるこれを オーバーロード(overload) といいます。
🟢 実際の例
#include <iostream>
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int main() {
/* int版が呼ばれる */
std::cout << add(3, 5) << std::endl;
/* double版が呼ばれる */
std::cout << add(2.5, 4.1) << std::endl;
}
3.8 再帰関数
int fact(int n) {
if (n == 1) return 1;
return n * fact(n-1);
}
👉 自分自身を呼び出す関数 のことを
再帰(recursion) と言います。
🟢 この関数は何をしている?
これは 階乗(factorial) を求める関数です。
階乗とは
5! = 5 × 4 × 3 × 2 × 1
🟦 動きを分解してみる
例:fact(4)
fact(4)
= 4 * fact(3)
= 4 * (3 * fact(2))
= 4 * (3 * (2 * fact(1)))
= 4 * (3 * (2 * 1))
= 24
第4章:ポインタ
4.1 ポインタとは
ポインタは:
・メモリアドレスを格納する変数
4.2 基本構文
int a = 10;
int* p = &a;
4.3 間接参照
*p = 20;
4.4 ポインタのメモリ図
a: 10
p: &a
4.5 配列とポインタ
int arr[3] = {1,2,3};
int* p = arr;
*(p+1) // 2
4.6 二重ポインタ
int** pp;
使う場面:
ポインタを書き換えたいとき
4.7 nullptr
int* p = nullptr;
👉 「どこも指していないポインタ」 を表します。
4.8 constポインタ
const int* p;
int* const p;
意味が違う。
const int * p
👉 int が const
つまり:
「p は int を指すポインタ」
「その int は変更できない」
int* const p;
分解すると:
int * const p
👉 p が const
つまり:
「p は固定されたポインタ」
「指す先は変えられない」
4.9 危険なケース
🟥 未初期化ポインタ
int* p; // 初期化していない
*p = 10; // 💥 未定義動作
なぜ危険?
p にはゴミアドレスが入っている可能性
存在しないメモリを書き換える
クラッシュ or 静かに壊れる
🟥 ダングリングポインタ
❌ 危険例(スコープ終了)
int* p;
{
int x = 10;
p = &x;
} // x はここで消える
std::cout << *p; // 💥 ダングリング
x はもう存在しない。
・解放後アクセス
❌ 危険例(delete後)
int* p = new int(10);
delete p;
std::cout << *p; // 💥 危険
delete後も p は同じアドレスを持っている。
でもそこはもう使えない。
🟥 解放後アクセス(Use After Free)
❌ 危険例
int* p = new int(5);
int* q = p;
delete p;
*q = 10; // 💥 解放済みメモリへアクセス
p だけ nullptr にしても
q はまだ古いアドレスを持っている。
第5章:構造体とクラス
5.1 struct
struct Person {
std::string name;
int age;
};
🔹 特徴
デフォルトアクセス修飾子は public
主に「データのまとまり」に使う
5.2 class
class Person {
private:
int age;
public:
Person(int a) : age(a) {}
};
🔹 特徴
デフォルトは private
カプセル化を意識した設計向き
5.3 コンストラクタ
Person(int a) : age(a) {}
🔹 役割
オブジェクト生成時に呼ばれる特別な関数
🟢 メンバ初期化リスト
: age(a)
これは:
・代入ではない
・直接初期化
効率が良い。
5.4 コピーコンストラクタ
Person(const Person& other);
🔹 いつ呼ばれる?
値渡し
戻り値
明示コピー
例:
Person p1(20);
Person p2 = p1; // コピーコンストラクタ
5.5 デストラクタ
~Person();
🔹 役割
オブジェクトが破棄されるときに呼ばれる
使用例
~Person() {
std::cout << "destroyed";
}
呼ばれるタイミング
・スコープ終了
・delete
・プログラム終了
5.6 継承
class Employee : public Person {};
🔹 意味
Employee は Person の一種
public継承とは?
親の public は public のまま
「is-a関係」
例
Employee e(30);
5.7 仮想関数
virtual void display();
🔹 目的
ポリモーフィズム(多態性)
🔹 仕組み
通常の関数(非virtual)は
👉 静的バインディング(コンパイル時決定)
仮想関数は
👉 動的バインディング(実行時決定)
🔹 イメージ
Person* p = new Employee();
👉 見た目:Person型
👉 実体:Employee
p->display();
👉 実体(Employee)の関数が呼ばれる
🔹 なぜ起きる?
内部的には👇
クラスに 仮想関数テーブル(vtable) がある
オブジェクトは vptr(ポインタ) を持つ
実行時にそこを参照して関数を決定
👉 つまり
「型じゃなくて実体を見る」
🔹 override の意味
void display() override
仮想関数の上書きであることを保証
間違えるとコンパイルエラーになる
👉 実務では必ず付けるべき
🔹 virtual を付ける場所
class Person {
public:
virtual void display();
};
👉 親クラスだけでOK
子クラスは自動的に仮想になる
🔹 よくあるミス
❌ virtual付け忘れ
void display(); // ←これだと静的バインディング
👉 結果
Person* p = new Employee();
p->display(); // "Person"になる
例
class Person {
public:
virtual void display() {
std::cout << "Person";
}
};
class Employee : public Person {
public:
void display() override {
std::cout << "Employee";
}
};
動作
Person* p = new Employee();
p->display();
👉 "Employee" が表示される
これが 動的バインディング
5.8 純粋仮想関数
virtual void foo() = 0;
🔹 意味
実装を持たない仮想関数
🔹 本質
👉 「この関数は必ず実装しろ」というルール
🔹 抽象クラス
class Base {
public:
virtual void foo() = 0;
};
👉 この時点で
→ Base は
インスタンス化 ❌
ポインタ・参照はOK ⭕
🔹 実装しないとどうなる?
class Derived : public Base {
};
👉 コンパイルエラー(未実装)
🔹 正しい実装
class Derived : public Base {
public:
void foo() override {
std::cout << "implemented";
}
};
🔹 仮想関数とデストラクタ
class Base {
public:
virtual ~Base() {}
};
👉 継承で使うクラスは必ずvirtualデストラクタ
🔹 理由
Base* p = new Derived();
delete p;
❌ virtualなし → Derivedのデストラクタ呼ばれない
⭕ virtualあり → 正しく両方呼ばれる
👉 メモリリークや不具合の原因になる
🟦 全体まとめ図
struct / class
↓
コンストラクタ
↓
コピーコンストラクタ
↓
デストラクタ
↓
継承
↓
virtual(動的バインディング)
↓
override(安全に上書き)
↓
純粋仮想(=0)(抽象クラス)
↓
インターフェース設計
第6章:動的メモリ
6.1 new/delete
int* p = new int(10);
delete p;
🔹 意味
new → ヒープ領域に確保
delete → 手動解放
6.2 配列
int* arr = new int[5];
delete[] arr;
🔹 重要
new[] には delete[]
new には delete
6.3 RAII
オブジェクトの寿命=リソース管理。
Resource Acquisition Is Initialization
意味:
・オブジェクトの寿命とリソース管理を結びつける
例
{
std::ofstream file("a.txt");
} // スコープ終了で自動クローズ
👉 デストラクタが自動で解放。
本質
確保 → コンストラクタ
解放 → デストラクタ
だから:
手動deleteを書かない設計が理想
6.4 smart pointer
std::unique_ptr<int> p;
🔹 unique_ptr
#include <memory>
std::unique_ptr<int> p = std::make_unique<int>(10);
所有者は1つだけ
自動解放
コピー不可
🔹 shared_ptr
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = p1;
・参照カウント管理
・最後の1つで解放
✨現代C++の鉄則
・生ポインタで new/delete は基本書かない
6.5 shallow copy vs deep copy
・ポインタメンバを持つクラスでは注意。
🔹 shallow copy(浅いコピー)
ポインタの「値」だけコピー。
class A {
public:
int* p;
};
A a1;
a1.p = new int(10);
A a2 = a1; // ポインタ値だけコピー
👉 同じメモリを指す。
危険
delete a1.p;
delete a2.p; // 💥 二重解放
🟦 deep copy(深いコピー)
新しいメモリを作る。
A(const A& other) {
p = new int(*other.p);
}
👉 完全に独立。
🟦 なぜ問題になる?
ポインタメンバを持つクラスは:
・コピーコンストラクタ
・代入演算子
・デストラクタ
を自分で書く必要がある。
🟦 Rule of Three
もし1つ書いたら:
デストラクタ
コピーコンストラクタ
コピー代入演算子
全部書く。
🟦 さらに現代版
Rule of Five:
・ムーブコンストラクタ
・ムーブ代入演算子
も含む。
🟦 まとめ図
new/delete → 手動管理(危険)
RAII → 自動管理
smart pointer → 安全なRAII実装
shallow copy → 事故の元
deep copy → 正しい設計
🟦 結論
現代C++では:
✔ new/deleteを直接書かない
✔ unique_ptr を使う
✔ 所有権を明確にする
第7章:配列と可変長データ
7.1 固定配列
int arr[5];
特徴
・サイズ固定(コンパイル時に決定)
・スタック領域
・サイズ変更不可
問題点
・サイズが分からないと扱いにくい
・配列サイズは sizeof(arr)/sizeof(arr[0])
7.2 vector
std::vector<int> v;
特徴
・可変長
・ヒープ管理
・自動メモリ管理
v.push_back(10);
v.resize(100);
本質
・C配列の安全な上位互換
7.3 string
std::string s = "Hello";
特徴
・内部的に可変長バッファ
・自動メモリ管理
・+, += 使用可能
s += " World";
注意
c_str() は const char* を返す。
7.4 可変長データ
std::vector<unsigned char> data;
可変長データとは:
・vector
・string
・動的配列
・可変長ログ
本質
サイズが実行時に決まるデータ
第8章:ファイル操作
8.1 書き込み
std::ofstream file("log.txt");
基本
file << "Hello\n";
RAIIにより自動close。
8.2 読み込み
std::ifstream file("log.txt");
std::string line;
while (std::getline(file, line)) {
}
8.3 ファイルサイズ取得
file.seekg(0, std::ios::end);
例:
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
8.4 日時取得
std::time_t now = std::time(nullptr);
std::cout << std::ctime(&now);
より安全なのは:
std::chrono
第9章:キュー構造
9.1 STLキュー
std::queue<int> q;
操作
q.push(10);
q.front();
q.pop();
FIFO構造。
9.2 循環キュー自作
class CircularQueue {
private:
std::vector<int> buffer;
int front;
int rear;
};
本質
rear → 挿入位置
front → 取り出し位置
インデックスは:
(index + 1) % capacity
第10章:共通ログモジュール
🟢設計思想
・ローテーション
・サイズ監視
・世代管理
・日時付与
🟢クラス構造
class Logger {
private:
std::string baseName;
int maxGeneration;
size_t maxSize;
int currentGen;
std::ofstream file;
void rotate();
size_t getFileSize();
std::string timestamp();
public:
Logger(std::string name, int gen, size_t size);
void write(const std::string& msg);
};
🟢ローテーション処理
流れ:
1 書き込み
2 サイズチェック
3 超過なら rotate()
4 世代番号更新
5 古いログ削除
open → write → flush → size確認
第11章:プリプロセッサ
11.1 #define
#define MAX 100
危険:
#define MAX 100+1
11.2 引数付きマクロ
#define DOUBLE(x) ((x)*2)
括弧必須。
11.3 条件付きコンパイル
#ifdef DEBUG
#endif
ビルド制御。
第12章:クラス = 設計図
0️⃣ まず「クラス」とは何か?
クラスとは:
「データ」と「そのデータを扱う関数」をまとめた設計図
です。
🧠 設計図と実体
class Person { ... };
これは 設計図 です。
Person man(38, "太郎");
これは 実体(オブジェクト) です。
1️⃣ ① Person クラス(基本形)
class Person {
private:
int age;
std::string name;
🔹 private の意味
外部から直接触れない
データの保護(カプセル化)
🔹 メモリ構造
Person man(38, "太郎");
メモリ上:
man
├─ age = 38
└─ name = "太郎"
🔹 コンストラクタ
Person(int age, const std::string& name)
: age(age), name(name) {}
これは:
メンバ変数を初期化する特別な関数
なぜ初期化リスト?
: age(age), name(name)
これは:
代入ではない
直接初期化
🔹 const std::string& の意味
const std::string& name
意味:
コピーしない
変更しない
参照で受け取る
高速かつ安全。
2️⃣ ①-テスト部分
Person man(38, "太郎");
実行順序
1 メモリ確保
2 コンストラクタ実行
3 age=38
4 name="太郎"
3️⃣ ② 引数なしコンストラクタ
Person2() : age(11), name("仮太郎") {}
ポイント
C++では:
デフォルトコンストラクタを自分で書くと
自動生成されなくなる
4️⃣ ③ 継承
class Employee3 : public Person3
意味:
Employee3 は Person3 を継承する
🔹 メモリ構造
Employee3 p(25, "田中 太郎", 111);
内部構造:
p
├─ Person3 部分
│ ├─ age = 25
│ └─ name = "田中 太郎"
└─ employeeID = 111
🔹 親コンストラクタ呼び出し
: Person3(age, name), employeeID(employeeID)
順序:
親クラスのコンストラクタ
子クラスのコンストラクタ
5️⃣ ④ オーバーライド
virtual void display() const
🔹 なぜ virtual が必要?
virtualがないと:
Person4* p = new Employee4(...);
p->display();
→ 親の display が呼ばれる
virtualがあると:
→ 子の display が呼ばれる
🔹 override の意味
void display() const override
意味:
本当に上書きしているかコンパイラに確認させる
安全機能。
6️⃣ 仮想関数の内部仕組み
C++は内部で:
vtable(仮想関数テーブル)
を持つ。
メモリ概念図
Employee4
├─ vptr → vtable
├─ age
├─ name
└─ employeeID
vptrが:
display() → Employee4::display
を指している。
7️⃣ virtual デストラクタ
virtual ~Person4() = default;
なぜ必要?
Person4* p = new Employee4(...);
delete p;
これが安全になる。
8️⃣ ⑤ 抽象クラス
virtual long long getInfluence() const = 0;
=0 は:
純粋仮想関数
🔹 抽象クラスの特徴
インスタンス化できない
子クラスが必ず実装する
🔹 メモリ構造
Influencer y(31, "則夫", 990000LL);
y
├─ PersonAbs 部分
│ ├─ age
│ └─ name
└─ influence
9️⃣ long long と LL
990000LL
LLは:
long long 型であることを明示
🔟 display() の流れ
void display() const override {
PersonAbs::display();
std::cout << "影響は " << influence << "\n";
}
実行順
親の display
子の処理
11️⃣ main の流れ
StartClass_Employee();
実行順
1 PGM:START 出力
2 man 作成
3 Person4 コンストラクタ
・ Employee4 コンストラクタ
・ display 呼び出し
4 woman 作成
5 display 呼び出し
12️⃣ ポリモーフィズムの本質
PersonAbs* p = new Influencer(...);
p->display();
これが可能なのは:
・virtual
・vtable
のおかげ。
✅ ① カプセル化
private + getter/setter
✅ ② 継承
親の機能を引き継ぐ
✅ ③ ポリモーフィズム
virtual による動的束縛
✅ ④ 抽象化
純粋仮想関数
✅ ⑤ メモリ構造
オブジェクトは:
[親の部分][子の部分][vptr]
でできている。
💠C++まとめ💠
💠Cまとめ💠