今回は、演算子のオーバーロードの使い方と、演算子のオーバーロードを使用する際の注意点について説明します。以下は実際のケースです。
最近、データ処理をする必要があり、Mat、Vector、Pointなどのデータ構造をカスタマイズしました。加算、減算、乗算、除算などの四則演算を繰り返し定義する必要があります。コードはあまり直観的ではなく、C++ や C# のような演算子のオーバーロードは非常に煩わしいので、実装のアイデアとして演算子のオーバーロードを自動的に実装することを考えました。実際には非常に簡単で、インタプリタを作成してコードをコンパイルするだけです。例:
S = A + B (B - C.fun())/2 + D
は
`S = replace(replace(A, '+', replace(replace(B, ' ',(replace(B,'-',C.fun())))),'/',2),'+',D)`
replace 関数では、対応する演算子関数を呼び出します。オブジェクト、replace 関数のコードは次のとおりです。
/** * 转换方法 * @param a * @param op * @param b * @returns {*} * @private */ export function __replace__(a,op,b){ if(typeof(a) != 'object' && typeof(b) != 'object'){ return new Function('a','b','return a' + op + 'b')(a,b) } if(!Object.getPrototypeOf(a).isPrototypeOf(b) && Object.getPrototypeOf(b).isPrototypeOf(a)){ throw '不同类型的对象不能使用四则运算' } let target = null if (Object.getPrototypeOf(a).isPrototypeOf(b)) { target = new Function('return ' + b.__proto__.constructor.name)() } if (Object.getPrototypeOf(b).isPrototypeOf(a)) { target = new Function('return ' + a.__proto__.constructor.name)() } if (op == '+') { if (target.__add__ != undefined) { return target.__add__(a, b) }else { throw target.toString() +'\n未定义__add__方法' } }else if(op == '-') { if (target.__plus__ != undefined) { return target.__plus__(a, b) }else { throw target.toString() + '\n未定义__plus__方法' } }else if(op == '*') { if (target.__multiply__ != undefined) { return target.__multiply__(a, b) }else { throw target.toString() + '\n未定义__multiply__方法' } } else if (op == '/') { if (target.__pide__ != undefined) { return target.__pide__(a, b) }else { throw target.toString() + '\n未定义__pide__方法' } } else if (op == '%') { if (target.__mod__ != undefined) { return target.__mod__(a, b) }else { throw target.toString() + '\n未定义__mod__方法' } } else if(op == '.*') { if (target.__dot_multiply__ != undefined) { return target.__dot_multiply__(a, b) }else { throw target.toString() + '\n未定义__dot_multiply__方法' } } else if(op == './') { if (target.__dot_pide__ != undefined) { return target.__dot_pide__(a, b) }else { throw target.toString() + '\n未定义__dot_pide__方法' } } else if(op == '**') { if (target.__power__ != undefined) { return target.__power__(a, b) }else { throw target.toString() + '\n未定义__power__方法' } }else { throw op + '运算符无法识别' } }
replace の実装は非常に簡単で、あまり説明する必要はありません。重要なのはコードのコンパイル方法です。大学でデータ構造を学ぶ際の四則演算の実装がこの翻訳の基礎になっていますが、若干の違いがあります。プロセスを簡単に説明します:
1. 式を分割し、変数と演算子を抽出してメタ配列 A を取得します
2. メタ配列を走査します
要素が演算子の加算、減算、乗算、除算の場合は、前の要素をスタックからポップし、変換して replace(last, 演算子,
要素が ')' の場合、要素をスタックからポップし、要素が '(' に遭遇するまで結合し、スタックにプッシュします。ここでは、要素が ')' であるかどうかに注意する必要があります。 '(' の先頭には関数呼び出しまたは置換があります。関数呼び出しまたは置換の場合は、データを前方にポップし続けて置換関数を閉じる必要があります。
一般要素の場合は、前の要素がそうでなければ、要素をスタックに直接プッシュして、コンパイルされた式を取得します。上記のプロセスで、コードを実装します:
/** * 表达式转换工具方法 * @param code */ export function translate (code) { let data = [] let tmp_code = code.replace(/\s/g,'') let tmp = [] let vari = tmp_code.split(/["]+[^"]*["]+|[']+[^']*[']+|\*\*|\+|-|\*|\/|\(|\)|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|\}|=|%|\.\/|\.\*|,/g) let ops = tmp_code.match(/["]+[^"]*["]+|[']+[^']*[']+|\*\*|\+|-|\*|\/|\(|\)|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|\}|=|%|\.\/|\.\*|,/g) for (let i = 0,len = ops.length; i < len; i++) { if (vari[i] != '') { tmp.push(vari[i]) } if (ops[i] != '') { tmp.push(ops[i]) } } tmp.push(vari[ops.length]) for (let i = 0; i < tmp.length; i++){ let item = tmp[i] if(/\*\*|\+|-|\*|\/|%|\.\/|\.\*/.test(tmp[i])) { let top = data.pop() let trans = '__replace__(' + top + ',\'' + tmp[i] + '\',' data.push(trans) }else{ if (')' == tmp[i]) { let trans0 = tmp[i] let top0 = data.pop() while (top0 != '(') { trans0 = top0 + trans0 top0 = data.pop() } trans0 = top0 + trans0 let pre = data[data.length - 1] while(/[_\w]+[\.]?[_\w]+/.test(pre) && !/^__replace__\(/.test(pre) && pre != undefined) { pre = data.pop() trans0 = pre + trans0 pre = data[data.length - 1] } pre = data[data.length - 1] while(pre != undefined && /^__replace__\(/.test(pre)){ pre = data.pop() trans0 = pre + trans0 + ')' pre = data[data.length - 1] } data.push(trans0) }else { let pre = data[data.length - 1] let trans1 = tmp[i] while(pre != undefined && /^__replace__\(/.test(pre) && !/\*\*|\+|-|\*|\/|\(|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|=|\}|%|\.\/|\.\*/.test(item) && !/^__replace__\(/.test(item)) { if(tmp[i + 1] == undefined){ pre = data.pop() trans1 = pre + trans1 + ')' break; }else{ pre = data.pop() trans1 = pre + trans1 + ')' pre = data[data.length - 1] } } data.push(trans1) } } } let result = '' data.forEach((value, key, own) => { result += value }) return result }
式のコンパイル メソッドが記述されます。次のステップは、記述されたコードをトランスレータによって翻訳する方法です。つまり、コンテナと 2 つのメソッドが必要です。1 つはメソッドの属性を再定義することです。次に、クラス コンストラクターに再定義メソッドを導入しましょう:
export default class OOkay { constructor () { let protos = Object.getOwnPropertyNames(Object.getPrototypeOf(this)) protos.forEach((proto, key, own) => { if(proto != 'constructor'){ Object.defineProperty(this, proto, { value:new Function(translate_block(proto, this[proto].toString())).call(this) }) } }) } }
上記からわかるように、Object.defineProperty を使用して、コンストラクター内でそれを再定義します。translate_block はコード ブロック全体を変換します。コードは次のとおりです。
/** * 类代码块转换工具 * @param name * @param block * @returns {string} */ export function translate_block (name , block) { let codes = block.split('\n') let reg = new RegExp('^' + name + '$') console.log(reg.source) codes[0] = codes[0].replace(name,'function') for(let i = 1; i < codes.length; i++) { if (codes[i].indexOf('//') != -1) { codes[i] = codes[i].substring(0,codes[i].indexOf('//')) } if(/\*\*|\+|-|\*|\/|%|\.\/|\.\*/g.test(codes[i])){ if (codes[i].indexOf('return ') != -1) { let ret_index = codes[i].indexOf('return ') + 7 codes[i] = codes[i].substring(0,ret_index) + translate(codes[i].substring(ret_index)) }else { let eq_index = codes[i].indexOf('=') + 1 codes[i] = codes[i].substring(0,eq_index) + translate(codes[i].substring(eq_index)) } } } return 'return ' + codes.join('\n') }
新しいクラスの場合は、OMany クラスを継承し、クラス内で演算子のオーバーロードを使用するだけです。 -OOOK クラスでは、次のようにインジェクションを使用できます:
/** * 非继承类的注入方法 * @param target */ static inject (target) { let protos = Object.getOwnPropertyNames(Object.getPrototypeOf(target)) protos.forEach((proto, key, own) => { if (proto != 'constructor') { Object.defineProperty(target, proto, { value:new Function(translate_block(proto, target[proto].toString())).call(target) }) } }) }
クラス以外のコードの場合、コンテナが必要です。ここでは 2 つのメソッドを使用します。1 つは、次のような ookay スクリプトを使用する方法です
もう 1 つは、コードを __$$__ メソッドにパラメータとして渡し、コードをコンパイルし、次のように実行します:
static __$__(fn) { if(!(fn instanceof Function)){ throw '参数错误' } (new Function(translate_block('function',fn.toString()))).call(window)() }
以上が演算子のオーバーロードの使用方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。