【问题标题】:How does WriteLn() really work?WriteLn() 是如何工作的?
【发布时间】:2009-03-06 04:04:37
【问题描述】:

从恐龙时代开始,Turbo Pascal 和现在的 Delphi 都有一个 Write() 和 WriteLn() 过程,它们可以安静地做一些简洁的事情。

  • 参数个数可变;

  • 每个变量都可以是各种类型;您可以提供整数、双精度、字符串、布尔值,并以任意顺序将它们混合在一起;

  • 您可以为每个参数提供额外的参数:

Write('Hello':10,'World!':7); //对齐参数

  • 它甚至以一种特殊的方式出现在代码完成下拉菜单中:
    • 写入 ([var F:File]; P1; [...,PN] )
    • WriteLn ([var F:File]; [ P1; [...,PN]] )

现在我正在输入此内容,我注意到 Write 和 WriteLn 在代码完成下拉列表中没有相同的括号。因此看起来这不是自动生成的,而是有人硬编码的。

不管怎样,我自己能写出这样的程序吗,还是所有这些都是一些神奇的硬编码编译器诡计?

【问题讨论】:

  • 不,不是。它有其局限性,但有时您只需要一种轻量级的写入文件的方式。
  • printfsprintf 和所有那些 C 怪人相比,writeln 是冠军。

标签: delphi arguments


【解决方案1】:

Writeln 是我们所说的编译器“魔术”函数。如果您在 System.pas 中查找,您将找不到一个被声明为您所期望的任何内容的 Writeln。编译器从字面上将其分解为对各种特殊运行时库函数的单独调用。

简而言之,在不修改编译器的情况下,没有办法实现您自己的版本,它与内置的 writeln 执行所有相同的操作。

【讨论】:

  • 在标准帕斯卡中,不,但您可以在具有变体开放数组参数的 Delphi 风格变体中非常接近(请参阅下面的答案)。
  • 是的,您可以使用一些技巧,例如 SysUtils.Format() 使用的“const 数组”技术。 OP 还提到了格式化/对齐参数语法,这显然是不可能仅使用语言的。
  • 如果我没记错的话,还可以选择编写文件驱动程序(在您的 Delphi 程序中),允许您使用 write/writeln 写入自己的接收器,例如到日志记录提供程序。 writeln(logvariable, '你好', World); (我在几年前做过类似的事情,所以我不确定是使用 Borland Pascal、Virtual Pascal 还是某些 Delphi 版本。)
【解决方案2】:

正如艾伦所说,你不能编写自己的函数来完成所有相同的事情。

但是,您可以编写一个执行自定义操作的文本文件驱动程序,并在使用标准 Write(ln) 写入您的文本文件驱动程序时。我们在过去的 DOS 时代就这样做了 :)

(上一条语句上下文中的“驱动程序”只是一段 Pascal 代码,它通过切换系统单元 IIRC 中的指针来连接到系统中。自从我上次使用这个技巧以来已经很长时间了。)

【讨论】:

  • 编写自己的文本文件驱动程序是一个强大的工具。这些年我写了好几篇。我仍然经常使用的一种是写入 tStringList。
  • 使用它的代码如下所示: AssignStringsText ( F, Memo1 . Lines );重写(F); writeln ( F, '正在写入备忘录控件' ); writeln(F,'X的值为',X,'.');关闭文件 ( F );
【解决方案3】:

据我所知,帕斯卡标准不包括可变参数。

话虽如此,IIRC,GNU Pascal 让我们说一下: 程序 Foo(a: Integer; b: Integer; ...);

尝试在您的编译器的语言文档中搜索“变量参数列表”或“一致性数组”。这是后者的示例:http://www.gnu-pascal.de/demos/conformantdemo.pas

正如上一张海报所说,writeln() 是神奇的。我认为问题与堆栈如何在 pascal 函数中组装有关,但是自从我考虑堆栈上的位置以来已经有很长时间了:)

但是,除非您正在编写“writeln”函数(已经编写好了),否则您可能不需要来实现带有可变参数的过程。尝试迭代或递归 :)

【讨论】:

  • 它实际上与堆栈无关。你也不能用 C 语言编写这个函数。即使您排除了对精度和宽度语法的支持,您仍然无法做到这一点,因为无法检测 varargs 列表中的参数类型。
【解决方案4】:

这是神奇的编译器行为,而不是常规过程。不,没有办法编写这样的子程序(不幸的是!)。代码生成解析实际参数及其类型的计数,并在编译时转换为适当的 RTL 调用(例如 Str())。这与经常建议的 array of const (实际上是单个变体数组形式参数)相反,这会导致在运行时执行相同的操作。我发现后来的方法很笨拙,它在一定程度上损害了代码的可读性,并且 Bugland(Borland/Inprise/Codegear/Embarcadero/name it)破坏了变体开放数组构造函数的 Code Insight(是的,我很在意,我使用 OutputDebugString(PChar(Format ('...', [...])))) 和代码完成在那里无法正常工作(或根本不工作)。 因此,模拟魔术行为的最接近的可能方法是声明许多重载子例程(实际上很多,每个特定形式参数类型在特定位置一个)。也可以称其为 kludge,但这是获得可变参数列表灵活性的唯一方法,并且可以隐藏在单独的模块中。

