use
语句引入模块和使用include
语句引入独立文件有什么实际区别?我的意思是,如果我有一个在整个程序中经常使用的子例程:何时或为什么应该将其放在模块中,而不是只写在单独的文件中并在程序的其他部分中使用它?此外,是否将所有打算放在模块中的子程序都编写在单独的文件中,并在模块内使用
include
会是一种好的做法?特别是当子程序中的代码很长时,可以使代码更加有组织(这样所有子程序都被打包到模块中,但如果我需要编辑其中一个,就不需要穿越大量代码)。这两种概念上的差异导致了非常明显的实际差异。
INCLUDE指令在源代码级别操作——它完成简单(“愚蠢”)的文本包含。如果在INCLUDE指令中没有任何特殊处理器对“文件名”进行解释(实际上并不需要是文件),那么程序员可以很容易地手动将完整的源代码拼接在一起,并将其与编译器一起使用,而源代码的语义没有任何区别。包含的源代码在隔离状态下没有真正的解释——其意义完全取决于引用所包含的源代码的上下文。
模块在程序的更高实体级别操作,即编译器实际考虑源码所描述的内容的级别。模块可以在其下游用户之外独立编译,一旦编译完成,编译器就知道模块能够为程序提供什么东西。
通常,使用INCLUDE指令的人想要做的是模块实际上设计目的。
示例问题:
由于实体声明可以分布在多个语句中,因此包含源代码所描述的实体可能不是您所期望的。请考虑以下要包含的源代码:
INTEGER :: i
在隔离状态下,它看起来像是将名称i
声明为整数标量(或者可能是一个函数?谁知道!)。现在考虑以下包括上述内容的作用域:
INCLUDE "source from above"
DIMENSION :: i(10,10)
i
现在是二维数组!也许您想将其设置为指针?可分配的?虚拟参数?也许这会导致错误,或者这是有效的源代码!加入隐式类型转换真的会使问题更复杂。
模块中定义的实体被模块“完全”定义。用途范围特定的属性可以更改(VOLATILE,可访问性等),但根本的实体保持不变。名称冲突明确指出,并且可以通过USE语句上的重命名子句轻松解决。
Fortran有关于语句排序的限制(规范语句必须在可执行语句之前等)。包含的源代码也受到这些限制的影响,再次提醒,是在包含点的上下文中,而不是源代码定义点。
对于某些完全晦涩的错误消息或更糟的是编译器对错误代码的静默接受,它与语句函数定义(规范部分)和赋值语句(可执行部分)之间的源代码模糊性混合在一起。
USE语句引用模块的位置有要求,但实际模块程序单元的源在其使用点上是完全独立的。
想要一些全局状态在相关过程之间共享并且您想要使用包含文件?那么让我介绍您认识一下公共块及其相关概念序列关联......
序列关联是早期基础Fortran处理器实现的不幸渗透,它是一个容易出错、不灵活、反优化的过时概念。模块变量使得公共块及其相关问题完全变得不必要。
如果您正在使用include语句,请注意您实际上没有包含常用程序的源代码(您第一段中的建议只会导致编译器产生大量语法错误)。您通常会包含描述该过程“接口”的源代码。对于任何非平凡过程,描述其接口的源与过程的完整源不同-这意味着您现在需要维护相同事物的两个源代码表示形式,这是一个易出错的维护负担。
如上所述-编译器自动获得了模块过程的接口信息(编译器知识是“显式”的,因为它实际上看到了过程的代码-因此术语“显式接口”)。程序员不需要做任何更多事情。
以上的结果是,除了存在循环或过于广泛的依赖关系(也许会有非常好的理由),外部子程序根本不应该被使用 - 基本起点应该将所有内容放入模块或主程序中。如果编译后的二进制文件大小对您很重要,使用模块可以减小文件大小。模块只需编译一次,当您使用它时,实际上是符号性地加载该模块以使用代码。当您包含一个文件时,实际上是将新代码插入到您的程序中。如果您经常使用include,这可能会导致二进制文件变大并增加编译时间。
您还可以通过巧妙地使用模块中的公共和私有函数以及用户定义类型,在Fortran 90中模拟OOP样式的编码。即使您不想这样做,它也提供了一种很好的方式来组合逻辑上属于一起的函数。