課題7: ポインタとスライスの実践

マンダトリー要件 (80点)

Part 1: ポインタ基礎 (20点)

ポインタを使った基本的な操作を実装してください。

ファイル: part1/pointer_basics.zig

const std = @import("std");

// TODO: 2つの値を交換
fn swap(comptime T: type, a: *T, b: *T) void {
    // 実装
}

// TODO: ポインタを使って配列の要素を変更
fn multiplyByTwo(array: []i32) void {
    // 実装
}

// TODO: ポインタ演算で配列の合計を計算
fn sumUsingPointer(ptr: [*]const i32, len: usize) i64 {
    // 実装
}

// TODO: 配列内の最大値のポインタを返す
fn findMaxPtr(array: []i32) ?*i32 {
    // 実装
}

pub fn main() void {
    std.debug.print("=== Swap ===\n", .{});
    var x: i32 = 10;
    var y: i32 = 20;
    std.debug.print("Before: x={}, y={}\n", .{x, y});
    swap(i32, &x, &y);
    std.debug.print("After: x={}, y={}\n", .{x, y});

    std.debug.print("\n=== Multiply by Two ===\n", .{});
    var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    std.debug.print("Before: ", .{});
    for (numbers) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});
    multiplyByTwo(&numbers);
    std.debug.print("After: ", .{});
    for (numbers) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});

    std.debug.print("\n=== Sum Using Pointer ===\n", .{});
    const arr = [_]i32{ 1, 2, 3, 4, 5 };
    const sum = sumUsingPointer(&arr, arr.len);
    std.debug.print("Sum: {}\n", .{sum});

    std.debug.print("\n=== Find Max Pointer ===\n", .{});
    if (findMaxPtr(&numbers)) |max_ptr| {
        std.debug.print("Max value: {}\n", .{max_ptr.*});
        max_ptr.* = 999;
        std.debug.print("Modified: {}\n", .{numbers[numbers.len - 1]});
    }
}

Part 2: スライス操作 (20点)

スライスを使った配列操作を実装してください。

ファイル: part2/slice_ops.zig

const std = @import("std");

// TODO: スライスを反転
fn reverseSlice(comptime T: type, slice: []T) void {
    // 実装
}

// TODO: スライス内の要素を検索
fn findInSlice(comptime T: type, slice: []const T, target: T) ?usize {
    // 実装
}

// TODO: 2つのスライスを連結(allocatorを使用)
fn concatSlices(
    comptime T: type,
    allocator: std.mem.Allocator,
    slice1: []const T,
    slice2: []const T,
) ![]T {
    // 実装
}

// TODO: スライスをコピー
fn copySlice(comptime T: type, dest: []T, src: []const T) usize {
    // 実装
    // 戻り値: コピーした要素数
}

// TODO: スライスが等しいか判定
fn slicesEqual(comptime T: type, slice1: []const T, slice2: []const T) bool {
    // 実装
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== Reverse Slice ===\n", .{});
    var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    std.debug.print("Before: ", .{});
    for (numbers) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});
    reverseSlice(i32, &numbers);
    std.debug.print("After: ", .{});
    for (numbers) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});

    std.debug.print("\n=== Find in Slice ===\n", .{});
    if (findInSlice(i32, &numbers, 3)) |index| {
        std.debug.print("Found 3 at index {}\n", .{index});
    }

    std.debug.print("\n=== Concat Slices ===\n", .{});
    const arr1 = [_]i32{ 1, 2, 3 };
    const arr2 = [_]i32{ 4, 5, 6 };
    const concat = try concatSlices(i32, allocator, &arr1, &arr2);
    defer allocator.free(concat);
    std.debug.print("Concatenated: ", .{});
    for (concat) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});

    std.debug.print("\n=== Slices Equal ===\n", .{});
    const eq = slicesEqual(i32, &arr1, &arr1);
    std.debug.print("arr1 == arr1: {}\n", .{eq});
}

Part 3: 文字列操作 (20点)

センチネル終端文字列を使った操作を実装してください。

ファイル: part3/string_ops.zig

const std = @import("std");

// TODO: 文字列の長さを計算
fn stringLength(str: [*:0]const u8) usize {
    // 実装
}

// TODO: 文字列をコピー
fn stringCopy(dest: [*]u8, src: [*:0]const u8) void {
    // 実装
}

// TODO: 文字列を連結
fn stringConcat(dest: [*]u8, src1: [*:0]const u8, src2: [*:0]const u8) void {
    // 実装
}

// TODO: 文字列を比較
fn stringCompare(str1: [*:0]const u8, str2: [*:0]const u8) i32 {
    // 実装
    // 戻り値: str1 < str2 なら負、等しいなら0、str1 > str2 なら正
}

