ざっくりZig - ポインタとスライス(*T, *[N]T, [*]T, []T, &変数, &関数)

2024/05/24に公開

ポインタ(Pointer)[1]は元の型に付加する型で、これに代入されるのはメモリ内にデータが格納された位置を示すアドレス(address)です。この値は自動的に割り当てられることがほとんどで、具体的な値を気にすることはあまりないでしょう。重要なのはそのアドレスで示される位置に格納されたデータであり、ポインタでそれを参照・更新できるようになっていることです。こうしたポインタを持つ型をポインタ型(Pointer Type)といったり、それを持つ変数をポインタ変数といったりします。

ポインタ変数は1つの値を示すものだけでなく、配列(文字列含む)や関数も含まれます。また、通常の変数からもポインタを取得できます。

ポインタの用途

ポインタがよく使われるのは以下のような場面でしょうか。(この他にもいろいろあると思います)

  • 動的なメモリ割り当て
    プログラム実行中に必要なメモリを確保する際に使われます。
  • 関数にメモリ領域を渡す
    大量のデータや配列などで用意したバッファ領域を関数に渡すとき、アドレスだけを渡して処理を効率化します。
  • 配列の操作
    ソートなど配列に格納されたデータを別の関数で変更する際に配列のポインタを渡したり、各要素のポインタを使用したりします。
  • 連結リスト
    スタックや木構造など動的に複数のデータを連結させたいときに使われます。
  • コールバック関数
    イベント処理を行うときなどに実行する関数をあらかじめ設定しておく際に関数ポインタを引数とすることがあります。

1つの値を示すポインタ

1つの値を示す(ポイントする)ポインタは、値の型がTだとすると*Tで表します。固定値の場合は*const Tとなります。オプション型にするときは?*Tあるいは?*const Tとします。また、この型を持つポインタ変数をpとすると、これが示す値はp.*となります。これを使って値を参照したり、p.* = 1のように値を代入したりできます。

一方、変数vのポインタは&vで取得できます。vの型がu8なら&vの型は*u8となります。vの型がconst u8のときはポインタの型が*const u8となります。たとえばvar v: u8 = 1;で代入され、その後vの値が変更されるとしてもアドレスの値は変わりません。そのためconst p: *u8 = &v;で代入し、p.* = 2;vの値を変更できます。

ポインタ変数を関数に渡すと戻り値の型がvoidであってもポインタを介して値を変更できます。これはポインタを扱うにあたって注意すべきところです。

1つの値を持つ変数とポインタ
const std = @import("std");

fn f(p: *u8) void {
    p.* += 1;   // voidでも値を変更できる
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var v: u8 = 1;          // 変数に1を代入
    const p: *u8 = &v;      // vのポインタをpに代入

    try stdout.print("v = {}, p.* = {}, p = {}\n", .{ v, p.*, p });

    p.* = 2;    // ポインタ変数から示す値を更新
    try stdout.print("v = {}, p.* = {}, p = {}\n", .{ v, p.*, p });

    f(p);
    try stdout.print("v = {}, p.* = {}, p = {}\n", .{ v, p.*, p });
}
結果(値を変更してもアドレスの値は変わらない)
v = 1, p.* = 1, p = u8@52a4fff537   (メモリアドレスの値は実行時点による)
v = 2, p.* = 2, p = u8@52a4fff537   (p.*を更新するとvの値も更新される)
v = 3, p.* = 3, p = u8@52a4fff537   (voidでも値を変更できる)

配列を示すポインタ

配列を示すポインタには、配列全体を示す*[N]T(Nは要素数)と配列の各要素を示す[*]Tがあります。固定値の場合はそれぞれ*const [N]Tもしくは[*]const Tとなります。オプション型はこれらの先頭に?を付加したものになります。

これらは以下のように定義できます。

配列のポインタを定義する
var a: [3]u8 = .{ 1, 2, 3 };
const p: *[3]u8 = &a;   // 配列全体のポインタ
var pe: [*]u8 = &a;     // 配列各要素のポインタ(演算可)

