C++入門:基本概念とクラス・オブジェクト

目次

C言語との比較による改良

  1. 名前空間

    • アクセス順序
    • アクセス方法
    • 名前空間のネスト
  2. デフォルト引数

    • 注意点
  3. 関数のオーバーロード

    • なぜC++はオーバーロードをサポートするのか?
  4. テンプレート

    • キーワード template
    • 注意点
  5. 参照

    • 参照の用途
    • 参照の特性
    • アクセス権の問題
    • 一時変数
    • 一時変数の生成条件
  6. インライン関数

    • キーワード inline
    • 注意点

クラスとオブジェクト

  1. メンバとオブジェクト

    • オブジェクトのインスタンス化と関数呼び出し
    • constによるメンバ関数の修飾
    • 注意点
  2. フレンド関数とフレンドクラス

  3. 静的メンバ変数と静的メンバ関数

    • 静的メンバ変数
    • 静的メンバ関数
  4. クラスの6つのデフォルトメンバ関数

    • コンストラクタ
    • デフォルトコンストラクタ
    • コピーコンストラクタ
    • 初期化リスト
    • デストラクタ
    • 演算子のオーバーロード
    • アドレス取得演算子のオーバーロード

C言語との比較による改良

1. 名前空間

C言語での共同開発では、特に関数名の重複が避けられません。C++の名前空間はこの問題を効果的に解決します。名前空間とは、変数名や関数名の重複を防ぐために設けられた領域です。

例えば、同じ名前の関数でも異なる名前空間に分けることで、アクセス時に名前空間を指定することで目的の関数を呼び出せます。

アクセス順序

名前空間が指定されていない場合、まずローカルスコープ、次にグローバルスコープが検索されます。名前空間が指定されている場合は、指定されたスコープが検索されます。

注:これは、C言語で変数を使用する際にローカル変数が優先される理由を説明できます。

アクセス方法

名前空間::関数名 名前空間::変数名

なぜ using namespace std; をコメントアウトすると下記のコードがエラーになるのでしょうか?

iostreamヘッダーには多くの名前空間が含まれており、using namespace std; はstd名前空間を展開する意味です。展開することで、std::cout << "text" << std::endl のように毎回名前空間を指定する必要がなくなります。もちろん、これは通常の練習のための便利な機能であり、すべての名前空間を展開してしまうと、名前空間の本来の意味が失われ、重複が依然として発生します。

名前空間のネスト

名前空間はネストすることができます。ネストした場合の呼び出しは、指定されたスコープを重ねることで行います(例:space1::space2::function())。ただし、一般的には多段にネストすることは避けるべきです。なぜなら、第一に必要以上、第二に使用時に煩雑になるためです。

2. デフォルト引数

デフォルト引数とは、関数呼び出し時に引数が指定されない場合に使用されるデフォルト値です。引数が指定された場合はその値が使用され、そうでなければデフォルト値が使用されます。これにより、関数はより柔軟になります。

下記の例では、引数bが指定されていないため、デフォルト値1が使用されています。

下記の例では、引数bに値が指定されているため、その値が優先的に使用され、デフォルト値は使用されません。

もちろん、すべての引数にデフォルト値を設定した関数を定義することもできます。一部の初期化関数などでこのような書き方がされ、呼び出し時に引数の指定が任意になります。または、一部の引数のみを指定することも可能です。ただし、指定された実引数と仮引数は一対一で対応している必要があります(最初の実引数は最初の仮引数に対応し、2番目は2番目の仮引数に対応する...)。

注意点

  1. デフォルト引数は右側に連続して配置する必要があります(またはすべての引数にデフォルト値を設定します)。そうでない場合は下記のようなエラーが発生します。
  2. 宣言と定義が分離されている場合、デフォルト引数は通常宣言側に記述します(宣言と定義の両方にデフォルト引数があると、リンク時に「デフォルト関数の再定義」エラーが発生します)。
  3. デフォルト値は定数またはグローバル変数である必要があります。

3. 関数のオーバーロード

C++では、機能が似ている名前が同じ関数を定義できますが、その引数は異なる必要があります

  1. 引数の型が異なる
  2. 引数の数が異なる
  3. 引数の型の順序が異なる

関数名は同じだが引数が異なる場合、これを関数のオーバーロードと呼びます。

なぜC++はオーバーロードをサポートするのか?

