21

use使用带有语句的模块或带有语句的隔离文件之间有什么实际区别include?我的意思是,如果我有一个在整个程序中经常使用的子例程:我什么时候或为什么应该将它放在一个模块中,或者只是将它写在一个单独的文件中,并将它包含在程序的每个其他需要它的部分中用过的?

另外,将所有打算进入模块的子例程编写在单独的文件中并include在模块内部使用是一种好习惯吗?特别是如果子例程中的代码很长,以便更好地组织代码(这样所有子例程都打包在 mod 中,但如果我必须编辑一个,我不需要经过迷宫般的代码)。

4

3 回答 3

28

两者之间的概念差异映射到非常显着的实际差异。

INCLUDE 行在源代码级别运行 - 它完成简单的(“哑”)文本包含。在包含行中没有对“文件名”的任何特殊处理器解释(不要求它实际上是一个文件)的情况下,完整的源代码可以很容易地由程序员手动拼接在一起并提供给编译器,没有什么区别- 在源的语义中如此。包含的源代码没有孤立的真正解释 - 它的含义完全取决于引用包含的源代码的包含行出现的上下文。

模块在程序的更高实体级别上运行,即在编译器正在考虑源实际描述的事物的级别上。一个模块可以独立于其下游用户进行编译,一旦它被编译,编译器就会确切地知道该模块可以为程序提供什么。

通常,使用包含行的人希望做的是模块实际设计要做的事情。

示例问题:

  • 因为实体声明可以分布在多个语句中,包含的源描述的实体可能不是您所期望的。考虑包含以下来源:

    INTEGER :: i

    孤立地看,这似乎将名称声明i为整数标量(或者可能是函数?谁知道!)。现在考虑包含上述内容的以下范围:

    INCLUDE "source from above"
    DIMENSION :: i(10,10)

    i现在是二阶阵!也许你想让它成为一个指针?一个可分配的?一个虚假的论点?也许这会导致错误,或者它可能是有效的来源!将隐式输入混入其中,以真正增加潜在的乐趣。

    在模块中定义的实体是由模块“完全”定义的。可以更改特定于使用范围的属性(VOLATILE、可访问性等),但基本实体保持不变。名称冲突被明确指出,并且可以通过 USE 语句中的重命名子句轻松解决。

  • Fortran 对语句顺序有限制(规范语句必须在可执行语句之前,等等)。包含的源代码也受到这些限制,同样是在包含点的上下文中,而不是在源定义点的上下文中。

    语句函数定义(规范部分)和赋值语句(可执行部分)之间的源歧义很好地混合在一起,以获得一些完全模糊的错误消息,或者更糟糕的是,编译器默默地接受错误代码。

    引用模块的 USE 语句出现在哪里是有要求的,但实际模块程序单元的来源完全独立于其使用点。

  • 想要在相关过程中共享一些全局状态并且您想使用包含?让我向您介绍常见的块以及序列关联的相关底层概念......

    序列关联是早期底层 Fortran 处理器实现的不幸渗入,它容易出错、不灵活、反优化不合时宜。

    模块变量使公共块及其相关的弊端完全没有必要。

  • 如果您使用的是包含行,请注意您实际上并未包含常用过程的源代码(第一段中的建议只会导致编译器出现大量语法错误)。您通常会做的是包含描述过程接口的源代码。对于任何重要的过程,描述接口的源代码都不同于过程的完整源代码——这意味着您现在需要维护同一事物的两个源代码表示。这是一个容易出错的维护负担。

    如前所述 - 编译器自动获得模块过程接口的知识(编译器知识是“显式”的,因为它实际上看到了过程的代码 - 因此称为“显式接口”)。程序员无需再做任何事情。

    上述的一个结果是,除非有很好的相反理由(可能存在循环或过度广泛的依赖关系),否则根本不应该使用外部子程序- 基本起点应该是把所有东西都放在一个模块或主程序中程序。

其他张贴者提到了模块的源代码组织优势——包括能够将相关过程和其他“东西”组合到一个包中,并控制内部实现细节的可访问性。

根据问题的第二段,我接受 INCLUDE 行的有效使用-大型模块的大小变得笨拙。F2008 通过子模块解决了这个问题,这也带来了许多其他好处。一旦它们得到广泛支持,就应该放弃包含行的解决方法。

第二个有效用途是克服语言对通用编程技术(C++ 中提供的模板)缺乏支持的问题——即,操作中涉及的对象类型可能会有所不同,但描述对这些技术做什么的标记序列对象本质上是一样的。在语言解决这个问题之前可能还需要十年左右的时间。

于 2013-03-27T19:55:31.173 回答
9

将过程放入模块并使用这些模块使过程的接口变得明确。它允许 Fortran 编译器检查调用中的实际参数和过程的虚拟参数之间的一致性。这可以防止各种程序员错误。Fortran >=90 的某些“高级”功能也需要显式接口;例如,可选或关键字参数。如果没有显式接口,编译器将不会生成正确的调用。仅仅包含一个文件并不能提供这些优势。

于 2013-03-27T15:24:03.797 回答
4

MSB 的回答很好,可能是更喜欢模块而不是包含的最重要原因。我想补充一些想法。

如果这对您很重要,那么使用模块可以减少编译后的二进制文件大小。一个模块被编译一次,当你编译use它时,你象征性地加载该模块以使用代码。当您include创建一个文件时,您实际上是将新代码插入到您的例程中。如果你使用include很多,它可能会导致你的二进制文件很大并且还会增加你的编译时间。

您还可以通过巧妙地使用模块中的公共和私有函数以及用户定义的类型,使用模块来伪造 Fortran 90 中的 OOP 样式编码。即使您不想这样做,它也提供了一种对逻辑上属于一起的函数进行分组的好方法。

于 2013-03-27T18:28:51.183 回答