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
需要一个闭包
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现