[C++] クラス、関数、そしてメモリ – 関係性と重要ポイントを解説

C++において、クラスはデータとその操作をまとめたオブジェクト指向の基本単位です。

クラス内で定義される関数(メンバ関数)は、そのクラスのデータ(メンバ変数)を操作します。

クラスのインスタンス(オブジェクト)はメモリ上に配置され、動的メモリ管理が必要な場合はnewdeleteを使用します。

特に、コンストラクタとデストラクタはオブジェクトの生成と破棄時に自動的に呼ばれ、メモリ管理において重要な役割を果たします。

この記事でわかること
  • C++におけるクラスとオブジェクトの基本
  • メモリ管理の重要性と手法
  • スマートポインタの活用方法
  • 動的メモリ確保の実践例
  • リソース管理のベストプラクティス

目次から探す

クラスとオブジェクトの基本

クラスとは何か

クラスは、C++におけるデータ構造の一つで、データとそのデータに関連する操作をまとめたものです。

クラスを使うことで、オブジェクト指向プログラミングの概念を活用し、コードの再利用性や可読性を向上させることができます。

クラスは、属性(メンバ変数)と振る舞い(メンバ関数)を持ちます。

オブジェクトの生成と破棄

オブジェクトは、クラスを基にして生成される実体です。

オブジェクトを生成する際には、クラスのインスタンスを作成します。

オブジェクトの生成には、スタックまたはヒープを使用することができます。

ヒープに生成したオブジェクトは、明示的にメモリを解放する必要があります。

以下は、オブジェクトの生成と破棄の例です。

#include <iostream>
using namespace std;
class MyClass {
public:
    MyClass() { // コンストラクタ
        cout << "オブジェクトが生成されました。" << endl;
    }
    ~MyClass() { // デストラクタ
        cout << "オブジェクトが破棄されました。" << endl;
    }
};
int main() {
    MyClass obj; // スタック上にオブジェクトを生成
    MyClass* ptr = new MyClass(); // ヒープ上にオブジェクトを生成
    delete ptr; // ヒープ上のオブジェクトを破棄
    return 0;
}
オブジェクトが生成されました。
オブジェクトが生成されました。
オブジェクトが破棄されました。
オブジェクトが破棄されました。

コンストラクタとデストラクタの役割

コンストラクタは、オブジェクトが生成される際に自動的に呼び出される特別な関数で、オブジェクトの初期化を行います。

一方、デストラクタは、オブジェクトが破棄される際に呼び出され、リソースの解放やクリーンアップを行います。

これにより、メモリリークを防ぐことができます。

メンバ変数とメンバ関数の関係

メンバ変数は、クラス内で定義された変数で、オブジェクトの状態を保持します。

メンバ関数は、クラス内で定義された関数で、オブジェクトの振る舞いを定義します。

メンバ関数は、メンバ変数にアクセスすることができ、オブジェクトの状態を操作するために使用されます。

以下は、メンバ変数とメンバ関数の例です。

#include <iostream>
using namespace std;
class MyClass {
private:
    int value; // メンバ変数
public:
    MyClass(int v) : value(v) {} // コンストラクタ
    void displayValue() { // メンバ関数
        cout << "値: " << value << endl;
    }
};
int main() {
    MyClass obj(10);
    obj.displayValue(); // メンバ関数を呼び出す
    return 0;
}
値: 10

アクセス修飾子(public, private, protected)

アクセス修飾子は、クラスのメンバに対するアクセスの制限を定義します。

主なアクセス修飾子は以下の通りです。

スクロールできます
修飾子説明
publicどこからでもアクセス可能
private同じクラス内からのみアクセス可能
protected同じクラスと派生クラスからアクセス可能

これにより、クラスの内部実装を隠蔽し、外部からの不正なアクセスを防ぐことができます。

関数とクラスの関係

メンバ関数の定義と呼び出し

メンバ関数は、クラス内で定義された関数で、オブジェクトの状態を操作するために使用されます。

メンバ関数は、クラスのインスタンスを通じて呼び出され、オブジェクトのメンバ変数にアクセスすることができます。

以下は、メンバ関数の定義と呼び出しの例です。

