Skip to content

Commit e26d4a7

Browse files
authored
init upload
1 parent 14848d6 commit e26d4a7

12 files changed

Lines changed: 4012 additions & 0 deletions

ForNotes.pdf

528 KB
Binary file not shown.

ForNotes.tex

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
% 编译方式: xelatex*2
2+
\documentclass{ctexbook}
3+
\usepackage{amsfonts}
4+
\usepackage{amsmath}
5+
\usepackage{amssymb}
6+
\usepackage{hyperref}
7+
\usepackage{syntonly}
8+
%\syntaxonly
9+
\pagestyle{plain}
10+
\makeatletter
11+
\newcommand{\starttoc}{
12+
\chapter*{\contentsname}
13+
\thispagestyle{empty}\@starttoc{toc}\thispagestyle{empty}
14+
}
15+
\makeatother
16+
\renewcommand{\tableofcontents}{\twocolumn\starttoc\onecolumn}
17+
\hypersetup{
18+
colorlinks,
19+
linkcolor=blue,
20+
filecolor=pink,
21+
urlcolor=cyan,
22+
citecolor=red,
23+
}
24+
\DeclareMathOperator{\sgn}{sgn}
25+
\newtheorem{principle}{规范}
26+
\title{Fortran笔记}
27+
\author{GasinAn}
28+
\begin{document}
29+
\maketitle
30+
\include{copyright.tex}
31+
\tableofcontents
32+
\setcounter{page}{1}
33+
\include{preface.tex}
34+
\include{compiler.tex}
35+
\include{basic.tex}
36+
\include{intrinsic_type.tex}
37+
\include{assignment_and_operation.tex}
38+
\include{construct.tex}
39+
\include{array.tex}
40+
\include{procedure.tex}
41+
\include{io.tex}
42+
\end{document}

