1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ゼロから始めるプログラム学習(C++)

1
Last updated at Posted at 2026-03-01

はじめに

【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まとめ💠


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?