最倧オヌバヌロヌド-C ++の䞖界でのJavaScriptの冒険

挔算子のオヌバヌロヌドを䜿甚しおプログラミング蚀語の機胜を適切に拡匵する方法。

プログラミング蚀語の䜜成者および保守者は、倚くの堎合、蚀語に新しい機胜を远加するように求められたす。 圌らから聞くこずができる最も䞀般的な答えは次のずおりです。

「なぜ、あなたが提䟛するものは、蚀語の手段で利甚可胜にできるからです。」

挔算子のオヌバヌロヌドは、自家補のデヌタ型、倚数、行列を䜿甚しお䟿利に操䜜したい物理孊者や数孊者の芁求により、C ++に登堎したした。

物理孊者ず数孊者はこの機胜を奜んでいたしたが、C ++クリ゚ヌタヌを含むプログラマヌは、挔算子のオヌバヌロヌドを本圓に奜むこずはありたせんでした。 耇雑すぎお、倚くの暗黙の事柄であるため、オペレヌタヌの過負荷は、たれなケヌスで䜿甚される有害なものの意芋を定着させたした。

今日は、その動䜜がJavaScriptの同様の型に可胜な限り近いvarずいう名前の新しい型を1぀䜜成するこずで、なぜそれが難しいのか、オヌバヌロヌドを正しく䜿甚する方法を瀺したす。

぀たり、数倀、文字列、配列、たたはオブゞェクトのいずれかを含むこずができるクラスを䜜成しようずしたす。 蚀語リテラルで初期化できるタむプ。 必芁に応じお正しく倉換される型。

最初に、クラス自䜓を宣蚀したす。

struct var { }; 


なぜクラスではなく構造䜓なのか唯䞀の違いは、構造䜓ではデフォルトですべおのメンバヌがパブリックであるずいうこずです。コヌドを読みやすくするために構造䜓がありたす。

varに数倀ず文字列を入れおみたしょう。

 struct var { char *str; double num; }; 


次に、コンストラクタヌを䜜成する必芁がありたす。 あなたが曞くずきに呌び出されたす

 var i = 100; var s = "hello"; struct var { char *str; double num; var (double initial) { num = initial; } var (char *initial) { str = initial; } } 


さお、すべおが実珟するために、画面に倀を衚瀺する必芁がありたす。

 var i = 100, s = "hello"; log(i); log(s); 


これを達成する方法は

 void log(var x) { ....     ? } 


2぀のコンテンツのどちらがvarの特定のむンスタンスで䜿甚されおいるかをどのようにしお知るのでしょうか

明らかに、内郚タむプを远加する必芁がありたす。 しかし、それを行う方法は enumを䜿甚するこずは論理的です

 enum varType { varNum, varStr }; 


クラス定矩を倉曎したす。

 struct var { varType type; char *str; double num; var (double initial); var (char *initial); }; 


次に、デザむナヌでタむプを割り圓おる必芁がありたす。

 var::var (double initial) { type = varNum; num = initial; } var::var (char *initial) { type = varStr; str = initial; } 


さお、今、あなたはログに戻るこずができたす

 void log(var x) { if (x.type == varNum) printf("%f\n", x.num); if (x.type == varStr) printf("%s\n", x.str); } 


そしお今、代入挔算子をブロックする必芁がありたす

 void var::operator = (double initial) { type = varNum; num = initial; } void var::operator = (char *initial) { type = varStr; str = initial; } 


今、あなたは曞くこずができたす

 var a = 10, b = "hello"; 


興味深いこずに、代入挔算子はコンストラクタヌの完党なコピヌであるこずが刀明したした。 倚分それは再利甚する䟡倀がありたすか やっおみたしょう。 「割り圓おコンストラクタ」のどこでも、「割り圓お挔算子」を呌び出すこずができたす。

珟時点では、完党に機胜するコヌドは次のずおりです。

 #include <stdio.h> enum varType { varNum, varStr }; struct var { varType type; char *str; double num; var (double initial); var (char *initial); void operator = (double initial); void operator = (char *initial); }; var::var (double initial) { (*this) = initial; } var::var (char *initial) { (*this) = initial; } void var::operator = (double initial) { type = varNum; num = initial; } void var::operator = (char *initial) { type = varStr; str = initial; } void log(var x) { if (x.type == varNum) printf("%f\n", x.num); if (x.type == varStr) printf("%s\n", x.str); } int main() { var x = 100, s = "hello"; log(x); log(s); } 


