[セットトップ]C++複素演算の演算子リロード
C++は演算子の再ロードを提供し、カスタムタイプ間の演算を極めて便利にします.
本文は最も簡単な複素演算実現について初心者に演算子の重荷の役割と演算子の重荷の原理を体得させる.
次のような複数のクラスがあるとします.
今私たちは2つの複素数を持っていて、計算とどのように実現したいですか?(下記)
以前はこう計算していました
複雑ではないように見えますが、このクラスの属性が多いと、何十もあるのではないでしょうか.このときC++演算子のリロードの利点が現れます.演算子のリロードが実現した後に上記のsum=c 1+c 2が直接計算できるようになった.
演算子のリロードを実現する前に、メンバー関数としてリロードし、友元関数としてリロードする2つの表現形式があることを知っておく必要があります.
わからない?急がないで!まず簡単な関数から出発します.
ただし、演算子のリロードの宣言形式は次のとおりです.
関数タイプoperate演算子(パラメータリスト)
{
...
}
このリロード関数は,加算された演算をComplexクラスの+演算にカプセル化し,そのクラスの直接演算をサポートできることが分かった.
+演算で2つの数を加算するには自然に2つのパラメータが必要ですが、なぜc 2だけのパラメータなのでしょうか.
上記のコードはComplexクラスの+をメンバー関数として再ロードするため、実際にはそのパラメータテーブルにはthisポインタが隠されており、実質的にこのメンバー関数には2つのパラメータ(Complex*this,Complex c 2)が含まれているが、隠されているにすぎない.
すなわち,この関数はthisポインタを用いてクラスのprivate変数realとimageに直接アクセスでき,thisポインタは省略して書かないこともできる.
このルールは、クラスのメンバー関数として再ロードするときに静的形式に書かなければならないことを意味します.すなわち、関数タイプの前にconstキーワードを付けて、thisポインタの乱用が変更すべきでないメンバー変数を変更することを防止します.
また,C++の効率的な利用のためには,値ではなく参照を用いるべきであるが,本稿では議論の範囲外であるため,後述しない.
最適化されたコード:
では、この演算は友元関数に再ロードできますか?友元関数とは?
まず、このコードを見てみましょう.
これはComplexクラスの+を友元関数として再ロードします.メンバー関数ではないので、隠されたthisポインタはないので、+の左右のパラメータを入れて、その関数が他のComplexオブジェクトのメンバー変数realやimageにアクセスできるように、
友元関数として明記する必要があります.
友元関数として宣言するより重要な理由は、演算子を再ロードして、「オブジェクト.メンバー名」で呼び出す必要がなくなります.次の表を参照してください.
このメタ関数のパラメータは、左から右への順に並べられた各オペランドを表す.
前置後置演算を区別するために、後置単一演算子+、--のリロードでは、パラメータテーブルにintを追加しますが、パラメータ名を書く必要はありません.
メンバー関数としての再ロードと友元関数としての再ロードの違い:
演算子メンバー関数の設計:
演算子
使用方法
とうかしき
両目演算子@
X1 @ X2
X1.operate @ (X2)
前置リスト@
@ X
X1.operate @()
後置リスト@
X @
X1.operate @(0)
演算子の友元関数の設計:
演算子
使用方法
とうかしき
両目演算子@
X1 @ X2
operate @ (X1,X2)
前置リスト@
@ X
operate @(X1)
後置リスト@
X @
operate @(X1,0)
*後置単眼演算で多く出た0パラメータは後置の意味のみを表す.
以上から分かるように、メンバー関数としてリロードする場合:パラメータ数=元のオペランド数-1(-1は隠しthisポインタ)(後置+--を除く)
友関数として再ロードする場合:パラメータ数=元のオペランド数
では、いつメンバー関数として再ロードすべきか、いつ友元関数として再ロードすべきか.
クラスにオペレータをリロードするように設計する場合は、オペレータをクラスメンバーに設定するか、友元関数に設定するかを選択する必要があります.場合によっては、プログラマは選択せず、オペレータはメンバーでなければなりません.別の状況では、いくつかの経験原則が私たちに決定を導くことができます.
次の原則に従います.割り当て(=)、下付き([])、呼び出し(((())、メンバーアクセス矢印(->)などのオペレータをメンバーとして定義する必要があります.これらのオペレータを非メンバー関数として定義すると、コンパイル時にエラーとしてマークされます. は、付与のように、複合付与オペレータは通常クラスのメンバー関数として定義されるべきであり、付与とは異なり、必ずしもそうしなければならないとは限らない.非メンバー複合付与オペレータを定義すると、コンパイルエラーは発生しない. オブジェクトの状態を変更するか、または特定のタイプに密接に関連する他のオペレータ、例えば、自己増加、自己減少、および参照は、通常、クラスメンバーとして定義されます. 対称オペレータ、例えば算術オペレータ、等しいオペレータ、関係オペレータは、友元関数として定義することが望ましい. >、<<オペレータは友元関数として定義されます.
演算子+と+=のリロードの違いと関連付け?
振り返ってみると、+演算子をリロードする場合、通常は+=演算子を同時にリロードしますが、この2つの演算子はどのようにリロードすべきか考えてみましょう. operator+は通常非メンバーとして定義され、operator+=は通常メンバーとして定義されます operator+=オペランドの参照を返し、operator+は一時オブジェクト を返します.通常operator+=を使用してoperator+ を実現します.その他の演算オペレータ(+,-,*,/,%)同+ くだらないことは言わないで、コードの解釈をします:
どうして+=で+を実現するのですか?
効率的な考慮に基づいて、+=オペランドの参照を返すことができ、+はできません.(従って+=効率は+よりはるかに大きい)
最後のポイント:
演算子が基本タイプのデータで呼び出す場合は、グローバルな友元関数として定義する必要があります.
例:complex a;complex b = 2 + a;
このoperator+はcomplexクラスのメンバー関数ではなく、他のクラスのメンバー関数でもありません.関数が1つも存在しないからです.
complex & int.operator+ ( complex& rhs)
最後の最後に、簡単な演算子のリロードコードを貼って、他の自分で考えます.
演算子リロードテーブル:
次の演算子以外のC++のすべての演算子を再ロードできます.
. .*:: ?: sizeof typeid # static_cast<> dynamic_cast<> const_cast<> reinterpret_cast<> || && ,
リロード可能な演算子:
+ - * /% ^ & | ~ ! = < > <= >=++ -->> == != += -=/= %= ^= &= |= *= <<= >>= [] () -> ->* new new [] delete delete []
本文は最も簡単な複素演算実現について初心者に演算子の重荷の役割と演算子の重荷の原理を体得させる.
次のような複数のクラスがあるとします.
class Complex
{
private:
double real;<span style="white-space:pre"> </span>//
double image;<span style="white-space:pre"> </span>//
public:
Complex(){real=0;image=0;}<span style="white-space:pre"> </span>// <span style="white-space:pre"> </span>
Complex(double r,double i)<span style="white-space:pre"> </span>//
{
real=r;
image=i;
}
}
今私たちは2つの複素数を持っていて、計算とどのように実現したいですか?(下記)
Complex sum,c1,c2;
sum=c1+c2;
以前はこう計算していました
Complex sum,c1,c2;
sum.real=c1.real+c2.real;
sum.image=c1.image+c2.image;
複雑ではないように見えますが、このクラスの属性が多いと、何十もあるのではないでしょうか.このときC++演算子のリロードの利点が現れます.演算子のリロードが実現した後に上記のsum=c 1+c 2が直接計算できるようになった.
演算子のリロードを実現する前に、メンバー関数としてリロードし、友元関数としてリロードする2つの表現形式があることを知っておく必要があります.
わからない?急がないで!まず簡単な関数から出発します.
Complex operate + (Complex c2)
{
Complex c;
c.real=this->real+c2.real;
c.image=this->image+c2.image;
}
ただし、演算子のリロードの宣言形式は次のとおりです.
関数タイプoperate演算子(パラメータリスト)
{
...
}
このリロード関数は,加算された演算をComplexクラスの+演算にカプセル化し,そのクラスの直接演算をサポートできることが分かった.
+演算で2つの数を加算するには自然に2つのパラメータが必要ですが、なぜc 2だけのパラメータなのでしょうか.
上記のコードはComplexクラスの+をメンバー関数として再ロードするため、実際にはそのパラメータテーブルにはthisポインタが隠されており、実質的にこのメンバー関数には2つのパラメータ(Complex*this,Complex c 2)が含まれているが、隠されているにすぎない.
すなわち,この関数はthisポインタを用いてクラスのprivate変数realとimageに直接アクセスでき,thisポインタは省略して書かないこともできる.
このルールは、クラスのメンバー関数として再ロードするときに静的形式に書かなければならないことを意味します.すなわち、関数タイプの前にconstキーワードを付けて、thisポインタの乱用が変更すべきでないメンバー変数を変更することを防止します.
また,C++の効率的な利用のためには,値ではなく参照を用いるべきであるが,本稿では議論の範囲外であるため,後述しない.
最適化されたコード:
const Complex operate + (const Complex &c2) // const , this
{ // const
Complex c;
c.real=real+c2.real; // this
c.image=image+c2.image;
return c;
}
では、この演算は友元関数に再ロードできますか?友元関数とは?
まず、このコードを見てみましょう.
friend Complex operator +(const Complex &c1,const Complex &c2)
{
return Complex(c1.real+c2.real,c1.image+c2.image);
}
これはComplexクラスの+を友元関数として再ロードします.メンバー関数ではないので、隠されたthisポインタはないので、+の左右のパラメータを入れて、その関数が他のComplexオブジェクトのメンバー変数realやimageにアクセスできるように、
友元関数として明記する必要があります.
友元関数として宣言するより重要な理由は、演算子を再ロードして、「オブジェクト.メンバー名」で呼び出す必要がなくなります.次の表を参照してください.
このメタ関数のパラメータは、左から右への順に並べられた各オペランドを表す.
前置後置演算を区別するために、後置単一演算子+、--のリロードでは、パラメータテーブルにintを追加しますが、パラメータ名を書く必要はありません.
メンバー関数としての再ロードと友元関数としての再ロードの違い:
演算子メンバー関数の設計:
演算子
使用方法
とうかしき
両目演算子@
X1 @ X2
X1.operate @ (X2)
前置リスト@
@ X
X1.operate @()
後置リスト@
X @
X1.operate @(0)
演算子の友元関数の設計:
演算子
使用方法
とうかしき
両目演算子@
X1 @ X2
operate @ (X1,X2)
前置リスト@
@ X
operate @(X1)
後置リスト@
X @
operate @(X1,0)
*後置単眼演算で多く出た0パラメータは後置の意味のみを表す.
以上から分かるように、メンバー関数としてリロードする場合:パラメータ数=元のオペランド数-1(-1は隠しthisポインタ)(後置+--を除く)
友関数として再ロードする場合:パラメータ数=元のオペランド数
では、いつメンバー関数として再ロードすべきか、いつ友元関数として再ロードすべきか.
クラスにオペレータをリロードするように設計する場合は、オペレータをクラスメンバーに設定するか、友元関数に設定するかを選択する必要があります.場合によっては、プログラマは選択せず、オペレータはメンバーでなければなりません.別の状況では、いくつかの経験原則が私たちに決定を導くことができます.
次の原則に従います.
演算子+と+=のリロードの違いと関連付け?
振り返ってみると、+演算子をリロードする場合、通常は+=演算子を同時にリロードしますが、この2つの演算子はどのようにリロードすべきか考えてみましょう.
// +=( )
Complex operator +=(const Complex &c2)
{
real+=c2.real;
image+=c2.image;
return *this;
}
// +( )
friend Complex operator +(const Complex &c1,const Complex &c2)
{
Complex c(c1);
c+=c2;
return c;
}
どうして+=で+を実現するのですか?
効率的な考慮に基づいて、+=オペランドの参照を返すことができ、+はできません.(従って+=効率は+よりはるかに大きい)
最後のポイント:
演算子が基本タイプのデータで呼び出す場合は、グローバルな友元関数として定義する必要があります.
例:complex a;complex b = 2 + a;
このoperator+はcomplexクラスのメンバー関数ではなく、他のクラスのメンバー関数でもありません.関数が1つも存在しないからです.
complex & int.operator+ ( complex& rhs)
最後の最後に、簡単な演算子のリロードコードを貼って、他の自分で考えます.
//
#include <iostream>
using namespace std;
class Complex
{
private:
double real;
double image;
public:
Complex(){real=0;image=0;}
Complex(double r,double i)
{
real=r;
image=i;
}
void ShowComplex()
{
cout<<real;
if(real>0) cout<<"+";
cout<<image<<"i"<<"
";
}
// <<
friend ostream& operator <<(ostream &out,const Complex &c)
{
out<<c.real;
if(c.real>0) out<<"+";
out<<c.image<<"i"<<"
";
return out;
}
// >>
friend istream& operator >>(istream &in,Complex &c)
{
cout<<"input r:"<<endl;
in>>c.real;
cout<<"input i:"<<endl;
in>>c.image;
return in;
}
// =
Complex operator =(const Complex &c)
{
if(this == &c) return *this; // !
real=c.real;
image=c.image;
return *this;
}
// -=
Complex operator -=(const Complex &c2)
{
real-=c2.real;
image-=c2.image;
return *this;
}
// +=( )
Complex operator +=(const Complex &c2)
{
real+=c2.real;
image+=c2.image;
return *this;
}
// +=( )
Complex operator +=(double c2)
{
real+=c2;
return *this;
}
// *=
Complex operator *=(const Complex &c2)
{
real=real*c2.real-image*c2.image;
image=image*c2.real+real*c2.image;
return *this;
}
// /=
Complex operator /=(const Complex &c2)
{
real=(real*c2.real+image*c2.image)/(c2.real*c2.real+c2.image*c2.image);
image=(image*c2.real-real*c2.image)/(c2.real*c2.real+c2.image*c2.image);
return *this;
}
// +( )
friend Complex operator +(const Complex &c1,const Complex &c2)
{
Complex c(c1);
c+=c2;
return c;
}
// +( )
friend Complex operator +(const Complex &c1,double c2)
{
Complex c(c2,0);
c+=c1;
return c;
}
// -
friend Complex operator -(const Complex &c1,const Complex &c2)
{
Complex c(c1);
c-=c2;
return c;
}
// *
friend Complex operator *(const Complex &c1,const Complex &c2)
{
Complex c(c1);
c*=c2;
return c;
}
// /
friend Complex operator /(const Complex &c1,const Complex &c2)
{
Complex c(c1);
c/=c2;
return c;
}
};
int main()
{
Complex c;
Complex c1(1.5,2.5);
Complex c2(2.5,3.5);
cout<<"c1:\t";
c1.ShowComplex();
cout<<"c2:\t";
c2.ShowComplex();
c=c1+c2;
cout<<"c1+c2:\t";
c.ShowComplex();
cout<<"c1+5:\t";
c1+=5;
c1.ShowComplex();
c=c1-c2;
cout<<"c1-c2:\t";
c.ShowComplex();
c=c1*c2;
cout<<"c1*c2:\t";
c.ShowComplex();
c=c1/c2;
cout<<"c1/c2:\t";
c.ShowComplex();
cin>>c1;
cout<<"c:"<<c1;
return 0;
}
演算子リロードテーブル:
次の演算子以外のC++のすべての演算子を再ロードできます.
. .*:: ?: sizeof typeid # static_cast<> dynamic_cast<> const_cast<> reinterpret_cast<> || && ,
リロード可能な演算子:
+ - * /% ^ & | ~ ! = < > <= >=++ -->> == != += -=/= %= ^= &= |= *= <<= >>= [] () -> ->* new new [] delete delete []