これはコンパイルプロセスに関連しています。C言語では、アセンブリプロセスで生成されるシンボルテーブル内の関数名は修飾されておらず、同名の場合コンパイラはどの関数を指しているか区別できません。 一方、C++は関数修飾ルールによって、これらの同名だが引数の異なる関数を区別します(簡単に言えば、修飾ルールの存在により、引数の型と順序が関数の最終的な名前に影響するためです)。

4. テンプレート

関数のオーバーロードを理解したら、テンプレートについて言及する必要があります。オーバーロードされた関数はしばしば機能が似ているか、またはコードの類似度が高いです。すべてのオーバーロードのケースを考慮して重複コードを記述すると非常に多くなりますが、テンプレートはこの問題を解決します。

キーワード template

テンプレートを使用する際は、以下のように型を作成します。

template

型形式パラメータリストには複数の型を指定することもできますし、1つの型だけを指定することもできます。

下記の例では、sumはここではテンプレートとして機能し、Tは同じ型です。2つの整数型の引数が渡されると、コンパイラはテンプレートと型形式パラメータリストに基づいて、戻り値と引数a,bが両方とも整数型の関数(int sum(int a, int b))を生成します。 2つのdouble型の引数が渡されると、戻り値と引数a,bが両方ともdouble型の関数(double sum(double a, double b))が生成されます。

型形式パラメータリストに複数の型がある場合の書き方は以下のようになります:

この場合、TとT2が必ず異なる型であると無意識に思うかもしれませんか?

最初のデータをデバッグすると、Tがint、T2がdoubleであることがわかります。

2番目のデータをデバッグすると、Tはint、T2もintです。したがって、テンプレートで使用される型の数は、どれだけ多くの型を扱えるかを示すだけであり、実際に呼び出された際には必ずしもその数の型が使われるわけではありません。

注意点

1つのキーワードの後に1つのテンプレートを続けることはできますが、1つのキーワードに複数の関数テンプレートを続けることはできません。

条件に合致する関数が存在する場合、コンパイラは記述された関数を優先的に使用します(テンプレートから関数を生成するにはコストがかかるため、条件に合致する関数がある場合は生成されません)。下記はコンパイラが文をステップ実行した結果です。

5. 参照

参照とは変数の別名であり、本質的には同じ変数です。

簡単な例:

aには別名bがあるため、b++はa++と同じ意味です。

参照の用途は何ですか?

C言語の値渡しはご存知でしょうか?これは実際に値を渡すのではなく、一時的なコピーを渡すものです。しかし、コピーはパフォーマンスの浪費であり、追加のスペースが必要です。 参照の役割は、関数の効率を向上させ、スペースを節約することです。(また、ポインタが複雑すぎる場合、参照を使用してコードを補助的に記述することも良いでしょう)。

参照の特性

  1. 参照は作成時に初期化する必要があるため、null参照は存在しません。有効なメモリ領域に接続されている必要があります。
  2. また、参照が誰の別名であるかが一度決定すると変更できません(ポインタは変更可能です)。

アクセス権の問題

ここでaは変更不可の定数変数ですが、bに割り当てて変更できてしまいました。アクセス権が拡大されています(これは許可されません)。

アクセス権は平移できます。

アクセス権は縮小できます。

一時変数

(変数は左値、定数は右値)ここでのa+bは実際には一時変数を生成しますが、一時変数はデフォルトで変更不可(constで修飾されている)とみなされるため、「非const参照の初期値は左値である必要があります」というエラーが発生します。なぜなら、アクセス権が拡大されているからです。

一時変数の生成条件
  1. 値渡しの場合に生成されます(例:関数から値を返す際、実引数を関数に渡す際)。
  2. 型変換の場合に生成されます(明示的な型変換を含む)。

6. インライン関数

関数にはいくつかの利点があります。1つは、使用頻度の高い機能を繰り返し記述する必要がないこと;2つは、より論理的でありコードの可読性が向上すること;3つは、プログラムの動作を統一し、デバッグを容易にすること;4つは、変更時にすべての場所を変更する必要がないことです。しかし、関数呼び出しは同等の式を実行するよりもはるかに遅いです。なぜなら、関数呼び出しにはスタックフレームの作成と破棄が必要だからです。

キーワード inline

キーワード inline を関数定義の前に記述することで、その関数をインライン関数として指定できます。

