You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Zig中如何无需分配器实现返回新数组的冒泡排序?

在Zig中实现返回新数组的冒泡排序:问题与解决方案

目标与遇到的问题

我希望在Zig中实现冒泡排序,让函数返回一个新数组,这样能更方便地编写简洁的单行测试。但直接尝试创建动态长度的栈数组时遇到了问题:

pub fn bubbleSort(arr: []const u8,) []const u8 {
    var arrSorted = [_]u8{0} ** arr.len;
    std.mem.copyForwards(u8, &arrSorted, arr);
    ...
}

Zig会报错,因为arr.len不是编译期已知的常量,栈数组的长度必须在编译期确定。

当前实现方案

我最终通过传入编译期已知的长度参数解决了问题,代码如下:

const std = @import("std");

/// O(n^2)
pub fn bubbleSort(arr: []const u8, comptime len: u8) []const u8 {
    var arrSorted = [_]u8{0} ** len;
    std.mem.copyForwards(u8, &arrSorted, arr);

    for (0..arrSorted.len) |i| {
        for (0..arrSorted.len - i - 1) |j| {
            if (arrSorted[j] > arrSorted[j + 1]) {
                const tmp = arrSorted[j];
                arrSorted[j] = arrSorted[j + 1];
                arrSorted[j + 1] = tmp;
            }
        }
    }

    return &arrSorted;
}

test "bubble sort" {
    try std.testing.expectEqualSlices(u8, &[_]u8{ 1, 4, 7, 8, 9, 102 }, bubbleSort(&[_]u8{ 1, 102, 7, 4, 8, 9 }, 6));
    try std.testing.expectEqualSlices(u8, &[_]u8{ 1, 4, 7, 8, 9, 102 }, bubbleSort(&[_]u8{ 102, 7, 4, 1, 8, 9 }, 6));
    try std.testing.expectEqualSlices(u8, &[_]u8{ 1, 4, 7, 8, 9, 102 }, bubbleSort(&[_]u8{ 1, 102, 7, 4, 9, 8 }, 6));
    try std.testing.expectEqualSlices(u8, &[_]u8{ 1, 4, 7, 8, 9, 102 }, bubbleSort(&[_]u8{ 9, 1, 102, 7, 4, 8 }, 6));
    try std.testing.expectEqualSlices(u8, &[_]u8{ 1, 4, 7, 8, 9, 102 }, bubbleSort(&[_]u8{ 1, 7, 4, 8, 9, 102 }, 6));
}

通过将数组长度作为编译期参数传入,让len成为编译期已知量,从而能创建对应长度的栈数组并返回其切片。

疑问解答

传入allocator是否是正确方式?

是的,传入allocator是Zig中实现这类通用场景的标准正确做法。当数组长度只有运行时才能确定时,栈上无法创建动态长度的数组(Zig要求栈数组长度必须是编译期常量),此时必须通过堆分配来创建新数组。

基于allocator的实现示例

const std = @import("std");

pub fn bubbleSort(allocator: std.mem.Allocator, arr: []const u8) ![]u8 {
    // 分配与原数组长度相同的内存
    var arrSorted = try allocator.alloc(u8, arr.len);
    // 确保后续出错时自动释放已分配的内存
    errdefer allocator.free(arrSorted);
    
    std.mem.copyForwards(u8, arrSorted, arr);

    for (0..arrSorted.len) |i| {
        for (0..arrSorted.len - i - 1) |j| {
            if (arrSorted[j] > arrSorted[j + 1]) {
                // 用std.mem.swap简化交换逻辑
                std.mem.swap(u8, &arrSorted[j], &arrSorted[j + 1]);
            }
        }
    }

    return arrSorted;
}

test "bubble sort with allocator" {
    const allocator = std.testing.allocator;
    try std.testing.expectEqualSlices(u8, &[_]u8{1, 4, 7, 8, 9, 102}, try bubbleSort(allocator, &[_]u8{1, 102, 7, 4, 8, 9}));
    try std.testing.expectEqualSlices(u8, &[_]u8{1, 4, 7, 8, 9, 102}, try bubbleSort(allocator, &[_]u8{102, 7, 4, 1, 8, 9}));
    try std.testing.expectEqualSlices(u8, &[_]u8{1, 4, 7, 8, 9, 102}, try bubbleSort(allocator, &[_]u8{1, 102, 7, 4, 9, 8}));
}

不同方案的对比

  • 编译期长度参数方案:仅适用于数组长度在编译期已知的场景,优势是不需要堆分配,性能略高,但通用性差,调用者必须手动传入长度常量。
  • allocator方案:支持运行时动态长度的数组,适用性更广,符合Zig的内存管理规范(明确内存所有权,由调用者负责释放内存),是通用场景下的最优解。

关于无需allocator和编译期参数返回新数组的可能性

Zig中无法实现这一点:函数返回的栈数组在函数执行结束后会随栈帧被销毁,返回的指针会变成悬垂指针,属于不安全行为,因此Zig不允许这样做。必须在栈上用编译期固定长度数组,或在堆上用allocator分配内存。

内容的提问来源于stack exchange,提问作者Arakan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 05:45:03