《FORTRAN SYNTAX》第6章 指针

(6.1) 指针的创建

指针用来保存一个内存地址来间接使用数据。当程序要读写指针变量时,会先取出指针中所保存的内存位置,再到这个内存位置读写数据。指针可以指向包括自定义数据类型在内的任何数据类型,但不管是哪种数据类型,指针变量都占用相同的内存空间(通常是4bytes),因为指针变量实际上是用来记录内存地址的。

!第一种指针的创建方法
PROGRAM main
    IMPLICIT NONE
    INTEGER,TARGET :: a = 1 !声明一个目标变量
    INTEGER,POINTER :: p !声明一个可以指向整数的指针
    p => a !p指向了a
    WRITE(*,*) p !1
    a = 2
    WRITE(*,*) p !2
    p = 3
    WRITE(*,*) a !3
END PROGRAM main
!第二种指针的创建方法
PROGRAM main
    IMPLICIT NONE
    INTEGER,POINTER :: p
    ALLOCATE(p) !配置一块内存空间给指针变量p
    p = 1 !得到内存后的指针变量p可以像整型数一样使用
    WRITE(*,*) p !1
    DEALLOCATE(p) !释放掉配置的内存
END PROGRAM main

FORTRAN的指针和C的指针有着明显的区别,当FORTRAN的指针指向某一个变量后,这个指针就相当于是这个变量的分身,使得使用这个指针和使用这个变量变得没有区别,但是如果是C的话则必须用取址符来访问指针所指向的变量。另外,如果指针指向变量,指针可以随时改变它的指向;但是如果指针是用ALLOCATE配置的,在改变指向前需要将它DEALLOCATE掉。
前面所讲的用ALLOCATABLE声明可变大小的数组和这里讲的用ALLOCATE配置空间给指针,都可以实现数组动态内存分配。它们的区别是:声明成ALLOCATABLE的数组的生存周期只在声明它的函数内,函数结束后数组会自动DEALLOCATE这些内存;如果声明成指针,等到程序结束才会自动释放掉这些内存。

!置空指针的第一种方法
INTEGER,POINTER :: p => NULL() !函数NULL()会返回一个不能使用的内存地址
!置空指针的第二种方法
INTEGER,POINTER :: p
NULLIFY(p)
!检查POINTER是否指向TARGET或POINTER是否有指向,返回值为布尔型
ASSOCIATED(POINTER,[TARGET])
!用指针高效访问高维数组中的某一个元素
INTEGER,TARGET,DIMENSION(5,5,5) :: target_arr
INTEGER,POINTER :: pointer_single
pointer_single => target_arr(2,2,2)
!用指针高效访问大型矩阵的某一部分
INTEGER,TARGET,DIMENSION(10000,10000) :: large_matrix
INTEGER,POINTER,DIMENSION(:,:) :: pointer_arr
pointer_arr => large_matrix(20:30,20:30)
!用指针高效访问自定义数据类型
TYPE person !一个person类型占28bytes
    CHARACTER(20) name
    REAL(KIND = 4) :: height,weight
END TYPE person
TYPE(person),TARGET :: target_p1,target_p2,target_temp
TYPE(person),POINTER :: pointer_p1,pointer_p2,pointer_temp
pointer_p1 => target_p1
pointer_p2 => target_p2
pointer_temp => target_temp
!交换person类型要移动28*3=84bytes空间
target_temp = target_p1
target_p1 = target_p2
target_p2 = target_temp
!交换person类型的指针要移动4*3=12bytes空间
pointer_temp = pointer_p1
pointer_p1 = pointer_p2
pointer_p2 = pointer_temp

(6.2) 指针数组

