\chapter{基础知识}

\section{程序}\label{fortran_program}

程序 (program) 是指挥电脑工作的一堆指令. 这堆指令要用一堆字符表示, 表示指令的所有字符合起来称为源代码 (source code). 源代码需要被保存在文件中, 保存源代码的所有文件合起来称为源代码文件 (source code file).

比如, 在一个名为 \ttt{main.f90} 的文件内写入下面这些内容\footnote{最左边的数字是行号, 别写进文件里啊!}并保存.
\begin{lstlisting}
program main
    implicit none
    call helloworld()
end program main
\end{lstlisting}
在另一个名为 \ttt{helloworld.f90} 的文件内写入下面这些内容并保存.
\begin{lstlisting}
subroutine helloworld()
    implicit none
    print *, "Hello, world!"
end subroutine helloworld
\end{lstlisting}
那么 \ttt{main.f90} 和 \ttt{helloworld.f90} 就是源代码文件, 这俩文件里头一共有 8 行字符, 这些字符合起来就是源代码, 表示的指令如表 \ref{source_and_command_main} 和表 \ref{source_and_command_helloworld} 所示, 这一大堆指令合起来就是程序.
\begin{table}[!htbp]
    \centering
    \begin{tabular}{|p{0.42\textwidth}|p{0.48\textwidth}|}
        \hline
        \multicolumn{1}{|c|}{源代码}&\multicolumn{1}{|c|}{指令}\\
        \hline
        \ttt{program main}&开始运行主程序 \ttt{main}\\
        \hline
        \ttt{implicit none}&禁止隐式声明\\
        \hline
        \ttt{call helloworld()}&调用子程序 \ttt{helloworld}\\
        \hline
        \ttt{end program main}&结束运行主程序 \ttt{main}\\
        \hline
    \end{tabular}
    \caption{\ttt{main.f90} 中源代码与指令的对照}\label{source_and_command_main}
\end{table}
\begin{table}[!htbp]
    \centering
    \begin{tabular}{|p{0.42\textwidth}|p{0.48\textwidth}|}
        \hline
        \multicolumn{1}{|c|}{源代码}&\multicolumn{1}{|c|}{指令}\\
        \hline
        \ttt{subroutine helloworld()}&开始运行子例行 \ttt{helloworld}\\
        \hline
        \ttt{implicit none}&禁止隐式声明\\
        \hline
        \ttt{print *, "Hello, world!"}&让电脑屏幕上出现 ``Hello, world!''\\
        \hline
        \ttt{end subroutine helloworld}&结束运行子例行 \ttt{helloworld}\\
        \hline
    \end{tabular}
    \caption{\ttt{helloworld.f90} 中源代码与指令的对照}\label{source_and_command_helloworld}
\end{table}

\section{标准与规范}

