Fortran初学者避坑指北(此篇连续加载)

  1. Fortran的save属性
  2. Fortran中函数块传递参数
  3. Fortran奇葩的指针

Fortran的save属性

save变量简介

函数子程序或子例行程序中用到的所有变量,在被调用前通常都没有确定的存储单元,每当子程序被调用时才临时分配存储单元,而且在退出子程序时这些存储单元又都被释放并重新分配另作它用。所以这些变量的值不被保留。在下次进入子程序时,给这些变量分配的可能是另外一些存储单元,上次调用时这些变量所具有的值已不复存在。我们称这些变量在子程序未调用时是无定义的。
在函数或子例行程序中可以使用save说明语句来指定子程序中的某些变量的存储单元不被释放,它们的内容在退出子程序期间保持不变,在下次调用时仍可使用。save属性的变量在下次调用函数时,依然保留上一次的值。

声明方式:

一般情况声明:

integer , save :: a = 1

另一种默认save属性的声明:

integer :: a = 1

注:虽然第二种没有说明save 属性。但在子程序中初始化的变量,则默认具有 save 属性,两者是等价的。

MODULE中变量save问题

module global

    implicit none
    integer::a
end module

program ex
    use global 
    implicit none

    a=10
    call sub()
    write(*,*)a
    end
    subroutine sub()
    use global 
    implicit none
    a=20
    write(*,*) a
    return
end subroutine

此代码来自于Fortran论坛
很明显module 中的变量 a 也具有save属性,use之后代码的a是同一个 a。module的作用域类似C++的命名空间namespace,只在"using namespace"的程序单元。

在这里插入图片描述

在这里插入图片描述
语法中也没明确说 module 变量一定具有 save 属性,彭国伦在书中提到,moudle中变量可能有编译器的认定有关,可能有save功能,但不能完全肯定(由于moudle类似于C++的名称空间或static变量,type自定义变量又类似于C++结构体,编译器的也很难不这样认定)。
可见其心里还是默认module中变量为局部变量。既然存在争议,那么建议使用全局变量时尽量加上save或者common,另外如果希望其没有全局变量的属性,那么建议不要用moudle定义的变量。

Fortran中函数块传递参数

C语言、C++以及其他一些编程语言,其函数一般用作传值使用(函数会对形参和中间变量重新分配空间,实参把值传给形参,但没有传地址,即对实参的修改无效,生成临时变量),当然也可以采用指针进行地址的传递。
但Fortran在向子程序subroutine或函数Function中传递参数时并非值传递,而是地址传递,因此在函数块内修改变量的值会使得主调函数中的变量的值也发生改变。
彭国伦一书是这样讲的,间接指出了Function也是传址使用,指出改变地址中数值的时候一般采用subroutine,function只用来读取数值,不做出改变(一般这么操作罢了)。
在这里插入图片描述
在这里插入图片描述
有时我们需要将形参声明为只读,以保证主调函数端的变量值不发生改变。这时就需要用到intent关键字。intent用于修饰形参的属性,常在函数块(subroutine和function)中定义(声明)形参时出现,形参的属性可声明为以下三种:

  • intent(in) :将变量声明为只读,若在函数块中修改了其值,编译时会报错
  • intent(out):将变量指定为要输出的变量,若在函数块中没有赋一个新的值给它,则编译时会报错 intent(inout)
  • intent(inout):将变量声明为可读可写的变量,当intent缺省时默认为intent(inout)
subroutine example(a, b, c, d)
	integer, intent(in) :: a
	integer, intent(out) :: b
	integer, intent(inout) :: c
	integer d !默认为intent(inout)
	....
	do some work
	...
end subroutine example

Fortran指针

救命,为什么Fortran在指针上抖机灵,与C++指针存在这么大区别,在C++里面指针是一个独立的变量,它可以保存它所指向的变量在内存中的地址,有了取地址和取值符号,相当的灵活方便。然而事实证明,Fortran并非存储地址(然而彭国伦书上写其存储变量所在地址,甚至书中多处说指针所占内存为4个字节),下面让我们看看到底指针是怎么回事。
Fortran指针并不是存储变量的地址,而更像是代表这个变量的别名,相当与C语言中的引用。通过指针,同一个变量存储单元可以通过多个变量名(指针)来访问。即可以认为Fortran的指针目的在于共享变量的内存地址,方便引用。
下面我们来看看彭国伦一书中说法:
在这里插入图片描述
这里来看,彭国伦这里还是C++的思维,指针里面存储的是变量的地址,地址为整数4个字节,似乎说得通。

