クラスAと、そのインスタンスへのポインターを取る関数を考えてみましょう。
class A { int a; } void foo(A a) {}
Cプログラマーにどのように呼ぶか尋ねてください。 きっとあなたはこのような何かを聞くでしょう:
void main() { auto b = new A; foo(b); }
秘trickは、Dの最初の引数が基本識別子として機能できることです。そのメソッドはこの関数です。 この関数は次のように呼び出すことができます。
b.foo();
これにより、非常に興味深い一貫性のある(そして高速な)デザインを構築するための大きな可能性が広がります。
注意:最後はカラー画像です!
そして、複数の引数がある場合はどうなりますか? 2?
class A { int a; } void foo(A a, int bar, string text) {} void main() { auto b = new A; int var = 42; b.foo(var, "This police box inside than outside"); // ! }
関数を呼び出す通常の形式は保持され、マシンコードの観点からは、これらの形式はほとんど同じであることに注意する必要があります...ほとんど... しかし、これはまったく別の話です。
さらに-もっと! 関数をパラメーター化できます。
void foo(T)(T a, string text = "Second argument\n") { writeln(T.stringof); writeln(a); writeln(text); } void main() { int var = 42; var.foo!(typeof(var))(); //int //42 //! var.foo("For this view this string is first argument!");// }
シンプルなビルトインタイプで、簡単にスイングします。 配列はどうですか? もちろん! 結局のところ、コードは両方のケースで同じです!
void foo(T)(T[] a) { writeln(T.stringof); writeln(a); writeln(a[0]); } void main() { int[] vars = [42, 15, 8]; vars.foo(); //int[] //[42, 15, 8] //42 }
今、私たちは最もおいしいものに渡します...しかし、スタートのために-小さな余談。
標準言語ライブラリには、配列で機能する膨大な数の関数があります。 この場合、マップ関数を検討してください。 この関数は、特定の関数とデータ配列をパラメーターとして受け取り、この関数が提供する要素に対してアクションを実行し、新しいデータの新しい配列を形成します。 std.algorithmモジュールにあります。
int foo(T)(T a) { return a + 2; } void main() { int[] vars = [42, 15, 8]; auto output = map!((a) => a.foo())(vars); writeln(output); }
ここにマップ関数があります。 記号の後の括弧内! すべての要素で実行される機能リテラルを説明します。 私たちの場合、これは(a)=> a.foo()です。 最初の括弧は、関数の「宣言」を象徴的に表し、その引数がどのように命名されるかを説明します。 ここでは、配列の要素になります。 記号=>は関数本体の始まりを意味し、最後の部分は理解できると思います。 プログラムを作成した後、期待するものを確認します。
[44, 17, 10]
UFCSスタイルを使用してみましょう。これはここに配置されており、機能チェーンの中間結果を第三者が使用することはありません。
@property int addTwo(T)(T a) { return a + 2; } void main() { int[] vars = [42, 15, 8]; vars .map!((a) => a.addTwo) .writeln; // ! }
writeln関数には、その機能的性質を示す括弧がありません。 実際には、元は@property属性で宣言されていました。 つまり、明示的な引数をとらない関数は、括弧なしで呼び出すことができ、多くの場合、クラスまたは構造体フィールドのように見えます。 クラスでは、これにより、このフィールドにアクセスするときにアクションを実行できます。UFCSスタイルでは、コマンドとしての機能の役割を強調するのに役立ちます。 同じ属性を関数に割り当て、コマンドの性質を伝えるために名前を変更しました。
次に、このコードを見てみましょう。さらに答えようとせずに、このパラメーター化されたクラスContainerの何が問題になっていますか? ヒントとして、彼の広告の一部を紹介します。
class Container(T) { this() { vars = [42, 15, 8]; } //…. T[] vars; } @property int addTwo(T)(T a) { return a + 2; } void main() { auto container = new Container!(int); // , container .map!((a) => a.addTwo) .writeln; }
推測? そう? わかりました、私はカードを開きます。
実際、標準言語ライブラリは配列などを使用しません。 Rangeなど、より抽象的な概念が使用されます。 概念Dでは、範囲は抽象型であり、いくつかの順序付けられた要素を格納およびアクセスできます。 言い換えれば、これは配列、スタック、および単純に接続されたリストを結合するものです...コンテナのセット全体に対する1つの作業スキームです! クラスでも構造体でも、どこでも動作します。 しかし、ここに問題があります...引数の型が範囲であるかどうかをどのように判断しますか?
テンプレート契約が助けになります! これらは、ユーザーが
そのため、map関数には独自のテンプレートコントラクトがあり、引数の型が範囲かどうかを決定します。
自動マップ(範囲)(範囲r)
if(isInputRange!(Unqual!Range));
つまり、タイプがいわゆる入力範囲に属しているかどうかがチェックされます(ロシア語ではどのアナログを使用できるかわかりません)。 したがって、マップで使用するタイプには、入力範囲のすべての属性が必要です。
-範囲voidを決定するための空の関数があります
-トップ要素を返すフロント関数を持っています
-上部の要素を削除するpopFront関数があります
これはまさにクラスコードで見落としていたものです! しかし、特に注意を払う人は、配列に入力範囲として定義するこれらの組み込みメソッドがないことに気付くでしょう。 いいや
しかし、UFCSがあります! そのおかげで、空の関数、front関数、およびpopFront関数(およびstd.range.primitivesモジュールで定義されている他の多くの関数)を配列に関して使用し、他のコンテナーと同等に使用できます!
UFCSを介して呼び出される関数が、他の関数が後で使用するために結果を返すことが有用であると言われるべきです。 これは、ユーザーaquaratixcから親切に提供されたコードを見るとわかります。
@property auto sayGav(Range)(Range c) { writeln("Gav!"); return c; } void main() { auto img = load(`Lenna.png`); img .takeArea(0, 0, 256, 256) .map!(a => toNegative(a, Color4f(1.0f,1.0f,1.0f)).front) .array .toSurface(256, 256) .drawPoint(Color4f(1.0f,1.0f,1.0f), 125, 125) .createImage(256, 256) .sayGav .savePNG("testing.png"); }
作業の結果、プログラムはターミナルで「Gav!」を出力し、下図に示すように画像を変換します。
PS Habravchanin ヴィンテージは、あるタイプのライン
auto output = map!((a) => a.foo())(vars);
このように書くことができます:
auto output = map!q{a.foo}(vars);
彼に特に感謝します!