const ac: [3]u8 = .{ 1, 2, 3 };
const pc: *const [3]u8 = ∾   // 配列全体のポインタ
var pec: [*]const u8 = ∾     // 配列各要素のポインタ(演算可)

両者の共通点はa[0], a[1], ...p[0], p[1], ...あるいはpe[0], pe[1], ...で表せることです。そのためp[0] = 1pe[0] = 1a[0] = 1と同じということになります。これらは同じ添え字のものをどれか1つ更新すればすべて同時に変更されます。

ポインタ変数により配列の要素を変更
const stdout = std.io.getStdOut().writer();

try stdout.print("a = [{}, {}, {}], p = [{}, {}, {}], pe = [{}, {}, {}]\n",
                    .{ a[0], a[1], a[2], p[0], p[1], p[2], pe[0], pe[1], pe[2] });

p[0] = 4;
p[1] = 5;
p[2] = 6;

try stdout.print("a = [{}, {}, {}], p = [{}, {}, {}], pe = [{}, {}, {}]\n",
                    .{ a[0], a[1], a[2], p[0], p[1], p[2], pe[0], pe[1], pe[2] });

pe[0] = 7;
pe[1] = 8;
pe[2] = 9;

try stdout.print("a = [{}, {}, {}], p = [{}, {}, {}], pe = [{}, {}, {}]\n",
                    .{ a[0], a[1], a[2], p[0], p[1], p[2], pe[0], pe[1], pe[2] });
結果
a = [1, 2, 3], p = [1, 2, 3], pe = [1, 2, 3]
a = [4, 5, 6], p = [4, 5, 6], pe = [4, 5, 6]
a = [7, 8, 9], p = [7, 8, 9], pe = [7, 8, 9]

ちなみに&a[0]なども配列の要素のポインタといえますが、型は*u8であって[*]u8ではありません。よってこれは1つの値を示すポインタとして扱われます。

pp.*で配列全体を表しp.lenで配列の長さを表します。pcも同様です。

ポインタ変数による配列の参照
try stdout.print("a = {any}, p.* = {any}\n", .{ a, p.* });
try stdout.print("a.len = {}, p.len = {}\n", .{ a.len, p.len });
try stdout.print("ac = {any}, pc.* = {any}\n", .{ ac, pc.* });
try stdout.print("ac.len = {}, pc.len = {}\n", .{ ac.len, pc.len });
結果
a = { 1, 2, 3 }, p.* = { 1, 2, 3 }
a.len = 3, p.len = 3
ac = { 1, 2, 3 }, pc.* = { 1, 2, 3 }
ac.len = 3, pc.len = 3

一方pepecにはこれらが存在しません。そのかわりpe + 1pe += 1のようなポインタ演算が可能です。

ポインタ演算とは、ポインタpe[0]が配列aの先頭の要素a[0]を示しているとき、(pe + 1)[0]a[1](pe + 2)[0]a[2]を示すというように、ポインタが持つアドレスを一時的な計算によってずらしたり、pe += 1によってpe[0]が示す値をa[1]に変更するなど、ポインタ変数が持つアドレスそのものを変更してポインタが示す値も同時に変更してしまう処理のことです。これにより配列の添え字とポインタ変数の添え字がずれますので注意しましょう。

ポインタ演算による示す値の変化
const pep = pe;
for (a, 0..) |v, i| {
    try stdout.print("a[{}] = {}, pe[{}] = {}, (pep + i)[0] = {}\n",
                        .{ i, v, i, pe[0], (pep + i)[0] });
    pe += 1;
}
const pecp = pec;
for (ac, 0..) |v, i| {
    try stdout.print("ac[{}] = {}, pec[0] = {}, (pecp + i)[0] = {}\n",
                        .{ i, v, pec[0], (pecp + i)[0] });
    pec += 1;
}
結果
a[0] = 7, pe[0] = 7, (pep + i)[0] = 7
a[1] = 8, pe[1] = 8, (pep + i)[0] = 8
a[2] = 9, pe[2] = 9, (pep + i)[0] = 9
ac[0] = 1, pec[0] = 1, (pecp + i)[0] = 1
ac[1] = 2, pec[0] = 2, (pecp + i)[0] = 2
ac[2] = 3, pec[0] = 3, (pecp + i)[0] = 3

