40用d编程函数指针与λ

函数指针用于存储函数地址,以备后用.类似c语言的函数指针.
闭包存储函数指针和上下文,上下文可以是函数执行区域.
闭包也允许其他语言的闭包.
&取地址.

import std.stdio;

int myFunction(char c, double d) {
    return 42;
}

void main() {
    myTemplate(&myFunction);//取函数地址并当作参数传递给模板
}

void myTemplate(T)(T parameter) {
    writeln("type : ", T.stringof);
    writeln("value: ", parameter);
}

模板参数可匹配任何类型(函数指针也可以),

struct MyStruct {
    void func() {
    }
}

void main() {
    auto o = MyStruct();

    auto f = &MyStruct.func;    // 类型
    auto d = &o.func;           // 对象

    static assert(is (typeof(f) == void function()));//函数
    static assert(is (typeof(d) == void delegate()));//闭包
}

&可取类型/对象的地址.可直接调用d,而f需要一个对象.
成员函数指针定义中型 function(parameters) ptr;,返回类型,参数必须都匹配.这是声明.
或这样int function(char, double) ptr = &myFunction;声明.前面ptr前为类型.
alias CalculationFunc = int function(char, double);这样更好读.
CalculationFunc ptr = &myFunction;或者书写.
auto ptr = &myFunction;最简单.
调用函数`

    int result = ptr('a', 5.67);//ptr==myFunction
    assert(result == 42);

存储它,就可方便以后调用.

  final switch (employee.type) {

    case EmployeeType.fullTime:
        fullTimeEmployeeWages();
        break;

    case EmployeeType.hourly:
        hourlyEmployeeWages();
        break;
    }

但上面代码难以维护,要支持所有类型,

interface Employee {
    double wages();
}

class FullTimeEmployee : Employee {
    double wages() {
        double result;
        // ...
        return result;
    }
}

class HourlyEmployee : Employee {
    double wages() {
        double result;
        // ...
        return result;
    }
}

// ...

    double result = employee.wages();

实现不同行为时,可以选择函数指针.在不支持面向对象的语言中更常见.
函数指针作为参数:

int[] filterAndConvert(const int[] numbers) {
    int[] result;

    foreach (e; numbers) {
        if (e > 0) {//过滤                       
            immutable newNumber = e * 10;//转换
            result ~= newNumber;
        }
    }

    return result;
}

随机值演示:

import std.stdio;
import std.random;

void main() {
    int[] numbers;

    //随机数
    foreach (i; 0 .. 10) {
        numbers ~= uniform(0, 10) - 5;
    }

    writeln("input : ", numbers);
    writeln("output: ", filterAndConvert(numbers));
}

定义两个操作:

alias Predicate = bool function(int);//整->极
alias Convertor = int function(int); //整->整

类似模板参数一样

int[] filterAndConvert(const int[] numbers,
                       Predicate predicate,
                       Convertor convertor) {
    int[] result;

    foreach (number; numbers) {
        if (predicate(number)) {
            immutable newNumber = convertor(number);
            result ~= newNumber;
        }
    }

    return result;
}

现在可根据具体的函数来实现过滤和转换

bool isGreaterThanZero(int number) {
    return number > 0;
}//可变

int tenTimes(int number) {
    return number * 10;
}//可变

// ...

    writeln("output: ", filterAndConvert(numbers,&isGreaterThanZero,&tenTimes));

又变了.

bool isEven(int number) {
    return (number % 2) == 0;
}

int negativeOf(int number) {
    return -number;
}

// ...

    writeln("output: ", filterAndConvert(numbers,&isEven,&negativeOf));

用λ更简单

   writeln("output: ", filterAndConvert(numbers,number => (number % 2) == 0,number => -number));

函数体都可以不写了.
函数指针也可作为类/构的成员.

class NumberHandler {
    Predicate predicate;Convertor convertor;
    //类型.

    this(Predicate predicate, Convertor convertor) {
        this.predicate = predicate;
        this.convertor = convertor;
    }//构造

    int[] handle(const int[] numbers) {
        int[] result;

        foreach (number; numbers) {
            if (predicate(number)) {
                immutable newNumber = convertor(number);
                result ~= newNumber;
            }
        }

        return result;
    }
}

调用

    auto handler = new NumberHandler(&isEven, &negativeOf);
    writeln("result: ", handler.handle(numbers));

λ函数,也叫函数字面量或匿名函数.函数指针可用的地方都可用.
完整语法function return_type(parameters){ /* 操作 */ }
完整语法太冗长了.
返回类型是可以推导的.不带参数时,可以去掉(),

void foo(double function() func) {
    // ...
}

由编译器推导是λ函数还是闭包.
foo({ return 42.42; });,除非函数体内有外围域中变量,否则都是函数.
完整function return_type(parameters) { return expression; }.
变成(parameters) { return expression; }
变成(parameters) => expression.
可解释为给定参数,生成表达式.
单参时:single_parameter => expression
无参时:() => expression
=>中使用{}可能要犯错.

    auto l0 = (int a) => a + 1
    auto l1 = (int a) => { return a + 1; }
    //返回λ的λ.带{}的是一个λ(无参λ)
    assert(l0(42) == 43);
    assert(l1(42)() == 43);    // Executing what l1 returns

D切片是区间.

import std.stdio;
import std.algorithm;

void main() {
    int[] numbers = [ 20, 1, 10, 300, -2 ];
    writeln(numbers.filter!(number => number > 10));//过滤
}

再如:

import std.exception;

int[] binaryAlgorithm(int function(int, int) func,const int[] slice1,const int[] slice2) {
    enforce(slice1.length == slice2.length);
    int[] results;
    foreach (i; 0 .. slice1.length) {
        results ~= func(slice1[i], slice2[i]);
    }

    return results;
}
//调用
import std.stdio;

void main() {
    writeln(binaryAlgorithm((a, b) => (a * 10) + b,
                            [ 1, 2, 3 ],
                            [ 4, 5, 6 ]));
}

闭包

闭包=指针+执行环境的上下文.
闭包也支持d的闭包(closure)
变量在离开定义它的域时,即没了.因而不能返回局部变量地址.

alias Calculator = int function(int);

Calculator makeCalculator() {
    int increment = 10;
    return value => increment + value;    // ← compilation ERROR
}
//上面是函数,编译不过,返回局部变量
alias Calculator = int delegate(int);

Calculator makeCalculator() {
    int increment = 10;
    return value => increment + value;
    //闭包扩展了`局部变量`生命期,即复制了.
}

闭包扩展λ上下文的生命期,这样局部变量生命期延长到与闭包一样长了
闭包使用后,这个变量与闭包一样长了.这样就可按需要修改了.
测试:

    auto calculator = makeCalculator();
    writeln("The result of the calculation: ", calculator(3));

未指定函数闭包时,由编译器决定.如果访问了本地状态,则为闭包.
无参闭包:

int[] delimitedNumbers(int count, int delegate() numberGenerator) {
    int[] result = [ -1 ];//切片
    result.reserve(count + 2);
    foreach (i; 0 .. count) {
        result ~= numberGenerator();//闭包.
    }
    result ~= -1;return result;
}

它需要两个参数来指定首尾间的其他元素.
writeln(delimitedNumbers(3, () => 42));来调用
记住,无参时为(). //简单的

    int lastNumber;
    writeln(delimitedNumbers(
                15, () => lastNumber += uniform(0, 3)));
    //本地变量
    writeln("Last number: ", lastNumber);

对象成员函数作为闭包.
闭包可由指针+执行环境上下文组成,
同样,闭包也可由成员(对象+函数指针)
语法&object.member_function.

import std.stdio;

struct Location {
    long x;long y;
    void moveHorizontally(long step){ x += step; }
    void moveVertically(long step)  { y += step; }
}

void main() {
    auto location = Location();
    writeln(typeof(&location.moveHorizontally).stringof);
}

先打印类型:
void delegate(long step)闭包类型.
注意:&仅用于定义构造闭包.

    auto directionFunction = &location.moveHorizontally;
    directionFunction(3);//按函数方式调用
    writeln(location);

函数指针,闭包,λ是表达式,可用于期望他们的类型的值的地方

 auto location = Location();
    void delegate(long)[] movements =
        [ &location.moveHorizontally,
          &location.moveVertically,
          &location.moveHorizontally ];
    //从对象和成员函数形成的闭包,构成的切片,
    foreach (movement; movements)movement(1);
    writeln(location);

函数+上下文闭包的属性有.funcptr 和 .ptr
ptr为对象,funcptr为函数指针

struct MyStruct {
    void func() {
    }
}

void main() {
    auto o = MyStruct();
    auto d = &o.func;

    assert(d.funcptr == &MyStruct.func);//func
    assert(d.ptr == &o);//ptr
}

可以显式指定

struct MyStruct {
    int i;

    void func() {
        import std.stdio;
        writeln(i);
    }
}

void main() {
    auto o = MyStruct(42);

    void delegate() d;//无效针
    assert(d is null);    // null to begin with
    d.funcptr = &MyStruct.func;
    d.ptr = &o;
    d();
}

参数是闭包.

void log(Level level, lazy string message) {
    if (level >= interestedLevel) {
        writefln("%s", message);
    }
}

// ...

    if (failedToConnect) {
        log(Level.medium,
            format("Failure. The connection state is '%s'.",getConnectionState()));
    }

消息是参,整个format格式,仅在log()里面使用这个参数时才求值,
实际上就是闭包,编译时自动生成.等价于:

void log(Level level, string delegate() lazyMessage) {  // (1)是个返回串闭包
    if (level >= interestedLevel) {
        writefln("%s", lazyMessage());//调用闭包取返回值
    }
}

// ...
    if (failedToConnect) {
        log(Level.medium,delegate string() {
                return format("Failure. The connection state is '%s'.",getConnectionState());
            });//整个表达式是个闭包,并从中返回
    }//因为是表达式,且为`懒`求值.

所以,只有当进入log()块后,才求值,即format这一堆都是等到进了本域后才求值,是个闭包.

import std.stdio;

void foo(double delegate()[] args...) {//一堆相同的
    foreach (arg; args) {
        writeln(arg());     // Calling each delegate
    }
}//

void main() {
    foo(1.5, () => 2.5);    // 'double' passed as delegate,`双精`,可以加一个闭包包装器.
}

可变参数.
问题,就是所有参数类型必须相同.可以看元组如何利用.

import std.stdio;
import std.string;

struct Point {
    int x;int y;

    string toString() const {
        return format("(%s,%s)", x, y);
    }
}

struct Color {
    ubyte r,g,b;
    string toString() const {
        return format("RGB:%s,%s,%s", r, g, b);
    }
}

struct ColoredPoint {
    Color color;
    Point point;

    string toString() const {
        return format("{%s;%s}", color, point);
    }//直接利用串.
}

struct Polygon {
    ColoredPoint[] points;

    string toString() const {
        return format("%s", points);
    }//利用上个构的串
}

void main() {
    auto polygon = Polygon(
        [ ColoredPoint(Color(10, 10, 10), Point(1, 1)),
          ColoredPoint(Color(20, 20, 20), Point(2, 2)),
          ColoredPoint(Color(30, 30, 30), Point(3, 3)) ]);

    writeln(polygon);//类似的
}

结构和类简单给一个格式函数来生成toString函数来表示自己.
构造了大量串,而高级函数仅调用了一次.仅最后一个打印了输出
为了提高性能:void toString(void delegate(const(char)[]) sink) const;,
重载toString带个闭包参数.
将要打印的字符传递给了闭包参数,
闭包来附加这些字符到将要打印输出的串.只一个.
程序员调用std.format.formattedWrite而不是std.string.format,参数,用闭包作为第一个参数(ufcs统调).

import std.stdio;
import std.format;

struct Point {
...
    void toString(void delegate(const(char)[]) sink) const {
        sink.formattedWrite!"(%s,%s)"(x, y);
    }
}

struct Color {
...
    void toString(void delegate(const(char)[]) sink) const {
        sink.formattedWrite!"RGB:%s,%s,%s"(r, g, b);
    }
}

struct ColoredPoint {
...
    void toString(void delegate(const(char)[]) sink) const {
        sink.formattedWrite!"{%s;%s}"(color, point);
    }
}

struct Polygon {
...
    void toString(void delegate(const(char)[]) sink) const {
        sink.formattedWrite!"%s"(points);
    }
}
//其余同上

所有调用提供格式化串为模板参数,来编译时检查.
编译时从而消除重复构造,这样,虽然10个调用,但总只生成1个简单串.
更有效的重载toString需要一个闭包

发布了440 篇原创文章 · 获赞 29 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/fqbqrr/article/details/104589580