しかし、次のように曞くずどうなりたすか

 int main() { var y; } 


私たちはコンパむラに虐埅されおいたす 初期化せずに倉数を宣蚀するこずはできたせん。 混乱、問題は䜕ですか そしお、すべおのデザむナヌが初期倀を必芁ずするずいう事実。

「空の」コンストラクタヌが必芁です。これは、デフォルトコンストラクタヌ、デフォルトコンストラクタヌでもありたす。 しかし、倉数が他のものず等しくない堎合、倉数は等しくなりたすか それが数字なのか文字列なのか、それずも䜕かなのかはただ䞍明です。

これを行うために、nullたたはundefinedずしお知られる「空の倀」の抂念が導入されたした。

 enum varType { varNull, varNum, varStr }; var::var() { type = varNull; } 


これで、型を考えずに倉数を簡単に宣蚀できたす。

 var a, b, c; 


そしお、コヌドに倀を割り圓おるには

 a = 1; b = "foo"; 


しかし、私たちはただ曞くこずができたせん

 a = b; 


代入挔算子var = varが必芁です。

 void var::operator= (var src) { type = src.type; num = src.num; str = src.str; } 


割り圓おられるず、タむプが倉曎されたす そしお、「a」は文字列になりたす。

先に進みたしょう。 数字ず線が未完成であるこずを䞀時的に忘れおください。 配列を凊理しおみたしょう。

たず、enumに新しい型が必芁です。

 enum varType { varNull, varNum, varStr, varArr }; 


次に、芁玠バッファぞのポむンタずサむズ

 struct var { ... int size; var *arr; ... } 


次に、芁玠アクセス挔算子をオヌバヌロヌドしたす。

 struct var { ... var operator [](int i); ... } 


このような挔算子は、「添字挔算子」たたはむンデックス挔算子ず呌ばれたす。

目暙var型の芁玠を配列に栌玍するこず。 ぀たり、再垰に぀いお話しおいるのです。

ずころで、同じ挔算子を䜿甚しお、文字列内の個々の文字ずオブゞェクトのプロパティにアクセスする必芁がありたす。 しかし、オブゞェクトの堎合、入力に線がありたす。 結局のずころ、キヌは文字列倀です。

 var operator [](char *key); 


いいえ、それは良くありたせん。 文字バッファぞのポむンタは必芁ありたせんが、文字列が必芁です。これをやっおみたしょう。

 struct var { ... var operator [](var key); ... } 


次に、すべおが機胜したら、次のように蚘述できたす。

 x[1] 


たたは

 x["foo"] 


コンパむラヌはvar なんで 結局のずころ、数倀ず文字列のリテラルからのコンストラクタヌが既にありたす。

次のように曞くこずができたす

 y = "foo"; x[y]; 


ずころで、リテラルリテラルは「リテラル倀」、぀たり、コヌドに盎接入力した倀です。 たずえば、割り圓お「int a = b;」は名前による割り圓おであり、「int a = 123;」はリテラル割り圓お、リテラル割り圓お、「by literal」123です。

1぀のこずは明らかではありたせん。varはどのように配列になりたすか 倉数「a」を䜜成し、それが配列であるず蚀うにはどうすればよいでしょうか

 var a ???; 


JavaScriptはいく぀かのメ゜ッドを䜿甚したす。

 var a = new Array; var a = []; 


䞡方詊しおみたしょう

 var newArray() { var R; R.type = varArr; R.size = 0; R.arr = new var [10]; return R; } 


これたでのずころ、より本質的なこずに集䞭するために、10の芁玠だけで十分であるず考えたす。

今興味深い点は、次のようなこずをしおみおください

 var a = []; 


C ++では[]を䜿甚できたせんが、任意の識別子、぀たり名前を䜿甚できたす。 たずえば、配列。

 var a = Array; 


どうやっおやるの これを行うには、次のように「構文タむプ」を適甚したす。

 enum varSyntax { Array }; 


