这篇,我们先从进入官网的例子开始见识下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可能范围提案会通过。这个例子里用到: map, each。
std.conv : 类型转换,用到: to 。
std.functional : 函数模块,用到:pipe 。
std.math : 数学模块, 用到 : round 。
std.regex: 正则模块, 用到:ctRegex,replaceAll。
std.stdio:标准输入输出模块, 用到: stdin,writeln。
然后是下一句:
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
谢谢分享,受用了
赞一个。。。。。。