如何编写类似Fortran内置函数的自定义函数?含ALLOCATE/PACK疑问
嘿,刚入门Fortran碰到这些内置函数的困惑太正常了——我当初学的时候也对着ALLOCATE的下标范围懵了好久!咱们一个个把这些问题掰扯清楚:
关于ALLOCATE的那些疑问
1. 自定义类似ALLOCATE的函数,参数和类型怎么定义?
首先得明确:Fortran里的ALLOCATE其实是语言内置语句,不是普通函数——它直接和编译器底层交互,负责内存分配、修改变量的分配状态,这种逻辑没法用普通Fortran函数完全复刻。但如果想实现“动态生成指定下标范围数组”的功能,可以写一个返回预分配数组的函数:
function create_custom_array(lower1, upper1, lower2, upper2) result(arr) integer, intent(in) :: lower1, upper1, lower2, upper2 integer :: arr(lower1:upper1, lower2:upper2) ! 直接指定下标范围 arr = 0 ! 可选:初始化数组元素 end function create_custom_array
调用时直接用arr = create_custom_array(-5,4,1,10),就能得到你要的下标范围的二维数组。不过这和原生ALLOCATE有区别:原生ALLOCATE可以直接修改已声明的可分配数组状态,而这个函数是返回新数组。
2. 未分配时的array(-5:4,1:10)是什么?
如果没先声明array是可分配数组或指针数组,直接写array(-5:4,1:10)会编译报错——Fortran要求数组必须先明确维度属性。
如果已经声明integer, allocatable :: array(:,:),那array(-5:4,1:10)只有在ALLOCATE语句里才有意义:这里的-5:4是下标范围表达式,属于编译器内部的特殊描述(用户没法直接定义这种类型),用来告诉ALLOCATE每个维度的起始/结束下标。单独写这个表达式(比如放在赋值左边)且数组未分配的话,会触发运行时错误。
3. array(10)和array(-5:4,1:10)的类型差异?
先明确前提:
array(10)一般是一维可分配数组integer, allocatable :: array(:)执行ALLOCATE(array(10))后的状态,下标默认从1:10,共10个元素;array(-5:4,1:10)是二维可分配数组执行ALLOCATE(array(-5:4,1:10))后的状态,第一个维度下标-5:4(10个元素),第二个维度1:10(10个元素),共100个元素。
它们的元素类型是一致的(比如都是integer),差异在于:
- 维度数不同(一维 vs 二维);
- 下标边界属性不同(Fortran数组会存储每个维度的上下界信息,你可以用
LBOUND(array, dim=1)/UBOUND(array, dim=1)直接获取)。
这和C的数组指针完全不一样:C数组本质是指向首元素的指针,下标只是内存偏移量,根本不存储边界信息;而Fortran数组更像一个带元数据的容器,包含了维度、下标范围、元素值等完整信息。
关于PACK函数的疑惑
1. pack(m, m /= 0)里的m /=0是什么?
你一开始以为是lambda很正常,但Fortran(直到2003以后才有限支持内部函数)没有这种写法。这里的m /=0是一个布尔数组——它的形状和输入数组m完全一致,每个元素是对应位置m的元素是否不等于0的判断结果。
比如m = [0,1,2,0],那m /=0就是[.false., .true., .true., .false.],PACK会把m中对应布尔数组为.true.的元素挑出来,拼成一维数组。
2. 大数组下内存效率低?怎么优化?
确实,超大数组下生成和原数组一样大的布尔数组会占用额外内存。这时候可以用手动循环实现类似PACK的功能,避免临时布尔数组:
function pack_nonzero(m) result(res) integer, intent(in) :: m(:,:) integer, allocatable :: res(:) integer :: i, j, count ! 第一步:统计非零元素个数 count = 0 do j = 1, size(m,2) do i = 1, size(m,1) if (m(i,j) /= 0) count = count + 1 end do end do ! 第二步:分配结果数组并填充 allocate(res(count)) count = 0 do j = 1, size(m,2) do i = 1, size(m,1) if (m(i,j) /= 0) then count = count + 1 res(count) = m(i,j) end if end do end do end function pack_nonzero
这种方式只需要两次遍历(统计+填充),不会生成额外的大布尔数组,内存效率更高。
3. 这类内置函数只能用C实现吗?能用Fortran写吗?
当然可以用Fortran写!刚才的pack_nonzero就是完全用Fortran实现的类似PACK的功能。原生内置函数(比如PACK)性能更好是因为编译器会做底层优化(比如向量指令、避免临时数组),但逻辑上完全可以用Fortran的循环、数组操作复刻。
不过像ALLOCATE这种内置语句,因为涉及编译器底层内存管理,没法用普通Fortran函数完全复刻,但绝大多数数组操作类的内置函数,都能用Fortran自己实现。
内容的提问来源于stack exchange,提问作者RandomB

