バイナリ演算子のオーバーロードc。 演算子のオーバーロードの基本。 配置演算子new()および演算子delete()
演算子のオーバーロードの基本
C#には、他のプログラミング言語と同様に、組み込み型で基本的な操作を実行するために使用される既製のトークンのセットがあります。 たとえば、+演算を2つの整数に適用して、それらの合計を求めることができることが知られています。
//整数を使用した演算+。 int a = 100; int b = 240; int c = a + b; // cは340になりました
ここでは何も新しいことはありませんが、同じ+操作をC#のほとんどの組み込みデータ型に適用できると思ったことはありますか? たとえば、次のようなコードについて考えてみます。
//操作+文字列。 string si = "こんにちは"; string s2 = "world!"; 文字列s3 = si + s2; // s3に「Helloworld!」が含まれるようになりました
基本的に、+操作の機能は、表現されたデータ型(この場合は文字列または整数)に一意に基づいています。 +演算を数値型に適用すると、オペランドの算術合計が得られます。 ただし、同じ操作を文字列タイプに適用すると、文字列の連結が取得されます。
C#言語は、同じ基本トークンのセット(+操作など)にも一意に応答する特別なクラスと構造を構築する機能を提供します。 組み込みのC#操作はすべてオーバーロードできないことに注意してください。 次の表に、基本操作のオーバーロード機能を示します。
C#操作 | 過負荷機能 |
+、-、!、++、-、true、false | この単項演算のセットはオーバーロードされる可能性があります |
+, -, *, /, %, &, |, ^, > | これらの二項演算はオーバーロードされる可能性があります |
==, !=, <, >, <=, >= | これらの比較操作はオーバーロードされる可能性があります。 C#は、「like」操作を一緒にオーバーロードする必要があります(つまり、< и >, <= и >=、==および!=) |
操作をオーバーロードすることはできません。 ただし、インデクサーは同様の機能を提供します。 | |
() | 操作()はオーバーロードできません。 ただし、特別な変換方法でも同じ機能が提供されます。 |
+=, -=, *=, /=, %=, &=, |=, ^=, >= | 省略された割り当てはオーバーロードできません。 ただし、適切な二項演算をオーバーロードすることで自動的に取得します |
演算子のオーバーロードは、メソッドのオーバーロードと密接に関連しています。 演算子をオーバーロードするには、キーワードを使用します オペレーター、は演算子メソッドを定義し、演算子メソッドはそのクラスに関連する演算子のアクションを決定します。 演算子メソッドには2つの形式があります。1つは単項演算子用、もう1つはバイナリ演算子用です。 以下は、これらのメソッドの各バリエーションの一般的な形式です。
//単項演算子のオーバーロードの一般的な形式。 public static return_type operator op(parameter_typeオペランド)(//操作)//二項演算子のオーバーロードの一般的な形式。 public static return_type operator op(parameter_type1operand1、parameter_type2operand2)(//操作)
ここでは、opの代わりに、オーバーロードされた演算子が置き換えられます。たとえば、+または/、および return_type指定された操作によって返される特定のタイプの値を示します。 この値はどのタイプでもかまいませんが、多くの場合、演算子がオーバーロードされているクラスと同じタイプです。 この相関関係により、式でオーバーロードされた演算子を簡単に使用できます。 単項演算子の場合 オペランドは送信されたオペランドを示し、二項演算子の場合は同じことを示します オペランド1と オペランド2..。 演算子メソッドには、パブリック型と静的型の両方の型指定子が必要であることに注意してください。
二項演算子のオーバーロード
簡単な例を使用して、バイナリ演算子のオーバーロードのアプリケーションを見てみましょう。
システムの使用; System.Collections.Genericを使用します。 System.Linqを使用します。 System.Textを使用します。 namespace ConsoleApplication1(class MyArr(// 3次元空間内の点の座標publicint x、y、z; public MyArr(int x = 0、int y = 0、int z = 0)(this.x = x; this.y = y; this.z = z;)//バイナリ演算子をオーバーロード+ public static MyArr operator +(MyArr obj1、MyArr obj2)(MyArr arr = new MyArr(); arr.x = obj1.x + obj2 .x; arr .y = obj1.y + obj2.y; arr.z = obj1.z + obj2.z; return arr;)//バイナリ演算子をオーバーロードします-public static MyArr operator-(MyArr obj1、MyArr obj2) (MyArr arr = new MyArr(); arr.x = obj1.x --obj2.x; arr.y = obj1.y --obj2.y; arr.z = obj1.z --obj2.z; return arr;)) class Program(static void Main(string args)(MyArr Point1 = new MyArr(1、12、-4); MyArr Point2 = new MyArr(0、-3、18); Console.WriteLine( "最初のポイントの座標: "+ Point1.x +" "+ Point1.y +" "+ Point1.z); Console.WriteLine(" 2番目のポイントの座標: "+ Point2.x +" "+ Point2.y +" "+ Point2。 z + "\ n"); MyArr Point3 = Point1 + Point2; Console.WriteLine( "\ nPoint1 + Point2 =" + Point3.x + "" + Point3.y + "" + Point3.z); Point3 = Point1-Point2; Console.WriteLine( "\ nPoint1-Point2 =" + Point3.x + "" + Point3.y + "" + Point3.z); Console.ReadLine(); ))))
単項演算子のオーバーロード
単項演算子は、二項演算子と同じ方法でオーバーロードされます。 もちろん、主な違いは、オペランドが1つしかないことです。 ++、-、-演算子のオーバーロードを追加して、前の例を最新化してみましょう。
システムの使用; System.Collections.Genericを使用します。 System.Linqを使用します。 System.Textを使用します。 namespace ConsoleApplication1(class MyArr(// 3次元空間内の点の座標publicint x、y、z; public MyArr(int x = 0、int y = 0、int z = 0)(this.x = x; this.y = y; this.z = z;)//バイナリ演算子をオーバーロード+ public static MyArr operator +(MyArr obj1、MyArr obj2)(MyArr arr = new MyArr(); arr.x = obj1.x + obj2 .x; arr .y = obj1.y + obj2.y; arr.z = obj1.z + obj2.z; return arr;)//バイナリ演算子をオーバーロードします-public static MyArr operator-(MyArr obj1、MyArr obj2) (MyArr arr = new MyArr(); arr.x = obj1.x --obj2.x; arr.y = obj1.y --obj2.y; arr.z = obj1.z --obj2.z; return arr;)/ / unary演算子をオーバーロードします-publicstatic MyArr operator-(MyArr obj1)(MyArr arr = new MyArr(); arr.x = -obj1.x; arr.y = -obj1.y; arr.z = -obj1.z ; return arr;)//単一演算子++をオーバーロードしますpublic static MyArr operator ++(MyArr obj1)(obj1.x + = 1; obj1.y + = 1; obj1.z + = 1; return obj1;)/ / unary演算子をオーバーロードします-publi c静的MyArr演算子-(MyArr obj1)(obj1.x- = 1; obj1.y- = 1; obj1.z- = 1; obj1を返します。 ))class Program(static void Main(string args)(MyArr Point1 = new MyArr(1、12、-4); MyArr Point2 = new MyArr(0、-3、18); Console.WriteLine( "の座標最初のポイント: "+ Point1.x +" "+ Point1.y +" "+ Point1.z); Console.WriteLine(" 2番目のポイントの座標: "+ Point2.x +" "+ Point2.y +" " + Point2.z + "\ n"); MyArr Point3 = Point1 + Point2; Console.WriteLine( "\ nPoint1 + Point2 =" + Point3.x + "" + Point3.y + "" + Point3.z); Point3 = Point1-Point2; Console.WriteLine( "Point1-Point2 =" + Point3.x + "" + Point3.y + "" + Point3.z); Point3 = -Point1; Console.WriteLine( "-Point1 =" + Point3.x + "" + Point3.y + "" + Point3.z); Point2 ++; Console.WriteLine( "Point2 ++ =" + Point2.x + "" + Point2.y + "" + Point2。 z); Point2--; Console .WriteLine( "Point2-- =" + Point2.x + "" + Point2.y + "" + Point2.z); Console.ReadLine();)))
最終更新日:2018年8月12日
メソッドに加えて、演算子をオーバーロードすることもできます。 たとえば、次のCounterクラスがあるとします。
クラスカウンター(public int Value(get; set;))
このクラスはいくつかのカウンターを表し、その値はValueプロパティに格納されます。
また、Counterクラスの2つのオブジェクトがあるとします。2つのカウンターは、標準の比較および加算操作を使用して、Valueプロパティに基づいて比較または加算します。
カウンターc1 =新しいカウンター(値= 23); カウンターc2 =新しいカウンター(値= 45); ブール結果= c1> c2; カウンターc3 = c1 + c2;
ただし、現時点では、Counterオブジェクトに対して比較操作も加算操作も使用できません。 これらの操作は、多くのプリミティブ型に使用できます。 たとえば、デフォルトでは数値を追加できますが、コンパイラは複合型のオブジェクト(クラスと構造)を追加する方法を知りません。 そしてこのために、必要な演算子をオーバーロードする必要があります。
演算子のオーバーロードは、演算子を定義するオブジェクトのクラスで特別なメソッドを定義することで構成されます。
public static return_type演算子演算子(パラメーター)()
オーバーロードされた演算子はこのクラスのすべてのオブジェクトに使用されるため、このメソッドにはパブリック静的修飾子が必要です。 次はリターンタイプの名前です。 戻りタイプは、オブジェクトを取得するタイプを表します。 たとえば、2つのCounterオブジェクトを追加した結果、新しいCounterオブジェクトを取得することが期待されます。 そして、2つを比較した結果、条件式がtrueかfalseかを示すbool型のオブジェクトを取得する必要があります。 ただし、タスクに応じて、戻り値のタイプは何でもかまいません。
次に、メソッド名の代わりに、operatorキーワードとoperator自体があります。 そして、パラメータは括弧内にリストされています。 二項演算子は2つのパラメーターを取り、単項演算子は1つのパラメーターを取ります。 また、いずれの場合も、パラメーターの1つは、タイプ(演算子が定義されているクラスまたは構造)を表す必要があります。
たとえば、Counterクラスのいくつかの演算子をオーバーロードしてみましょう。
クラスCounter(public int Value(get; set;)public static Counter operator +(Counter c1、Counter c2)(return new Counter(Value = c1.Value + c2.Value);)public static bool operator>(Counter c1、カウンターc2)(return c1.Value> c2.Value;)public static bool operator<(Counter c1, Counter c2) { return c1.Value < c2.Value; } }
オーバーロードされた演算子はすべてバイナリであるため、つまり、2つのオブジェクトに対して実行されるため、オーバーロードごとに2つのパラメーターがあります。
追加操作の場合、Counterクラスの2つのオブジェクトを追加するため、オペレーターはこのクラスの2つのオブジェクトを取得します。 また、追加の結果として新しいCounterオブジェクトを受け取りたいので、このクラスは戻り型としても使用されます。 この演算子のすべてのアクションは、新しいオブジェクトの作成に還元されます。そのオブジェクトのValueプロパティは、両方のパラメーターのValueプロパティの値を組み合わせたものです。
パブリック静的カウンター演算子+(カウンターc1、カウンターc2)(新しいカウンターを返す(値= c1.Value + c2.Value);)
2つの比較演算子も再定義されました。 これらの比較操作の1つをオーバーライドする場合は、これらの操作の2番目もオーバーライドする必要があります。 比較演算子自体がValueプロパティの値を比較し、比較の結果に応じて、trueまたはfalseのいずれかを返します。
ここで、プログラムでオーバーロードされた演算子を使用します。
Static void Main(string args)(Counter c1 = new Counter(Value = 23); Counter c2 = new Counter(Value = 45); bool result = c1> c2; Console.WriteLine(result); // false Counter c3 = c1 + c2; Console.WriteLine(c3.Value); // 23 + 45 = 68 Console.ReadKey();)
実際、演算子の定義はメソッドであるため、このメソッドをオーバーロードすることもできます。つまり、別のバージョンを作成することもできます。 たとえば、Counterクラスに別の演算子を追加しましょう。
public static int operator +(Counter c1、int val)(return c1.Value + val;)
このメソッドは、Valueプロパティの値と数値を加算し、それらの合計を返します。 また、この演算子を使用することもできます。
カウンターc1 =新しいカウンター(値= 23); int d = c1 + 27; // 50 Console.WriteLine(d);
オーバーロード中は、パラメーターを介してオペレーターに渡されるオブジェクトを変更しないように注意する必要があります。 たとえば、Counterクラスのインクリメント演算子を定義できます。
パブリック静的カウンター演算子++(カウンターc1)(c1.Value + = 10; return c1;)
演算子は単項であるため、この演算子が定義されているクラスのオブジェクトである1つのパラメーターのみを取ります。 しかし、オペレーターはパラメーターの値を変更してはならないため、これは増分の誤った定義です。
そして、インクリメント演算子のより正確なオーバーロードは次のようになります。
Public static Counter operator ++(Counter c1)(return new Counter(Value = c1.Value + 10);)
つまり、Valueプロパティに増分値を含む新しいオブジェクトが返されます。
同時に、1つの実装が両方の場合に機能するため、プレフィックスとポストフィックスの増分(および減分)に個別の演算子を定義する必要はありません。
たとえば、プレフィックスインクリメント操作を使用してみましょう。
カウンターカウンター=新しいカウンター()(値= 10); Console.WriteLine($ "(counter.Value)"); // 10 Console.WriteLine($ "((++ counter).Value)"); // 20 Console.WriteLine($ "(counter.Value)"); // 20
コンソール出力:
ここで、接尾辞の増分を使用します。
カウンターカウンター=新しいカウンター()(値= 10); Console.WriteLine($ "(counter.Value)"); // 10 Console.WriteLine($ "((counter ++)。Value)"); // 10 Console.WriteLine($ "(counter.Value)"); // 20
コンソール出力:
また、true演算子とfalse演算子をオーバーライドできることにも注意してください。 たとえば、Counterクラスでそれらを定義しましょう。
クラスカウンター(public int Value(get; set;)public static bool operator true(Counter c1)(return c1.Value!= 0;)public static bool operator false(Counter c1)(return c1.Value == 0;) //残りのクラスコンテンツ)
この型のオブジェクトを条件として使用する場合、これらの演算子はオーバーロードされます。 例えば:
カウンターカウンター=新しいカウンター()(値= 0); if(counter)Console.WriteLine(true); else Console.WriteLine(false);
演算子をオーバーロードするときは、すべての演算子がオーバーロードできるわけではないことに注意してください。 特に、次の演算子をオーバーロードできます。
単項演算子+、-、!、〜、++、-
二項演算子+、-、*、/、%
比較演算==、!=、<, >, <=, >=
論理演算子&&、||
代入演算子+ =、-=、* =、/ =、%=
また、等式演算子=や三項演算子?:など、オーバーロードできない演算子やその他の演算子がいくつかあります。
オーバーロードされた演算子の完全なリストは、msdnのドキュメントにあります。
演算子をオーバーロードする場合、演算子の優先順位やその結合性を変更したり、新しい演算子を作成したり、型の演算子のロジックを変更したりすることはできません。これは.NETのデフォルトです。
多くのプログラミング言語は演算子を使用します:少なくとも割り当て(=、:=、または同様のもの)と算術演算子(+、-、*および/)。 ほとんどの静的型付け言語では、これらの演算子は型にバインドされています。 たとえば、Javaでは、+演算子を使用した加算は、整数、浮動小数点数、および文字列に対してのみ可能です。 数学的オブジェクト、たとえば行列のクラスを定義する場合、それらを追加するためのメソッドを実装できますが、それを呼び出すことができるのは次のようなものだけです:a = b.add(c)。
C ++にはこの制限はありません。ほとんどすべての既知の演算子をオーバーロードできますが、 可能性は無限です。オペランドタイプの任意の組み合わせを選択できます。唯一の制限は、少なくとも1つのユーザー定義のオペランドが存在する必要があることです。 つまり、組み込み型に新しい演算子を定義するか、既存の演算子を上書きします 禁止されています.
いつ演算子をオーバーロードする必要がありますか?
主なことを覚えておいてください:それが理にかなっているときだけ、演算子をオーバーロードします。 つまり、過負荷の意味が明白であり、隠れた驚きをもたらさない場合です。 オーバーロードされた演算子は、基本バージョンと同じように動作する必要があります。 当然のことながら、例外は許容されますが、理解できる説明が付いている場合に限ります。 オペレーター<< и >> iostream標準ライブラリ。これは明らかに通常の演算子のようには動作しません。
演算子のオーバーロードの良い例と悪い例を次に示します。 上記の行列の加法は実例です。 ここで、加算演算子のオーバーロードは直感的であり、正しく実装されている場合は自明です。
マトリックスa、b; 行列c = a + b;
追加演算子の不適切なオーバーロードの例は、ゲーム内の2つのプレーヤーオブジェクトの追加です。 クラスの作成者はどういう意味ですか? 結果はどうなりますか? 操作が何をしているのかわからないため、この演算子を使用するのは危険です。
演算子をオーバーロードする方法は?
演算子のオーバーロードは、特別な名前を使用した関数のオーバーロードに似ています。 実際、コンパイラーは、演算子とユーザー定義型を含む式を検出すると、その式をオーバーロードされた演算子の適切な関数の呼び出しに置き換えます。 それらの名前のほとんどは、キーワード演算子で始まり、対応する演算子の指定が続きます。 指定が特殊文字で構成されていない場合、たとえば、キャストまたはメモリ管理演算子(new、deleteなど)の場合、単語演算子と演算子指定はスペースで区切る必要があります(演算子new)。そうしないと、スペースが無視される可能性があります(演算子+)。
ほとんどの演算子は、クラスメソッドと単純な関数の両方でオーバーロードできますが、いくつかの例外があります。 オーバーロードされた演算子がクラスのメソッドである場合、最初のオペランドのタイプはそのクラス(常に* this)である必要があり、2番目のオペランドはパラメーターリストで宣言する必要があります。 また、メモリ管理ステートメントを除いて、メソッドステートメントは静的ではありません。
演算子がクラスメソッドでオーバーロードされると、クラスのプライベートフィールドにアクセスできますが、最初の引数の非表示の変換は使用できません。 したがって、バイナリ関数は通常、フリー関数としてオーバーロードされます。 例:
クラスRational(public://コンストラクタはintからの暗黙の変換に使用できます:Rational(int分子、int分母= 1); Rational演算子+(Rational const&rhs)const;); int main()(Rational a、b、c; int i; a = b + c; // ok、変換は不要a = b + i; // ok、2番目の引数の暗黙的な変換a = i + c; //エラー:最初の引数は暗黙的に変換できません)
単項演算子がフリー関数としてオーバーロードされている場合、暗黙の引数変換を使用できますが、これは通常は使用されません。 一方、二項演算子にはこのプロパティが必要です。 したがって、主なアドバイスは次のようになります。
単項演算子と二項演算子を実装します。 バツ=”はクラスメソッドとして、その他の二項演算子は自由関数として。
どの演算子をオーバーロードできますか?
次の例外と制限を除いて、ほとんどすべてのC ++演算子をオーバーロードできます。
- 演算子**などの新しい演算子を定義することはできません。
- 次の演算子はオーバーロードできません。
- ?:(三項演算子);
- ::(ネストされた名前へのアクセス);
- ..。 (フィールドへのアクセス);
- 。*(インデックスによるフィールドへのアクセス);
- sizeof、typeid、およびcast演算子。
- 次の演算子は、メソッドとしてのみオーバーロードできます。
- =(割り当て);
- ->(インデックスによるフィールドへのアクセス);
- ()(関数呼び出し);
- (インデックスによるアクセス);
- -> *(ポインタによるポインタからフィールドへのアクセス);
- 変換およびメモリ管理演算子。
- オペランドの数、実行の順序、および演算子の結合性は、標準バージョンによって決定されます。
- 少なくとも1つのオペランドはユーザー定義型である必要があります。 Typedefはカウントされません。
次のパートでは、C ++のオーバーロードされた演算子をグループで個別に紹介します。 各セクションは、セマンティクスによって特徴付けられます。 予想される行動。 さらに、演算子を宣言および実装する一般的な方法を示します。
たとえば、加算演算子演算子+などの二項演算子は、1つのパラメーターを持つクラスのメンバーである非静的関数として、または2つのパラメーターを持つクラスのメンバーではない関数として定義する必要があります。 。
あなたに与えられたコンパイラメッセージ
Main.cpp:17:5:エラー:C ++では、すべての宣言に型指定子が必要ですoperator +(Cat a、Cat b)(^ main.cpp:18:16:エラー:「int」型の戻りオブジェクトを初期化できませんタイプ「Cat *」の右辺値を使用すると、新しいCat(a.value + b.value); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~を返します。
定義した演算子に戻りタイプがないことを示します。
変換関数のみが戻り型を持つことができます。 加算演算子の場合は、戻り値のタイプを指定する必要があります。
クラスの2つのオブジェクトを追加する場合、オブジェクトへのポインタを返すことは意味がありません。 この場合、追加の演算子を使用せずに追加の演算子をチェーンすることはできず、さらに、メモリリークが発生する可能性があります。
オペレーターは、const修飾子の有無にかかわらず、オブジェクト自体を返す必要があります。
上記のように、ステートメントは、1つのパラメーターを持つクラスの非静的メンバー関数として宣言できます。
この場合、演算子+は次のようになります。
クラスCat(private:int value = 1; public:Cat(int _value)(value = _value;)Cat operator +(const Cat&a)const(return Cat(this-> value + a.value);));
クラスCat(private:int value = 1; public:Cat(int _value)(value = _value;)const Cat operator +(const Cat&a)const(return Cat(this-> value + a.value);)) ;
次のような右辺値参照の演算子もオーバーロードできます。
Cat演算子+(const Cat && a)const(return Cat(this-> value + a.value);)
Cat演算子+(Cat && a)const(return Cat(this-> value + a.value);)
しかし、大きなリソースを消費しないこのような単純なクラスの場合、これは問題ではありません。
パラメータリストの後にcondt修飾子が存在することに注意してください。 これは、対応する演算子パラメーターもconst修飾子で定義されているため、演算子の左側に存在するオブジェクト自体は、右側のオブジェクトと同様に変更されないことを意味します。
クラスのコンストラクターは変換コンストラクターとして宣言されているため、この場合、Catクラスのオブジェクトに数値を追加できることに注意してください。 例えば、
#含む
この加算演算子の意味に基づいて、これがどの程度正当化されるかはあなた次第です。 数値からCatクラスのオブジェクトへのそのような暗黙の変換を許可したくない場合は、コンストラクターを明示的に宣言できます。 この場合、Catオブジェクトを数値に追加しようとすると、プログラムはコンパイルされません。
#含む
このプログラムの場合、コンパイラは次のようなエラーメッセージを発行します。
Prog.cpp:24:5:エラー:「operator +」に一致しません(オペランドタイプは「Cat」と「double」です)c1 + 5.5; ^
この演算子を宣言する2番目の方法は、クラスのメンバーではない関数として宣言することです。 この関数は値クラスのプライベートメンバーにアクセスできる必要があるため、クラスのフレンドリ関数として宣言する必要があります。
関数自体は、クラス定義の内部と外部の両方で定義できます。
例えば、
#含む
アンダースコアで始まる変数を宣言することは悪い考えであることに注意してください。 一般に、アンダースコアで始まるこのような名前は、コンパイラー用に予約されています。
コンストラクターパラメーターでは、クラスメンバーの名前に対応する名前を割り当てるのが通例です。 この場合、どのパラメーターがどのクラスメンバーを初期化しているかをすぐに確認できます。 したがって、このようなコンストラクターを定義できます
Cat(int value):value(value)()
値クラスのメンバーが負の値をとることができない場合は、それとコンストラクターの対応するパラメーターをunsignedint型として宣言することをお勧めします。