Fortran关键字
在fortran中有许多关键字并非是保留关键字(reservedWords)存在,即一个词是否被作为关键字并起到设定作用,取决于其出现的位置,由编译器以语境区分来判断。
多数语言不允许直接使用保留字作为普通标识符,也有语言提供转义机制。
本文代码
#include <stdio.h>
int main(){
int do = 3;
printf("do=%d",do);
return 0;
}
program do
integer :: program = 3
print *,"program=",program
end program do
编译运行
(base) hong@hongdeMacBook-Pro 013.contextualKeywordsAndIdentifiers % gfortran test.f90
(base) hong@hongdeMacBook-Pro 013.contextualKeywordsAndIdentifiers % ./a.out
program= 3
(base) hong@hongdeMacBook-Pro 013.contextualKeywordsAndIdentifiers % gcc test.c
test.c:3:9: error: expected identifier or '('
3 | int do = 3;
| ^
test.c:4:20: error: expected expression
4 | printf("do=%d",do);
| ^
2 errors generated.
(base) hong@hongdeMacBook-Pro 013.contextualKeywordsAndIdentifiers %
结果分析
- fortran代码可以顺利编译运行,
- 尽管使用fortran关键字do作为主程序标识符,但依旧被编译器允许。在program do中,program是关键字用以声明主程序开始,do作为标识符并非循环体开始的关键字。
- 甚至把program声明为整数型变量,在integer :: program = 3中,integer是关键字用以声明整数型变量,program作为变量名。
- c代码不可以通过编译
- 因为do作为c语言的保留关键字严禁作为变量名。
- 编译器会看到do,会优先按语法关键字解析,而不是变量名所以报expected identifier。
类似do、program、subroutine、这种词作为标识符尽管是合法的,但因其本身已经承担了语法角色,会使得阅读体验下降,因此不适应被用来作标识符、变量名。
Fortran为什么会这么设计?
主要是因为早期Fortran66/77的“历史包袱”,关键字不严格保留、空格也不重要、是编译器几乎靠猜的设计,可以看一段“反人类”的代码,其中因历史局限我不作过多评价。
早期代码样例
program test
DO10I = 1,10
print *, "正常循环"
10 continue
DO10E = 1.10
print *, "DO10E=?", DO10E
end
编译运行
(base) hong@hongdeMacBook-Pro 013.contextualKeywordsAndIdentifiers % gfortran fortran6677temp.for -o fortran6677temp
(base) hong@hongdeMacBook-Pro 013.contextualKeywordsAndIdentifiers % ./fortran6677temp
正常循环
正常循环
正常循环
正常循环
正常循环
正常循环
正常循环
正常循环
正常循环
正常循环
DO10E=? 1.10000002
(base) hong@hongdeMacBook-Pro 013.contextualKeywordsAndIdentifiers %
运行结果
- 输出了10次“正常循环”对应的语句是DO10I = 1,10,其中DO10I就是DO 10 I = 1,10,循环十次。
- 而DO10E=? 1.10000002是因为我故意写错把1,10写成了1.10,该表达式实际是将1.10赋值给了DO10E,这简直是灾难,此后的编程语言越来越倾向于使用强保留关键字与规范格式语法。所以应处处需要注意代码的可读性、可维护性、可复用性。
命名规则
标识符(identifier):该词的概念很宽泛,几乎所有需要编者自行命名的都可称之为标识符,用来给主程序、变量、数组、函数、子程序、模块、程序、常量等结构体命名。
基本规则
1. 标识符必须以字母开头,如
count
velocity
matrix3
以数字、下划线开头是非法的
3dSolve
_version2
2.后续字符可以是_,0-9,A-Z,a-z,
0-9 数字
A-Z,a-z 字母
_ 下划线
3.其他的所有字符都是非法字符,包括空格。
user-name
a+b
a value
大小写不敏感规则
以下都是同一个名字
count
Count
COUNT
CoUnT
fortran关键字大部分并非严格意义的保留关键字
非严格意义的保留关键字不等于任何位置都能随便用,语法上下文仍会限制解析。以下代码都是合法的,当读到program就会把do当成主程序名,而不是把do当成循环语句。但这是个危险的写法,不建议在学习中使用。在工程规范上多数也是严禁使用的。简写如:
program do
integer :: program
传统命名习惯
用I J K L M N作为整数循环变量是因为隐式声明规则。在不显示声明类型的情况下如I开头就是integer,X开头就是real。
implicit none禁止隐式推导类型
几乎是现代fortran项目的“必备”,我极少有见到2010年以后的项目还使用隐式声明的代码。
fortran标识符长度
我尝试过在gfortran14版本下使用63位长度的变量名,可以顺利通过编译,但长度达到64位以上会报错。一般也写不到这个长度,只是我个人好奇并在此刻记录。
一则趣事:fortran II(1958年发布)不支持大于6字符的标识符,不同实现可能有差异,或许也支持但只保留前6位。如X123456和X123457是同一个变量,如今的编译器再也无法复现这种奇葩问题。
x123456=12345
x123457=98765
print *,"x123456= ?",x123456
print *,"x123457= ?",x123457
! 照我的理解,由于变量x123456只取x12345,而在语句x123457=98765中x12345被重新赋值为98765。最终都会输出98765。
现代流行的命名法
蛇形命名法snake_case,使用下划线作间隔。
velocity_x
pressure_value
matrix_size
如module fluid_solver
小驼峰命名法lowerCamelCase,在c语言中,小驼峰常用于变量名,大驼峰常用于类名。
velocityX ! 小驼峰
PressureValue ! 大驼峰
matrixSize ! 小驼峰
如module fluidSolver
匈牙利命名法Hungarian notation,是把变量类型的缩写加上比如数组大小的前缀写在变量名的里,我记得古早basic、c语言教材中还要将该变量更具体的特征写进变量名从而长到离谱,在此不再展开。我在下面代码中只用一个字符表示类型来演示该命名法。
i_count ! 在阅读编写距声明区很远的代码中也一眼看出该表达式中的变量是integer类型
rValue ! real 匈牙利+驼峰
c_format_Name ! character 匈牙利+蛇形+小驼峰
lDone ! 逻辑
都有可取之处,本无优劣之分。我本不太愿讲自己的主观,但这里我简单提一句:我通常使用的是驼峰,但越来越觉得蛇形更“优雅”。例如:
subroutine compute_stress_tensor(strain_tensor, stress_tensor)
module mesh_utils
integer :: max_iteration
integer :: current_step
real :: poisson_ratio
logical :: is_converged
max_iteration = 100
current_step = 0
poisson_ratio = 0.3
is_converged = .false.
其他的例如短横线命名法。因fortran不允许将–写进标识符所以不再赘述。
不使用意义不明的词或单字符标识符
使用意义不明的词易造成误解,使用单字标识符更是“折磨”。简写如
real :: aPairOf
logical :: A
integer,parameter :: r
a pair of是什么变量? A大致是什么逻辑?常量r指密度rho还是半径radius又或是比值ratio?编译器只需要读通语法没问题即可,但读者/维护者恐怕很难接受这样的写法。
大小写不敏感到ASR-33、ibm穿孔卡、fortran历史沿革与传承
本文我已写了很久,但总觉意犹未尽,有些事没说清楚,索性再讲讲fortran与计算机发展的一些渊源。
早期的fortran写在穿孔卡上,穿孔卡由打孔、不打孔两种状态,比较近似今天二进制。有孔=1,没孔=0(严格来说更接近“机械字符的矩阵编码”而不是现代CPU纯bitstream)。最知名的产品莫过于IBM 80col punch card,早期的一行代码就是一张卡,该卡的作用类似今天的硬盘,是离线程序存储介质。想像这样一种情景:在纸盒一样的“存储设备”中有大量卡片,即使有稳固机械结构,也难免会有打乱、掉落,所以必须要对卡片进行编号,用以还原正确的顺序。这也直接影响了早期fortran的固定格式:
- 1-5列:语句标签
- 第6列:续行标记
- 7-72列:真正代码
- 73-80列:卡片编号
很多fortran编译器对固定格式里的“72列代码限制”,本质上并非语言特性,而是IBM 80列穿孔卡的物理遗产,73-80列是为了卡片意外乱掉后还可重新排序恢复程序,在某些现代项目的代码中还看得到后八位编号的存在。而且fortran的大小写不敏感并不是孤立设计,它和早期输入输出设备、字符集、穿孔卡生态有关。
由于使用电传打印机(teletype),而大量设备都不支持大小写区分,主要是设计/修理难度与经济性的原因。比如最著名的ASR-33 TELETYPE(该型号设备只能输出大写字符,在此我用全大写致敬)。该型号也深刻影响微软的诞生与早期发展,比如在Altair basic时代很多用户就是通过ASR-33与Altair8800交互,甚至今天微软仍在沿用的 CR(carriageReturn) 回车 / LF(lineFeed) 换行,本质上也是机械打印机时代传承下来的物理动作:CR是打印头回行首,LF是纸张向前滚一行。
因为ASR-33内存小处理慢打印速度低(约8-10字符/秒),启发了后来Unix基础设计哲学——“小工具+高效文本+管道”,比如:cat file | grep hello | sort。核心需求就是”让简单程序通过文本流自由组合”。这又深刻影响到今天的linux、bsd、macos、git、docker、kubernetes、ssh等。另外电传打印机teletypewriter简称的tty,今天在linux终端中的tty其实就是传承其名,例如:/dev/tty /dev/pts/0本质上也都是“teletype抽象接口”的历史遗产。
至于在类unix中普遍使用ctrl+c来终止程序,还是来源于ASR-33的ascii控制字符体系中的ETX(end of text)退出码ascii3。按顺序ctrl+@是ascii0,ctrl+a是ascii1,ctrl+b是ascii2。ctrl+c并非我起先想当然理解的control+cancel,而是按ascii码排的操作。在ASR-33上的ctrl键不是现代GUI里的快捷键,而是“生成控制字符”。后来unixTTY子系统(准确说是line discipline)将ETX解释为sigint并发送给前台进程组,于是ctrl+c逐渐成为终止程序的标准方式。打从unix所以到今天的linux、macos、bsd也依旧沿用了这套terminal signal机制。
话说回来,a==A必须成为fortran语言设计的一部分。