#include <iostream>
using namespace std;
class MyClass {
private:
    int value; // メンバ変数
public:
    MyClass(int v) : value(v) {} // コンストラクタ
    void displayValue() { // メンバ関数
        cout << "値: " << value << endl;
    }
};
int main() {
    MyClass obj(20); // オブジェクトの生成
    obj.displayValue(); // メンバ関数の呼び出し
    return 0;
}
値: 20

クラス外での関数定義

クラス外でメンバ関数を定義することも可能です。

この場合、関数名の前にクラス名とスコープ解決演算子::を使用します。

これにより、クラスの外部でメンバ関数を実装することができます。

以下は、クラス外での関数定義の例です。

#include <iostream>
using namespace std;
class MyClass {
private:
    int value; // メンバ変数
public:
    MyClass(int v) : value(v) {} // コンストラクタ
    void displayValue(); // メンバ関数の宣言
};
// クラス外でのメンバ関数の定義
void MyClass::displayValue() {
    cout << "値: " << value << endl;
}
int main() {
    MyClass obj(30); // オブジェクトの生成
    obj.displayValue(); // メンバ関数の呼び出し
    return 0;
}
値: 30

インライン関数とパフォーマンス

インライン関数は、関数呼び出しのオーバーヘッドを削減するために使用される関数です。

inlineキーワードを使用して定義され、コンパイラは関数の呼び出しをその場で展開します。

これにより、パフォーマンスが向上する場合がありますが、関数が大きい場合は逆効果になることもあります。

以下は、インライン関数の例です。

#include <iostream>
using namespace std;
class MyClass {
public:
    inline int square(int x) { // インライン関数
        return x * x;
    }
};
int main() {
    MyClass obj;
    cout << "4の二乗: " << obj.square(4) << endl; // インライン関数の呼び出し
    return 0;
}
4の二乗: 16

静的メンバ関数の使い方

静的メンバ関数は、クラスのインスタンスに依存せずに呼び出すことができる関数です。

静的メンバ関数は、staticキーワードを使用して定義され、クラス全体で共有されます。

以下は、静的メンバ関数の例です。

#include <iostream>
using namespace std;
class MyClass {
public:
    static int count; // 静的メンバ変数
    MyClass() {
        count++; // コンストラクタでカウントを増加
    }
    static void displayCount() { // 静的メンバ関数
        cout << "オブジェクトの数: " << count << endl;
    }
};
int MyClass::count = 0; // 静的メンバ変数の初期化
int main() {
    MyClass obj1;
    MyClass obj2;
    MyClass::displayCount(); // 静的メンバ関数の呼び出し
    return 0;
}
オブジェクトの数: 2

仮想関数とポリモーフィズム

仮想関数は、基底クラスで定義され、派生クラスでオーバーライドされる関数です。

仮想関数を使用することで、ポリモーフィズム(多態性)を実現し、同じインターフェースで異なる動作を持つオブジェクトを扱うことができます。

以下は、仮想関数の例です。

#include <iostream>
using namespace std;
class Base {
public:
    virtual void show() { // 仮想関数
        cout << "Baseクラスのshow関数" << endl;
    }
};
class Derived : public Base {
public:
    void show() override { // オーバーライド
        cout << "Derivedクラスのshow関数" << endl;
    }
};
int main() {
    Base* ptr; // 基底クラスのポインタ
    Derived obj; // 派生クラスのオブジェクト
    ptr = &obj; // 基底クラスのポインタに派生クラスのアドレスを代入
    ptr->show(); // 仮想関数の呼び出し
    return 0;
}
Derivedクラスのshow関数

メモリ管理の基礎

スタックとヒープの違い

スタックとヒープは、プログラムがメモリを管理するための2つの異なる領域です。

スタックは、関数の呼び出しやローカル変数のために使用され、メモリの割り当てと解放が自動的に行われます。

一方、ヒープは、動的にメモリを確保するために使用され、プログラマが明示的にメモリを管理する必要があります。

以下は、スタックとヒープの主な違いです。

スクロールできます
特徴スタックヒープ
メモリの割り当て自動的に行われるプログラマが手動で行う
速度高速比較的遅い
サイズ限定的大きなサイズを確保可能
生命期間関数のスコープ内明示的に解放されるまで生存

動的メモリ確保(newとdelete)

C++では、new演算子を使用して動的にメモリを確保し、delete演算子を使用してメモリを解放します。

newを使うことで、ヒープ領域にオブジェクトを生成し、必要なときにメモリを解放することができます。