スライス

スライス(Slice)[2]は配列の型を[N]T(Nは要素数)とするとき[]Tで表します。[N]const Tの場合は[]const Tとなります。これは配列に[*]Tもしくは[*]const Tを追加したものです。なお、オプション型にするときは先頭に?を付加します。

これらは以下のように定義できます。

スライスの定義
var a = [_]u8{ 0, 1, 2, 3, 4 };
var asl: []u8 = a[0..]; // &aも可
var psl: [*]u8 = asl.ptr;

const aslc: []const u8 = a[0..]; // &aも可;
var pslc: [*]const u8 = aslc.ptr;

aslは配列aのスライスです。asl[0], asl[1], ...a[0], a[1], ...と同じで、asl.lena.lenと同じで要素数を取得できます。

配列とスライスによる長さの取得
try stdout.print("a = {any}, a.len = {}\n", .{ a, a.len });
try stdout.print("asl = {any}, asl.len = {}\n", .{ asl, asl.len });
try stdout.print("aslc = {any}, aslc.len = {}\n", .{ aslc, asl.len });
結果
a = { 0, 1, 2, 3, 4 }, a.len = 5
asl = { 0, 1, 2, 3, 4 }, asl.len = 5
aslc = { 0, 1, 2, 3, 4 }, aslc.len = 5

aslはさらにasl.ptrによって[*]u8型のポインタを取得できます。これが配列との違いです。[*]Tはポインタ演算によってアドレスが配列の範囲を超えてしまう可能性がありますが、スライスはlenによって配列の長さを取得できますので、ポインタ演算の有効範囲をコントロールしやすくなります。ちなみに&asl[0]の型は&a[0]と同じ*u8です。

スライスから取得するポインタの型
try stdout.print("@TypeOf(asl.ptr) = {}, @TypeOf(&asl[0]) = {}\n",
                    .{ @TypeOf(asl.ptr), @TypeOf(&asl[0]) });
try stdout.print("@TypeOf(aslc.ptr) = {}, @TypeOf(&aslc[0]) = {}\n",
                    .{ @TypeOf(aslc.ptr), @TypeOf(&aslc[0]) });
結果
@TypeOf(asl.ptr) = [*]u8, @TypeOf(&asl[0]) = *u8
@TypeOf(aslc.ptr) = [*]const u8, @TypeOf(&aslc[0]) = *const u8

スライスおよびそこから取得したポインタのどちらからでも配列の各要素を参照・変更できます。

配列の各要素の参照
for (asl, 0..) |v, i| {
    try stdout.print("a[{}] = {}, asl[{}] = {}, psl[0] = {}\n",
                        .{ i, a[i], i, v, psl[0] });
    try stdout.print("aslc[{}] = {}, pslc[0] = {}, (pslcp + i)[0] = {}\n",
                        .{ i, aslc[i], pslc[0], (pslcp + i)[0] });
    psl += 1;
    pslc += 1;
}
結果
a[0] = 0, asl[0] = 0, psl[0] = 0
aslc[0] = 0, pslc[0] = 0, (pslcp + i)[0] = 0
a[1] = 1, asl[1] = 1, psl[0] = 1
aslc[1] = 1, pslc[0] = 1, (pslcp + i)[0] = 1
a[2] = 2, asl[2] = 2, psl[0] = 2
aslc[2] = 2, pslc[0] = 2, (pslcp + i)[0] = 2
a[3] = 3, asl[3] = 3, psl[0] = 3
aslc[3] = 3, pslc[0] = 3, (pslcp + i)[0] = 3
a[4] = 4, asl[4] = 4, psl[0] = 4
aslc[4] = 4, pslc[0] = 4, (pslcp + i)[0] = 4
配列の各要素の変更
try stdout.print("a = {any}\n", .{a});