インライン関数の場合、主な利点はスタックフレームの作成と破棄のコストを省くことであり、関数を呼び出す場所で同等の式を実行するようなものです。関数のオーバーロードとインライン関数の存在により、マクロはC++ではほとんど意味を持ちません。

注意点

  1. 宣言と定義が分離されている場合、キーワードinlineを宣言側に記述してもインライン関数にはなりません。定義側にも必ずキーワードinlineを付ける必要があります。
  2. クラス宣言内で定義されたメンバ関数はデフォルトでインライン関数です。
  3. インライン関数ではループ文やswitch文を使用できません。これらの文がある場合、コンパイラは通常の関数として扱います。
  4. 再帰関数はインライン関数として使用できません。

クラスとオブジェクト

クラスはC++のカスタム型であり、オブジェクトの形式を指定するために使用されます。その役割はテンプレートのようなもので、対応する属性のオブジェクトをインスタンス化できます。

1. メンバとオブジェクト

クラス内のデータをメンバ変数、クラス内の関数をメンバ関数と呼びます。

// 日付クラス
class Date
{
// メンバ関数
public:
    // 指定された年月の日数を取得
    int GetMonthDay(int year, int month)
    {
        assert(month > 0 && month < 13);
        static int monthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

        if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
        {
            return 29;
        }

        return monthDays[month];
    }
// メンバ変数
private:
    int _year;
    int _month;
    int _day;
};

クラス内のメンバには、プライベート、パブリック、プロテクトの3つの属性のいずれかを割り当てることができます。

  • パブリック(キーワード public):クラス内外からアクセス可能
  • プライベート(キーワード private):フレンド(フレンドについては後述)、クラス内からのみアクセス可能。クラス外からはアクセス不可。
  • プロテクト(キーワード protected):継絡を考慮しない場合、プライベートと同じです。

オブジェクトのインスタンス化と関数呼び出し

class ItemCounter
{
public:
    void DisplayCount()
    {
        // クラス内の関数がプライベートメンバ変数_numを呼び出す
        cout << _num << endl;
    }

private:
    int _num = 1000;
};

int main()
{
    // ここではItemCounterクラスを型としてオブジェクトitemを作成
    ItemCounter item;

    // オブジェクトitemを介してパブリック関数DisplayCountを呼び出す
    item.DisplayCount();
    return 0;
}
オブジェクトのインスタンス化方法 クラス名 変数名(クラス名というクラスをテンプレートとして、変数名という変数を作成)
オブジェクトによる関数呼び出し オブジェクト名.メンバ関数名(引数)

下記の図のように、オブジェクトaとオブジェクトbがそれぞれ関数DisplayCountを呼び出した結果(aの_numは1に初期化され、bの_numは2に初期化されています。コンストラクタについては後述で詳しく説明します)。

同じ関数DisplayCountを呼び出していますが、引数が渡されていないのに、コンパイラはどのようにしてそれぞれの値を区別しているのでしょうか?

実際にオブジェクトで関数を呼び出すと、そのオブジェクトを指すポインタという隠し引数が渡されます。このポインタはthisと呼ばれます。展開すると簡単に理解できます(下記図)。this->と入力すると、アクセス可能なメンバが表示されます(もちろん、通常はこのポインタを明示的に記述する必要はなく、コンパイラが自動的に追加します)。

constによるメンバ関数の修飾

従来との違いとして、constは関数の括弧の後に記述され、thisポインタを修飾します。オブジェクトの値が変更されないようにしたい場合、constでメンバ関数を修飾します。

class ItemCounter
{
public:
    void DisplayCount() const
    {
        cout << _num << endl;
    }

private:
    int _num = 1000;
};

メンバ関数の宣言と定義の分離

関数名の前に クラス名:: を追加して、どのGetMonthDay関数なのかを指定する必要があります。

ItemCounter.hファイル内

// 日付クラスの宣言 class ItemCounter { // メンバ関数 public: // 指定された年月の日数を取得 int GetMonthDay(int year, int month); // メンバ変数 private: int _year; int _month; int _day; };

ItemCounter.cファイル内

// 関数名の前にクラス名::を追加して、どのGetMonthDay関数なのかを指定する

int ItemCounter::GetMonthDay(int year, int month)

{ assert(month > 0 && month < 13); static int monthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { return 29; }

return monthDays[month];

}