program name
    implicit none
    
    real(kind=8), pointer :: a

    real(kind=8), target :: b = 1.0
    
    a => b

    write(*, *) b, "b的地址", loc(b), sizeof(b)
    write(*, *) a, "a的地址", loc(a), sizeof(a)

end program name

创建了指针a和双精度型浮点型目标b,让指针a指向变量b,输出结果。
在这里插入图片描述
可以发现a和b输出结果相同,其地址相同,且指针a和b所占的内存为8,由此可见,指针表示变量的别名的解释更加合理。

module func
    implicit none

    type person
        character(len=10) :: name 
        integer :: age
    end type
        
    type pperson
        type(person), pointer :: p
    end type

contains
    
end module func

program main  
    use func
    implicit none
    
    type(person), target :: student = person("zhangyue", 22)
    
    type(pperson) :: pt
    write(*, *) loc(pt), loc(pt%p), sizeof(pt), sizeof(pt%p)
    pt%p => student
    write(*, *) loc(pt), loc(pt%p), sizeof(pt), sizeof(pt%p)

    write(*, "(A10, I5, I8, A20, I5, I5, I5)") student, loc(student),  "sizeof(student):", &
    sizeof(student), sizeof(student%name), sizeof(student%age)
    
    write(*, *) "sizeof(pt):", sizeof(pt), "sizeof(pt%p):", sizeof(pt%p)
    
    

end program main

在这里插入图片描述

结果也很能说明问题,指针刚刚创建之时,pperson类所实例化创建的变量pt以及pt%p,畏之随机分配,如图所示loc(pt) = 6618584和loc(pt%p) = 29。当设置指针指向student时,指针的地址loc(pt%p)与loc(student)的地址相同,且两者所占内存大小均为16个字节,即sizeof(student)与sizeof(pt%p)相等。因此pt对象在实例化之后,pt%p指针也会进行初始化,在随机位置(实例中为29)生成type(person)的指针(即指针变量大小为type(person)所所占字节的大小,本例中type大小为16字节, 其中type(person).name字符串对应10个字节,type(person).age整型对应四个字节,至于为啥多两个字节,我猜测是type自定义类似于Structure结构体,其数据结构占据了2个字节)。
计算结果也显示,type(pperson)结构生成的自定义变量,其结构体指针属性type(pperson)%p与所指向的变量type(person)占据相同的存储空间(此实例中占用内存为16个字节),但是所创建的含有指针属性的type(pperson)变量占据内存仅为8个字节(不清楚为什么这些字节?)。

此代码为彭国伦Fortran95中例ex1009.f90:

module func
! person类型最少占用18 bytes
! 因为它有10个字元及两个浮点数
type person
  character(len=10) :: name
  real :: height, weight
end type
! pperson类型通常占用4 bytes
! 因为它里面只有一个指针, 指针在PC中固定使用4 bytes
type pperson
  type(person), pointer :: p
end type

contains 
subroutine sort(p)
  implicit none
  type(pperson) :: p(:)
  type(pperson) :: temp
  integer i,j,s

  s = size(p,1)
  do i=1,s-1
    do j=i+1, s
      if ( p(j)%p%height < p(i)%p%height ) then
        temp = p(i)
        p(i) = p(j)
        p(j) = temp
      end if
    end do
  end do

  return
end subroutine

end module

program ex1009
  use func
  implicit none
  type(person), target :: p(5) = (/ person("陈同学", 180.0, 75.0), &
                                    person("黄同学", 170.0, 65.0), &
                                    person("刘同学", 175.0, 80.0), &
                                    person("蔡同学", 182.0, 78.0), &
                                    person("许同学", 178.0, 70.0)  /)                                
  type(pperson) :: pt(5)
  integer i
  write(*, *) "p(1)", sizeof(p(1))   
  ! 把pt数组中的指针全部指向数组p
  forall(i=1:5)
    pt(i)%p => p(i)
  end forall
  ! 依照身高从小到大排序
  call sort(pt)
  ! 输出排序的结果
  write(* ,"(5(A8,F6.1,F5.1/))") (pt(i)%P, i=1,5)

  stop
end

在这里插入图片描述
彭国伦关于例子描述利用了C++中指针的概念,认为指针存储了所指向变量的内存地址,解释存疑???

后续还将继续更新…

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值