两者之间的概念差异映射到非常显着的实际差异。
INCLUDE 行在源代码级别运行 - 它完成简单的(“哑”)文本包含。在包含行中没有对“文件名”的任何特殊处理器解释(不要求它实际上是一个文件)的情况下,完整的源代码可以很容易地由程序员手动拼接在一起并提供给编译器,没有什么区别- 在源的语义中如此。包含的源代码没有孤立的真正解释 - 它的含义完全取决于引用包含的源代码的包含行出现的上下文。
模块在程序的更高实体级别上运行,即在编译器正在考虑源实际描述的事物的级别上。一个模块可以独立于其下游用户进行编译,一旦它被编译,编译器就会确切地知道该模块可以为程序提供什么。
通常,使用包含行的人希望做的是模块实际设计要做的事情。
示例问题:
因为实体声明可以分布在多个语句中,包含的源描述的实体可能不是您所期望的。考虑包含以下来源:
INTEGER :: i
孤立地看,这似乎将名称声明i
为整数标量(或者可能是函数?谁知道!)。现在考虑包含上述内容的以下范围:
INCLUDE "source from above"
DIMENSION :: i(10,10)
i
现在是二阶阵!也许你想让它成为一个指针?一个可分配的?一个虚假的论点?也许这会导致错误,或者它可能是有效的来源!将隐式输入混入其中,以真正增加潜在的乐趣。
在模块中定义的实体是由模块“完全”定义的。可以更改特定于使用范围的属性(VOLATILE、可访问性等),但基本实体保持不变。名称冲突被明确指出,并且可以通过 USE 语句中的重命名子句轻松解决。
Fortran 对语句顺序有限制(规范语句必须在可执行语句之前,等等)。包含的源代码也受到这些限制,同样是在包含点的上下文中,而不是在源定义点的上下文中。
语句函数定义(规范部分)和赋值语句(可执行部分)之间的源歧义很好地混合在一起,以获得一些完全模糊的错误消息,或者更糟糕的是,编译器默默地接受错误代码。
引用模块的 USE 语句出现在哪里是有要求的,但实际模块程序单元的来源完全独立于其使用点。
想要在相关过程中共享一些全局状态并且您想使用包含?让我向您介绍常见的块以及序列关联的相关底层概念......
序列关联是早期底层 Fortran 处理器实现的不幸渗入,它容易出错、不灵活、反优化不合时宜。
模块变量使公共块及其相关的弊端完全没有必要。
如果您使用的是包含行,请注意您实际上并未包含常用过程的源代码(第一段中的建议只会导致编译器出现大量语法错误)。您通常会做的是包含描述过程接口的源代码。对于任何重要的过程,描述接口的源代码都不同于过程的完整源代码——这意味着您现在需要维护同一事物的两个源代码表示。这是一个容易出错的维护负担。
如前所述 - 编译器自动获得模块过程接口的知识(编译器知识是“显式”的,因为它实际上看到了过程的代码 - 因此称为“显式接口”)。程序员无需再做任何事情。
上述的一个结果是,除非有很好的相反理由(可能存在循环或过度广泛的依赖关系),否则根本不应该使用外部子程序- 基本起点应该是把所有东西都放在一个模块或主程序中程序。
其他张贴者提到了模块的源代码组织优势——包括能够将相关过程和其他“东西”组合到一个包中,并控制内部实现细节的可访问性。
根据问题的第二段,我接受 INCLUDE 行的有效使用-大型模块的大小变得笨拙。F2008 通过子模块解决了这个问题,这也带来了许多其他好处。一旦它们得到广泛支持,就应该放弃包含行的解决方法。
第二个有效用途是克服语言对通用编程技术(C++ 中提供的模板)缺乏支持的问题——即,操作中涉及的对象类型可能会有所不同,但描述对这些技术做什么的标记序列对象本质上是一样的。在语言解决这个问题之前可能还需要十年左右的时间。