PS:我故意省略了格式说明符,因为语法不允许分号使用 where Str()Write()Writeln( ) 正在接受它们。

【讨论】:

    【解决方案5】:

    是的,您可以在 Delphi 和朋友(例如 free pascal、Kylix 等)中做到这一点,但不能在更“标准”的 pascal 中做到这一点。查找变体开放数组参数,这些参数与以下语法一起使用:

    procedure MyProc(args : array of const);
    

    (已经几年了,我手头没有手册,所以在继续之前检查详细信息)。这为您提供了一个开放数组 TVarData(或类似的东西),您可以从中提取 RTTI。

    但请注意:我认为您无法匹配 x:y 格式化语法(这是特殊的),并且可能必须使用稍微冗长的包装器。

    【讨论】:

    • 嗯,从技术上讲,这是一个参数,不是吗?由于函数/过程在退出时清理了堆栈,因此它需要确切地知道它给出了多少参数。 WriteLn() 是不同的,因为它不是一个真正的过程。
    • @mghie 这曾经被认为是一个很大的区别,但是随着支持柯里化的语言和向量化参数的 VM 的兴起,它并不那么清楚。无论如何,这是实现的属性,而不是语言的属性。任何一种方式都可以实现。
    【解决方案6】:

    大部分已经说完了,但我想补充几点。

    首先您可以使用格式化功能。将几乎任何类型的变量转换为字符串并控制其大小都很棒。虽然它有缺陷:

    myvar := 1;
    while myvar<10000 do begin
      Memo.Lines.Add(Format('(%3d)', [myVar]));
      myvar := myvar * 10;
    end;
    

    生产:

    (  1)
    ( 10)
    (100)
    (1000)
    

    所以尺寸是最小尺寸(就像 :x:y 结构一样)。

    要获得最少数量的变量参数,您可以使用默认参数和重载函数:

    procedure WriteSome(const A1: string; const A2: string = ''; const A3: string = '');
    

    procedure WriteSome(const A1: string); overload;
    procedure WriteSome(const A1: Integer); overload;
    

    【讨论】:

      【解决方案7】:

      你不能用旧的 Pascal 编写自己的 write/writeln。它们是由编译器、格式化、对齐等生成的。这就是为什么一些程序员喜欢 C 语言,甚至是灵活的标准函数,例如printf、scanf,任何有能力的程序员都可以实现。

      如果您倾向于创建比 C 供应商实现的功能更高的功能,您甚至可以为 C 创建一个相同的 printf 函数。它们中没有魔术,您的代码只需要“遍历”变量参数。

      附言

      但正如 MarkusQ 所指出的,Pascal 的一些变体(Free Pascal、Kylix 等)可以促进可变参数。自 DOS 时代以来,我最后一次修补 Pascal,Turbo Pascal 7。

      【讨论】:

        【解决方案8】:

        Writeln 不是基于“const 数组”,而是由编译器分解为各种调用,这些调用将参数转换为字符串,然后调用原语 writestring。 “LN”只是一个将 lineending 写为字符串的函数。 (取决于操作系统)。原语的过程变量(函数指针)是文件类型(Textrec/filerec)的一部分,这就是它们可以定制的原因。 (例如TP中的AssignCrt)

        如果开启 {$I+} 模式,则在每个元素之后调用 iocheck 函数。

        上面的 GPC 结构是无限的 C 开放数组。 FPC(和 afaik Delphi 也一样)也支持这一点,但语法不同。

        procedure somehting (a:array of const);cdecl;

        将转换为与 C、printf 样式的 ABI 兼容。这意味着相关函数(在这种情况下为somehting)无法获取参数的数量,而必须依赖格式字符串解析。所以这与 const 数组不同,它是安全的。

        【讨论】:

        • 在 Delphi 中,cdecl 是 function printf(Format: PChar): Integer; cdecl; varargs;(参见 docwiki.embarcadero.com/RADStudio/en/Procedures_and_Functions)。
        • 自 2.4.0 于 1 月 1 日发布以来,FPC 现在也支持 ISO 样式的 read/writestr(类似于 read/write(ln),但后来改为字符串而不是文件)。 GPC 可能也支持这个
        【解决方案9】:

        虽然不是直接回答您的问题,但我想添加以下评论: 我最近使用 Writeln(...) 语法将一些代码改写为使用 StringList,用 Format(...) 填充“行”,只是简单的 IntToStr(...)、FloatToStr(...) 函数和喜欢。

        这种变化的主要原因是速度的提高。使用 StringList 和 SaveFileTo 比 WriteLn、Write 组合快得多。

        如果您正在编写一个创建大量文本文件的程序(我正在开发一个网站创建程序),这会产生很大的不同。

        【讨论】:

        • 这是在 Delphi 还是 FreePascal 中?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-24
        • 2016-11-13
        • 2017-10-11
        • 2021-10-13
        • 2011-02-24
        相关资源
        最近更新 更多