注意点

  1. クラスを定義したということは、そのクラスから作成されたオブジェクトでデータを管理するためのものなので、メンバ変数は通常プライベートに設定し、外部からの直接変更を防ぐべきです。
  2. インスタンス化されたオブジェクトはメンバ変数のスペースのみを確保し、メンバ関数は共通のコード領域に格納されます(すべてのオブジェクトが関数を格納するとスペースの浪費になります)。
  3. クラスはネストできます。
// クラスのネスト

class HelperClass
{
public:
    void HelperFunction()
    {
        cout << "helper" << endl;
    }
private:
    int _helperValue;
};

class MainClass
{
public:
    MainClass(int value = 0)
    {
        _value = value;
    }

    void Display()
    {
        cout << this->_value << endl;
    }

    class NestedClass
    {
    public:
        void NestedFunction()
        {
            cout << "nested" << endl;
        }
    private:
        int _nestedValue;
    };

private:
    int _value;
    HelperClass _helper;
};

2. フレンド関数とフレンドクラス

プライベートなメンバ変数はクラス外部からアクセスできませんが、クラス内でフレンドとして宣言された関数やクラスからは、そのプライベートなメンバにアクセスできます。

フレンド関数:Display関数はグローバル関数ですが、クラスMainClassでフレンドとして宣言されているため、直接プライベートなメンバにアクセスできます。

フレンドクラス:クラスNestedClassはクラスMainClassのフレンドであるため、クラスNestedClass内から直接クラスMainClassのプライベートなメンバにアクセスできます。

フレンドクラスは時折「プラスチックフレンド」のような関係になることがあります。つまり、お互いのプライベートなメンバにアクセスできるようにするには、両方のクラスでフレンドとして宣言する必要があります。同時に、誰が誰のフレンドであり、誰が誰のプライベートなメンバにアクセスできるのかを明確に理解する必要があります。

3. 静的メンバ変数と静的メンバ関数

staticで修飾されたメンバ変数を静的メンバ変数と呼びます。静的メンバ変数はクラススコープ内のグローバル変数です。静的メンバ変数はオブジェクト間でのデータ共有を実現できます。

静的メンバ変数

静的メンバ変数の特性:

  1. 初期化が必要であり、通常はクラス外部で初期化します。
  2. 静的メンバ変数はクラスに属し、特定のオブジェクトに固有のものではありません。したがって、静的メンバ変数はオブジェクトのメモリを占有せず、すべてのオブジェクトの外部にメモリが確保されます。オブジェクトを作成しなくてもアクセスできます。
  3. コンパイル時に静的データ領域にメモリが割り当てられ、プログラム終了時に解放されます(通常のメンバ変数はオブジェクト作成時にメモリが割り当てられ、オブジェクト破棄時に解放されます)。
  4. 静的メンバ変数は初期化されない場合、デフォルトで初期化され、通常は0になります。
  5. 静的メンバ変数はメンバ関数のオプションの引数になり得ますが、通常のメンバ変数はなりません。

静的メンバ変数の特性の第5点について、なぜ通常のメンバ変数はならないのでしょうか?

通常のメンバ関数はthisポインタがあるからこそメンバ変数にアクセスできますが、下記の図のように、このポインタは非静的メンバ関数内でのみ使用できます。

しかし、静的メンバ関数はthisポインタを必要としないので、なぜならそれがクラスに属し、オブジェクトに属さないからです。

class TaskCounter
{
public:
    // Display関数を呼び出すたびにcountが1増加
    void Display()
    {
        count++;
        cout << count << endl;
    }

private:
    int _value;

    // 静的メンバ変数を定義
    static int count;
};

// 0で初期化
int TaskCounter::count = 0;

int main()
{
    TaskCounter task1;
    task1.Display();
    TaskCounter task2;
    task2.Display();
    return 0;
}

実行結果:

なぜ実行結果は1 2なのでしょうか?

静的メンバ変数の特性の第2点によると:静的メンバ変数はクラスに属し、特定のオブジェクトに固有のものではありません。したがって、静的メンバ変数はオブジェクトのメモリを占有せず、すべてのオブジェクトの外部にメモリが確保されます。オブジェクトを作成しなくてもアクセスできます。実際には、オブジェクトtask1とtask2は同じ静的メンバ変数を共有しています。task1が呼び出された後countが1になり、task2が呼び出されると2になるのです。