任何编程\uline{语言}都有自己的句法/语法 (syntax), 比如 ``让电脑屏幕上出现 `A'{}'', Fortran 要写成 \ttt{print *, "A"}, C 要写成 \ttt{printf("A");}, Python 2 要写成 \ttt{print "A"}, Python 3 要写成 \ttt{print("A")}. 编程语言对语法的统一规定称为标准 (standard). Fortran 的现行标准是 Fortran 2023 (第\ref{introduction}章中已介绍), 旧标准有 FORTRAN 66, FORTRAN 77, Fortran 90, Fortran 95, Fortran 2003, Fortran 2008, Fortran 2018.

不太好玩儿的是, 虽然 Fortran 语法已由现行标准规定好了, Fortran 编译器们却偶尔不按 Fortran 标准来干活儿, 自己制定了一些规则, 还有些时候, Fortran 标准直接就把权利赋予编译器, 让他们自己制定一些规则. 这样, Fortran 编译器们时不时就有和 ``官规'' 不同的 ``器规''.

编程语言的标准是 ``硬规则'', 除此之外还有 ``软规则'', 称为规范 (convention). 所谓规范, 就是写代码的人在遵守代码语法规则之余, 自己强迫自己另外遵守的规则. 虽然在遵守代码语法规则之余还要遵守规范显得麻烦, 但不遵守规范就会遇到更大的麻烦, 这些麻烦可大致总结成三类: 代码写不出, 代码读不懂, 代码不好用.

编译器各有各的器规, 这可能会出事. 想象一下, 同学们期末考写好程序, Ifx 和 Gfortran 都说程序没问题, 同学们兴冲冲地交卷了, 然后老师掏出 Visual Fortran (某老款编译器), Visual Fortran 说程序有问题, 同学们就挂科啦. 为防止这种悲惨情形发生, 我们需要尽量避免使用 Fortran 标准没制定而编译器自己制定的规则.
\begin{convention}\label{use_standard_only}
    如果 Fortran 现行标准有自行规定规则, 则只使用这些规则.
\end{convention}
这样, 编译器要是不认我们的程序, 安安唯一能想到的可能就是 Fortran 标准更新了, 而编译器没更新, 还用着旧标准. 不过同学们没必要担心这一情形, 本笔记里的东东绝大多数 Fortran 90 就有了 (我猜老师现在也只教到 Fortran 90), 即使是 Visual Fortran 也会认的, 要是某 Fortran 编译器竟连 Fortran 90 都不认, 我们就把它卸载了\dots{} 遵守这规范还有其他好处. 想象一下, 原本我们写了个程序, 是用 Ifx 编译的, 但 Intel 近些年不行了, 我们也许需要换 \href{https://www.amd.com/en/developer/aocc.html}{AMD} 或 \href{https://developer.nvidia.com/hpc-sdk#}{NVIDIA} 的编译器. 如果我们的程序用了一堆 Ifx 的器规, 我们就麻了, 因为 AMD, NVIDIA 和 Intel 是死对头, 想来就不会认 Ifx 的器规, 我们只好把 AMD, NVIDIA 不认的那部分全改写一遍, 苦しいね!

\section{程序单元}\label{program_unit}

每个 Fortran 程序都由若干程序单元 (program unit) 组成. 程序单元可能是主程序 (main program), 或外部子程序 (external subprogram), 或模块 (module), 或子模块 (submodule). 一个 Fortran 程序中必须有且只有一个主程序, 其他程序单元数目随便.

主程序长下面这样.
\begin{lstlisting}[numbers=none]
program [program-name]
    ...
end program [program-name]
\end{lstlisting}
其中 \ttt{[program-name]} 处是一个名称 (name), 名称指的是由字母, 数字和下划线 ``\ttt{\_{}}''\footnote{
    下划线是空格的替代字符, 长得还是蛮像滴!
} 组成的, 开头是字母的字符串\footnote{后文中的 ``某某名'' 若不另加说明则都是名称, 不再赘述.}, ``\ttt{...}'' 处则是各种指令, 一行一条. 比如, 之前 \ref{fortran_program} 节里 \ttt{main.f90} 里的程序就是一个名称为 \ttt{main} 的主程序\footnote{安安习惯把主程序的名称取做 \ttt{main} 并放在 \ttt{main.f90} 里.} (\ttt{helloworld.f90} 里的程序则是一个外部子程序).

外部子程序在第\ref{fortran_procedure}章里讲, 模块在第\ref{fortran_module}章里讲, 子模块在第\ref{fortran_submodule}章里讲.\footnote{??\mbox{}的内容安安会在猴年马月写好的!}

每个 Fortran 源代码文件里都可以放若干完整的程序单元, 但习惯上一个源代码文件里只放一个程序单元, 并且文件名和文件里的程序单元名相同 (比如主程序 \ttt{main} 放在 \ttt{main.f90} 里).
\begin{convention}
    一个源代码文件里只放一个程序单元, 并且文件名和文件里的程序单元名相同.\label{one_program_unit}
\end{convention}
当然, 安安偶尔也会在一个源代码文件里放多个程序单元, 比如安安在玩 \href{https://iausofa.org/}{SOFA} 的时候, 习惯把除 \ttt{t\_{}sofa\_{}f.for} 外的所有文件里的东东通通复制粘贴到一个名为 \ttt{sofa.for} 的新文件里, 这个文件里就有一大堆外部子程序了.

今后程序若有多个程序单元, 我将把这多个程序单元写在一起 (虽然按规范 \ref{one_program_unit} 它们应该被分别保存). 比如 \ref{fortran_program} 节 \ttt{main.f90} 和 \ttt{helloworld.f90} 中的程序会合写成如下模样.
\begin{lstlisting}
program main
    implicit none
    call helloworld()
end program main
\end{lstlisting}
\begin{lstlisting}
subroutine helloworld()
    implicit none
    print *, "Hello, world!"
end subroutine helloworld
\end{lstlisting}

\section{编译与运行}\label{run_fortran}

我们用 Fortran 语写好了程序, 命令电脑照此干活儿, 然而电脑会说干不了. 原因在于, 电脑不认得 Fortran 语, 电脑只懂得电脑语 (学名是机器语言), 甚至不同电脑的电脑语还不一样. 而电脑语身为正常人类的我们完全搞不懂\dots{}

这时我们就得请编译器 (compiler) 们出场干活儿了, 它们会在操作系统的帮助下, 把 Fortran 语翻译成电脑语, 这个过程就是 \ref{use_ifx} 小节所讲的编译+链接 (我们也常直接用 ``编译'' 指代编译+链接两步). 把程序翻译成电脑语后, 电脑没借口不干活儿了, 我们就可以开心地命令电脑照电脑语程序干活儿, 这个过程就是 \ref{use_ifx} 小节所讲的运行. Fortran 程序中指令运行的基本顺序是从主程序第 1 行 \ttt{program [program-name]} 开始, 向下逐行依次运行, 到主程序最后 1 行 \ttt{end program [program-name]} 结束. 本笔记里会介绍许多例外情况, 遇到例外时会称之为跳转 (jump). 例如 \ref{fortran_program} 节 \ttt{main.f90} 和 \ttt{helloworld.f90} 中的程序, 指令的运行顺序如下, 其中指令 3 跳转到指令 4, 指令 7 跳转到指令 8.
\begin{table}[!htbp]
    \centering
    \begin{tabular}{|p{0.42\textwidth}|p{0.48\textwidth}|}
        \hline
        \multicolumn{1}{|c|}{源代码}&\multicolumn{1}{|c|}{指令}\\
        \hline
        \ttt{program main}&1.开始运行主程序 \ttt{main}\\
        \hline
        \ttt{implicit none}&2.禁止隐式声明\\
        \hline
        \ttt{call helloworld()}&3.调用子程序 \ttt{helloworld}\\
        \hline
        \ttt{end program main}&8.结束运行主程序 \ttt{main}\\
        \hline
    \end{tabular}
    \caption{\ttt{main.f90} 中指令运行顺序}
\end{table}
\begin{table}[!htbp]
    \centering
    \begin{tabular}{|p{0.42\textwidth}|p{0.48\textwidth}|}
        \hline
        \multicolumn{1}{|c|}{源代码}&\multicolumn{1}{|c|}{指令}\\
        \hline
        \ttt{subroutine helloworld()}&4.开始运行子例行 \ttt{helloworld}\\
        \hline
        \ttt{implicit none}&5.禁止隐式声明\\
        \hline
        \ttt{print *, 'Hello, world!'}&6.让电脑屏幕上出现``Hello, world!''\\
        \hline
        \ttt{end subroutine helloworld}&7.结束运行子例行 \ttt{helloworld}\\
        \hline
    \end{tabular}
    \caption{\ttt{helloworld.f90} 中指令运行顺序}
\end{table}

当年编程语言刚刚出世的时候, 人们对程序设计的认识不足, 程序里的指令可以胡乱跳转, 总有人写出让人根本读不懂的鬼魅程序. 后来人们制定了结构化 (structured) 程序设计原则, 其基本特征是只保留尽可能少的, 有明确规则的指令跳转. Fortran 语法如今已基本符合结构化原则了, 但仍有少数不符合的. 本笔记里会介绍一些不符合结构化原则的语法, 但只是因为不用那些语法, 有些活儿干不了. 请同学们尽可能少用那些语法.
\begin{convention}
    尽可能少用不符合结构化程序设计原则的语法.\label{no_no_structured}
\end{convention}

最后补充一下, 玩 Fortran 时我们是先用编译器把 Fortran 程序从头到尾全翻译了然后再运行. 但编程语言还有另一种运行方式, 是用解释器 (interpreter) 把程序一点点翻译成机器语言, 翻译一点运行一点, 好像电脑装了解释器后就能直接读懂编程语言一样, 这个过程称为解释 (interpret). 这两种方式还可以混用, 同学们以后玩 Python 的时候就会遇到混合方式.

\section{异常}\label{fortran_exception}

程序编译和运行时可能会有不妙的事情发生, 那就是抛出 (raise) 异常 (exception). 异常分为错误 (error) 和警告 (warning). 如果我们的程序编译和运行时并不符合我们的设想, 我们便称程序有故障 (bug\footnote{``bug'' 的本意是 ``虫'', 请同学们自行搜索 ``bug'' 的来历.}), 排除故障我们称为调试 (debug), 但只有被电脑揪出来的故障才称为异常.

\subsection{错误}\label{fortran_error}

抛出错误有两种情况. 最常见的是我们写的程序不符合 Fortran 语法, 编译器读不懂, 无法翻译, 就抛出错误然后罢工了. 比如, 编译这样一个保存在 \ttt{main.f90} 中的程序.
\begin{lstlisting}
program main
    implicit none
    print 'Hello, world!'
end program main
\end{lstlisting}
Ifx 给出的结果如下\footnote{
    假定屏幕宽度是 50 个字符宽, 且略去版权声明等无关紧要的内容, 下同.
}.
\begin{lstlisting}[numbers=none]
main.f90(3): error #6899: First non-blank characte
r in a character type format specifier must be a l
eft parenthesis.  ['Hello, world!']
    print 'Hello, world!'
-----------^
compilation aborted for main.f90 (code 1)
\end{lstlisting}
Gfortran 给出的结果如下.
\begin{lstlisting}[numbers=none]
main.f90:3:10:

    print 'Hello, world!'
         1
Error: Missing leading left parenthesis in format 
string at (1)
\end{lstlisting}
由于程序第3行不符合 Fortran 语法 (\ttt{print} 后少了 \ttt{*,} 之类的东东), 编译器不知怎么干活儿了, 只好报个错. 智能的 VS Code 会在编译前就请 Gfortran 来检查我们的程序是否有语法错误, 这将节约我们的时间.

因为有器规, 所以报错还和编译器有关. 比如运行下面这个程序.
\begin{lstlisting}
program main
    implicit none
    print *, 1.0/0.0
end program main
\end{lstlisting}
Gfortran 会报错说不能除以 $0$, 但 Ifx 不会报错并会算出正无穷.

另一种抛出错误的情况是我们写的程序符合 Fortran 语法, 编译器能翻译, 但之后运行的时候程序请操作系统帮忙干活儿, 操作系统发现没法儿干, 程序就抛出错误然后罢工了. 比如在 Windows 系统里运行这样一个程序.
\begin{lstlisting}
program main
    implicit none
    open(10, file='C:\*')
end program main
\end{lstlisting}
这回编译时 Ifx 和 Gfortran 都不认为有什么问题, 但运行时程序让 Windows 打开文件 \ttt{C:\bs{}*} (若无此文件则新建一个后打开), 而 Windows 文件名里不可以有 \ttt{*}, 程序只好报个错. 这个程序在同学们以后会学的 Linux 系统里就可以正常工作, 因为 Linux 文件名里可以有 \ttt{*}.

\subsection{警告}\label{fortran_warning}

有时候我们写的程序符合 Fortran 语法, 编译器读得懂能翻译, 但它觉得我们写的程序十分 ``危险'', 很有可能程序的实际执行过程并不是我们预想的那样, 这时编译器就会抛出警告. 什么时候应该给个错误, 不同编译器的观点还是比较一致的, 但什么时候给个警告, 那区别就相当大了.

比如, 编译这样一个保存在 \ttt{main.f90} 中的程序.
\begin{lstlisting}
program main
    implicit none
    integer :: i
    i = 1000000000000
    print *, i
end program main
\end{lstlisting}
Ifx 给出这样的结果.
\begin{lstlisting}[numbers=none]
main.f90(4): warning #8221: This integer constant 
is outside the default integer range - using INTEG
ER(8) instead.   [1000000000000]
    i = 1000000000000
--------^
main.f90(4): warning #6384: This value is out-of-r
ange for an INTEGER(KIND=4) type.   [1000000000000]
    i = 1000000000000
--------^
\end{lstlisting}
Ifx 认为 \ttt{1000000000000} 过大, 无法正确存储, 于是就给个警告, 但程序仍然可以运行. 正常想来, 程序运行后电脑屏幕上应该出现 \ttt{1000000000000}, 结果出现的却是 \ttt{-727379968}, 果然很奇怪! 至于 Gfortran 嘛, 它也认为 \ttt{1000000000000}过大, 但它不是给个警告, 而是直接报错.

再比如, 编译这样一个保存在 \ttt{main.f90} 中的程序.
\begin{lstlisting}
program main
    implicit none
    integer :: i
    i = .true.
    print *, i
end program main
\end{lstlisting}
Gfortran 给出这样的结果.
\begin{lstlisting}[numbers=none]
main.f90:4:8:

    4 |     i = .true.
      |        1
Warning: Extension: Conversion from LOGICAL(4) to 
INTEGER(4) at (1)
\end{lstlisting}
Gfortran 觉得令整数 \ttt{i} 等于逻辑 ``真'' 的操作其实是标准不允许的, 是自己放水才能这么干的, 于是就给个警告 (VS Code 会提前请 Gfortran 来看是否会弹警告), 但程序仍然可以运行. 运行后电脑屏幕上出现 \ttt{1}. 至于 Ifx 嘛, 它倒是不报错也不给警告, 干脆利落地编译完了, 但因为器规不同, 运行后电脑屏幕上出现的是 \ttt{-1}\dots{}

\section{字符集}

Fortran 可以在程序中使用所有能直接用英语输入法打出的字符, 其他字符 (比如汉字, 汉语标点) 能否在程序中用则由器规决定. 注意, 英文标点和中文标点是\uline{完全不同}的\footnote{用 VS Code 的同学会发现英文标点和中文标点显示出来完全不一样.}, 英文标点是``半角标点'', 中文标点是``全角标点''哟!\footnote{本笔记里使用的全部是英文标点呢.}

经俺测试, Ifx 和 Gfortran 都是支持汉字和汉语标点的, 但直接让电脑屏幕上出现汉字或汉语标点可能会出现 ``穞堵臸猭畍\footnote{不认识这东东的同学请自行搜索.}'' 之类的乱码 (这可以解决).

为避免各种各样的问题, 还是只用正常字符为好. 另外源代码文件名最好也如此, 不然编译器可能会无法识别源代码文件呢.
\begin{convention}
    在程序和源代码文件名中只使用能直接用英语输入法打出的字符.\label{english_character_convention}
\end{convention}

Fortran 是大小写不敏感的 (case-insensitive), 也就是说, 很多情况下程序里的字母大写小写效果是完全相同的 (效果不同的地方见 \ref{fortran_char} 节和 \ref{character_string_edit_descriptor} 小节). 比如, 下面这两个程序和 \ref{fortran_program} 节的 \ttt{main.f90} 内的程序是完全等价的.
\begin{lstlisting}
PROGRAM MAIN
    IMPLICIT NONE
    CALL HELLOWORLD()
END PROGRAM MAIN
\end{lstlisting}

Fortran 大小写不分是有历史原因的. 当年电脑太烂了, 为节省存储空间, 一开始 Fortran 只许用大写字母, 后来电脑不那么烂了, 能用小写字母了, Fortran 大概是因为想尽量保证老程序能用, 所以直接规定大小写不分, 结果挖了个如今没法儿填的史诗级奆坑, 学 Fortran 玩 Fortran 的历来都深受其害. 举个例子, 当年俺搞毕设, 需要解 Kepler 方程 $E=M+e\cos E$,在程序里对应地写了 \ttt{E = M + e*cos(E)}, 然而 Fortran 是大小写不分的, 所以 \ttt{E = M + e*cos(E)} 其实对应于 $e=M+e\cos e$ 或 $E=M+E\cos E$, 然后俺忘了这茬, 结果死活找不出自己哪里写错了\dots{}

被 Fortran 反复毒打后, 人们决定做些什么来避免被毒打. 既然 Fortran 大小写不分, 那避免混淆的最好办法就是在程序里统一只用小写或大写字母 (俺搞毕设时当然知道这事, 但俺心存侥幸偷懒了, 结果惨遭报应\dots{}). 因为单词写成小写字母比写成大写字母好认, 所以现在人们都统一只用小写字母.
\begin{convention}
    只在程序中使用小写字母.\label{lower_case_convention}
\end{convention}
如上所述, 有时 Fortran 大小写是分的, 这时规范 \ref{lower_case_convention} 没法儿遵守, 当然就不遵守了. 除此之外, 规范 \ref{lower_case_convention} 还有其他一些可以不遵守的情形, 俺留着后面说. 这样, \ttt{E = M + e*cos(E)} 就得改写了, 改写方法倒没有定则. 最简单的办法就是直接上英文全称, \ttt{E} 改写成 \ttt{eccentric\_{}anomaly}, \ttt{e} 改写成 \ttt{eccentricity}, 不过这么写太长了. 想来想去, 安安觉得 \ttt{E} 改写成 \ttt{ea}, \ttt{e} 不改写就可以了.

\section{源代码格式}

Fortran 有两种源代码格式 (source form): 自由格式 (free form) 和固定格式 (fixed form). 固定格式是将被废弃的 (obsolescent) 老格式. 安安的笔记里只讲自由格式.
\begin{convention}
    永远编写自由格式的程序.
\end{convention}

自由格式源代码文件拓展名一般是 \ttt{.f90}, 固定格式源代码文件拓展名一般是 \ttt{.for}/\ttt{.f}, 这是 Fortran 编译器的惯例. 查 \href{https://cdrdv2.intel.com/v1/dl/getContent/824360?fileName=fortran-compiler_developer-guide-reference_2024.2-767251-824360.pdf}{Ifx 文档} 和 \href{https://gcc.gnu.org/onlinedocs/gcc-13.2.0/gfortran.pdf}{Gfortran 文档} 可知, 它俩都遵从这个惯例. 请同学们把自由格式的 Fortran 程序保存在拓展名为 \ttt{.f90} 的文件里, 以保证它俩能认得. 要是某 Fortran 编译器竟不遵从惯例, 我们就把它卸载了\dots{}
\begin{convention}
    保证自由格式源代码文件拓展名为 \ttt{\emph{.f90}}.
\end{convention}

这里需澄清, 有习惯说法是把自由格式程序说成 ``Fortran 90 程序'', 把固定格式程序说成``FORTRAN 77 程序''. 这种说法的根源是 Fortran 历史上有一场巨变: FORTRAN 77 只有固定格式, 而 Fortran 90 新引入了自由格式. 然而, 这并不意味着固定格式程序中不能使用 Fortran 90 及以后的标准新出的语法.

在 VS Code 中, 把鼠标移到右下角铃铛图标按键左边第一个, 鼠标移到后会显示 ``Select Language Mode'' 的按键上, 点击之, 然后弹出的菜单下拉选择 ``Fortran (FortranFreeForm)'', VS Code 便会自动检查程序是否符合 Fortran 自由格式.

\subsection{空格}

Fortran 代码里空格 (blank/space) 通常没有意义 (\ref{fortran_char} 节和 \ref{character_string_edit_descriptor} 小节中有例外), 可多可少可没有, 但有些时候空格是必要的, 否则会出现混淆, 比如 \ttt{program main} 就不能写成 \ttt{programmain} (没有编译器能智能到把它拆成两个词).

虽然空格没有意义, 但能让代码更清楚. 如果把 \ttt{print *, 'A'} 写成 \ttt{print*,'A'}, 挤成一团, 也不能说是错误, 但即便像 VS Code 里那样代码有颜色区分, 也让人看得晕乎, 更何况像笔记里这样代码没颜色呢.
\begin{convention}
    在程序中适当地加入空格.\label{fortran_blank}
\end{convention}
至于什么时候加入空格, 其实没什么具体原则, 不同的人意见不一. 同学们可以参考本笔记,  Fortran \href{https://fortran-lang.org/}{官网}上的代码示例和 Python 的 \href{https://peps.python.org/pep-0008/}{PEP 8 规范}来决定是否加空格.

\subsection{缩进}\label{indent}

缩进 (indent) 是指源代码每行最前面的空格. 对 Fortran 而言, 缩进本身没有意义, 但适当的缩进能表示程序的某个结构嵌套在另一个结构里, 这能使程序更容易被理解. 来看下面这个程序.
\begin{lstlisting}
program main
implicit none
integer :: i
do i = 1, 1
if (.true.) then
print *, 'I'
end if
end do
end program main
\end{lstlisting}
在上面这个程序中, 第 1 行到第 9 行是个主程序, 第 4 行到第 8 行是个 \ref{do_construct} 小节会介绍的 \ttt{do} 结构, 第 5 行到第 7 行是个 \ref{if_construct} 小节会介绍的 \ttt{if} 结构. 但这么写代码, 即使是想安安这样的老手也得努力分析一波才能看出这个程序的结构来. 所以我们应该把程序写成下面这样.
\begin{lstlisting}
program main
    implicit none
    integer :: i
    do i = 1, 1
        if (.true.) then
            print *, 'I'
        end if
    end do
end program main
\end{lstlisting}
第 2 行到第 8 行的代码缩进一层, 有 4 个空格的缩进, 表示这些代码是在主程序里的; 第 5 行到第 7 行的代码缩进一层, 有 8 个空格的缩进, 表示这些代码是在主程序里的 \ttt{do} 结构里的; 第 6 行的代码又又缩进一层, 有 12 个空格的缩进, 表示这些代码是在主程序里的 \ttt{do} 结构里的\ttt{if} 结构里的. 如此这般代码的结构就一目了然了.
\begin{convention}
    在程序中加入适当的缩进, 以 $4$ 个空格为单位.\label{fortran_indent}
\end{convention}
``以 4 个空格为单位'', 是指保持缩进为 4 个空格, 或 8 个空格, 或 12 个空格, 以此类推. 有些人可能更喜欢以 8 个空格为单位的缩进, 如果他们的电脑屏幕比较宽, 不怕代码跑到屏幕右侧以外的话. 比如, 上面的程序如果缩进改以 8 个空格为单位的话, 就会变成下面的程序.
\begin{lstlisting}
program main
        implicit none
        integer :: i
        do i = 1, 1
                if (.true.) then
                        print *, 'I'
                end if
        end do
end program main
\end{lstlisting}

请注意, 在文本编辑器中按 [Tab] 键, 也会出现一长串 ``空格'', 但实际上, 电脑会认为 [Tab] 和一堆空格是不同的东东, 编译器不一定会允许在程序中使用 [Tab] 键. 智能的文本编辑器, 比如 VS Code, 会猜我们是不是实际上需要一堆空格, 然后自动把 [Tab] 转换为适当数目的空格, 但它完全可能猜错. 而其他经典的优秀编辑器, 比如 \href{https://www.gnu.org/software/emacs/}{Emacs} 和 \href{https://www.vim.org/}{Vim}, 是会严格区分 [Tab] 和空格的\footnote{
    这并不见得是坏事, 因为有些特殊文件是必须使用 [Tab] 的.
}, 除非另装外挂.
\begin{convention}
    不要使用\emph{[Tab]}键.
\end{convention}

在 VS Code 中, 把鼠标移到右下角铃铛图标按键左边第四个, 鼠标移到后会显示 ``Select Indentation'' 的按键上, 点击之, 然后弹出的菜单下拉选择 ``Indent Using Spaces'', 再选择 ``4'', VS Code 便会智能添加以 4 个空格为单位的缩进了.

\subsection{空行}\label{empty_line}

为使得程序更容易被理解, 还要在程序中加入适当的空行, 以表示程序可划分成多个部分. 我们可以给上面 \ref{indent} 小节的程序加一个空行, 变成下面的程序.
\begin{lstlisting}
program main
    implicit none
    integer :: i

    do i = 1, 1
        if (.true.) then
            print *, 'I'
        end if
    end do
end program main
\end{lstlisting}
学第\ref{fortran_intrinsic_type}章后同学们可知, 在上面的程序中, 空行以上的部分可以称为 ``说明部分'', 空行以下的部分可以称为 ``执行部分''. 还常用双空行和单空行来进一步划分. 比如下面这个程序.
\begin{lstlisting}
program main
    implicit none
    integer :: m, n


    do m = 1, 1
        if (.true.) then
            print *, 'M'
        end if
    end do

    do n = 1, 1
        if (.true.) then
            print *, 'N'
        end if
    end do
end program main
\end{lstlisting}
在上面的程序中, 双空行划分 ``说明部分''和 ``执行部分'', 单空行划分 ``执行部分''中的各部分 (每个部分都是一个 \ref{do_construct} 小节介绍的 \ttt{do} 结构).
\begin{convention}
    在程序中加入适当的空行.
\end{convention}
至于什么时候加入一个或几个空行, 其实没什么具体原则, 完全看个人心情. 如果读程序里长长的一段, 感觉头昏脑涨, 或觉得以后再读这段肯定会头昏脑涨, 插入空行来把这段划分成各自能完成一件事的几段就是不错的选择.

\subsection{注释}

程序每一行中, ``\ttt{!}'' 及后面的所有内容都会被编译器无视 (例外见 \ref{fortran_char} 节和 \ref{character_string_edit_descriptor} 小节), 也就相当于 ``\ttt{!}'' 及后面的内容不存在. 会被无视的 ``\ttt{!}'' 及后面的内容称为注释 (comment). 例如下面两个程序是完全一样的.
\begin{lstlisting}
program main
    !                   _oo0oo_
    !                  o8888888o
    !                  88" . "88
    !                  (| -_- |)
    !                  0\  =  /0
    !                ___/`---'\___
    !              .' \\|     |// '.
    !             / \\|||  :  |||// \
    !            / _||||| -:- |||||- \
    !           |   | \\\  - /// |   |
    !           | \_|  ''\---/''  |_/ |
    !           \  .-\__  '-'  ___/-. /
    !         ___'. .'  /--.--\  `. .'___
    !      ."" '<  `.___\_<|>_/___.' >' "".
    !     | | :  `- \`.;`\ _ /`;.`/ - ` : | |
    !     \  \ `_.   \_ __\ /__ _/   .-` /  /
    ! =====`-.____`.___ \_____/___.-`___.-'=====
    !                   `=---='
    implicit none
    print *, 'No bugs forever!' ! We are blessed!
end program main
\end{lstlisting}
\begin{lstlisting}
program main
    implicit none
    print *, 'No bugs forever!'
end program main
\end{lstlisting}

注释看起来没什么用, 其实用处大极了, 主要功能就是在代码中插入 ``笔记'', 方便读代码的人理解代码在干什么. 比如下面这个程序, 其实是算 $10$ 的阶乘, 但是代码本身只能表示 ``从 $1$ 乘到 $10$'' 的意思, 要理解是算 $10$ 的阶乘, 还要稍稍费点脑子, 而加上个注释, 就能一眼看出代码是在算 $10$ 的阶乘了 (除非看不懂英语\dots{}).
\begin{lstlisting}
program main
    implicit none
    integer :: i, f
    ! Calculate f == factorial(10).
    f = 1
    do i = 1, 10
        f = f * i
    end do
    print *, f
end program main
\end{lstlisting}

因为经常有偷懒不加注释, 结果写代码的人自己过几天都看不懂自己之前写的代码的事情发生, 人们总是强调要写注释. 但这种说法有偏颇之处, 因为有些代码自己足以表达意思, 根本没必要加注释. 比如下面这个程序, 注释就是废话, 因为看代码本身就能一眼看出代码是在算 $\tan 0$.
\begin{lstlisting}
program main
    use iso_fortran_env, only: dp => real64
    implicit none
    ! Calculate tan(0).
    print *, tan(0.0_dp)
end program main
\end{lstlisting}
当然, 如果代码自己不足以表达意思, 还是尽可能加上注释吧, 偷懒可是要遭报应的呀!
\begin{convention}
    在代码本身不足以表明代码含义的时候尽可能加入注释以说明.
\end{convention}

因为注释会被忽略, 所以规范 \ref{lower_case_convention} 我们可以无视. 同学们乃华夏儿女, 看来都很希望能在注释里用中文, 然而用中文可能导致编译器不能干活, 还是用英文吧! (尤其是代码要写给外国人看的时候)

\subsection{续行}

写代码的时候, 我们不希望一行里有过多的字符, 因为代码会跑到屏幕右侧以外, 不方便阅读.
\begin{convention}
    程序每一行不应超过 $80$ 个字符.\label{fortran_no_more_than_80}
\end{convention}
有些人可能会放宽到不超过 $120$ 个字符, 配合以 8 个空格为单位的缩进和宽屏电脑.

用 VS Code 的话, 可以在 \ttt{settings.json} 里加入下面这两个键值对.
\begin{lstlisting}[numbers=none]
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 80
\end{lstlisting}
这样如果一行超过 80 个字符, 就会分行显示. 注意, 只是分行显示, 这一行还是超过 80 个字符的 (可以看到 ``分行'' 后下面一 ``行'' 是没有行号的). 加入这两个键值对只是起提醒作用, 太长还是要分行写的 (照顾那些没有像 VS Code 这样智能的编辑器的可怜人).

但有时代码一行确实写不完, 这时我们可以使用续行 (continuation). 首先我们需要介绍注释行 (comment line). 注释行就是相当于空行的行, 包括空行, 只有空格的行和除空格外只有注释的行. 而续行就是在一行的末尾加上 ``\ttt{\&{}}'', 表示把下面第一个不是注释行的行剪切下来粘贴到这行后 (并去掉用于表示续行的 ``\ttt{\&{}}''). 比如下面两个程序是一样的.
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, ' &
             //'are illusions.'
end program main
\end{lstlisting}
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, '//'are illusions.'
end program main
\end{lstlisting}
如果一个续行不够, 我们还可以连着用, 像下面的程序这样.
\begin{lstlisting}
program main
    implicit none
    print *, 'All the Relative, are like ' &
             //'dreams, illusions, ' &
             //'bubbles, shadows, ' &
             //'dew drops and lightning flashes: ' &
             //'This is what we must believe in.'
end program main
\end{lstlisting}

我猜同学们会觉得续行的定义非常繁复难懂, 非得先定义一个 ``注释行'', 然后再巴拉巴拉, 难道不能直接定义成 ``把下面第1行剪切下来粘贴到这行后''? 还真不行. 来看下面这个程序.
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, ' &
             ! Important!
             //'are illusions.'
end program main
\end{lstlisting}
如果把续行定义成 ``把下面第1行剪切下来粘贴到这行后'', 上面那个程序就等同于下面这个程序.
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, ' ! Important!
             //'are illusions.'
end program main
\end{lstlisting}
这个程序无法工作. 实际上按正确定义, 上面上面那个程序等同于下面这个程序 (注释被忽略), 没有问题.
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, '//'are illusions.'
end program main
\end{lstlisting}

续行有许多需要补充说明的. 首先注释相比续行是优先的, 所以下面这个程序不行, 因为 ``\ttt{\&{}}'' 在 ``\ttt{!}'' 后, 被当成注释了 (笔记里显示成斜体 ``\emph{\ttt{\&{}}}'').
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, ' ! --- The Buddha &
             //'are illusions.'
end program main
\end{lstlisting}
写成下面这样才行, 注释被删去后可以成功续行.
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, ' & ! --- The Buddha
             //'are illusions.'
end program main
\end{lstlisting}
\ref{fortran_char} 节和 \ref{character_string_edit_descriptor} 小节中还有其他续行失效的例子.

乱用续行搞幺蛾子可不行. 来看下面这个程序. 这个程序没有错, 但让人看得晕乎.
\begin{lstlisting}
program &
main
    implicit &
    none
    print &
    * &
    , & 
    'All Phenomena, '//'are illusions.'
end &
program &
main
\end{lstlisting}
为了不晕乎, 我们首先要尽量少用续行 (把 \ttt{program main} 硬拆成两行简直是犯罪), 其次我们不应该在不应加空格的地方用续行 (比如在 \ttt{*,} 之间用续行拆成两半).
\begin{convention}
    只在不用续行就无法遵守规范 \emph{\ref{fortran_no_more_than_80}} 的时候使用续行.
\end{convention}
\begin{convention}
    不在不应加空格的地方使用续行.
\end{convention}

有同学可能会突然发现不对, 本章的许多程序不符合规范 \ref{fortran_indent}, 例如本章的第一个程序. 其实用续行的时候, 规范 \ref{fortran_indent} 可以不遵守. 本章的第一个程序采用的那种缩进方式, 是让下一行和上一行 ``内容对齐'', 这也是续行时常用的方式. 具体到本章的第一个程序, 就是让 \ttt{*,} 所在的行的下一行的第一个非空格字符, 和 \ttt{*,} 之后的第一个非空格字符对齐. 不过续行时的缩进还有其他方式. 同学们可以参考本笔记,  Fortran \href{https://fortran-lang.org/}{官网}上的代码示例和 Python 的 \href{https://peps.python.org/pep-0008/}{PEP 8 规范}来决定续行时如何缩进.

续行还有一个冷门规则, 在程序的每一行中, 都不能除空格和注释外, 单单只有一个用于续行的 ``\ttt{\&{}}''. 比如下面这个程序是不行的. 然而 Ifx 和 Gfortran 却认为这个程序没问题, 同学们遇到器规啦!
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, '//'are illusions.'
    & ! --- The Buddha
end program main
\end{lstlisting}
至于为什么有这么个怪怪规则, 我猜是由于其他我不想讲的续行规则, 如果不定这个规则, 代码就可能会出现歧义. 而为什么那些续行规则我不想讲, 是因为那些规则会让同学们不遵守规范搞幺蛾子, 写出读不懂的程序来.

\subsection{标号}

除后面会说的例外情况外, 在程序中的每一行的开头都可以加上标号 (label). 标号是用不超过五个数字表示的 1--99999 的整数, 开头可以是 0, 后面需要跟至少一个空格. 习惯上使用四位数的, 整千或整百的标号. 下面的程序第3行使用了标号.
\begin{lstlisting}
program main
    implicit none
    1000 print *, 'label'
end program main
\end{lstlisting}

显然在一个程序单元内不能有重复的标号, 比如下面这个程序不成 (两个标号都是 9999).
\begin{lstlisting}
program main
    implicit none
    09999 print *, '09999'
    9999  print *, '9999'
end program main
\end{lstlisting}

注释行不被允许加标号, 原因可能和续行一样, 也是会有歧义 (但这回 Ifx 和 Gfortran 的器规也是不允许). 另外续行符 ``\ttt{\&{}}'' 所在的行的下面第一个不是注释行的行, 也就是 ``用于续行的行'', 也不被允许加标号, 因为 ``用于续行的行'' 其实只是续行符 ``\ttt{\&{}}'' 所在的行的后面部分, 它的开头其实不是一行的开头. 以上规则的反例如下.
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, '//'are illusions.'
    1000 ! --- The Buddha
end program main
\end{lstlisting}
\begin{lstlisting}
program main
    implicit none
    print *, 'All Phenomena, ' &
             ! Important!
             1000 //'are illusions.'
end program main
\end{lstlisting}

标号是 Fortran 老得要死的语法, 不是个好东西. 首先, 标号只是个数字, 没法儿为理解标号所在的那行是干什么用的提供任何有效信息. 更要命的是, 使用标号写出来的程序极可能不符合结构化原则. 请同学们根据规范 \ref{no_no_structured} 少用标号.

\subsection{标签}

在程序中某些特殊的地方 (见 \ref{fortran_exit} 小节和 \ref{fortran_cycle} 小节) 可以加标签\footnote{\href{https://j3-fortran.org/doc/year/24/24-007.pdf}{标准解释文档}里没这个词, 但\href{https://fortran-lang.org/learn/quickstart/}{官网教程}里有.} (tag). 标签用其标签名表示. 下面的程序第4行和第6行使用了标签名为 \ttt{tag} 的标签.
\begin{lstlisting}
program main
    implicit none
    integer :: i
    tag: do i = 1, 1
        print *, 'tag'
    end do tag
end program main
\end{lstlisting}

显然在一个程序单元内也不能有重复的标签, 比如下面这个程序不成.
\begin{lstlisting}
program main
    implicit none
    integer :: i, j
    tag: do i = 1, 1
        tag: do j = 1, 1
            print *, 'tag'
        end do tag
    end do tag
end program main
\end{lstlisting}

标签不像标号, 是好东西. 同学们可以尽情使用.