「配列」ずいう蚀葉に蚀及するず、コンパむラは「varSyntax」型が必芁であるこずを認識したす。 ただし、コンパむラヌは、䜿甚する関数、コンストラクタヌ、たたは挔算子のタむプをタむプごずに遞択したす。

 struct var { ... var (varSyntax initial) { if (initial == Array) { type = varArr; size = 0; arr = new var[10]; } } ... } var a = Array; 


もちろん、コンストラクタがある堎所に割り圓おがあり、varSyntax型の割り圓お挔算子をすぐに呌び出しお蚘述したす。

 void var::operator=(varSyntax initial) { ... } 


次のコヌドでは、最初に「a」がvarvarSyntaxコンストラクタヌで初期化され、次に「b」が空のコンストラクタヌで初期化され、「var operator =varSyntax」挔算子で割り圓おられたす。

 var a = Array, b; b = Array; 


コンストラクタヌず「=」による割り圓おは垞にペアで行われるため、同じトリックを適甚し、コンストラクタヌの割り圓おからのコヌドを再利甚するこずは論理的です。

 struct var { ... var (varSyntax initial) { (*this) = initial; } operator= (var Syntax); ... }; void var::operator= (varSyntax initial) { if (initial == Array) { type = varArr; size = 0; arr = new var*[10]; } // else if (initial == Object) { // ... // } } 


どこかで、空のオブゞェクトを䜜成できたす。 しかし、それは埌です。

さお、詊しおみる時間です

 int main() { var a = Array; a[0] = 100; log(a[0]); } 


 error: conversion from 'int' to 'var' is ambiguous a[0] = 100.0; 


うわヌ、それは私たちがvarから挔算子[]を宣蚀したこずです。 䜕らかの理由で、コンパむラはintを予期しおいたす。 var [0]をvar [1]に倉曎するず、すべおがコンパむルされたす。 なに

 int main() { var a = Array; a[1] = 100; log(a[1]); } 


だから、1぀で、それはコンパむルされたす...

ただ挔算子[]を曞いおいないので、このコヌドだけはただ䜕もしたせん。

曞かなければならない おそらくこのようなもの

 var var::operator [](var key) { return arr[key]; } 


 error: no viable overloaded operator[] for type 'var *' return arr[i]; ~~~^~ 


コンパむラ、他に䜕が間違っおいるのですか

ポむンタぞのむンデックスアクセスにはintが必芁ですが、コンパむラはvarをintに倉換する方法をただ知りたせん。

さお、あなたはint挔算子を定矩するこずができたす、C ++にはそのようなこずがありたす ただし、新しい挔算子を䜜成できず、䜜成しない長い履歎方が良いので、これを実行したしょう。

 struct var { ... int toInt() { return num; } ... } var var::operator[] (var i) { return arr[i.toInt()]; } 


コンパむルはされたすが、起動埌は䜕も出力されたせん。問題は䜕ですか

しかし、䞀般的にどのように機胜したすか 同じ挔算子を䜿甚しお芁玠の内容をどのように読み曞きできたすか

結局、䞡方の行が機胜するはずです

 a[1] = 100; log(a[1]); 


ある蚘録では、別の読曞では。 operator =は芁玠ぞの参照を返す必芁があるこずがわかりたす。 蚘号に泚意しおください。この堎合は次のずおりです。

 var& var::operator[] (var i) { return arr[i.toInt()]; } 


しかし、「a [1]」は機胜したしたが、「a [0]」は誓い続けおいたす。 なぜすべお同じですか

事実、0は数倀ずポむンタヌの䞡方ず芋なすこずができたすが、varには2぀のコンストラクタヌがあり、1぀は数倀double甚、もう1぀はポむンタヌchar *甚です。 このため、リテラルずしお0を䜿甚するず、完党に正垞なコヌドのように芋え、突然コンパむル゚ラヌが発生したす。 これは、C ++ずあいたいな呌び出しシリヌズの最も掗緎された拷問の1぀です。

しかし、䞀般的に、コンパむラはたず敎数れロ、぀たりintを考慮したす。

幞いなこずに、varをintから初期化するように教えるだけで十分です。 い぀ものように、すぐにコンストラクタず挔算子=を曞きたす。

 var::var (int initial) { (*this) = (double) initial; } void var::operator = (int initial) { (*this) = (double) initial; } 


ここでは、コヌドを再利甚するために、operator =doubleの䞡方の呌び出しが単玔にリダむレクトされたす。

それで、珟時点で䜕が起こったのか

 #include <stdio.h> enum varType { varNull, varNum, varStr, varArr }; enum varSyntax { Array }; struct var { varType type; char *str; double num; var (); var (double initial); var (int initial); var (char *initial); void operator = (double initial); void operator = (int initial); void operator = (char *initial); var *arr; int size; var &operator [](var i); var (varSyntax initial) { (*this) = initial; } void operator= (varSyntax initial); void operator= (var src) { type = src.type; num = src.num; str = src.str; arr = src.arr; } int toInt() { return num; } }; var::var() { type = varNull; } var::var (double initial) { (*this) = initial; } var::var (int initial) { (*this) = (double)initial; } var::var (char *initial) { (*this) = initial; } void var::operator = (double initial) { type = varNum; num = initial; } void var::operator = (int initial) { (*this) = (double) initial; } void var::operator = (char *initial) { type = varStr; str = initial; } void log(var x) { if (x.type == varNum) printf("%f\n", x.num); if (x.type == varStr) printf("%s\n", x.str); } void var::operator= (varSyntax initial) { if (initial == Array) { type = varArr; size = 0; arr = new var[10]; } } var &var::operator[] (var i) { return arr[i.toInt()]; } int main() { var x = 100, s = "hello"; var a = Array; a[0] = 200; log(a[0]); log(x); log(s); } 


ずころで、画面に配列を衚瀺したい堎合はどうでしょうか

 void log(var x) { if (x.type == varNum) printf("%f\n", x.num); if (x.type == varStr) printf("%s\n", x.str); if (x.type == varArr) printf("[Array]\n"); } 


これたでのずころ、そうだけです。

しかし、もっず欲しい。

最初に、自己調敎配列の長さを䜜成する必芁がありたす。

 var &var::operator[] (var i) { int pos = i.toInt(); if (pos >= size) size = pos+1; return arr[pos]; } 


そしお、あなたはプッシュを行う必芁がありたす-最埌に1぀の芁玠を远加したす

 var var::push(var item) { if (type != varArr) { var nil; return nil; } (*this)[size] = item; size++; return item; } 


ポむンタを䜿甚しおいるため、型をチェックするのに堎所はずれではありたせん。 この蚘事を準備する過皋で、これはたさにプログラムが萜ちた堎所です。 さお、ただサむズを確認しおいたせん。グロヌバルデザむンに取り組んでいたすが、この問題に戻りたす。

log関数を曞き換えお、配列党䜓を衚瀺できるようになりたした。

 void log(var x) { if (x.type == varNum) printf("%f ", x.num); if (x.type == varStr) printf("%s ", x.str); if (x.type == varArr) { printf("["); for (int i = 0; i < x.size; i++) log(x[i]); printf("]"); } } 


最小限の䜜業が必芁でしたが、生呜を䞎える再垰は䜕をしたすか

 int main() { var a = Array; a[0]=100; a.push(200); log(a[0]); log(a[1]); log(a); } 


起動埌のデヌタ出力

 100.000000 200.000000 [100.000000 200.000000] 


たあ、玠晎らしい、いく぀かの基本的なポリモヌフィズムがありたす。

すでに配列に配列を配眮し、文字列や数字ず混同するこずさえできたす。

 int main() { var a = Array; a.push(100); a.push("foo"); a[2] = Array; a[2][0] = 200; a[2][1] = "bar"; log(a); } 


 [100.000000 foo [200.000000 bar ]] 


次のように曞き蟌もうずするずどうなるでしょうか。

 var a = Array; var b = a.push(Array); b.push(200); b.push("foo"); log(a); 


そしおここに䜕がありたす

 [[]] 


なぜこれが起こったのですか

この簡単な方法で確認しおください。

 printf("%\n", a.arr[0].size); printf("%\n", b.size); 


論理的には、同じ番号が衚瀺されたす2。

しかし、実際にはa.arr [0] .size == 0

問題は、a [0]ずbが2぀の異なる倉数、2぀の異なるむンスタンスであるずいうこずです。 リタヌンを介しお関数内でa.pushが割り圓おられた時点で、それらのフィヌルドは䞀臎したした。぀たり、サむズ、arrは同じでしたが、b.pushの埌、b.sizeの増加ずa [0]の増加はありたせんでした。サむズ。

これは脳の問題であり、蚀葉で説明するこずさえ難しく、おそらく「参照枡し」ず呌ばれる最埌の行を読んでいる間、読者は完党に混乱しおいたす。

C ++では、が匕数の前にある堎合、通垞、参照枡しは呌び出されたすが、これは特別な堎合です。 䞀般に、これは、コピヌを倉曎するずオリゞナルが倉曎されるこずを意味したす。

そのような問題を解決する方法を芋おみたしょう。 最初は、配列に接続されおいたすべおのものが別のクラスに入れられたため、歎史的に起こり、私はそれをlstず呌びたした。 特に圌のデバむスには入らないでください。そのため、䞀般的な本質を把握しおください。

 class lst { typedef var** P; P p; int capacity, size; void zeroInit(); public: lst(); ~lst(); int length(); void resize(int newsize); var pop(); void push(const var &a); var& operator [](int i); void delIns(int pos, int delCount, var *item, int insCount); }; 


これは、動的にサむズを倉曎する機胜を備えたポむンタヌのリストず、远加のpush/pop/delInsを栌玍するための小さなクラスであるこずを説明したしょう。

配列がJavaScript配列ず厳密に䞀臎するようにするために必芁なこずはこれだけです。

さお、前に「var」がどのように配眮されたかを忘れお、「lst」を正しく入力しおみおください。

 struct Ref { int uses; void *data; Ref () { uses = 1; } }; struct var { varType type; union { double num; Ref* ref; }; ... }; 


たず、numずrefを組み合わせたした。すべお同じであるず同時に、これらのプロパティは必芁ないからです。 メモリを節玄したす。

第二に、配列に接続されおいるすべおのものの盎接的な倀の代わりに、内郚にカりンタヌずのリンクがありたす。 これは参照カりントず呌ばれたす。

同じリンクに、オブゞェクトを保存したす。

カりンタはすぐに1に蚭定されるこずに泚意しおください。

参照カりントがプログラムされるたびに、「コネクタ」ず「ディスコネクタ」ずいう2぀の䞻芁な方法がすぐに蚘述されたす。

最初は「ref = src.ref、ref->は++を䜿甚」で、通垞はコピヌ、リンク、アタッチ、たたは実際には参照ず呌ばれたす。

 void var::copy(const var &a) { //       . type = a.type; if (type == varNum || type == varBool) num = a.num; else { if (a.type == varNull) { return; } ref = a.ref; if (ref) ref->uses++; } } 


次に、逆のプロセスが発生し、䜿甚カりンタが枛少し、れロになった堎合、元のメモリが解攟されたす。

通垞、リンク解陀、参照解陀、切り離しず呌ばれたす。 以前はunrefず呌んでいたした。

 void var::unref() { if (type == varNum || type == varNull || type == varBool) return; else if (type == varStr) { ref->uses--; if (ref->uses == 0) { delete (chr*)ref->data, delete ref; } } else if (type == varArr) { ref->uses--; if (ref->uses == 0) { deleteLst(); } } else if (type == varObj) { ref->uses--; if (ref->uses == 0) { deleteObj(); } } type = varNull; ref = 0; } 


Ref構造䜓のデヌタはvoid *型、぀たり単なるポむンタであり、配列lstたたはオブゞェクトobjの実際のむンスタンスぞのリンクを栌玍したす。 オブゞェクトずいう単語では、JavaScript [オブゞェクトオブゞェクト]に埓っおキヌ/倀のペアを栌玍するオブゞェクトに぀いお説明しおいたす。

基本的に参照カりントは、ガベヌゞコレクタヌの䞀皮です。

通垞、「ガベヌゞコレクタヌ」GCずいう蚀葉はタむマヌで実行されるむンタヌバルコレクタヌを意味したすが、技術的には、Wikipediaの分類によるず、リンクカりントは最も単玔なガベヌゞコレクタヌです。

そしお、あなたが芋るこずができるように、それはそれほど単玔ではありたせん、脳は䞀床に壊れるこずができたす。

読者が混乱しないように、もう䞀床繰り返したす。

varクラスを䜜成し、その䞭にdouble、lst配列の堎合、chr文字列の堎合、たたはkeyvalオブゞェクトの堎合をカプセル化したす。

文字列クラスは次のずおりです。

 struct chr { int size; wchar_t *s; chr (); ~chr(); void set(double i); void set(wchar_t *a, int length = -1); void setUtf(char *a, int length = -1); void setAscii(char *a, int length = -1); char * getAscii(); char * getUtf(); wchar_t operator [](int i); double toNumber (); int intToStr(int i, char *s); void dblToStr (double d, char *s); int cmp (const chr &other); int find (int start, wchar_t *c, int subsize); chr substr(int pos, int count = -1); int _strcount(const chr &substring); void _cpto(int from, const chr &dest, int to, int count); chr clone(); void replace(chr &A, chr &B, chr &dest); }; 


そしお、これがオブゞェクトのクラスです

 struct keyval { var keys, vals; keyval (); void set(var key, var val); var &get(var key); }; 


すでに完党な再垰ずポリモヌフィズムがありたす。芋お、keyvalはvarの圢匏の配列を䜿甚したす。 varの䞀郚になるため。 そしおそれは動䜜したす

参照カりントを䜿甚する最も重芁な機胜の1぀は、オブゞェクトを倉曎する堎合、それを参照するすべおのナヌザヌも倉曎されたオブゞェクトを受け取るこずを理解する必芁があるこずです。

䟋

 void f(var t) { t += "world"; log(t); } var s = "hello"; f(s); log(s); 


結論

 world world 


文字列内のすべおの文字をコピヌする代わりにsをfに枡すず、1぀のポむンタヌのみがコピヌされ、1぀のカりンタヌがむンクリメントされたす。

ただし、文字列tを倉曎するず、文字列sも倉曎されたす。 配列の堎合に必芁なものですが、文字列の堎合には必芁ありたせん これは、参照枡しず呌ばれたす。

参照カりントを介しお枡される倉数をその゜ヌスずは別に倉曎する必芁がある堎合、各倉曎の前にdetach / unref / unlink関数を呌び出す必芁がありたす。

これは、たずえば、Delphiで文字列が機胜する方法です。 これは、コピヌオンラむトずいう甚語ず呌ばれたす。

これは悪い決定であるず考えられおいたす。 しかし、コピヌオンラむトを拒吊する方法はありたすが、参照枡しずコピヌポむンタずむンクリメントを可胜に保぀には参照カりント

答えは珟代のプログラミングの暙準ずなっおいたす。倉数を倉曎する代わりに、それを䞍倉にしたす これは䞍倉性ず呌ばれたす。

䞍倉性の原則によれば、JavaScriptの行は䞀床だけ蚭定され、その埌は倉曎できたせん。 䜕かを倉曎するストリングを操䜜するすべおの機胜は、改行を返したす。 これにより、すべおのcopy / unrefs、ポむンタチェック、およびメモリを䜿甚するその他の䜜業を慎重に配眮するずいう困難な䜜業が倧幅に容易になりたす。

ここで、突然、蚘事が読者にずっお快適な20K文字を超えたため、䞭断しなければなりたせん。 しかし、ただ玄20人のオペレヌタヌをリロヌドする必芁がありたす 偶数挔算子コンマ。 オブゞェクトず配列を組み合わせ、JSON.parseを蚘述し、文字列ずブヌル倀の比范を実装し、ブヌルのコンストラクタヌを蚘述し、配列ずオブゞェクトの倀を初期化する衚蚘法を考え出し、実装し、耇数匕数ログの問題を解決したす...、未定矩、typeof亀換/スラむスなどを正しく実装したす。 そしお、これはすべお単䞀のテンプレヌトなしで、挔算子ず関数のオヌバヌロヌドのみです。

そのため、興味がある堎合は、すぐに継続したす。

最も興味深いのは、ラむブラリリポゞトリぞのリンクです

github.com/exebook/jslike

Source: https://habr.com/ru/post/J261351/


All Articles