静的メンバ関数

通常のメンバ関数はすべてのメンバ変数を呼び出すことができますが、静的メンバ関数は静的メンバ変数しか呼び出せません。なぜなら、静的メンバ関数はオブジェクトとは関係がないため、thisポインタを持たないからです。

静的メンバ関数の用途:

  1. クラスのインスタンス(オブジェクト)とは関係ないため、オブジェクトがなくても呼び出せます。
  2. クラス名を名前空間として使用できます。
  3. その関数へのアクセス権を制御できます(通常のメンバ変数にはアクセスできません)。
  4. クラス内のstatic変数を制御できます。

4. クラスの6つのデフォルトメンバ関数

クラスの6つのデフォルトメンバ関数
1. コンストラクタ(オブジェクトの初期化に使用)
2. デストラクタ(オブジェクトの破棄、メモリの解放に使用)
3. コピーコンストラクタ(コンストラクタの一種だが、オブジェクトを引数として受け取り初期化する)
4. 代入演算子のオーバーロード(必要な演算子をオーバーロードし、そのクラスに適用する)
5. アドレス取得演算子のオーバーロード
6. constアドレス取得演算子のオーバーロード(アドレスが変更されるのを防ぎたい場合に使用)

コンストラクタ

コンストラクタはオブジェクトを作成時に自動的に呼び出され、そのオブジェクトを初期化します。

  1. コンストラクタの名前はクラス名と一致する必要があります
  2. 記述しない場合、コンパイラは自動的にデフォルトコンストラクタを生成します(ただし、生成される関数は何も行いません)
  3. コンストラクタを記述した場合、それがデフォルトコンストラクタでなくても、コンパイラはもうデフォルトコンストラクタを生成しません
  4. 少なくとも1つのデフォルトコンストラクタが存在する必要があり、適切な初期化ができるようにします
  5. 戻り値の型を定義できません(コンストラクタは戻り値の型を定義できません)
  6. メンバ変数にカスタムクラス型がある場合、その型のコンストラクタが自動的に呼び出されます。

以下は一般的な書き方の例です

class DataProcessor
{
public:

   // 初期化方法が固定されており、推奨されない
   // オブジェクトの作成方法:DataProcessor dp;
	DataProcessor()
   {
       _value = 1;
   }

   // すべての引数にデフォルト値があり、引数の指定が任意であるため、推奨される書き方
    DataProcessor(int value = 1)
   {
       _value = value;
   }

   // 引数の指定が必須であり、制約が大きい
   // オブジェクトの作成方法:DataProcessor dp(1);
    DataProcessor(int value)
   {
       _value = value;
   }

private:
	int _value;
};

デフォルト引数付きのコンストラクタでのオブジェクト作成方法:

デフォルトコンストラクタ

デフォルトコンストラクタとは、引数なしで呼び出されるコンストラクタです。

デフォルトコンストラクタは以下の3種類に分けられます:

  1. 引数がないコンストラクタ DataProcessor()
  2. すべての引数にデフォルト値があるコンストラクタ(引数を指定しなくてもよいため) DataProcessor(int value = 1)
  3. 記述しなかった場合、コンパイラが自動的に生成するデフォルトコンストラクタ

コピーコンストラクタ

コピーコンストラクタもコンストラクタの一種ですが、オブジェクトを引数として受け取り初期化します。

class DataContainer
{
public:

   // オブジェクトcontainerのデータを現在のオブジェクト(thisポインタが指すオブジェクト)にコピー
	DataContainer(const DataContainer& container)
   {
       _value = container._value;
   }

private:
	int _value;
};

使用方法:

気づいた人もいるかもしれませんが、コピーコンストラクタの引数は参照です。const DataContainer container を引数として直接記述することは可能でしょうか?

答えはできません。これは値渡しの引数渡しが一時的なコピーを生成するためであり、オブジェクトをコピーする際にコピーコンストラクタが呼び出されるからです。参照を使用しないと無限再帰が発生してしまいます。

初期化リスト

コンストラクタには名前、引数リスト、関数体のほかに、初期化リストがあります(注:初期化は代入とは異なります)。

初期化リストの書き方:引数リストの後にコロンで始め、メンバ変数名(初期化値)をカンマで区切って記述し、その後に関数体を記述します。