array.tex

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
\chapter{数组}\label{fortran_array}
2+
3+
迄今为止我们折腾的东东都是标量(scalar), 那都是小case, 大case是数组(array). 用非常严谨的方式来讨论数组, 以我这语文水平肯定是不行滴, 讲着讲着同志们就头疼, 所以我接下来讲的内容会不太严谨, 但对于实际应用来说肯定是不成问题的.
4+
5+
数组是形如$a\!:\!\{j_1,\dots,k_1\}\!\times\!\dots\!\times\!\{j_n,\dots,k_n\}\rightarrow\mathbb{C},(s_1,\dots,s_n)\mapsto a_{s_1\dots s_n}$的东东. 正整数$n$称为数组$a$的维数/秩(rank). 任意$i\in\{1,\dots,n\}$, $j_i$$k_i$称为数组$a$的第$i$个维度(dimension)的下界(lower-bound)和上界(upper-bound), $k_i-j_i+1$称为数组$a$的第$i$个维度的长度(extent). 矢量$(k_1-j_1+1,\dots,k_n-j_n+1)$称为数组$a$的形状(shape), 其本身是一维数组. $\prod_{i=1}^n(k_i-j_i+1)$称为数组$a$的大小(size). 标量可以当成维数为$0$的数组.
6+
7+
形如$a_{s_1\dots s_n}$的东东称为数组$a$中的元素(element). 任意$i\in\{1,\dots,n\}$, 称$s_i$$a_{s_1\dots s_n}$的第$i$个下标(subscript)或第$i$个索引(index). 同一个数组中的所有元素都有相同的类型和种别.
8+
9+
数组中的元素有一个被规定死的排列顺序: 任意$a_{s_1\dots s_n}$$a_{t_1\dots t_n}$, 当$s_{i+1}=t_{i+1},\dots,s_n=t_n$时, 若$s_i<t_i$, 则$a_{s_1\dots s_n}$$a_{t_1\dots t_n}$前面, 若$s_i>t_i$, 则$a_{s_1\dots s_n}$$a_{t_1\dots t_n}$后面\footnote{这和Matlab的默认情形一样, 但和C/Python的默认情形不一样!}.
10+
11+
\section{数组声明}\label{fortran_array_specification}
12+
13+
用数组之前当然要声明它. 数组的声明和标量的声明还是很像的, 也是需要给一个数据类型(整型/实型/复型), 还可以给一个种别. 数组的类型和种别就是数组中每一个元素的类型和种别.
14+
15+
为表明声明的变量是个数组, 还需给出$j_1,\dots,j_n,k_1,\dots,k_n$. 有两种方法可行, 一种是在变量名后加\verb|(j1:k1,...,jn:kn)|, 另一种是在类型和种别后加\verb|,dimension(j1:k1,...,jn:kn)|. 所有\verb|ji|, \verb|ki|都必须是整型常量或整型常量表达式\footnote{
16+
就是只含整型常量的表达式.
17+
}. 可以省略任意\verb|ji:|, 如果省略\verb|ji:|, 则\verb|ji|为\verb|1|\footnote{这和Matlab的默认情形一样, 但和C/Python的默认情形不一样!}.
18+
19+
请大家猛戳\href{https://fortran-lang.org/learn/quickstart/arrays_strings#array-declaration}{这个链接}获取数组声明示例.
20+
21+
选择哪种声明方式呢? 一般大家喜欢用第一种方式, 毕竟少打几个字, 而且看着比较简洁. 但如果是要一口气声明一堆一样的数组\footnote{
22+
方法同标量声明.
23+
}, 这时第一种方式反而不好使了, 大家就喜欢用第二种方式.
24+
25+
和字符串一样, 有些时候我们并不清楚到底要用一个什么样的数组, 这时我们就可以和字符串一样, 先在类型和种别后加\verb|,allocatable|, 然后再在变量名后加\verb|(:,...,:)|或在类型和种别后加\verb|,dimension(:,...,:)|, 其中\verb|:|的个数等于数组的维数, 这样我们就搞出一个延迟形状数组(deferred-shape array). 假如我们声明的数组叫\verb|a|, 接下来我们就可以像延迟长度字符串那样, 用\verb|allocate(a(j1:k1,...,jn:kn))|把$j_1,\dots,j_n,k_1,\dots,k_n$确定下来(仍可省略\verb|ji:|), 用\verb|deallocate(a)|将$j_1,\dots,j_n,k_1,\dots,k_n$重新变成未定义的, 然后再来个\verb|allocate(a(j1:k1,...,jn:kn))|\dots
26+
27+
请大家猛戳\href{https://fortran-lang.org/learn/quickstart/arrays_strings#allocatable-dynamic-arrays}{这个链接}获取延迟形状数组声明示例.
28+
29+
\section{数组构造}
30+
31+
数组构造器(array constructor)是一维数组常量, 形如\verb|[e1,...,em]|, 其中\verb|ei|可以是标量也可以是数组. 若将\verb|[e1,...,em]|记为$(a_1,\dots,a_n)$, \verb|ei|的大小记为$S_i$\footnote{
32+
标量的大小当然是$1$.
33+
}, 则$a_{(S_1+\dots+S_{i-1}+l)}$\verb|ei|的第$l$个元素. 如果\verb|ei|维数大于$1$, 则按本章开头``数组元素顺序''找到第$l$个元素.
34+
35+
直接了当的说法是: 把所有\verb|ei|重新排成一维数组并保证元素顺序不变, 然后首尾相接拼起来.
36+
37+
在数组构造器\underline{中}$\!$$\!$还可以使用一种神奇的隐式do循环(implied \verb|do| loop), 这玩意儿形如\verb|((...(e(i1,...,in),i1=p1,q1,r1)...),in=pn,qn,rn)|, 里头\verb|e(i1,...,in)|是个数据实体, 通常是个含有\verb|i1|,\dots ,\verb|in|的表达式, \verb|i1|,\dots ,\verb|in|是一堆事先声明过的整型变量, 整个隐式do循环相当于\underline{一维}$\!$$\!$数组. 下面这样的两个程序总是等价的\footnote{
38+
看懂这两个程序, 同志们可能需要先读\ref{fortran_array_assignment}节.
39+
}. 自然任意\verb|,ri|都可以省略(默认为\verb|1|).
40+
\begin{verbatim}
41+
program normal_do_loop
42+
...
43+
integer :: i1
44+
...
45+
integer :: in
46+
integer :: i
47+
i = 0
48+
do in = pn, qn, rn
49+
...
50+
do i1 = p1, q1, r1
51+
i = i+1
52+
a(i) = e(i1,...,in) ! a is [a(1),...,a(S)].
53+
end do
54+
...
55+
end do
56+
! Now i == S.
57+
end program normal_do_loop
58+
\end{verbatim}
59+
\begin{verbatim}
60+
program implied_do_loop
61+
...
62+
integer :: i1
63+
...
64+
integer :: in
65+
a = [((...(e(i1,...,in),i1=p1,q1,r1)...),in=pn,qn,rn)]
66+
end program implied_do_loop
67+
\end{verbatim}
68+
69+
隐式do循环属于比较高级的语法, 还是稍稍让人不好理解滴. 幸好不用隐式do循环也总是可以完成任务, 所以可以干脆不用. Python中也有一个类似的叫列表推导式的东东, 不过当年老师好像根本没讲过, 我也只是在耍帅时会用这玩楞.
70+
71+
用Ifort的话, 可以用\verb|p:q:r|代替\verb|(i1,i1=p,q,r)|, 当然\verb|:r|可以省略. 这是Ifort的器规, Gfortran是不认的\footnote{
72+
所以造轮子时最好不用这东东.
73+
}. \verb|(i1,i1=p,q,r)|这样的一重简单隐式do循环比较常用, 是要掌握的. 我再把上一对示例程序的简单情形重新列一下, 其中\verb|a(i) = i1|的意思就是令$a_i$为那时的$i_1$.
74+
\begin{verbatim}
75+
program normal_do_loop
76+
...
77+
integer :: i1
78+
integer :: i
79+
i = 0
80+
do i1 = p, q, r
81+
i = i+1
82+
a(i) = i1
83+
end do
84+
end program normal_do_loop
85+
\end{verbatim}
86+
\begin{verbatim}
87+
program implied_do_loop
88+
...
89+
integer :: i1
90+
a = [(i1,i1=p,q,r)]
91+
end program implied_do_loop
92+
\end{verbatim}
93+
举个更具体的例子: 输出$1$$9$中奇数的平方.
94+
\begin{verbatim}
95+
program main
96+
integer :: i, odd_squares(5)
97+
! integer :: s
98+
! s = 1
99+
! do i = 1, 9, 2
100+
! odd_squares(s) = i**2
101+
! s = s+1
102+
! end do
103+
odd_squares = [(i**2, i=1,9,2)]
104+
print *, odd_squares
105+
end program main
106+
\end{verbatim}
107+
108+
我们可以对先前构造出来的一维数组进行变形(reshape)操作来获取多维数组, 只需来个\verb|a_new = reshape(a_old,s)|就成. \verb|a_new|和\verb|a_old|是两个数组, \verb|s|是\verb|a_new|的形状(当然得是整型一维数组), \verb|a_new|的第$l$个元素和\verb|a_old|的第$l$个元素总是相同的\footnote{
109+
不考虑赋值时的类型和种别转化的情况下.
110+
}. 整个变形操作说白了就是: 将数组\verb|a_old|中的元素复制到形状为\verb|s|的数组\verb|a_new|中, 并保证元素顺序不变, 比如下面这样.
111+
\begin{verbatim}
112+
program main
113+
implicit none
114+
integer :: one2four(2,2) ! a11=1 a12=3
115+
one2four = reshape([1,2,3,4],[2,2]) ! a21=2 a22=4
116+
end program main
117+
\end{verbatim}
118+
119+
\section{数组切片}
120+
121+
数组切片(slicing)是用一个数组得到另一个数组的操作. 现假设有一个维数为\verb|n|的数组\verb|a|, 则\verb|a(e1,...,en)|是另一个数组, 其中\verb|e1|,\dots ,\verb|en|乃整型一维数组或整型标量. 如何确定\verb|a(e1,...,en)|?
122+
123+
引入\verb|v1|,\dots ,\verb|vn|, 保证若\verb|ei|为数组则\verb|vi|为\verb|ei|, 若\verb|ei|为标量则\verb|vi|为\verb|[ei]|. 这样\verb|a(v1,...,vn)|等于\verb|b|. 记\verb|vi|为$(v_{i;1},\dots,v_{i;{l_i}})$, 则$b_{{\iota_1},\dots,{\iota_n}}=a_{{v_{1;\iota_1}},\dots,{v_{n;\iota_n}}}$.
124+
125+
假设\verb|e1|,\dots ,\verb|en|中\verb|ei1|,\dots ,\verb|eim|\ ($i_1\!<\!\dots\!<\!i_m$)是长度为$l_{i_1},\dots,l_{i_m}$的数组, 其他是标量, 则可将\verb|b|变形成形状为$(l_{i_1},\dots,l_{i_m})$\verb|c|, \verb|c|就是\verb|a(e1,...,en)|.
126+
127+
我本来处心积虑地想再来几段话来把这切片讲得更明白些, 然后我就放弃了, 只好先来个示例给同志们做练习. 我敢保证自己写的东东肯定是真实不虚的, 但看来是很难理解记忆了. 幸好非常复杂的数组切片一般是用不上的. 如果老师敢考那些难死人的切片, 我们就当即暴动$\!\text{\~{}}$
128+
\begin{verbatim}
129+
program main ! a000=1 a001=5 a100=2 a101=6
130+
implicit none ! a010=3 a011=7 a110=4 a111=8
131+
integer :: i, one2eight(0:1,0:1,0:1)
132+
integer :: result(1,4)
133+
one2eight = reshape([(i,i=1,8)],[2,2,2])
134+
result = one2eight(0,[0],[0,1,1,0]) ! The shape is [1,4].
135+
! result X one2eight(0,0,[0,1,1,0]) ! The shape is [4]!
136+
print *, result
137+
end program main
138+
\end{verbatim}
139+
140+
先前用向量下标(vector subscript)来切片, 我们还可以用三元下标(triplet subscript)\footnote{
141+
官方文档里用的是``subscript triplet''.
142+
}, 以\verb|p:q:r|代替\verb|(i,i=p,q,r)|, 当然\verb|:r|可以省略. 这不是器规, 是通用的. 而且三元下标中\verb|p|和\verb|q|也可以省略(但是注意\verb|p|和\verb|q|之间的\verb|:|不能省), \verb|p|省略就等于那一维的下界, \verb|q|省略就等于那一维的上界. 这样的切片简单且比较常用(尤其是省略\verb|:r|的时候), 是要掌握的. 比如我们可以方便地摘出$1$$9$中的奇数.
143+
\begin{verbatim}
144+
program main
145+
implicit none
146+
integer :: i
147+
integer :: singles(9), odds(5)
148+
singles = [(i, i=1,9)]
149+
odds = singles(::2) ! singles(1:9:2)
150+
end program main
151+
\end{verbatim}
152+
还有, 如果\verb|i1,...,in|都是整型标量, 则\verb|a(i1,...,in)|就是$a_{i_1\dots i_n}$, 这更要掌握. 问: \verb|a(i1,...,in)|和\verb|a(i1:i1,...,in:in)|有什么区别?
153+
154+
对数组切片, 可以得到一个新数组, 看起来可以对这个新数组再切片. 然而这是不成的, 原因在于对数组切片得到的新数组, 其每一维的上下界其实都是不确定的\footnote{
155+
虽然在介绍切片规则时看起来有确定的上下界, 那只是为了说话方便.
156+
}, 所以新数组中每个元素的下标都是不确定的, 因此没法切片. 同样地, 由数组构造器得到的数组也是不能切片的.
157+
158+
\section{数组运算}
159+
160+
有了数组, 总是要用数组来算些什么东西. 现在我们可以在表达式中混用数组, 标量和\ref{fortran_opration}节中的所有运算符. 运算符的优先级, 和之前是一样的, \verb|+|和\verb|-|之前如果什么也没有, 也还是默认有个\verb|0|. 只需要知道, 当运算符两边出现数组时会有什么结果, 我们就能推理出任意表达式的结果了. 这又分两种情况.
161+
\begin{itemize}
162+
\item 运算符两边都是数组.
163+
\item 运算符一边是标量, 另一边是数组.
164+
\end{itemize}
165+
166+
如果运算符两边都是数组, 我们首先必须保证这两个数组形状完全一致, 绝对一致, 这样这两个数组中的元素按所处的位置, 自然就能一一对应. 我们假设两个数组分别是$a$$b$, 并用符号$\star$表示一个运算符, 现在我们要算$a\star b$. 假设$a$$b$的下界分别为$j_{a;1},\dots,j_{a;n}$$j_{b;1},\dots,j_{b;n}$, 则首先可以搞到另外两个数组$\alpha$$\beta$, 使得$\alpha_{i_1\dots i_n}=a_{(i_1+j_{a;1})\dots(i_n+j_{a;n})}$, $\beta_{i_1\dots i_n}=b_{(i_1+j_{b;1})\dots(i_n+j_{b;n})}$, 然后可以弄出一个数组$c$, 使得$c_{i_1\dots i_n}=\alpha_{i_1\dots i_n}\star\beta_{i_1\dots i_n}$, 则$c$就是$a\star b$. 简单来说就是对$a$$b$中两两对应的元素进行$\star$运算, 得到新数组. 示例如下.
167+
\begin{verbatim}
168+
program main ! 1+5=6 3+7=10
169+
implicit none ! 2+6=8 4+8=12
170+
integer :: one2four(2,2), five2eight(2,2,1)
171+
one2four = reshape([1,2,3,4],[2,2])
172+
five2eight = reshape([5,6,7,8],[2,2,1])
173+
print *, one2four+reshape(five2eight,[2,2])
174+
! print *, one2four+five2eight (X)
175+
end program main
176+
\end{verbatim}
177+
178+
如果运算符一边是标量, 另一边是数组, 不妨设数组为$a$, 标量为$b$, 仍设运算符为$\star$. 此时若$a$的形状为$\vec{s}$, 则可以另搞一个形状为$\vec{s}$的数组$\tilde{b}$, 使得$\tilde{b}$中任意元素都是$b$, 然后就有$a\star b=a\star\tilde{b}$, $b\star a=\tilde{b}\star a$. 示例如下.
179+
\begin{verbatim}
180+
program main ! 1+9=10 3+9=12
181+
implicit none ! 2+9=11 4+9=13
182+
integer :: one2four(2,2)
183+
one2four = reshape([1,2,3,4],[2,2])
184+
print *, one2four+9
185+
end program main
186+
\end{verbatim}
187+
188+
即使表达式的结果是一个数组, 也不能对其切片, 因为此时数组的上下界依然是不确定的. 比如, 有两个一维数组, 一个下界是$1$, 一个下界是$0$, 把这俩加起来, 得到的数组上下界应该是多少? 不好规定. 有些情况下, 比如让两个下界都是$1$的一维数组相加, 看起来数组上下界是好规定的, 然而若真来个规定, 造编译器的人就得处心积虑地要让编译器能够区分这两种情形, 他们会很不开心, 很不快乐, 所以统统规定上下界不确定是好的.
189+
190+
\section{数组赋值}\label{fortran_array_assignment}
191+
192+
数组赋值可分两种, 一种``\verb|=|''右侧是数组, 另一种``\verb|=|''右侧是标量.
193+
194+
如果``\verb|=|''右侧是数组, 则俩玩楞形状必须一样滴. 假设我们要令\verb|a=b|, 并设$n$维数组$a$$b$的下界分别为$j_{a;1},\dots,j_{a;n}$$j_{b;1},\dots,j_{b;n}$, 则$a$$b$的元素自然能一一对应. 首先我们要搞一个$\tilde{b}$, 使得$\tilde{b}_{(i_1-j_{b;1}+j_{a;1})\dots(i_n-j_{b;n}+j_{a;n})}=b_{i_1\dots i_n}$, 然后令$a$等于$\tilde{b}$即可. 简单来说就是令$a$$b$中两两对应的元素相等. 这里$b$的上下界可能是不确定的, 但赋值给$a$后, $a$先前声明过, 所以其上下界一定是确定的.
195+
196+
如果``\verb|=|''右侧是标量, 比如数组是$a$, 标量是$b$, 则先把$b$变成和$a$形状相同的数组$c$, 使得$c$中任意元素都是$b$, 然后令\verb|a=c|即可.
197+
198+
对于数组, 我们还可以用一些特殊东东来赋值, 比如forall, where和do concurrent, 不过这些东东我自己貌似会用, 却研究不出它们的明确规则. 我计划在第\ref{fortran_parallel_conpute}章介绍它们.

0 commit comments

Comments
 (0)