`
canonical
  • 浏览: 368198 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

D语言与tpl之编译期动作

阅读更多
   最近D语言发布了1.0版,这是一个由编译器开发者所设计的编译语言,语法类似C++, 但是针对C++的弊病作了大量修正,并增加了很多现代特征,其中还是有一些新意在其中的。http://www.digitalmars.com/d/overview.html 我对其比较感兴趣的部分是D语言明确提出的编译期运行的概念。虽然C++让大众了解了meta programming技术,很多人因此认为meta programming的威力在于类型演算,但是在我看来meta programming的真正作用在于在编译期可以“动态”产生或调整代码结构。它依赖于类型是因为我们只能利用类型来承载一些额外信息, 这无疑也是对于类型的一种滥用。在D语言中template所接受的不仅仅是类型, 或者类型的类型,它可以是任何符号.
  template MixInAttr(T, char[] name){ 
     mixin(" T _" ~ name ~";");
     mixin(" T "~name~"(){ return _"~name~"; }");
     mixin(" void "~name~"(T v){ _"~name~" = v;}");
  } 
 
  template MixInOtherAttr(T, T v){
    T _otherAttr = v;
   
    int otherAttr(){ return _otherAttr; }
  }
 
  const int myValue = 1;
 
  int addFunc(int x){
    return x + 1;
  }
  
  class MyTest{ 
     mixin MixInAttr!(int, "myAttr"); 
     mixin MixInOtherAttr!(int,4);
    
     static if(addFunc(myValue) 〉 0){
        int testFunc(){ return 5;}
     }
  }
 
  void main(){ 
     auto t = new MyTest;
     t.myAttr = 3;
     int v = t.myAttr;   
     v = t.otherAttr;
     t.testFunc();
  }  
  不过现在编译期运行无疑是一个正在探索的方向, 在D语言中的使用方式并不是非常理想的, 在形式和功能实现上都存在着重大改进的可能. 
 
  在witrix平台的tpl模板语言中,我们也引入了编译期运行的概念,只是tpl的语言特征非常简单再加上xml格式特殊的规范性,编译期运行在tpl中的实现是非常直接和明确的.在tpl中定义了<cp:run〉标签,它的内容在编译期运行, 输出结果(xml字符串)将被继续编译. 在<cp:run〉中可以执行任何有效的tpl代码, 它们在编译期状态空间中运行. 例如
 〈cp:run〉
   〈c:forEach var="_x" items="${tagBody.children()}"〉
       bla bla bla...
   〈/c:forEach〉
 〈/cp:run〉
在EL表达式中我们通过cp前缀实现普通调用和编译期调用的混杂.
  〈cp:const class="mypkg.MyConstantClass"/〉
  〈c:eval expr="${myFunc(cp:const.MY_CONST, commonVar, cp:funcInCompileTime())}" /〉
  〈cp:compile test="${exprInCompileTime}"〉
     any tpl ...
  〈/cp:compile〉

 其实当我们把改进的目光开始放到编译期的结构方面的时候, 可以做的工作是非常多的. 例如从结构上看, 继承策略相当是类结构的一种一阶替换策略.
  类B(methodB) extends 类A(methodA, methodB)  ==〉 类B(methodA[inA], methodB[inB])
如果我们放弃概念层面上的纠缠,而如同template那样只关注语法结构, 则可以发展出更加复杂的替换操作.
  NODE_B(                              NODE_A(                                    NODE_B(
    SUB_NODE_A1        〉〉    SUB_NODE_A1             ==〉    SUB_NODE_A1
      SUB_NODE_B11                   SUB_NODE_A11                         SUB_NODE_B11
                                                         SUB_NODE_A12                          SUB_NODE_A12
  )                                                  )                                                  )
C++中及D语言中的template技术在某种意义上相当于是在代码结构中预留了空洞, 可以实现跨越类结构的替换填充. 只是D语言挖洞的能力无疑比C++强大的多. 
  如果我们考察的再细致一些, 就会发现两个语法节点的融合也是存在多种策略的.
  B 〉〉 A ==〉 (remove_A, replace_A, insert_B_before_A, insert_B_after_A, insert_B_into_A, insert_A_into_B)
而通过insert_A_into_B/insert_B_into_A策略既可实现所谓的AOP intercept操作http://canonical.iteye.com/blog/34941 在witrix平台的BizFlow技术中, 我们通过高级的结构融合策略实现为任意实体引入流程支持. 最简单的情况下我们所需要做的工作只是增加一个语法元素
 〈bizflow extends="myflow"〉
引入流程意味着对界面的一系列修正, 例如增加,删除某些菜单, 同时要增加很多流程相关函数, 对某些函数实施AOP操作, 在各处引入权限控制等等, 这些都可以通过extends操作引入.

  在传统上, 编译器结构是固化的, 它所编译的代码结构是固化的, 而它所编译出的代码也是固化的, 但是现代语言的各种发展正致力于在各个层面引入动态性. 编译期运行抑或是编译期结构操纵技术的引入是在一个层面上打开了潘多拉魔盒. 它让我们看到了前所未有的技术可能性, 但它无疑也是危险的,难以控制的. 我们目前的做法是尽量克制,只在局部使用, 但我相信它在很多语言中都是可以在理论上严格定义,并精确实现的, 我们最终也能够找到合适的形式来约束它的破坏性. 在这个过程中我们需要引入更多的物理化的视角。
分享到:
评论
21 楼 hyf 2007-05-08  
一种方法是忘掉他,
在你觉得很需要他的时候,一般都是恰当时机。
20 楼 weixiao 2007-05-08  
hyf 写道
七猫 写道
hyf 写道
七猫 写道
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。

两者都需要。
象c++Ox中的一条关于引入lambda的建议,如果不接受。像boost这样搞下去也没有活路
语法有没有,大家可以绕道走。
但没有库,就得自己写了。

其实现有的语法已经能满足大部分人需要了,对于所谓的原编程之类的,我很少看到大量使用,而且这种风格的代码的阅读性和可维护性我想大家心里都有数的。

对于要完成一个项目,库当然重要。
但依靠语法的逻辑抽象能力,我可以做到更细致的复用设计,有些是功能库做不到的。

MP之所没有大量使用,
一来是没有这种习惯,所以就没有需要
二来是现在的语法导致这样做 复杂、鸡肋

其实,现在的C++只要适当使用,不会降低可读性和可维护性。

“只要适当使用”,几个字,包含了多少血和泪。一个资质中上的程序员,没个三几年时间,如何能够“适当使用”?
19 楼 qiezi 2007-03-09  
qiezi 写道
hyf 写道
假设变参模板用TArgs表示
除非你有一种方法能把类型转化成字符串,实现
mixin(str(head(TArgs)) ~ " item" ~ i;

你说D语言的mixin表达式要求参数字符串必须是一个完整的表达式
head(TArgs) mixin("item" ~ i); 应该不行吧


等待你的例子。

出差在外,上网不方便,这边网吧连USB也禁了。已经完成了,加上空行和整个文件大概60行左右,包括一个简单的使用测试。

完整代码:
import std.stdio;
import std.metastrings;

private template attr_accessor(T, char[] name){
	mixin("
		private T _" ~ name ~ ";
		public T " ~ name ~ "(){
			return _" ~ name ~ ";
		}
		public void " ~ name ~ "(T v){
			_" ~ name ~ " = v;
		}"
	);
}

template attrs(T){
	mixin attr_accessor!(T, "item1");
}

template attrs(T, Args ...){
	mixin attrs_without_names!(0, T, Args);
}

template attrs(T, char[] s, Args ...){
	mixin attrs_with_names!(0, T, s, Args);
}

// 这个模板参数n是为了避免mixin递归错误。
private template attrs_with_names(int n, T, Args ...){
	static if (Args.length){
		mixin attr_accessor!(T, Args[0]);
		mixin attrs_with_names!(n + 1, T, Args[1..$]);
	}
}

private template attrs_without_names(int n, Args ...){
	static if (Args.length){
		mixin attr_accessor!(Args[0], "item" ~ ToString!(n + 1));
		mixin attrs_without_names!(n + 1, Args[1..$]);
	}
}

void main(){
	class Foo{
		mixin attrs!(int, "aaa");
		mixin attrs!(int, float, char[]);
	}

	auto foo = new Foo;
	foo.aaa = 1;
	foo.item1 = 3;
	foo.item2 = 5;
	foo.item3 = "abc";

	writefln(foo.aaa);
	writefln(foo.item1);
	writefln(foo.item2);
	writefln(foo.item3);
	writefln(typeid(typeof(foo.tupleof)));
}
18 楼 canonical 2007-03-07  
to Elminster:
  meta programming对于C++而言是基于类型运算的,或者你把meta programming等价于C++中的类型运算。而在我的概念中,meta programming意味着对编译期运算能力的开发。在tpl语言中,编译期运算是个明确的概念,而它是符合一定的形式要求的,但是并不是类型运算,实际上在tpl中通过其他渠道来传递信息。
17 楼 hyf 2007-03-07  
看了你attr_accessor模板,明白你的意思了
16 楼 qiezi 2007-03-07  
hyf 写道

我还以为可以做到 mixin(typeof(T).fullname() ~ ...
qiezi 写道
template foo(Args ...){
    static if (is(Args[0] == char[]))
        ...
}

这个判断总是不能为真,不知是D的问题还是我使用上的问题,我改用匹配的方法:
template attrs(T, Args ...){
}

template attrs(T, char[] s, Args ...){
}

如果是手工枚举所有类型的话就没没法用于自定义类型了。


这个并不是手工枚举所有类型,而是判断第2个模板参数,如果是字符串值,就把它和后面的参数作为属性的名称。否则就把所有模板参数当作类型,依次定义item1, item2 ...

全部用字符串来生成是最差的做法,即便能够实现,实现起来也不方便。
15 楼 hyf 2007-03-07  

我还以为可以做到 mixin(typeof(T).fullname() ~ ...
qiezi 写道
template foo(Args ...){
    static if (is(Args[0] == char[]))
        ...
}

这个判断总是不能为真,不知是D的问题还是我使用上的问题,我改用匹配的方法:
template attrs(T, Args ...){
}

template attrs(T, char[] s, Args ...){
}

如果是手工枚举所有类型的话就没没法用于自定义类型了。
14 楼 qiezi 2007-03-07  
hyf 写道
假设变参模板用TArgs表示
除非你有一种方法能把类型转化成字符串,实现
mixin(str(head(TArgs)) ~ " item" ~ i;

你说D语言的mixin表达式要求参数字符串必须是一个完整的表达式
head(TArgs) mixin("item" ~ i); 应该不行吧


等待你的例子。

出差在外,上网不方便,这边网吧连USB也禁了。已经完成了,加上空行和整个文件大概60行左右,包括一个简单的使用测试。

对字符串的判断似乎有问题:
template foo(Args ...){
    static if (is(Args[0] == char[]))
        ...
}

这个判断总是不能为真,不知是D的问题还是我使用上的问题,我改用匹配的方法:
template attrs(T, Args ...){
}

template attrs(T, char[] s, Args ...){
}

来进行区分,在里面调用一个递归的mixin模板,把各个成员生成出来。在这个mixin模板中,不断调用attr_accessor模板(我的blog中有),并递归地调用同一个模板,把剩余参数传递过去,递归是模板中使用非常频繁的技术。有一点要注意的是,递归mixin在D中是不允许的,但可以传递一个多余的整数模板参数避开这个问题。

并不需要把类型转成字符串,当然如果想做也能做成,参考pyd里面的实现代码,我的blog中有一篇讲了这个,只是不知道是否适用所有类型。

最后的调用方式如下:

class Foo{
  mixin attrs!(int, "foo");
  mixin attrs!(float, "bar");
  mixin attrs!(char, char[], float, short);
}

auto foo = new Foo;
foo.foo = 1;
foo.bar = 3.0f;
foo.item1 = 'c';
foo.item2 = "cc";
foo.item3 = 5.0f;
foo.item4 = 7;
13 楼 Elminster 2007-03-07  
<br/>
<strong>canonical 写道:</strong><br/>
<div class='quote_div'>   最近D语言发布了1.0版,这是一个由编译器开发者所设计的编译语言,语法类似C++, 但是针对C++的弊病作了大量修正,并增加了很多现代特征,其中还是有一些新意在其中的。<a href='http://www.digitalmars.com/d/overview.html '>http://www.digitalmars.com/d/overview.html </a>我对其比较感兴趣的部分是D语言明确提出的编译期运行的概念。虽然C++让大众了解了meta programming技术,很多人因此认为meta programming的威力在于类型演算,但是在我看来meta programming的真正作用在于在编译期可以“动态”产生或调整代码结构。它依赖于类型是因为我们只能利用类型来承载一些额外信息, 这无疑也是对于类型的一种滥用。在D语言中template所接受的不仅仅是类型, 或者类型的类型,它可以是任何符号. <br/>
...</div>
<div class='quote_div'/>
<div class='quote_div'/>
<div class='quote_div'/>
<p class='quote_div'>我很奇怪你为什么认为你下面列举的这些不是对类型的计算。meta-programming 所带来的新东西,就是我们可以通过一些类型或是编译时常量为参数,让编译器在编译的时候生成某些我们想要的类型。D 的 mixin 也好,C++ 的 Policy Based Design 和 Expression-Template 也好,都是这种新的表达能力的体现。</p>
12 楼 hyf 2007-03-06  
假设变参模板用TArgs表示
除非你有一种方法能把类型转化成字符串,实现
mixin(str(head(TArgs)) ~ " item" ~ i;

你说D语言的mixin表达式要求参数字符串必须是一个完整的表达式
head(TArgs) mixin("item" ~ i); 应该不行吧


等待你的例子。
11 楼 qiezi 2007-03-06  
hyf 写道
  template MixInAttr(T, char[] name){ 
     mixin(" T _" ~ name ~";");
     mixin(" T "~name~"(){ return _"~name~"; }");
     mixin(" void "~name~"(T v){ _"~name~" = v;}");
  }  


问几个问题,
我要MixInAttr!(int, ["myAttr1", "myAttr2", ..]); 
可以?

MixInAttr!(int, string, float);
得到 int item1; string item2; float item3;
可以?


完全可以,这有两个方面的问题,一是变参模板,二是对于模板参数是类型还是字符串的判断,这在D里面都是轻而易举。现在没有时间,有空我写个简单的例子。

hyf 写道

要真是那么追求code generate的能力,那就索性把整个D语言弄进编译时执行算了.

D语言目前正在把更多的东西加进编译时执行。D语言模板到现在已经和C++大不相同了,编译期计算也是为了解决以前编译时和执行时要写两套代码而作的。
hyf 写道

况且, 你展示的这些代码, 让我想起MACRO, C++不推荐使用MACRO就是因为它可以随意产生代码, 类型不安全, 可见性(可读)差.

D语言的mixin表达式要求参数字符串必须是一个完整的表达式,这和MACRO完全不同。MACRO也不是C++语言的一部分,如果你愿意,你也可以把C宏用在任何语言中,调用一个预处理器程序进行处理。这些东西都是强大的工具,会用就能用好,不会用不用它就是了。


hyf 写道

我觉得C++的MP设计非常适合,
在C++0x出来之后, 就可以更好的应付MP的复杂性. (现在已经够复杂了)
你可以去看看OpenC++, 他的元编程在语法树上做文章, 灵活性最强了.  是啥下场?

在这个世界上有好多工具可以做代码生成, (生成我还可以看到整个结果, 怎么不对一目了然)
在语言中做这种事情, 一来IDE智能提示废了, 而且没有编译期调试还玩不转.

D并不是做了这个就扔了那个,你不使用编译期执行,它还是一种类似C++的语言,一个项目中编译期执行的部分也不会太多。IDE的智能提示和调试这两种东西并不是人人都必须要用,编译期执行也还是可以写单元测试的。
10 楼 canonical 2007-03-05  
实际上因为C++的复杂性,以至于设计师很难有余裕在语法上再做创新。C++对于计算是够用的,但是对于描述性内容并不适当。MP其实很有用,只是受技术限制一般人想不到它的用处而已。
9 楼 hyf 2007-03-05  
注意,某些观点我跟你一致。
我不是说库不重要,也不是想证明Boost可读性好。

我想说语法的改进一样很重要,他的能力直接影响我们如何设计库,如何复用代码。
而这次C++改良正是要降低这些复杂性。
8 楼 hyf 2007-03-05  
我是说适当使用,而不是所有。

我在项目中对引入外部的模板库很谨慎,即使是STL也不是全部用上。

Boost来和Qt这些应用库很难比较,大家的方向不一样。
比方,Qt为了可用性,signal/solt是外部编译器做代码生成的。
7 楼 七猫 2007-03-05  
我觉得比较boost和qt的原代码可以得到一些启示。

到底哪种代码可读性和可维护性更好。
6 楼 hyf 2007-03-05  
七猫 写道
hyf 写道
七猫 写道
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。

两者都需要。
象c++Ox中的一条关于引入lambda的建议,如果不接受。像boost这样搞下去也没有活路
语法有没有,大家可以绕道走。
但没有库,就得自己写了。

其实现有的语法已经能满足大部分人需要了,对于所谓的原编程之类的,我很少看到大量使用,而且这种风格的代码的阅读性和可维护性我想大家心里都有数的。

对于要完成一个项目,库当然重要。
但依靠语法的逻辑抽象能力,我可以做到更细致的复用设计,有些是功能库做不到的。

MP之所没有大量使用,
一来是没有这种习惯,所以就没有需要
二来是现在的语法导致这样做 复杂、鸡肋

其实,现在的C++只要适当使用,不会降低可读性和可维护性。
5 楼 七猫 2007-03-05  
hyf 写道
七猫 写道
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。

两者都需要。
象c++Ox中的一条关于引入lambda的建议,如果不接受。像boost这样搞下去也没有活路
语法有没有,大家可以绕道走。
但没有库,就得自己写了。

其实现有的语法已经能满足大部分人需要了,对于所谓的原编程之类的,我很少看到大量使用,而且这种风格的代码的阅读性和可维护性我想大家心里都有数的。
4 楼 hyf 2007-03-05  
七猫 写道
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。

两者都需要。
象c++Ox中的一条关于引入lambda的建议,如果不接受。像boost这样搞下去也没有活路
3 楼 七猫 2007-03-05  
我只是认为C++开发者更需要的是一大堆库,而不是一些语法上的改变。
2 楼 canonical 2007-03-04  
关于D语言的语法问题建议自己去试验, 我并不精通。

关于tpl的编译期运行能力问题,我只能说我们需要这些能力。我们也基于这些能力作了很多工作。 并不是简单的“把整个D语言弄进编译时执行"或者 使用  MACRO就可以了。 这里的关键是如何定义合适的形式约束。OpenC++的失败是它自己的问题,而不是meta programming的问题。 就如同EJB的失败不意味着它所对应的理想的失败。

相关推荐

    tpl存储序列化

    **存储序列化(TPL)详解** 存储序列化是一种将数据结构或对象的状态转换为可存储或传输的形式的技术。在计算机科学中,它通常用于将内存中的数据保存到磁盘上,或者在网络上传输数据。TPL(Template Packet ...

    TPL0401数字电位器源码

    4. **C语言编程**:源码通常会用C语言编写,因为这是嵌入式系统最常用的编程语言之一。理解基本的C语法、变量声明、函数调用、条件语句和循环是必不可少的。 5. **中断和定时器**:在某些应用场景中,STM32可能需要...

    TPL521中文资料 光耦

    TPL521 中文资料 光耦 TPL521 是一款可控制的光电耦合器件,广泛应用在电脑终端机、可控硅系统设备、测量仪器、影印机、自动售票、家用电器等领域,旨在实现信号传输的隔离,以增加安全性、减小电路干扰、减化电路...

    Laravel开发-tpl

    8. **与前端框架集成**:随着Vue.js等前端框架在Laravel中的广泛使用,`tpl`可能提供了更好的与这些框架集成的方式,使开发者能更方便地在模板中嵌入和管理Vue组件。 总的来说,“Laravel开发-tpl”是一个关于如何...

    stm32控制tpl0202数控电位器

    在这个系统中,STM32作为微控制器,通过SPI(Serial Peripheral Interface)总线与TPL0202数控电位器进行通信,从而实现对电位器的数字控制。 首先,我们要了解STM32。STM32是意法半导体(STMicroelectronics)推出...

    基于PHP的tmd_tpl国产PHP模板引擎.zip

    总的来说,tmd_tpl模板引擎以其易用性、灵活性和高性能的特点,成为了国产PHP开发者的首选之一。通过了解和掌握tmd_tpl,开发者可以更高效地组织代码,提高项目的可维护性和开发效率。在实际项目中,结合具体需求,...

    C# TPL 同步实例代码

    C# TPL(Task Parallel Library)是.NET Framework中用于并行编程的重要库,它使得开发者可以轻松地在多核或多处理器系统上实现并发和并行处理。同步在多线程和并行编程中扮演着至关重要的角色,确保了数据一致性、...

    TPL与C语言的混合编程方法研究.pdf

    1. TPL(测试过程语言)的基本概念及其与传统测试语言ATLAS的不同之处。 2. TPL作为一种嵌入式语言在自动测试系统中的应用,其与传统语言的区别与优势。 3. TPL与C语言混合编程的实现方法,以及这种编程方式对测试...

    tpl7407l.pdf

    • 与 与 1.8V 至 至 5.0V 微 微控 控制 制器 器和 和逻 逻辑 辑接 接口 口兼 兼容 容 这个器件包含 7 个特有高压输出的 NMOS 晶体管,这 • 用 用于 于感 感应 应反 反冲 冲保 保护 护的 的内 内部 部自 自振 振荡 ...

    gulp-wind-tpl2js:将模板编译为js字符串,并包装为amdcmd模块

    gulp-wind-tpl2js 将模板编译为js字符串,并包装为amd / cmd模块。安装使用NPM安装软件包: npm install gulp-wind-tpl2js 用法 var tpl2js = require ( 'gulp-wind-tpl2js' ) ;gulp . task ( 'tpl2js' , function ...

    TPL7407L_16数据手册

    随着电子技术的飞速发展,对于能够适应多种应用场合的驱动器件...无论是在可靠性、效率还是设计灵活性方面,TPL7407L都充分满足了现代电子设计的高标准要求,是设计者在进行电源管理与驱动解决方案设计时的理想选择。

    TPL Performance Improvements in .Net 4.5

    文章指出,在.NET 4.5的开发过程中,微软承认了基于继续(continuation-style)编程与分叉-连接编程同等重要,很大程度上是由于.NET 4.5中async/await支持的出现(它是建立在继续之上的)。在.NET 4.5的开发中,微软...

    TPL0501EVM数字电位器.pdf

    TPL0501EVM由德州仪器公司设计,用于与基于MSP430的低成本LaunchPad平台配合使用。EVM具备一个预先编程的MSP430G2553微控制器,该微控制器应插入LaunchPad上的DIP插座中。用户可以通过简单的图形用户界面(GUI)来...

    前端开源库-dot-tpl-loader

    点模板文件(.tpl)是一种常见的模板语言格式,它将HTML结构与逻辑分离,使得代码更易于阅读和维护。dot-tpl-loader支持这种格式,使得前端开发者能够利用熟悉的模板语法编写视图层,同时结合Webpack的强大功能,...

    关于tpl.js的一个demo

    这里我们关注的是`tpl.js`,一个专为JavaScript设计的模板引擎。`tpl.js`允许开发者创建动态的HTML片段,通过将数据注入预先定义的模板来生成最终的视图。这个“关于tpl.js的一个demo”提供了实践`tpl.js`功能的机会...

    TPL-E3_win7驱动

    标题“TPL-E3_win7驱动”表明这是一份专为TPL-E3网卡设计的Windows 7操作系统驱动程序。在IT领域,驱动程序是计算机硬件与操作系统之间的重要桥梁,允许操作系统识别并有效控制硬件设备。TPL-E3可能指的是某个特定...

    tpl5010 (1).pdf

    TPL5010 纳瓦级计时器是一款超低功耗计时器,其看门狗功能专为占空比、电池供电型 应用 (比如物联网中的应用)中的系统唤醒功能而设计。

    WinHex tpl模板.rar

    tpl模板是WinHex中的一个重要概念,它允许用户预定义一系列的查看和分析设置,以便在处理特定类型的数据时快速应用。本压缩包包含了一系列的WinHex模板文件,用于帮助用户更高效地解析和理解各种文件格式。 1. **...

    php 网站tpl模板以及解析类

    在PHP开发中,TPL模板是一种常见的页面视图分离技术,它的主要目的是为了将HTML结构与PHP代码分开,提高代码的可读性和维护性。这里我们主要探讨PHP网站中TPL模板的使用及其解析类的工作原理。 TPL模板允许开发者...

Global site tag (gtag.js) - Google Analytics