// TODO: 文字列内で文字を検索
fn stringFindChar(str: [*:0]const u8, ch: u8) ?usize {
    // 実装
}

pub fn main() void {
    std.debug.print("=== String Length ===\n", .{});
    const str1: [*:0]const u8 = "Hello, Zig!";
    const len = stringLength(str1);
    std.debug.print("Length of '{s}': {}\n", .{str1, len});

    std.debug.print("\n=== String Copy ===\n", .{});
    var dest: [100]u8 = undefined;
    stringCopy(&dest, str1);
    std.debug.print("Copied: {s}\n", .{dest[0..len]});

    std.debug.print("\n=== String Concat ===\n", .{});
    const src1: [*:0]const u8 = "Hello";
    const src2: [*:0]const u8 = " World";
    var concat: [100]u8 = undefined;
    stringConcat(&concat, src1, src2);
    const concat_len = stringLength(&concat);
    std.debug.print("Concatenated: {s}\n", .{concat[0..concat_len]});

    std.debug.print("\n=== String Compare ===\n", .{});
    const cmp = stringCompare("abc", "abc");
    std.debug.print("'abc' vs 'abc': {}\n", .{cmp});

    std.debug.print("\n=== String Find Char ===\n", .{});
    if (stringFindChar(str1, 'Z')) |index| {
        std.debug.print("Found 'Z' at index {}\n", .{index});
    }
}

Part 4: メモリ管理 (20点)

安全なメモリ操作を実装してください。

ファイル: part4/memory_safe.zig

const std = @import("std");

// TODO: 動的配列の作成と初期化
fn createArray(comptime T: type, allocator: std.mem.Allocator, size: usize, value: T) ![]T {
    // 実装
}

// TODO: 配列のリサイズ
fn resizeArray(
    comptime T: type,
    allocator: std.mem.Allocator,
    old_array: []T,
    new_size: usize,
) ![]T {
    // 実装
    // 新しいサイズに配列を再割り当て
    // 既存の要素をコピー
}

// TODO: 安全な配列アクセス
fn safeGet(comptime T: type, array: []const T, index: usize) ?T {
    // 実装
}

// TODO: 安全な配列設定
fn safeSet(comptime T: type, array: []T, index: usize, value: T) bool {
    // 実装
    // 戻り値: 成功したらtrue
}

// TODO: メモリプールの実装
const MemoryPool = struct {
    buffer: []u8,
    offset: usize,

    pub fn init(buffer: []u8) MemoryPool {
        return MemoryPool{
            .buffer = buffer,
            .offset = 0,
        };
    }

    pub fn alloc(self: *MemoryPool, size: usize) ?[]u8 {
        // 実装
    }

    pub fn reset(self: *MemoryPool) void {
        // 実装
    }
};

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== Create Array ===\n", .{});
    const arr = try createArray(i32, allocator, 5, 42);
    defer allocator.free(arr);
    std.debug.print("Array: ", .{});
    for (arr) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});

    std.debug.print("\n=== Resize Array ===\n", .{});
    const resized = try resizeArray(i32, allocator, arr, 10);
    defer allocator.free(resized);
    std.debug.print("Resized to {} elements\n", .{resized.len});

    std.debug.print("\n=== Safe Get/Set ===\n", .{});
    if (safeGet(i32, arr, 2)) |value| {
        std.debug.print("arr[2] = {}\n", .{value});
    }
    const set_ok = safeSet(i32, arr, 2, 100);
    std.debug.print("Set arr[2] = 100: {}\n", .{set_ok});

    std.debug.print("\n=== Memory Pool ===\n", .{});
    var buffer: [1024]u8 = undefined;
    var pool = MemoryPool.init(&buffer);
    if (pool.alloc(100)) |mem1| {
        std.debug.print("Allocated {} bytes\n", .{mem1.len});
    }
    if (pool.alloc(200)) |mem2| {
        std.debug.print("Allocated {} bytes\n", .{mem2.len});
    }
    std.debug.print("Total used: {} bytes\n", .{pool.offset});
}

ボーナス課題 (20点)

Bonus 1: スマートポインタの実装 (10点)

参照カウント付きスマートポインタを実装してください。

ファイル: bonus1/smart_pointer.zig

const std = @import("std");