!第一种指针数组的创建方法
PROGRAM main
    IMPLICIT NONE
    INTEGER,TARGET :: target_arr(5) = (/1,2,3,4,5/)
    !INTEGER,TARGET,DIMENSION(5) :: target_arr = (/1,2,3,4,5/) !也可以这样写
    INTEGER,POINTER :: pointer_arr(:) !指针数组在声明时只需要说明它的维数就行了
    !INTEGER,POINTER,DIMENSION(:) :: pointer_arr !也可以这样写
    pointer_arr => target_arr !pointer_arr(1:5) => target_arr(1:5)
    WRITE(*,*) pointer_arr !1 2 3 4 5
    pointer_arr => target_arr(1:3) !pointer_arr(1:3) => target_arr(1:3)
    WRITE(*,*) pointer_arr !1 2 3
END PROGRAM main
!第二种指针数组的创建方法
PROGRAM main
    IMPLICIT NONE
    INTEGER,POINTER,DIMENSION(:) :: pointer_arr
    ALLOCATE(pointer_arr(5))
    pointer_arr = (/1,2,3,4,5/)
    WRITE(*,*) pointer_arr !1 2 3 4 5
    DEALLOCATE(pointer_arr)
END PROGRAM main
PROGRAM main
    IMPLICIT NONE
    INTEGER,TARGET,DIMENSION(2,2,2) :: target_arr
    INTEGER,POINTER,DIMENSION(:,:) :: pointer_arr !定义pointer_arr是2维指针数组
    DATA target_arr /1,2,3,4,5,6,7,8/
    pointer_arr => target_arr(:,:,1) !pointer_arr(i,j) => target_arr(i,j,1)
    WRITE(*,"(8I2)") pointer_arr !1 2 3 4
END PROGRAM main

(6.3) 指针与函数

指针可以作为函数参数和函数返回值,这时必须要写函数的接口块,并且指针参数在声明时不能有INTENT属性。

PROGRAM main
    IMPLICIT NONE
    
    INTERFACE
        FUNCTION get_min(pointer_arr) RESULT(min)
            INTEGER,POINTER,DIMENSION(:) :: pointer_arr
            INTEGER,POINTER :: min
            INTEGER :: i,lenth
        END FUNCTION get_min
    END INTERFACE
    
    INTEGER,TARGET,DIMENSION(5) :: target_arr = (/2,3,1,5,4/)
    INTEGER,POINTER,DIMENSION(:) :: pointer_arr
    pointer_arr => target_arr
    WRITE(*,*) get_min(pointer_arr)
END PROGRAM main

FUNCTION get_min(pointer_arr) RESULT(min)
    IMPLICIT NONE
    INTEGER,POINTER,DIMENSION(:) :: pointer_arr !pointer_arr指针作为函数参数
    INTEGER,POINTER :: min !min指针作为函数返回值
    INTEGER :: i,lenth
    lenth = SIZE(pointer_arr,1) !获取矩阵的列数(这里就是获取p的长度)
    min => pointer_arr(1) !注意不能写成“=”
    DO i = 2,lenth
        IF(min > pointer_arr(i)) THEN
            min => pointer_arr(i)
        END IF
    END DO
END FUNCTION get_min

之前讲过,可以将函数封装在模块中的,减少编写接口块带来的麻烦:

MODULE func_module
    IMPLICIT NONE
    CONTAINS
    
    FUNCTION get_min(pointer_arr) RESULT(min)
        INTEGER,POINTER,DIMENSION(:) :: pointer_arr
        INTEGER,POINTER :: min
        INTEGER :: i,lenth
        lenth = SIZE(pointer_arr,1)
        min => pointer_arr(1)
        DO i = 2,lenth
            IF(min > pointer_arr(i)) THEN
                min => pointer_arr(i)
            END IF
        END DO
    END FUNCTION get_min
    
END MODULE func_module
 
PROGRAM main
    USE func_module
    IMPLICIT NONE
    INTEGER,TARGET,DIMENSION(5) :: target_arr = (/2,3,1,5,4/)
    INTEGER,POINTER,DIMENSION(:) :: pointer_arr
    pointer_arr => target_arr
    WRITE(*,*) get_min(pointer_arr)
END PROGRAM main

(6.4) 串行结构

接下来的内容,将介绍指针的一个非常常见的应用——“串行(háng)”。串行是指多个同类的东西连接在一起。先来看一个单向串行的程序:

MODULE type_def
    IMPLICIT NONE
    TYPE node
        INTEGER :: content !节点存储的内容
        TYPE(node),POINTER :: next !指向下一个节点的指针
    END TYPE node
END MODULE type_def

PROGRAM main
    USE type_def
    IMPLICIT NONE
    TYPE(node),POINTER :: node1,node2,node3
    ALLOCATE(node1)
    ALLOCATE(node2)
    ALLOCATE(node3)
    !将数据装入节点中
    node1%content = 10
    node2%content = 20
    node3%content = 30
    !创建单向串行
    node1%next => node2
    node2%next => node3
    node3%next => NULL() !node3没有下一个可以指向了,需要置空
    !根据串行的关系访问节点存储的数据
    WRITE(*,*) node1%content !10
    WRITE(*,*) node1%next%content !20,相当于node2%content
    WRITE(*,*) node1%next%next%content !30,相当于node3%content
    DEALLOCATE(node1)
    DEALLOCATE(node2)
    DEALLOCATE(node3)
END PROGRAM main

上面这个程序只是示范了串行创建的大概原理,实际上创建串行更多用到的是下面这种搭配循环的方法:

MODULE type_def
    IMPLICIT NONE
    TYPE node
        INTEGER :: content
        TYPE(node),POINTER :: next
    END TYPE node
END MODULE type_def

PROGRAM main
    USE type_def
    IMPLICIT NONE
    TYPE(node),POINTER :: head !串行头
    TYPE(node),POINTER :: p !访问串行节点的指针变量
    INTEGER :: i,n
    WRITE(*,*) "请输入节点数量:"
    READ(*,*) n
    !节点头的数据要先设定
    ALLOCATE(head)
    head%next => NULL()
    WRITE(*,*) "请输入第 1个节点数据:"
    READ(*,*) head%content
    p => head
    DO i = 2,n
        ALLOCATE(p%next)
        p => p%next
        WRITE(*,"('请输入第',I2,'个节点数据:')") i
        READ(*,*) p%content
    END DO
    p%next => NULL() !串行最后一个节点的下一个需要置空
    p => head
    DO WHILE(ASSOCIATED(p))
        WRITE(*,*) p%content
        p => p%next
    END DO
END PROGRAM main

加强一下串行结构,使之成为可以沿前后两个方向移动的双向串行

MODULE type_def
    IMPLICIT NONE
    TYPE node
        INTEGER :: content
        TYPE(node),POINTER :: previous !指向上一个节点的指针
        TYPE(node),POINTER :: next !指向下一个节点的指针
    END TYPE node
END MODULE type_def

PROGRAM main
    USE type_def
    IMPLICIT NONE
    TYPE(node),POINTER :: head !串行头
    TYPE(node),POINTER :: tail !串行尾
    TYPE(node),POINTER :: p !访问串行节点的指针变量
    TYPE(node),POINTER :: p_temp
    INTEGER :: i,n
    WRITE(*,*) "请输入节点数量:"
    READ(*,*) n
    !节点头(或者节点尾)的数据要先设定
    ALLOCATE(head)
    head%previous => NULL()
    head%next => NULL()
    WRITE(*,*) "请输入第 1个节点数据:"
    READ(*,*) head%content
    p => head
    DO i = 2,n
        ALLOCATE(p%next)
        p_temp => p
        p => p%next
        p%previous => p_temp
        WRITE(*,"('请输入第',I2,'个节点数据:')") i
        READ(*,*) p%content
    END DO
    p%next => NULL()
    tail => p
    !正向输出
    p => head
    DO WHILE(ASSOCIATED(p))
        WRITE(*,*) p%content
        p => p%next
    END DO
    !反向输出
    p => tail
    DO WHILE(ASSOCIATED(p))
        WRITE(*,*) p%content
        p => p%previous
    END DO
END PROGRAM main

目前为止所介绍的串行结构都是有头有尾的结构,串行结构还有另外一种类型叫做环状串行。它可以把串行的头尾连接起来,变成一个圈:

MODULE type_def
    IMPLICIT NONE
    TYPE node
        INTEGER :: content
        TYPE(node),POINTER :: previous
        TYPE(node),POINTER :: next
    END TYPE node
END MODULE type_def

PROGRAM main
    USE type_def
    IMPLICIT NONE
    TYPE(node),POINTER :: node1,node2,node3
    TYPE(node),POINTER :: p
    INTEGER :: n = 6 !环状串行可以一直向前或者向后抓取数据,n可以设置成任意大小
    INTEGER :: i
    ALLOCATE(node1)
    ALLOCATE(node2)
    ALLOCATE(node3)
    node1 = node(10,node3,node2)
    node2 = node(20,node1,node3)
    node3 = node(30,node2,node1)
    !正向输出
    p => node1
    DO i = 1,n
        WRITE(*,*) p%content
        p => p%next
    END DO
    !反向输出
    p => node3
    DO i = 1,n
        WRITE(*,*) p%content
        p => p%previous
    END DO
    DEALLOCATE(node1)
    DEALLOCATE(node2)
    DEALLOCATE(node3)
END PROGRAM main

利用串行可以实现快速地插入或删除一条数据:

MODULE nodes_module
    IMPLICIT NONE
    
    TYPE node
        INTEGER :: content
        TYPE(node),POINTER :: previous
        TYPE(node),POINTER :: next
    END TYPE node
    
    CONTAINS
    
    !在pos之前插入new_node(pos%previous、new_node、pos)
    SUBROUTINE insert_before_node(pos,new_node)
        TYPE(node),POINTER :: pos
        TYPE(node),POINTER :: new_node
        new_node%next =>pos
        new_node%previous => pos%previous
        IF(ASSOCIATED(pos%previous)) pos%previous%next => new_node
        pos%previous => new_node
    END SUBROUTINE insert_before_node
    
    !在pos之后插入new_node(pos、new_node、pos%next)
    SUBROUTINE insert_after_node(pos,new_node)
        TYPE(node),POINTER :: pos
        TYPE(node),POINTER :: new_node
        new_node%next => pos%next
        new_node%previous => pos
        IF(ASSOCIATED(pos%next)) pos%next%previous => new_node
        pos%next => new_node
    END SUBROUTINE insert_after_node
    
    !删除pos节点(pos%previous、pos、pos%next)
    SUBROUTINE delete_node(pos)
        TYPE(node),POINTER :: pos
        TYPE(node),POINTER :: previous
        TYPE(node),POINTER :: next
        previous => pos%previous
        next => pos%next
        DEALLOCATE(pos)
        !重新连接pos释放后的前后两个节点需要注意两点问题:
        !1.如果pos是第一条数据,不需要向前重连
        !2.如果pos是最后一条数据,不需要向后重连
        IF(ASSOCIATED(previous)) previous%next => next
        IF(ASSOCIATED(next)) next%previous => previous
        pos => next
    END SUBROUTINE delete_node
    
    !输出串行
    SUBROUTINE print_nodes(head)
        TYPE(node),POINTER :: head
        TYPE(node),POINTER :: p
        p => head
        DO WHILE(ASSOCIATED(p))
            WRITE(*,*) p%content
            p => p%next
        END DO
    END SUBROUTINE print_nodes
    
    !释放整个串行的内存
    SUBROUTINE delete_nodes(head)
        TYPE(node),POINTER :: head,p_temp
        DO WHILE(ASSOCIATED(head))
            p_temp => head%next
            DEALLOCATE(head)
            head => p_temp
        END DO
    END SUBROUTINE delete_nodes
    
END MODULE nodes_module