以下は、newdeleteの使用例です。

#include <iostream>
using namespace std;
class MyClass {
public:
    MyClass() {
        cout << "オブジェクトが生成されました。" << endl;
    }
    ~MyClass() {
        cout << "オブジェクトが破棄されました。" << endl;
    }
};
int main() {
    MyClass* obj = new MyClass(); // 動的メモリ確保
    delete obj; // メモリの解放
    return 0;
}
オブジェクトが生成されました。
オブジェクトが破棄されました。

メモリリークの防止

メモリリークは、動的に確保したメモリが解放されずに残る現象です。

これにより、プログラムのメモリ使用量が増加し、最終的にはシステムのパフォーマンスに影響を与える可能性があります。

メモリリークを防ぐためには、以下のポイントに注意することが重要です。

  • newで確保したメモリは必ずdeleteで解放する。
  • 例外処理を行う際に、確保したメモリを適切に解放する。
  • スマートポインタを使用して、メモリ管理を自動化する。

スマートポインタの活用(unique_ptr, shared_ptr)

スマートポインタは、C++11以降で導入されたメモリ管理のためのクラスです。

unique_ptrは、所有権を持つポインタで、他のポインタにコピーできません。

shared_ptrは、複数のポインタが同じメモリを共有できるようにするポインタです。

これにより、メモリ管理が簡素化され、メモリリークを防ぐことができます。

以下は、スマートポインタの使用例です。

#include <iostream>
#include <memory> // スマートポインタのヘッダ
using namespace std;
class MyClass {
public:
    MyClass() {
        cout << "オブジェクトが生成されました。" << endl;
    }
    ~MyClass() {
        cout << "オブジェクトが破棄されました。" << endl;
    }
};
int main() {
    unique_ptr<MyClass> ptr1(new MyClass()); // unique_ptrの使用
    shared_ptr<MyClass> ptr2 = make_shared<MyClass>(); // shared_ptrの使用
    return 0;
}
オブジェクトが生成されました。
オブジェクトが生成されました。
オブジェクトが破棄されました。
オブジェクトが破棄されました。

RAII(Resource Acquisition Is Initialization)の概念

RAIIは、リソースの獲得をオブジェクトの初期化に結びつけるプログラミングの原則です。

RAIIを使用することで、リソースの管理が自動化され、スコープを抜けるときに自動的にリソースが解放されます。

これにより、メモリリークやリソースの不正使用を防ぐことができます。

以下は、RAIIの概念を示す例です。

#include <iostream>
#include <memory> // スマートポインタのヘッダ
using namespace std;
class Resource {
public:
    Resource() {
        cout << "リソースを獲得しました。" << endl;
    }
    ~Resource() {
        cout << "リソースを解放しました。" << endl;
    }
};
int main() {
    unique_ptr<Resource> res(new Resource()); // RAIIの適用
    // リソースはスコープを抜けると自動的に解放される
    return 0;
}
リソースを獲得しました。
リソースを解放しました。

クラスとメモリ管理

クラスの動的メモリ確保

C++では、クラスのインスタンスを動的に生成することができます。

これにより、プログラムの実行時に必要なメモリを確保し、柔軟なメモリ管理が可能になります。

new演算子を使用してクラスのオブジェクトをヒープに生成し、delete演算子で解放します。

以下は、クラスの動的メモリ確保の例です。

#include <iostream>
using namespace std;
class MyClass {
public:
    MyClass() {
        cout << "オブジェクトが生成されました。" << endl;
    }
    ~MyClass() {
        cout << "オブジェクトが破棄されました。" << endl;
    }
};
int main() {
    MyClass* obj = new MyClass(); // 動的メモリ確保
    delete obj; // メモリの解放
    return 0;
}
オブジェクトが生成されました。
オブジェクトが破棄されました。

コピーコンストラクタとムーブコンストラクタ

コピーコンストラクタは、オブジェクトのコピーを作成するための特別なコンストラクタです。

ムーブコンストラクタは、リソースの所有権を移動するために使用されます。

これにより、効率的なメモリ管理が可能になります。

以下は、コピーコンストラクタとムーブコンストラクタの例です。

