Fortran入门教程(八)——子例程及函数
过程
在写程序时常常会有一些代码块需要经常使用, 我们可以将其封装起来,封装好后的代码块可以统一调用,减少代码的书写量,提高程序的可读性、拓展性等。
1. 子例程 (subroutine)
1.1 语法
封装一个子例程按如下语法封装
subroutine subroutine_name([argument1[, argument2, ..., argumentn]])argument_type1 :: argument1argument_type2 :: argument2...argument_typen :: argumentn!> do somethingreturn
end subroutine
argument1, ..., argumentn
为子例程的参数。
在子例程代码开始前,需要指定每一个参数的类型,以便编译器为函数申请内存空间。
子例程, 使用时, 需要使用call
语句
call subroutine_name([argument1[, argument2, ..., argumentn]]
子例程可以在源文件的任意位置定义, 但是不可以在program
及subroutine
, function
内定义。
在单文件内编写子历程及函数时, 尽量将 program 代码放置到整个文件的最前面, subroutine 及 function 在 program 后面。这是为了方便用户阅读代码的主体逻辑。
子例程遇到 return
语句后会立刻退出, 但一个子例程并不一定需要编写 return
语句, 当子例程内的所有代码被运行完后,会自动退出。
下面的程序将输出Hello World!
封装成子例程并且调用:
!> program 9-1
program sayHelloimplicit noneinteger :: ido i = 1, 5call say()end doend programsubroutine say()write(*, *) "Hello World!"return
end subroutine
1.2 带参数的子例程
一个代码块如果单纯只能运行一串特定的语句,很难被我们灵活的使用,比如说如果要输出一个给定的整数, 我们并不需要为每一个整数都编写子例程, 而是可以为子例程传参, 让子例程根据传入的参数运行。
!> program 9-2
program subroutine_demoimplicit noneinteger :: iwrite(*, *) "Enter an integer>>>"read(*, *) icall show_integer(i)
end programsubroutine show_integer(i)integer :: iwrite(*, *) "The number is", i
end subroutine show_integer
或是让两个整数的数值交换
!>program 9-3
program swap_demoimplicit noneinteger :: a, ba = 3b = 2write(*, *) "a = ", a, "b = ", bcall swap(a, b)write(*, *) "a = ", a, "b = ", bend program swap_demosubroutine swap(a, b)implicit noneinteger :: a, binteger :: tt = aa = bb = t
end subroutine
2. 函数 (function)
函数与子例程一样, 也是一种封装代码的方式, 但是与子例程不同的是, 函数有返回值。即函数内的代码块运行结束后, 可以返回一个值作为该函数的值
。
2.1 函数的定义
function function_name([argument1[, argument2[, ...], argumentn])argument_type1 :: argument1argument_type2 :: argument2...argument_typen :: argumentnreturn_type :: function_name!> do somethingend function
函数在定义时也需要像子例程一样给定每一个传入参数的数据类型。
特别的,函数还需要给出返回值的数据类型, 且返回值的变量名称与函数的名称一致。
下面的程序定义一个函数将两个整数相加:
!> program 9-4
function add(a, b)!> Add two integer and return the result.integer :: a, binteger :: add !> give the return type of this function.add = a + b
end functionprogram add_demoimplicit noneinteger :: a, bwrite(*, *) "Input two integer splited by space>>>"read(*, *) a, bwrite(*, *) add(a, b)
end program add_demo
函数在使用时需要注意以下问题:
1. 函数定义在主程序之后(不在模块内)
如:
program add_demoimplicit noneinteger :: a, bwrite(*, *) "Input two integer splited by space>>>"read(*, *) a, bwrite(*, *) add(a, b)end program add_demofunction add(a, b)integer :: a, binteger :: add !> give the return type of this function.add = a + b
end function
执行该代码, 程序会报错
6 | write(*, *) add(a, b)| 1
Error: Function 'add' at (1) has no IMPLICIT type
由于函数代码行在主程序之后, 程序在被编译时, 主程序还不知道函数add
的返回值是什么类型,因此无法合理分配内存。
因此需要在主程序内对使用到的函数进行声明, 并用external
修饰词表明该函数定义在主程序后。
因此, 只需要在程序第三、四行之间加上语句
integer, external :: add
程序即可运行。
2. 函数定义时可以在定义时直接表明返回值类型
如:
integer function add(a, b)integer :: a, badd = a + bend function
3. 函数定义时也可以指定一个变量来表示其返回值
需要使用result
语句
如:
integer function add(a, b) result(r)integer :: ainteger :: binteger :: rr = a + b
end funciton
3. 参数 (argument)
3.1 作用域
在上面的程序9-4
中, 可以看到有program
和函数add
内都有整数变量a
和b
, 为了防止subroutine, function
在编写时用到的变量与program
中的变量产生冲突, 每一个变量都有其作用域, 即变量名可以被使用的范围。
即:
在
program, function, subroutine
中定义的变量名只能在其内部使用。不传递参数的情况下, 不同
program, function, subroutine
中定义的变量之间互不影响, 可以使用相同的变量名。
对于第一点
如:
program mainimplicit noneinteger :: a = 5 ! declated at main programend programsubroutine exampleimplicit nonewrite(*, *) a ! used at example subroutine
end subroutine
该程序会报错
11 | write(*, *) a ! used at example subroutine| 1
Error: Symbol 'a' at (1) has no IMPLICIT type
这是因为在progarm
中定义的变量作用域只在program
中。
对于第二点
如:
program mainimplicit noneinteger :: a = 5write(*, *) "Porgram Main: before call subroutine, the value of a is:", acall example()write(*, *) "Porgram Main: after call subroutine, the value of a is:", a
end programsubroutine exampleimplicit noneinteger :: a = 5a = a * 5 + 10write(*, *) "Subroutine Example: the value of a is:", a
end subroutine
生命周期
一个变量被声明后, 当其所在的subroutine, function, program
结束时, 其所占有的内存空间会被删除。
如:
program count_demoimplicit noneinteger :: iinteger, parameter :: n = 5do i = 1, ncall count_one()end do
end programsubroutine count_one()!! count and show how many times the subroutine be used.implicit noneinteger :: count = 0count = count + 1write(*, *) "Count is : ", countreturn
end subroutine
会输出
Count is : 1
Count is : 1
Count is : 1
Count is : 1
Count is : 1
SAVE
为了让 count
保持不变, 可以用 SAVE 来延长变量的声明周期, 被save后的变量不会被销毁。
program count_demoimplicit noneinteger :: iinteger, parameter :: n = 5do i = 1, ncall count_one()end do
end program!! count and show how many times the subroutine be used.
subroutine count_one()implicit noneinteger, save :: count = 0 !> give the feature of savecount = count + 1write(*, *) "Count is : ", countreturn
end subroutine
输出
Count is : 1
Count is : 2
Count is : 3
Count is : 4
Count is : 5
注意
在
gfortran
编译器中, 函数中所有的变量默认都为save
类型。
3.2 实参与形参
在function/subroutine
定义时, 给定的参数为形式参数
。
而调用 function/subroutine
时, 实际给定的参数称为实际参数
。
如:
subroutine demo(a, b)^^^^ ^^^这里的a, b为实际参数...
end function demoprogram maininteger :: a, b...call demo(a, b)^^^ ^^^这里的a, b为形式参数...
end program
3.3 参数传递
在上面的程序中9-4
中, 可以看到将program
中的a
变量作为参数传递给函数add
, 这个变量a
在program
和 函数add
是否指代的是同一个内存地址呢?
我们直接给出结论
fortan 中, 所有的参数传递都是传址
这句话的含义是,如果在函数中修改形参的值, 那么实参的值也会被跟着改变, 这是因为fortran形参与实参公用同一个内存地址。
如:
!> program 9-5
program maininteger :: a = 4write(*, *) "Program Main: before subroutine, the value of a is ", acall division_by_two(a)write(*, *) "Program Main: after subroutine, the value of a is ", a
end programsubroutine division_by_two(number)integer :: numbernumber = number / 2
end subroutine
输出如下:
Program Main: before subroutine, the value of a is 4
Program Main: after subroutine, the value of a is 2
3.4 参数修饰词
由于 fortran
传递参数时是按址传递的, 因此在function, subroutine
中,某些参数是不希望运行中被改变的, 比如add(a, b)
函数中的变量a, b
。
fortran
提供了参数修饰词来让编辑器检查是否可以将某一参数改变。
3.3.1 INTENT(IN)
被intent(in)
修饰的参数在函数或子例程中如果被改变, 编译器会报错。
如:
function intent_in_test(a)integer, intent(in) :: aa = 5
end function
会报错
4 | a = 5| 1
Error: Dummy argument 'a' with INTENT(IN) in variable definition context (assignment) at (1)
对于不需要被改变的参数, 用 intent(in)
修饰是良好的习惯。
3.3.2 INTENT(OUT)
与intent(in)
类似, intent(out)
, 用intent(out)
指定的参数必须是可赋值的变量而不是表达式。
比如:
subroutine intent_out_test(a)integer, intent(out) :: awrite(*, *) a
end subroutineprogram mainimplicit nonecall intent_in_test(10)end program
将会报错, 这是因为给子例程传入的是一个表达式。
9 | call intent_out_test(10)| 1
Error: Non-variable expression in variable definition context (actual argument to INTENT = OUT/INOUT) at (1)
3.3.3 INTENT(INOUT)
被该修饰词修饰的参数既不能被修改也不能是表达式。
3.4 特殊参数
3.4.1 数组传参
- 当数组作为参数时, 如果将整个数组都传递给
function/subroutine
, 会占用大量的内存, 但是数组在内存中占用是一段连续的内存单元, 因此在传递数组时, 只需要传递数组中某一个(一般是第一个)元素的地址就可以了。 - 传递数组参数时, 由于数组是有大小的, 编译器需要知道数组的上下界以防止访问错误地址, 因此在申明数组参数时, 需要给出数组的大小
- 数组的大小也可以使用
*
默认符号申请, 通过该符号申请的数组会贪心的获取大小(传入的数组可以有多大就会申请多大)。
下面的程序一个用于打印数组元素的子例程:
!> program 9-6
subroutine print_array(array, n)!! print an array for the first n elements.implicit noneinteger, intent(in) :: array(*) ! declaction of arrayinteger, intent(in) :: n ! the number to be printedinteger :: i ! control variable of loopdo i = 1, nwrite(*, "(I0, ' ', $)") array(i)end do
end subroutineprogram maininteger, parameter :: n = 10integer :: iinteger :: array(n)array(1: 2) = [0, 1]do i = 3, narray(i) = array(i - 1) + array(i - 2)end docall print_array(array, n)
end program
程序输出如下:
0 1 1 2 3 5 8 13 21 34
如果实参数组与形参数组的纬度或是大小不对应, fortarn
程序也可以运行, 并且按照内存的顺序开数组。
如:
program maininteger, parameter :: n = 3integer :: i, jinteger :: array(n, n)do i = 1, ndo j = 1, narray(i, j) = i + jend doend docall print_array(array, 3)end program
程序会输出
0 1 2
即为数组的第一列, 因为该array二维数组的前三个地址分别为array(1, 1), array(2, 1), array(3, 1)
3.4.2 函数或子例程传参
实际上, 函数或是子例程也是内存中被存储的一段空间, 也可以被当作参数传递给函数。
如
!> program 9-7
program mainimplicit noneexternal :: sub1, sub2 !! use external to give definition of an subroutine.call call_subroutine(sub1)call call_subroutine(sub2)
end programsubroutine call_subroutine(sub)!! just use the subroutine.implicit noneexternal :: sub !! use external give definition.call sub()
end subroutinesubroutine sub1()implicit nonewrite(*, *) "Sub1: Be used!"
end subroutinesubroutine sub2()implicit nonewrite(*, *) "Sub2: Be used!"
end subroutine
3.5 更加特殊的参数
3.5.1 接口
对于满足下面条件的subroutine/function
, 由于其参数或是返回值比较特殊, 需要在使用之前告诉编译器其返回值或各个参数的定义, 称为接口interface
。
使用语法如下:
interfacefunction subroutine_name(args)arg_type, arg_declaction :: arg_name...end functionfunction function_name(args)arg_type, arg_decoration :: arg_namereturn_type, return_decoration :: function_name...end function
end interface
- 返回值为数组
- 存在可选参数
- 指定参数位置来传参
- 输入指标参数
- 返回值为指针
3.5.2 可选参数
在部分需求下, function/subroutine
的部分参数有时需要被传入, 有时不需要被传入, 这时就需要用到特殊的修饰词optional
, 表明该参数是一个可选参数。另外, 可以用present
函数来检查一个参数是否被传入。
如:
!> program 9-7program option_demoimplicit none!> This subroutine need interface to use.interfacesubroutine useless(number)integer, optional :: numberend subroutineend interfacecall useless(10)call useless()
end programsubroutine useless(number)implicit noneinteger, optional :: numberif (present(number)) thenwrite(*, *) "The optional argument number is :", numberelsewrite(*, *) "The optional argument don't find."end ifend subroutine
程序输出:
The optional argument number is : 10
The optional argument don't find.
可以使用optional
实现给参数赋默认值:
integer, optional :: a
integer :: real_a
integer :: default_value = 5if (.not. present(a)) thenreal_a = default_value
elsereal_a = a
end if
3.5.3 指定参数位置传参
在常规的函数中, 传递参数需要按照参数列表的顺序逐个传参。
但是为了程序编写的方便, fortran
允许用户更改参数传递的顺序。
如:
subroutine demo(a, b, c)integer :: a, b, c...
end subroutineprogram main...interfacesubroutine demo(a, b, c)integer :: a, b, cend subroutineend interface...call demo(b=2, a=1, c=3) !! use argument's name to pass....
end program
4. 特殊函数
4.1 递归函数
fortran
中传递参数时是按址传参的, 所以普通的函数并不能实现递归。
为此, fortran
提供了特殊的递归函数类型。
递归函数的参数是按值传递的, 修改它不会改变实参。
要按照如下方式定义:
recursive return_type function function_name(args) result(result_name)arg_type, arg_adj :: arg......
end function
在
Fortran90
标准中, 递归函数的返回值一定要使用result
下面的程序展示了用递归函数求阶乘:
!> program 9-8
program mainimplicit noneinteger, external :: factinteger :: n = 5write(*, *) fact(n)end programrecursive integer function fact(n) result(ans)!! get the result of n!!! Arguments:implicit noneinteger, intent(in) :: nif (n < 0) thenwrite(*, *) 'ERROR: function fact argument `n` should be an positive integer.'ans = -1returnelse if (n == 0) thenans = 1elseans = fact(n - 1) * nend ifend function
4.2 LOCAL 函数
fortran90
以后可以将函数归属给某一program
或是function/subroutine
, 让其他的program/function/subroutine
无法调用这个被归属的函数。
表明一个归属函数需要使用contains
语句
program/subroutine/function name[()]implicit none...containssubroutine localsub() !! This subroutine can only be used in this code block...end subroutinefunction localfunc() !! This funcion can only be used in this code block...end function localfuncend program/subroutine/function
4.3 PURE 函数
pure
函数是一类支持并行运算的函数, 它的运算速度比普通函数快, 只需要在 subroutine/function
前面增加 pure
, 但是为了支持并行运算, pure
函数必须满足以下全部条件:
- 所有的参数为
intent(in)
- 每个参数都要赋值属性
- 不能使用SAVE
- 调用的函数也必须为
pure
函数 - 不能使用
STOP
及输入输出相关的语句 - 不能改变全局变量的值
如果不满足上述条件, 在并行运算时会出现多线程同时修改、同时打印导致程序不稳定或是出现意想不到的结果。
4.4 ELEMENTAL 函数
正如其名, elemental
函数是一类专门操作数组的函数, 该类函数支持并行运算, 且会对传入的数组中每一个元素进行函数内的操作。
使用该函数需要满足pure
函数的所有条件。
该函数传入的全部参数必须有相同大小, 如果大小不同会报错。
19 | write(*, "(10I3)") multiply_by_two(a, b)| 1
Error: Different shape for elemental procedure at (1) on dimension 1 (5 and 10)
该函数会返回一个数组, 大小与传入的操作次数相同。
下面的程序将数组中的每一个元素乘以2:
!> program 9-9
program mainimplicit noneinterfaceelemental function multiply_by_two(num) result(res)implicit noneinteger, intent(in) :: numinteger :: resend functionend interfaceinteger :: iinteger :: a(10) = [(i, i = 1, 10)]write(*, "(10I3)") awrite(*, *) "After operation"write(*, "(10I3)") multiply_by_two(a)end programelemental function multiply_by_two(num) result(res)implicit noneinteger, intent(in) :: numinteger :: resres = num * 2
end function
5. 全局变量
5.1 COMMON
有时, 部分变量在整个程序运行过程中需要被多个子历程或是函数读取或是修改, 每一次都传递参数过于麻烦, fortran
提供了 common
语句来申明全局变量。
!> program 9-10
program common_demoimplicit noneinteger :: a, bcommon a, bcall print_number()a = 1b = 2call print_number()end programsubroutine print_number()implicit noneinteger :: n1, n2common n1, n2write(*, *) n1, n2
end subroutine
Fortran入门教程(八)——子例程及函数相关推荐
- Numpy入门教程:05. 逻辑函数
背景 什么是 NumPy 呢? NumPy 这个词来源于两个单词 – Numerical和Python.其是一个功能强大的 Python 库,可以帮助程序员轻松地进行数值计算,通常应用于以下场景: 执 ...
- Numpy入门教程:04. 数学函数
背景 什么是 NumPy 呢? NumPy 这个词来源于两个单词 – Numerical和Python.其是一个功能强大的 Python 库,可以帮助程序员轻松地进行数值计算,通常应用于以下场景: 执 ...
- Django入门教程(八)Form表单
有时候我们需要在前台用 get 或 post 方法提交一些数据,所以需要用到 html 表单的知识. 1.创建项目,选择File–>New project–>Django ①根据个人需要, ...
- OPENCV入门教程四:imread函数读入图像
一.目标 学习imread()函数正确读入图像的方式,imread()用不对,对以后的图像处理有很大的影响. 有时候图片是灰度图,但是你用imread()读入后它就变成了彩色图,只不过它的三个通道的值 ...
- 【Protocol Buffer】Protocol Buffer入门教程(八):Windows平台部署Protobuf环境
00. 目录 文章目录 00. 目录 01. Protobuf源码下载 02. 安装CMake 03. 生成动态库 04. 添加环境变量 05. 附录 01. Protobuf源码下载 CSDN下载: ...
- SpringCloud 入门教程(八): 断路器指标数据监控Hystrix Dashboard 和 Turbine
1. Hystrix Dashboard (断路器:hystrix 仪表盘) Hystrix一个很重要的功能是,可以通过HystrixCommand收集相关数据指标. Hystrix Dashboa ...
- 【QT】QT从零入门教程(八):QT常用控件 [QLabel、QPushButton、QLineEdit、QTextEdit]
本节介绍一些常用的控件,是在"图像处理自编软件"中用到的几种,包括QLabel.QPushButton.QLineEdit.QTextEdit.QSlider.QSpinBox ...
- 无废话ExtJs 入门教程八[脚本调试Firefox:firebug]
Firebug是一个Firefox插件,功能:HTML查看和即时编辑.控制台.网络状况等,是开发JavaScript.ExtJs的得力调试工具. 一.Firefox的安装.下载地址: htt ...
- c++ 结构体赋值_《零基础看得懂的C语言入门教程》—(十二)结构体是这么回事
一.学习目标 了解C语言的结构体的使用方法 了解C语言结构体的结构的赋值 了解多种C语言结构体变量的赋值方法和取值方法 目录 <零基础看得懂的C语言入门教程>--(二)简单带你了解流程 & ...
- 取得数组下标_《零基础C++入门教程》——(8)搞定二维数组与循环嵌套
一.学习目标 了解二维数组的使用方法 了解循环嵌套的使用方法 目录 预备第一篇,使用软件介绍在这一篇,C++与C使用的软件是一样的,查看这篇即可:<零基础看得懂的C语言入门教程>--(二) ...
最新文章
- 该文件 linux命令,Linux网络系统,如果执行行命令#chmod 746 file.txt,那么该文件的权限是?...
- 基于Hi3559A ARM64位嵌入式平台的OpenCV2.4.9+ffmpeg2.0.7移植
- 手机访问网站自动跳转到手机版
- python学习六:数据结构
- Nvidia Jetson TX2入门指南(白话版)
- Mysql读写锁保姆级图文教程
- Myeclipse连接数据库删除数据库(JDBC)
- 理解metrics.classification_report
- 软件测试第四组第一周作业第一天
- 抖音SEO,抖音排名优化,抖音排名规则
- 青蛙的约会(ojld)
- Hadoop 命令操作大全
- WIN7中文专业版安装日文语言包的方法
- matlab最炫名族风,Matlab演奏《最炫民族风》
- 求生之路2服务器消息,求生之路2服务器公告设置
- 嵌入式系统和计算机系统
- 蚂蚁即将上市:P7可获1200万元期权,千万身价!我酸了...
- gpio驱动重构版,未优化,附上测试demo
- JavaScript编程精粹
- 【鸿蒙】鸿蒙操作系统应用开发入门级初体验
热门文章
- 互不相识的人在什么情况下会给你点赞呢?
- Qt 判断集合中的元素是否全部相同
- python case when用法_oracle菜鸟学习之 select case when的使用
- 硬件厂商 Linux社区 代码,Linux企业版需加强的10个方面
- Visual Assist X 颜色配置习惯
- C++11中的原子操作(atomic operation)
- javaweb JAVA JSP学生信息管理系统源码(JSP学生成绩管理系统 学生管理 JSP学生管理系统)
- 计算机win7卡顿如何解决方法,win7系统运行卡顿的解决方法
- 下载频道用户使用指南!!
- go的编程哲学和设计理念