var c: u8 = a.len;
for (0..a.len) |i| {
    c -= 1;
    asl[i] = c;
}
try stdout.print("a = {any}\n", .{a});

for (0..a.len) |_| {
    psl[0] = c;
    c += 1;
    psl += 1;
}
try stdout.print("a = {any}\n", .{a});
結果
a = { 0, 1, 2, 3, 4 }
a = { 4, 3, 2, 1, 0 }
a = { 0, 1, 2, 3, 4 }

一部要素の抽出とポインタ変数

配列は一部の要素を抽出できます。たとえばa[1..4]a[1]からa[3]までの要素を持つ配列として扱われます。これはスライスも同じです。そして実は[*]T型のポインタ変数にも同じ機能があります。ポインタ変数でこれを行うとき、添え字が示す位置がポインタ演算によってずれることがあるため、抽出する範囲が配列の範囲を超えないようにしなくてはなりません。ポインタ演算処理を事前に精査しておきましょう。

ポインタ変数により一部の要素を抽出
// 配列とスライスによる要素の抽出
for (3..a.len + 1) |i| {
    try stdout.print("a[{}..{}] = {any}, asl[{}..{}] = {any}\n",
                        .{ i - 3, i, a[i - 3 .. i], i - 3, i, asl[i - 3 .. i] });
}
// 添え字は固定でもポインタ演算によって抽出される範囲が変化する
for (0..a.len - 2) |i| {
    try stdout.print("psl[0] = {}, psl[0..3] = {any}\n",
                        .{ psl[0], psl[0..3] });
    try stdout.print("pslc[0] = {}, pslc[0..3] = {any}\n",
                        .{ pslc[0], pslc[0..3] });
    try stdout.print("(pslcp + i)[0] = {}, (pslcp + i)[0..3] = {any}\n",
                        .{ (pslcp + i)[0], (pslcp + i)[0..3] });
    psl += 1;
    pslc += 1;
}
結果
a[0..3] = { 0, 1, 2 }, asl[0..3] = { 0, 1, 2 }
a[1..4] = { 1, 2, 3 }, asl[1..4] = { 1, 2, 3 }
a[2..5] = { 2, 3, 4 }, asl[2..5] = { 2, 3, 4 }
psl[0] = 0, psl[0..3] = { 0, 1, 2 }
pslc[0] = 0, pslc[0..3] = { 0, 1, 2 }
(pslcp + i)[0] = 0, (pslcp + i)[0..3] = { 0, 1, 2 }
psl[0] = 1, psl[0..3] = { 1, 2, 3 }
pslc[0] = 1, pslc[0..3] = { 1, 2, 3 }
(pslcp + i)[0] = 1, (pslcp + i)[0..3] = { 1, 2, 3 }
psl[0] = 2, psl[0..3] = { 2, 3, 4 }
pslc[0] = 2, pslc[0..3] = { 2, 3, 4 }
(pslcp + i)[0] = 2, (pslcp + i)[0..3] = { 2, 3, 4 }

文字列のポインタとスライス

ZigではUTF-8の文字列の場合、u8の配列に格納されるため、上記のポインタやスライスを利用できます。ただし、文字列の配列には番兵(Sentinel)付きの配列もよく利用されるため、それに対応した以下のポインタやスライスが用意されています。(Nは要素数、0はSentinel値)

型名 番兵(Sentinel)付き const
配列のポインタ *[N]T *[N:0]T *const [N:0]T
要素のポインタ [*]T [*:0]T [*:0]const T
スライス []T [:0]T [:0]const T

これらは代入するときに型名を設定すると取得できます。まずconstがある型の場合は以下の通りです。

