面试题答案
一键面试利用指针进行动态内存分配与释放
- 链表:
- 动态内存分配:在Fortran中,可以使用
ALLOCATE
语句为链表节点分配内存。假设定义链表节点类型如下:
要创建一个新节点,可以这样做:TYPE :: node_type INTEGER :: data TYPE(node_type), POINTER :: next END TYPE node_type
TYPE(node_type), POINTER :: new_node ALLOCATE(new_node) new_node%data = 10 new_node%next => NULL()
- 动态内存释放:释放链表时,需要遍历链表并逐个释放节点。
TYPE(node_type), POINTER :: current, temp current => head DO WHILE (ASSOCIATED(current)) temp => current current => current%next DEALLOCATE(temp) END DO
- 动态内存分配:在Fortran中,可以使用
- 树:
- 动态内存分配:以二叉树为例,定义树节点类型:
创建新节点:TYPE :: tree_node_type INTEGER :: data TYPE(tree_node_type), POINTER :: left TYPE(tree_node_type), POINTER :: right END TYPE tree_node_type
TYPE(tree_node_type), POINTER :: new_node ALLOCATE(new_node) new_node%data = 20 new_node%left => NULL() new_node%right => NULL()
- 动态内存释放:释放树时,可以使用后序遍历(先左子树,再右子树,最后根节点)来确保所有节点都被正确释放。
RECURSIVE SUBROUTINE free_tree(node) TYPE(tree_node_type), POINTER :: node IF (ASSOCIATED(node)) THEN CALL free_tree(node%left) CALL free_tree(node%right) DEALLOCATE(node) END IF END SUBROUTINE free_tree
内存泄漏问题分析
- 忘记释放内存:在复杂数据结构中,如果在节点创建后没有正确的释放逻辑,比如在链表插入新节点后忘记将之前的链表尾节点释放,或者在树删除节点时没有正确处理子树节点的释放,就会导致内存泄漏。
- 指针悬空:当一个指针所指向的内存被释放后,如果没有将指针设置为
NULL()
,再次使用该指针就会导致未定义行为,同时也可能导致内存泄漏。例如,在释放链表节点后没有更新前一个节点的next
指针。
避免内存泄漏及优化性能的策略
- 确保释放所有分配的内存:在程序逻辑中,仔细设计内存释放流程。对于链表和树,确保在删除操作或程序结束时,所有节点都被释放。使用像上面提到的后序遍历释放树节点的方法。
- 设置指针为NULL:在释放内存后,立即将相应指针设置为
NULL()
。例如在释放链表节点后:temp => current current => current%next DEALLOCATE(temp) temp => NULL()
- 使用智能指针(Fortran 2003+ 可模拟):虽然Fortran没有原生的智能指针,但可以通过自定义类型和
FINAL
过程来模拟智能指针的行为。例如:TYPE :: smart_ptr_type TYPE(node_type), POINTER :: ptr CONTAINS FINAL :: free_smart_ptr END TYPE smart_ptr_type IMPLICIT NONE TYPE(smart_ptr_type) :: sp ALLOCATE(sp%ptr) sp%ptr%data = 30 CONTAINS SUBROUTINE free_smart_ptr(this) TYPE(smart_ptr_type), INTENT(INOUT) :: this IF (ASSOCIATED(this%ptr)) THEN DEALLOCATE(this%ptr) END IF END SUBROUTINE free_smart_ptr
- 优化性能:
- 减少频繁的内存分配和释放:对于频繁插入和删除操作的链表或树,可以考虑对象池技术,预先分配一定数量的节点,需要时从对象池中获取,使用完后放回对象池,而不是频繁地调用
ALLOCATE
和DEALLOCATE
。 - 使用合适的数据结构:根据实际应用场景选择最适合的复杂数据结构。例如,如果需要快速查找,可以使用平衡树;如果需要频繁插入和删除,可以使用链表。
- 减少频繁的内存分配和释放:对于频繁插入和删除操作的链表或树,可以考虑对象池技术,预先分配一定数量的节点,需要时从对象池中获取,使用完后放回对象池,而不是频繁地调用
代码示例
完整的链表示例:
PROGRAM linked_list_example
TYPE :: node_type
INTEGER :: data
TYPE(node_type), POINTER :: next
END TYPE node_type
TYPE(node_type), POINTER :: head, new_node, current
INTEGER :: i
head => NULL()
DO i = 1, 5
ALLOCATE(new_node)
new_node%data = i
new_node%next => NULL()
IF (.NOT. ASSOCIATED(head)) THEN
head => new_node
ELSE
current => head
DO WHILE (ASSOCIATED(current%next))
current => current%next
END DO
current%next => new_node
END IF
END DO
current => head
DO WHILE (ASSOCIATED(current))
WRITE(*,*) current%data
current => current%next
END DO
current => head
DO WHILE (ASSOCIATED(current))
new_node => current
current => current%next
DEALLOCATE(new_node)
END DO
END PROGRAM linked_list_example
完整的二叉树示例:
PROGRAM binary_tree_example
TYPE :: tree_node_type
INTEGER :: data
TYPE(tree_node_type), POINTER :: left
TYPE(tree_node_type), POINTER :: right
END TYPE tree_node_type
TYPE(tree_node_type), POINTER :: root
root => NULL()
CALL insert(root, 10)
CALL insert(root, 20)
CALL insert(root, 5)
CALL postorder_traversal(root)
CALL free_tree(root)
CONTAINS
RECURSIVE SUBROUTINE insert(node, value)
TYPE(tree_node_type), POINTER :: node
INTEGER, INTENT(IN) :: value
IF (.NOT. ASSOCIATED(node)) THEN
ALLOCATE(node)
node%data = value
node%left => NULL()
node%right => NULL()
ELSE IF (value < node%data) THEN
CALL insert(node%left, value)
ELSE
CALL insert(node%right, value)
END IF
END SUBROUTINE insert
RECURSIVE SUBROUTINE postorder_traversal(node)
TYPE(tree_node_type), POINTER :: node
IF (ASSOCIATED(node)) THEN
CALL postorder_traversal(node%left)
CALL postorder_traversal(node%right)
WRITE(*,*) node%data
END IF
END SUBROUTINE postorder_traversal
RECURSIVE SUBROUTINE free_tree(node)
TYPE(tree_node_type), POINTER :: node
IF (ASSOCIATED(node)) THEN
CALL free_tree(node%left)
CALL free_tree(node%right)
DEALLOCATE(node)
END IF
END SUBROUTINE free_tree
END PROGRAM binary_tree_example