class Calendar
{
public:
    // 初期化リスト
	Calendar(int year = 2000, int month = 1, int day = 1)
	: _year(year)
	, _month(month)
	, _day(day)
    {
       // 関数体
    }

private:
	
	int _year;

	int _month;

	int _day;
};

初期化リストを使用しなければならない3つのケース:

  1. 定数メンバ(定数は初期化はできるが代入はできない)
  2. 参照型(参照は定義時に初期化する必要があり、誰の別名であるかが決定すると変更できない)
  3. デフォルトコンストラクタを持たないクラス(初期化リストは必ずしもデフォルトコンストラクタを呼び出して初期化する必要はなく、直接コピーコンストラクタを呼び出して初期化することもできます)

メンバ変数の初期化順序:

初期化リストで_monthを_yearの前に配置しても、_yearが先に初期化されます。

なぜなら、初期化の順序はメンバ変数の定義順と一致するためです。したがって、一般的には両者の順序を一致させておくべきであり、そうしないとエラーが発生することがあります。

class Calendar
{
public:
    // 初期化リスト
	Calendar(int year = 2000, int month = 1, int day = 1)
	: _month(month)
	, _year(year)
	, _day(day)
    {
       // 関数体
    }

private:
	
	int _year;

	int _month;

	int _day;
};

下記の図のように、順序が一致しない場合、代入の順序によって問題が発生することがあります。a1が先に初期化されますが、a2はまだ初期化されていないためランダムな値であり、a1が初期化された後にランダムな値になってしまいます。

class DataTest
{
public:
    // 初期化リスト
	DataTest()
	: _secondValue(1)
	, _firstValue(_secondValue)
    {
       // 関数体
    }

    void Display()
    {
       cout<< _firstValue << _secondValue <<endl;
    }

private:
	
	int _firstValue;

	int _secondValue;

};

int main()
{
   DataTest test;
   test.Display();
   return 0;
}

デストラクタ

初期化用のコンストラクタがあるならば、リソースを解放するデストラクタもあります。

デストラクタの特性:

  1. 名前もクラス名と一致しますが、名前の前に~記号を付けます。
  2. オブジェクトのライフサイクル終了時に自動的に呼び出されます(メモリリークをある程度防ぐことができます)
  3. 記述しなくてもコンパイラが自動的に生成します(ただし、何も行いません)
  4. 引数を受け取らず、戻り値もありません。同時にconst、volatile、staticとして宣言することはできません
  5. メンバ変数にカスタムクラス型がある場合、その型のデストラクタが自動的に呼び出されます。

デストラクタは組み込み型のみを持つクラスにとってはあまり意味がありませんが、

class MemoryBuffer
{
public:

   // コンストラクタ
    MemoryBuffer(int size = 1)
   {
       _size = size;
       _buffer = (int*)calloc(sizeof(int),_size);
   }

   // デストラクタ
    ~MemoryBuffer()
   {
      free(_buffer);
      _buffer = NULL;
   }

   
private:
	int _size;
    int* _buffer;
};

演算子のオーバーロード

演算子のオーバーロードは特殊な関数のオーバーロードであり、演算子をクラス型で使用できるようにします。

戻り型 operatorオーバーロードする演算子(引数リスト)

{

関数本体;

}

Calendar& Calendar::operator+=(int days)
{
	_day += days;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}

Calendar Calendar::operator+(int days)
{
	// コピーコンストラクタ
	Calendar tmp = *this;
	tmp += days;

	// tmpの一時的なコピーを返す
	return tmp;
}

後置++と前置++のオーバーロードの違い(--も同様):

前置++と後置++を区別するために、後置++の引数リストに引数の型を追加してオーバーロードするというルールがあります。また、実際には引数を受け取る必要がないため、変数名を記述せずに型だけを記述してオーバーロードします。

// 前置++
Calendar& Calendar::operator++()
{
	*this += 1;
	return *this;
}

// 後置++
Calendar Calendar::operator++(int)
{
	Calendar tmp = *this;
	*this += 1;
	return tmp;
}
アドレス取得演算子のオーバーロード
// アドレス取得演算子のオーバーロード
Calendar* Calendar::operator&()
{
	return this;
}

// constアドレス取得演算子のオーバーロード
const Calendar* Calendar::operator&() const
{
	return this;
}

タグ: C++ プログラミング言語 クラス オブジェクト テンプレート

6月7日 20:11 投稿