ポインタやスライスの取得(constがある型)
const s1 = "こんにちはZig!"; // *const [19:0]u8
const sp1: [*:0]const u8 = s1;  // "こんにちはZig!"も可
const ssl1: [:0]const u8 = s1;  // "こんにちはZig!"も可

s1は文字列の定数が代入されているように見えますが、型名は*const [19:0]u8で、配列全体を示すポインタ変数です。sp1s1を代入する時に[*:0]const u8を設定することで要素を示すポインタを取得しています。番兵付き(:0)のため(sp1 + s1.len)[0]の値は0となります。最後にssl1s1を代入する際にスライスを設定しています。

s1はポインタ変数でした。ではs1.*は何かというと[19:0]u8という番兵付きの配列になります。ここからもやはり要素を示すポインタやスライスを取得できます。s1.*s2に代入したとすると、要素を示すポインタやスライスを取得するときは&s2を代入します。

ポインタやスライスの取得(配列から)
const s2 = s1.*; // [19:0]u8
const sp2: [*:0]const u8 = &s2;
const ssl2: [:0]const u8 = &s2;

sp1, sp2, ssl1, ssl2はいずれも配列と同様にsp1[0..15]のような一部の範囲を指定できます。

ポインタとスライスによる文字列の範囲指定
try stdout.print("s1 = {s}, s2 = {s}\n", .{ s1, s2 });
try stdout.print("sp1[0..15] = {s}, sp2[0..15] = {s}\n", .{ sp1[0..15], sp2[0..15] });
try stdout.print("ssl1[0..15] = {s}, ssl2[0..15] = {s}\n", .{ ssl1[0..15], ssl2[0..15] });
結果
s1 = こんにちはZig!, s2 = こんにちはZig!
sp1[0..15] = こんにちは, sp2[0..15] = こんにちは
ssl1[0..15] = こんにちは, ssl2[0..15] = こんにちは

sp1はポインタ演算ができます。

文字列のポインタ演算(sp2も同様)
var i: usize = 0;
while (i < 15) : (i += 3) {
    try stdout.print("{s} / ", .{(sp1 + i)[0..3]});
}
try stdout.print("{s}\n", .{(sp1 + i)[0..]});
結果
こ / ん / に / ち / は / Zig!

続いてconstがない型の場合です。

ポインタやスライスの取得(constがない型)
var sv1: [19:0]u8 = undefined;
var svp1: *[19:0]u8 = undefined;
var svpe1: [*:0]u8 = undefined;
var svsl1: [:0]u8 = undefined;

sv1 = "こんにちはZig!".*;
svp1 = &sv1;
svpe1 = &sv1;
svsl1 = &sv1;

svp1, svpe1, svsl1のいずれによってもsv1の値を変更できます。ただしsvpe1svsl1.ptrはポインタの示す位置が文字列の範囲を超えないように注意してください。

ポインタとスライスによる文字列の変更(一部のみ)
try stdout.print("sv1 = {s}\n", .{sv1});
@memcpy(svp1[0..15], "ありがとう");
try stdout.print("sv1 = {s}\n", .{sv1});
@memcpy(svpe1, "おこしやす");   // svpe1の示す位置が文字列の範囲を超えないよう注意
try stdout.print("sv1 = {s}\n", .{sv1});
@memcpy(svsl1[0..15], "がんばろう");
try stdout.print("sv1 = {s}\n", .{sv1});
@memcpy(svsl1.ptr, "おいでませ");   // svsl1.ptrの示す位置が文字列の範囲を超えないよう注意
try stdout.print("sv1 = {s}\n", .{sv1});
結果
sv1 = こんにちはZig!
sv1 = ありがとうZig!
sv1 = おこしやすZig!
sv1 = がんばろうZig!
sv1 = おいでませZig!

引数としての配列

関数の引数が配列になるとき、型名がスライスとなっていることが多いです。例としてstd.mem.copyForwards関数を取り上げます。これはある配列の要素を別の配列にコピーする処理を行うものです。