PROGRAM main
    USE nodes_module
    IMPLICIT NONE
    TYPE(node),POINTER :: head
    TYPE(node),POINTER :: pos
    TYPE(node),POINTER :: p,p_temp
    INTEGER i,n
    WRITE(*,*) "请输入节点数量:"
    READ(*,*) n
    ALLOCATE(head)
    head%previous => NULL()
    head%next => NULL()
    WRITE(*,*) "请输入第 1个节点数据:"
    READ(*,*) head%content
    p => head
    DO i = 2,n
        ALLOCATE(p%next)
        p_temp => p
        p => p%next
        p%previous => p_temp
        WRITE(*,"('请输入第',I2,'个节点数据:')") i
        READ(*,*) p%content
    END DO
    p%next => NULL()
    CALL print_nodes(head)
    !删掉第三条数据
    CALL delete_node(head%next%next)
    CALL print_nodes(head)
    !在第三个位置插入数据
    ALLOCATE(pos)
    pos%content = 30
    CALL insert_after_node(head%next,pos)
    CALL print_nodes(head)
    !释放整个串行的内存
    CALL delete_node(head)
END PROGRAM main

在读取文件时,有时候我们事先不确定文件中会有多少条数据,就不能用数组来读取,因为不知道数组该声明成多大,这时可以用串行来很好地解决这个问题。现在有两个文件数据data1.txtdata2.txt记录了两个人数不同的班级学生的考试成绩,截取其中一部分如下图所示,现在要编写一个可以读取成绩的程序,让用户输入文件名来决定要读取哪一个文件,还要提供给用户通过座位号来查询成绩的功能。

在这里插入图片描述

MODULE nodes_module
    IMPLICIT NONE
    
    TYPE student
        INTEGER :: id
        INTEGER :: chinese
        INTEGER :: math
        INTEGER :: english
    END TYPE student
    
    TYPE node
        TYPE(student) :: s
        TYPE(node),POINTER :: next
    END TYPE node
    
    CONTAINS
    
    !根据id查找学生并返回串行位置
    FUNCTION search_student(head,id) RESULT(pos)
        TYPE(node),POINTER :: head
        INTEGER :: id
        TYPE(node),POINTER :: pos
        pos => NULL()
        DO WHILE(ASSOCIATED(head))
            IF(head%s%id == id) THEN
                pos => head
                RETURN
            END IF
            head => head%next
        END DO
    END FUNCTION search_student
    
END MODULE nodes_module

PROGRAM main
    USE nodes_module
    IMPLICIT NONE
    CHARACTER(20) :: file_name
    CHARACTER(79) :: header !记录文件的第一行
    TYPE(node),POINTER :: head,p,pos
    INTEGER :: id,n,read_state = 0
    WRITE(*,*) "请输入文件名:"
    READ(*,*) file_name
    OPEN(10,file = file_name,STATUS = 'old')
    READ(10,"(A79)") header
    ALLOCATE(head)
    head%next => NULL()
    READ(10,*) head%s
    p => head
    n = 0 !记录学生人数
    DO WHILE(read_state == 0)
        ALLOCATE(p%next)
        p => p%next
        n = n + 1
        READ(10,FMT = *,IOSTAT = read_state) p%s
    END DO
    WRITE(*,"('一共有',I2,'名学生。')") n
    WRITE(*,*) "请输入想要查询的学生的ID:"
    READ(*,*) id
    pos => search_student(head,id)
    IF(ASSOCIATED(pos)) THEN
        WRITE(*,"('查询结果如下:',/,A79)") header
        WRITE(*,"(4(I3,2X))") pos%s%id,pos%s%chinese,pos%s%math,pos%s%english
    ELSE
        WRITE(*,*) "未找到该学生!"
    END IF
END PROGRAM main

《 F O R T R A N   S Y N T A X 》 系 列 博 客 创 作 参 考 资 料 来 源 《FORTRAN\ SYNTAX》系列博客创作参考资料来源 FORTRAN SYNTAX

  1. 《Fortran95程序设计》.彭国伦.中国电力出版社.
  2. 《工程分析程序设计》.陈斌、周屈兰.西安交通大学出版社.
  3. 《Fortran程序设计(第四版)》.Stephen J.Chapman.中国电力出版社.

博 客 创 作 : A i d e n   L e e 博客创作:Aiden\ Lee Aiden Lee
特别声明:文章仅供学习参考,转载请注明出处,严禁盗用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值