不如D:2)官网主页例子解析

这篇,我们先从进入官网的例子开始见识下D的强大。

此篇不是入门介绍,没有D基础,不要深究里面的实现细节,当开阔眼界即可。

进入D官网,就有一个例子,那个例子加上注释也就17行代码, 真正main函数里就一句代码,但是却完成了:从控制台按行读取,然后正则提取可能是浮点数的数,把其替换为四舍五入后的整数,然后在输出。

其代码:

 // Round floating point numbers
import std.algorithm, std.conv, std.functional,
    std.math, std.regex, std.stdio;

alias round = pipe!(to!real, std.math.round, to!string);
static reFloatingPoint = ctRegex!`[0-9]+\.[0-9]+`;

void main()
{
    // Replace anything that looks like a real
    // number with the rounded equivalent.
    stdin
        .byLine
        .map!(l => l.replaceAll!(c => c.hit.round)
                                (reFloatingPoint))
        .each!writeln;
}

main 函数里一句话,实现了那么多功能,可见D的表达能力是多么强大。下面我们就分析下这段强大的代码,从这个例子开始我们进入D的世界。当然,这段代码理解对于不熟悉D来说还是有点困难的,也不用这一次就搞懂,希望等“不如D”系列基本结尾的时候,在回来看,一眼就看懂了。

代码解析:

先从第一句有效代码开始:

import std.algorithm, std.conv, std.functional, std.math, std.regex, std.stdio;

这句话是加载标准库中依赖的模块,而且一次导入了6个模块:

import 是个关键字。D是基于module(模块),import 是用来导入其他模块符号的,可以制定符号,也可以不指定, 不制定则导入此模块下可见的全部符号。

std.algorithm : 算法模块,D的算法基于range(范围的),不是像cpp那样基于迭代器,cpp20可能范围提案会通过。这个例子里用到: mapeach

std.conv : 类型转换,用到: to

std.functional :  函数模块,用到:pipe

std.math :  数学模块, 用到 : round

std.regex: 正则模块, 用到:ctRegexreplaceAll

std.stdio:标准输入输出模块, 用到: stdinwriteln

然后是下一句:

alias round = pipe!(to!real, std.math.round, to!string);

alias :  D的关键字:一种符号可以被声明为另一种符号的“别名(alias)”。这里值值 round符号现在代表pipe!(to!real, std.math.round, to!string)这个模板了。(注:类似Cpp中#define和typedef的合体,此比喻不严谨,只是帮助理解)。

pipe!(to!real, std.math.round, to!string) : pipe是个模板,在D中!是模板实例化符号,其转为cpp类似于:pipe<to!real, std.math.round, to!string>。 这个模板是依序管道传送函数。把一个函数的参数作为参数,然后每个参数的返回值作为下个函数的参数,对于参数名为arg,这句话的函数调用相当于:to!string(std.math.round(to!real(arg)))

to!real : to 也是个模板,to的作用是执行类型转换(不是类型强转), 这句是把参数转换为real(实数)类型。

std.math.round :  标准库中计算四舍五入的函数

to!string :  转换为string

下面一句是个静态变量的定义:

static reFloatingPoint = ctRegex!`[0-9]+\.[0-9]+`;

定一个可以编译期匹配的正则表达式。编译的时候就会对正则处理。 static 是个关键字,不用多介绍了吧?只是,没有定义类型,你发现没?

这是利用D强大的类型推导能力,其实reFloatingPoint的类型是: StaticRegex!`[0-9]+\.[0-9]+`。

接下来是main函数的定于, 函数定义部分我们就略过吧,直接去看运行的主角:

stdin .byLine .map!(l => l.replaceAll!(c => c.hit.round) (reFloatingPoint)) .each!writeln;

stdin : 标准输入流。 这是个全局变量,定义在stdio模块中,类型是std.stdio.File。

byLine: 是 std.stdio.File的成员函数,返回一个范围,其元素是File的一行。

map!(l => l.replaceAll!(c => c.hit.round) (reFloatingPoint)) :map是一个模板, 其作用是对范围的每个元素都执行其模板参数的操作。

=> :是lambda表达式定义的符号,形参=>函数体, 返回值是自动推导的

replaceAll : 是模板函数,声明为 R replaceAll(alias fun, R, RegEx)( R input,RegEx re ) 类似于cpp的template<typename  fun, typename R, typename RegEx> R replaceAll( R input,RegEx re )。, 看声明就简洁好多。这里实例化后是:string replaceAll!(string function(Captures!string) fun, string,StaticRegex!`[0-9]+\.[0-9]+`). 其作用是对input进行正则匹配,每次的匹配都去调用fun函数,匹配结果作为参数,并把函数的返回值替换原来元素中匹配到的内容。

c => c.hit.round 这个lambda表达式是也就相当与函数:

string function(Captures!string m){
        to!string( std.math.round( to!real( m.hit ) ) ); // hit是 StaticRegex 的成员函数,返回匹配到内容。
}

那么我们应该就应该明白了:l => l.replaceAll!(c => c.hit.round) (reFloatingPoint) 也是个lamba表达式,其相当与的函数我就不列举了。

只是到此,应该还有个疑问,replaceAll函数不是两个参数?为什么这里就一个,还有l不是应该作为replaceAll的参数,怎么变成replaceAll成l的成员函数了?

这里就和上面调用map一样,也和下面each的调用一个,D语言的一个特性:UFCS(Uniform Function Call Syntax) .

each!writeln : each也是个模板,其作用是遍历范围。 这句话的意思对每个元素都进行输出。

输入输出:

Dpaste 上运行和输出:https://dpaste.dzfl.pl/573770e28de7

 

《不如D:2)官网主页例子解析》有2个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注