#include <iostream>
using namespace std;
class MyClass {
private:
    int* data; // 動的メモリを指すポインタ
public:
    MyClass(int value) {
        data = new int(value); // 動的メモリ確保
        cout << "オブジェクトが生成されました。" << endl;
    }
    // コピーコンストラクタ
    MyClass(const MyClass& other) {
        data = new int(*other.data); // 深いコピー
        cout << "コピーコンストラクタが呼ばれました。" << endl;
    }
    // ムーブコンストラクタ
    MyClass(MyClass&& other) noexcept {
        data = other.data; // 所有権を移動
        other.data = nullptr; // 元のポインタを無効化
        cout << "ムーブコンストラクタが呼ばれました。" << endl;
    }
    ~MyClass() {
        delete data; // メモリの解放
        cout << "オブジェクトが破棄されました。" << endl;
    }
};
int main() {
    MyClass obj1(10); // オブジェクトの生成
    MyClass obj2 = obj1; // コピーコンストラクタの呼び出し
    MyClass obj3 = std::move(obj1); // ムーブコンストラクタの呼び出し
    return 0;
}
オブジェクトが生成されました。
コピーコンストラクタが呼ばれました。
オブジェクトが破棄されました。
オブジェクトが破棄されました。

デストラクタでのメモリ解放

デストラクタは、オブジェクトが破棄される際に呼び出される特別な関数で、リソースの解放を行います。

動的に確保したメモリを解放するために、デストラクタを適切に実装することが重要です。

以下は、デストラクタでのメモリ解放の例です。

#include <iostream>
using namespace std;
class MyClass {
private:
    int* data; // 動的メモリを指すポインタ
public:
    MyClass(int value) {
        data = new int(value); // 動的メモリ確保
        cout << "オブジェクトが生成されました。" << endl;
    }
    ~MyClass() {
        delete data; // メモリの解放
        cout << "オブジェクトが破棄されました。" << endl;
    }
};
int main() {
    MyClass obj(20); // オブジェクトの生成
    return 0;
}
オブジェクトが生成されました。
オブジェクトが破棄されました。

深いコピーと浅いコピーの違い

深いコピーは、オブジェクトの全てのメンバを新しいメモリにコピーすることを指します。

一方、浅いコピーは、ポインタのアドレスをコピーするだけで、同じメモリを指すことになります。

深いコピーを行わないと、複数のオブジェクトが同じメモリを指すことになり、メモリリークや二重解放の原因となります。

以下は、深いコピーと浅いコピーの違いを示す例です。

#include <iostream>
using namespace std;
class MyClass {
private:
    int* data; // 動的メモリを指すポインタ
public:
    MyClass(int value) {
        data = new int(value); // 動的メモリ確保
    }
    // 浅いコピー
    MyClass(const MyClass& other) : data(other.data) {
        cout << "浅いコピーが行われました。" << endl;
    }
    // 深いコピー
    MyClass deepCopy(const MyClass& other) {
        MyClass newObj(*other.data); // 深いコピー
        return newObj;
    }
    ~MyClass() {
        delete data; // メモリの解放(二重解放が発生する)
    }
};
int main() {
    MyClass obj1(30); // オブジェクトの生成
    MyClass obj2 = obj1; // 浅いコピー
    return 0;
}
浅いコピーが行われました。

メモリ管理における例外処理

メモリ管理において、例外処理は非常に重要です。

動的メモリ確保中に例外が発生した場合、適切にリソースを解放する必要があります。

RAIIを使用することで、例外が発生しても自動的にリソースが解放されるため、メモリリークを防ぐことができます。

以下は、例外処理を考慮したメモリ管理の例です。

#include <iostream>
#include <memory> // スマートポインタのヘッダ
using namespace std;
class Resource {
public:
    Resource() {
        cout << "リソースを獲得しました。" << endl;
    }
    ~Resource() {
        cout << "リソースを解放しました。" << endl;
    }
};
int main() {
    try {
        unique_ptr<Resource> res(new Resource()); // RAIIの適用
        throw runtime_error("例外が発生しました。"); // 例外を発生させる
    } catch (const exception& e) {
        cout << e.what() << endl; // 例外メッセージの表示
    }
    // リソースはスコープを抜けると自動的に解放される
    return 0;
}
リソースを獲得しました。
例外が発生しました。
リソースを解放しました。

応用例:クラスとメモリ管理の実践

動的配列の管理

動的配列は、サイズが実行時に決定される配列です。

C++では、new演算子を使用して動的配列を作成し、delete[]演算子で解放します。