ある配列の要素を別の配列にコピーする関数
std.mem.copyForwards(comptime T: type, dest: []T, source: []const T) void

引数の型はコピー元の第3引数が[]const Tでコピー先の第2引数が[]Tとなっています。どちらもスライスで、コピー元は配列の要素が変更されないため型名にconstがあり、コピー先は配列の要素が変更されますので型名にconstがありません。両者に配列を渡すときは&配列名とします。

引数としての配列
const ca: [5]u8 = .{ 3, 8, 4, 2, 6 };
var va: [5]u8 = undefined;

std.mem.copyForwards(u8, &va, &ca);     // 配列の引数は &配列名
try stdout.print("ca = {any}, va = {any}\n", .{ ca, va });
結果
ca = { 3, 8, 4, 2, 6 }, va = { 3, 8, 4, 2, 6 }

forにおける配列とポインタ

forでは配列aの要素を扱うときfor (&a) |*p| {...}のようにすると要素のポインタを*pで取得でき、p.* = ...で配列の要素を変更できます。aがスライスの時はfor (a) ...で同様の処理が可能です。これを使って配列の要素をすべて同じ値に設定するfill関数を定義してみました。配列の要素を変更するため、引数の型は[]Tとしています。

配列の要素をすべて同じ値に設定するfill関数
fn fill(comptime T: type, a: []T, value: T) void {
    for (a) |*p| {      // *pは配列の各要素のポインタ、aはスライスなので&aにしなくてよい
        p.* = value;    // 配列の要素をvalueに変更
    }
}
fill関数の実行
var va: [5]u8 = undefined;
fill(u8, &va, 0);   // 配列の引数は &配列名
try stdout.print("fill(u8, &va, 0): {any}\n", .{va});
結果
fill(u8, &va, 0): { 0, 0, 0, 0, 0 }

配列の要素を変更しない関数の例として配列の要素の最大値を取得する関数maxを定義してみました。こちらは引数の型名を[]const Tとしました。

配列の要素の最大値を取得するmax関数
fn max(comptime T: type, a: []const T) T {
    var pos: usize = 0;
    for (a, 0..) |v, i| {
        if (a[pos] < v) {
            pos = i;
        }
    }
    return a[pos];
}
max関数の実行
const ca: [5]u8 = .{ 3, 8, 4, 2, 6 };
const v = max(u8, &ca);
try stdout.print("max(u8, &ca): {}\n", .{v});
結果
max(u8, &ca): 8

関数のポインタ

関数にもポインタがあります。型名は関数の型の先頭に*constを追加したものになります。例えばfn twice(elem: u8) u8 {...}という関数があるとき、関数の型はfn (u8) u8ですが、関数のポインタ型は*const fn (u8) u8となります。

関数の型名とポインタ型
// 関数の型はfn (u8) u8、ポインタ型は*const fn (u8) u8
fn twice(elem: u8) u8 {
    return elem *% 2;
}

では、配列の各要素を引数の関数ポインタが示す関数で処理した結果とする配列を作成するmap関数を定義してみます。callbackが関数ポインタの引数で、callback(v)でポインタが示す関数を実行します。

関数のポインタが引数となる関数
fn map(dest: []u8, source: []const u8, callback: *const fn (u8) u8) void {
    for (dest, source) |*p, v| {    // *pはdestの各要素のポインタ、vはsourceの各要素の値
        p.* = callback(v);
    }
}

このmap関数を実行するとき、引数となる関数のポインタは&関数名とします。

map関数の実行
const ca: [5]u8 = .{ 3, 8, 4, 2, 6 };
var va: [5]u8 = undefined;
map(&va, &ca, &twice);
try stdout.print("ca = {any}, va = {any}\n", .{ ca, va });
結果
ca = { 3, 8, 4, 2, 6 }, va = { 6, 16, 8, 4, 12 }