fn RefCounted(comptime T: type) type {
    return struct {
        const Self = @This();

        value: T,
        ref_count: usize,
        allocator: std.mem.Allocator,

        // TODO: 新しいインスタンスを作成
        pub fn init(allocator: std.mem.Allocator, value: T) !*Self {
            // 実装
        }

        // TODO: 参照を追加
        pub fn addRef(self: *Self) void {
            // 実装
        }

        // TODO: 参照を削除(0になったら解放)
        pub fn release(self: *Self) void {
            // 実装
        }
    };
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("Creating RefCounted<i32>\n", .{});
    const rc1 = try RefCounted(i32).init(allocator, 42);
    std.debug.print("Value: {}, RefCount: {}\n", .{rc1.value, rc1.ref_count});

    rc1.addRef();
    std.debug.print("After addRef: RefCount: {}\n", .{rc1.ref_count});

    rc1.release();
    std.debug.print("After release: RefCount: {}\n", .{rc1.ref_count});

    rc1.release();
    std.debug.print("Freed\n", .{});
}

Bonus 2: リングバッファの実装 (10点)

固定サイズのリングバッファを実装してください。

ファイル: bonus2/ring_buffer.zig

const std = @import("std");

fn RingBuffer(comptime T: type, comptime size: usize) type {
    return struct {
        const Self = @This();

        buffer: [size]T,
        read_pos: usize,
        write_pos: usize,
        count: usize,

        pub fn init() Self {
            return Self{
                .buffer = undefined,
                .read_pos = 0,
                .write_pos = 0,
                .count = 0,
            };
        }

        // TODO: 要素を追加
        pub fn push(self: *Self, value: T) bool {
            // 実装
            // バッファが満杯ならfalseを返す
        }

        // TODO: 要素を取り出し
        pub fn pop(self: *Self) ?T {
            // 実装
            // バッファが空ならnullを返す
        }

        // TODO: 要素数を取得
        pub fn len(self: Self) usize {
            return self.count;
        }

        // TODO: バッファが空か判定
        pub fn isEmpty(self: Self) bool {
            return self.count == 0;
        }

        // TODO: バッファが満杯か判定
        pub fn isFull(self: Self) bool {
            return self.count == size;
        }
    };
}

pub fn main() void {
    var buffer = RingBuffer(i32, 5).init();

    std.debug.print("=== Push ===\n", .{});
    _ = buffer.push(1);
    _ = buffer.push(2);
    _ = buffer.push(3);
    std.debug.print("Pushed 3 elements, count: {}\n", .{buffer.len()});

    std.debug.print("\n=== Pop ===\n", .{});
    if (buffer.pop()) |value| {
        std.debug.print("Popped: {}\n", .{value});
    }

    std.debug.print("\n=== Fill Buffer ===\n", .{});
    _ = buffer.push(4);
    _ = buffer.push(5);
    _ = buffer.push(6);
    std.debug.print("Is full: {}\n", .{buffer.isFull()});

    std.debug.print("\n=== Empty Buffer ===\n", .{});
    while (buffer.pop()) |value| {
        std.debug.print("Popped: {}\n", .{value});
    }
    std.debug.print("Is empty: {}\n", .{buffer.isEmpty()});
}

評価基準

項目 配点
Part 1: ポインタ基礎 20点
Part 2: スライス操作 20点
Part 3: 文字列操作 20点
Part 4: メモリ管理 20点
**マンダトリー合計** **80点**
Bonus 1: スマートポインタの実装 10点
Bonus 2: リングバッファの実装 10点
**ボーナス合計** **20点**

合格基準

  • マンダトリー: 64点以上で合格(80点満点の80%)
  • ボーナス: 追加評価(最終成績の加算)
  • 提出方法

    exercise07/
    ├── part1/
    │   └── pointer_basics.zig
    ├── part2/
    │   └── slice_ops.zig
    ├── part3/
    │   └── string_ops.zig
    ├── part4/
    │   └── memory_safe.zig
    ├── bonus1/
    │   └── smart_pointer.zig
    └── bonus2/
        └── ring_buffer.zig
    

    ヒント

  • ポインタの交換:
const temp = a.*;
a.* = b.*;
b.* = temp;

  • スライスの反転:
var left: usize = 0;
var right = slice.len - 1;
while (left < right) {
    // 交換
}

  • センチネル終端文字列の長さ:
var len: usize = 0;
while (str[len] != 0) : (len += 1) {}

  • メモリプール:
if (self.offset + size > self.buffer.len) return null;
const result = self.buffer[self.offset..self.offset + size];
self.offset += size;

参考資料

  • Zig Language Reference - Pointers: https://ziglang.org/documentation/master/#Pointers
  • Zig Language Reference - Slices: https://ziglang.org/documentation/master/#Slices
  • Ziglearn - Pointers: https://ziglearn.org/chapter-2/#pointers
  • Ziglearn - Allocators: https://ziglearn.org/chapter-2/#allocators