以下は、動的配列の管理の例です。

#include <iostream>
using namespace std;
class DynamicArray {
private:
    int* arr; // 動的配列を指すポインタ
    int size; // 配列のサイズ
public:
    DynamicArray(int s) : size(s) {
        arr = new int[size]; // 動的配列の確保
        for (int i = 0; i < size; i++) {
            arr[i] = i; // 配列の初期化
        }
    }
    ~DynamicArray() {
        delete[] arr; // 動的配列の解放
    }
    void display() {
        for (int i = 0; i < size; i++) {
            cout << arr[i] << " "; // 配列の表示
        }
        cout << endl;
    }
};
int main() {
    DynamicArray array(5); // 動的配列の生成
    array.display(); // 配列の表示
    return 0;
}
0 1 2 3 4

リンクリストの実装

リンクリストは、各要素が次の要素へのポインタを持つデータ構造です。

これにより、動的に要素を追加・削除することが容易になります。

以下は、単方向リンクリストの実装の例です。

#include <iostream>
using namespace std;
class Node {
public:
    int data; // ノードのデータ
    Node* next; // 次のノードへのポインタ
    Node(int value) : data(value), next(nullptr) {} // コンストラクタ
};
class LinkedList {
private:
    Node* head; // リストの先頭
public:
    LinkedList() : head(nullptr) {} // コンストラクタ
    void append(int value) {
        Node* newNode = new Node(value); // 新しいノードの生成
        if (!head) {
            head = newNode; // リストが空の場合
        } else {
            Node* temp = head;
            while (temp->next) {
                temp = temp->next; // リストの末尾を探す
            }
            temp->next = newNode; // 新しいノードを追加
        }
    }
    ~LinkedList() {
        Node* current = head;
        while (current) {
            Node* nextNode = current->next; // 次のノードを保存
            delete current; // 現在のノードを解放
            current = nextNode; // 次のノードに移動
        }
    }
    void display() {
        Node* temp = head;
        while (temp) {
            cout << temp->data << " "; // ノードのデータを表示
            temp = temp->next; // 次のノードに移動
        }
        cout << endl;
    }
};
int main() {
    LinkedList list; // リストの生成
    list.append(1); // 要素の追加
    list.append(2);
    list.append(3);
    list.display(); // リストの表示
    return 0;
}
1 2 3

カスタムメモリアロケータの作成

カスタムメモリアロケータは、特定のニーズに応じてメモリの割り当てと解放を管理するためのクラスです。

以下は、シンプルなカスタムメモリアロケータの例です。

#include <iostream>
#include <cstdlib> // std::malloc, std::free
using namespace std;
class CustomAllocator {
public:
    void* allocate(size_t size) {
        return malloc(size); // メモリの割り当て
    }
    void deallocate(void* ptr) {
        free(ptr); // メモリの解放
    }
};
int main() {
    CustomAllocator allocator; // カスタムアロケータの生成
    int* arr = static_cast<int*>(allocator.allocate(5 * sizeof(int))); // メモリの割り当て
    for (int i = 0; i < 5; i++) {
        arr[i] = i; // 配列の初期化
    }
    for (int i = 0; i < 5; i++) {
        cout << arr[i] << " "; // 配列の表示
    }
    cout << endl;
    allocator.deallocate(arr); // メモリの解放
    return 0;
}
0 1 2 3 4

スマートポインタを使ったリソース管理

スマートポインタは、リソースの管理を自動化し、メモリリークを防ぐために使用されます。

以下は、unique_ptrを使用したリソース管理の例です。

#include <iostream>
#include <memory> // スマートポインタのヘッダ
using namespace std;
class Resource {
public:
    Resource() {
        cout << "リソースを獲得しました。" << endl;
    }
    ~Resource() {
        cout << "リソースを解放しました。" << endl;
    }
};
int main() {
    unique_ptr<Resource> res(new Resource()); // unique_ptrの使用
    // リソースはスコープを抜けると自動的に解放される
    return 0;
}
リソースを獲得しました。
リソースを解放しました。

メモリプールの実装

メモリプールは、特定のサイズのメモリブロックを事前に確保し、必要に応じて再利用するための手法です。

これにより、メモリの断片化を防ぎ、パフォーマンスを向上させることができます。

以下は、シンプルなメモリプールの実装の例です。