まとめ

  • ポインタ(Pointer)はデータの格納位置を示すアドレス(address)が代入されていて、そのアドレスが示すデータを参照、変更できる型
  • ポインタを持つ型をポインタ型(Pointer Type)といい、その型の変数をポインタ変数という

1つの値をもつ変数のポインタ型

  • 元の型がTのときポインタ型は*Tで、const Tのときのポインタ型は*const Tとなる
  • ポインタ型のオプションは?*Tもしくは?*const Tとなる
  • ポインタ型でないT型の変数からポインタを取得するときは&変数名とする
  • ポインタ変数をpとすると、これに代入されたアドレスが示すデータはp.*で、p.* = ...でデータを変更できる
  • ポインタ型でない変数vのポインタは'&v'で取得でき、vの型がu8のときのポインタ型は*u8で、これがconst u8のときは*const u8となる
  • 変数の値が変更されても、そのポインタに代入されているアドレスは変更されない
  • 関数の引数でポインタを渡すと、これを介して値を変更できる

配列のポインタ

  • 配列のポインタには、配列全体を示す*[N]Tもしくは*[N]const Tと配列の各要素を示す[*]Tもしくは[*]const Tがある
  • 配列a全体を示す*[N]T型のポインタpがあるときa[0], a[1], ...p[0], p[1], ...と同じでp[0]を介してa[0]の値を変更できる
    • a.lenと同じp.lenが存在する
  • T型の配列aがあるとき&a[0]*u8であって[*]u8は異なる
  • [*]Tもしくは[*]const Tはポインタ演算が可能で、これらのアドレスが示す先が配列の範囲を超えないように注意が必要
    • ポインタ演算はpe + 1もしくはpe += 1のようにポインタが示すアドレスを変更すること
    • ポインタが示すアドレスが配列の範囲を外れないよう注意
    • [N]T型の配列aの要素を示す[*]T型のポインタpepe[0], pe[1], ...a[0], a[1], ...を示すが、ポインタ演算によって(pe + 1)[0]a[1]を、(pe + 2)[0]a[2]を示すというようにpeaの添え字が同じでも示す値がずれる

スライス

  • スライス(Slice)は配列の型を[N]Tとすると[]Tで表され、[N]const Tの場合は[]const Tとなるが、オプション型は先頭に?がつく
  • スライスは長さ(len)と各要素のポインタ([*]T)の両方を持ち、後者は変数名.ptrで表される
  • 添え字で配列の一部の要素を抽出する際、ポインタ演算によって配列の範囲を外れないように注意

文字列のポインタ

  • 文字列は*[N:0]T[*:0]T[:0]Tなど番兵(Sentinel)付きのポインタやスライスが利用できる
  • 文字列s1から取得した[*:0]T型のポインタsp1があるとき(sp1 + s1.len)[0]の値は0になる
  • 文字列s1から取得した[:0]T型のスライスssl1があるとき(ssl1.ptr + ssl1.len)[0]の値は0になる

引数としての配列

  • 配列を関数の引数にするとき、引数の型がスライスの時は&配列名とする

forにおける配列とポインタ

  • forでは配列aの要素を扱うときfor (&a) |*p| {...}のようにすると要素のポインタを*pで取得でき、p.* = ...で配列の要素を変更できる

関数のポインタ

  • 配列を関数の引数に設定するとき、その内容を変更する可能性がある場合は[]T、変更しない場合は[]const Tとする

  • 配列を関数の引数に設定するときは&配列名とする

  • 配列aの要素をforで扱うときfor (&a) |*p| {...}とするとaの各要素のポインタをpで取得でき、p.* = ...で配列の要素を変更できる

  • 関数のポインタ型は関数の型名の先頭に* constを追加したもの

  • 関数のポインタを引数に設定するときは&関数名とする


< 関数の終了処理(defer, errdefer, unreachable, @panic)
ざっくりZig 一覧


脚注
  1. Pointers ↩︎

  2. Slices ↩︎

Discussion