#include <iostream>
#include <vector>
using namespace std;
class MemoryPool {
private:
    vector<void*> pool; // メモリプール
    size_t blockSize; // ブロックサイズ
public:
    MemoryPool(size_t size, size_t block) : blockSize(block) {
        for (size_t i = 0; i < size; i++) {
            pool.push_back(malloc(blockSize)); // メモリブロックの確保
        }
    }
    ~MemoryPool() {
        for (void* ptr : pool) {
            free(ptr); // メモリブロックの解放
        }
    }
    void* allocate() {
        if (pool.empty()) return nullptr; // 空の場合はnullptrを返す
        void* block = pool.back(); // 最後のブロックを取得
        pool.pop_back(); // プールから削除
        return block;
    }
    void deallocate(void* block) {
        pool.push_back(block); // プールに戻す
    }
};
int main() {
    MemoryPool pool(5, sizeof(int)); // メモリプールの生成
    int* num = static_cast<int*>(pool.allocate()); // メモリの割り当て
    *num = 42; // 値の設定
    cout << "値: " << *num << endl; // 値の表示
    pool.deallocate(num); // メモリの解放
    return 0;
}
値: 42

よくある質問

newとmallocの違いは何ですか?

newmallocは、C++でメモリを動的に確保するための異なる方法です。

主な違いは以下の通りです。

  • 型安全性: newはオブジェクトを生成し、コンストラクタを呼び出します。

これにより、型安全性が確保されます。

一方、mallocは単にバイト数を指定してメモリを確保するだけで、型情報は持ちません。

  • 初期化: newはオブジェクトを初期化しますが、mallocはメモリを確保するだけで初期化は行いません。

確保したメモリの内容は未定義です。

  • 解放方法: newで確保したメモリはdeleteで解放し、mallocで確保したメモリはfreeで解放します。

これにより、適切な解放方法を使用することが重要です。

メモリリークを防ぐためのベストプラクティスは?

メモリリークを防ぐためには、以下のベストプラクティスを守ることが重要です。

  • 適切な解放: newで確保したメモリは必ずdeleteで解放し、mallocで確保したメモリはfreeで解放します。
  • スマートポインタの使用: unique_ptrshared_ptrなどのスマートポインタを使用することで、メモリ管理を自動化し、メモリリークを防ぐことができます。
  • RAIIの原則: リソースの獲得をオブジェクトの初期化に結びつけるRAIIを活用し、スコープを抜けるときに自動的にリソースが解放されるようにします。
  • 例外処理: 例外が発生した場合でもリソースが解放されるように、適切な例外処理を行います。

特に、動的メモリを使用する場合は注意が必要です。

  • メモリ使用の監視: ツールを使用してメモリ使用状況を監視し、メモリリークを検出することが重要です。

Valgrindなどのツールが役立ちます。

スマートポインタを使うべきタイミングは?

スマートポインタは、以下のような状況で使用することが推奨されます。

  • リソース管理が必要な場合: 動的に確保したメモリやリソースを管理する必要がある場合、スマートポインタを使用することで、メモリリークを防ぎ、コードの可読性を向上させることができます。
  • 所有権の管理が必要な場合: unique_ptrを使用することで、リソースの所有権を明確に管理できます。

所有権が移動する場合は、std::moveを使用して所有権を移動させます。

  • 共有リソースが必要な場合: shared_ptrを使用することで、複数のポインタが同じリソースを共有できるようになります。

リソースの参照カウントが0になると、自動的にリソースが解放されます。

  • 例外処理が必要な場合: スマートポインタを使用することで、例外が発生した場合でも自動的にリソースが解放されるため、例外処理が簡素化されます。

これらの状況でスマートポインタを使用することで、メモリ管理が容易になり、プログラムの安全性が向上します。

まとめ

この記事では、C++におけるクラス、関数、メモリ管理の関係性について詳しく解説しました。

クラスの基本的な概念から、メモリ管理の重要性、動的メモリの確保や解放、さらにはスマートポインタの活用方法まで、幅広いトピックを取り上げました。

これらの知識を活用することで、より効率的で安全なプログラムを作成することが可能になります。

今後は、実際のプロジェクトにおいてこれらの技術を積極的に取り入れ、メモリ管理のスキルを向上させていくことをお勧めします。

  • URLをコピーしました!
目次から探す