ざっくりZig - ポインタとスライス(*T, *[N]T, [*]T, []T, &変数, &関数)
ポインタ(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
であってもポインタを介して値を変更できます。これはポインタを扱うにあたって注意すべきところです。
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] = 1
やpe[0] = 1
はa[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つの値を示すポインタとして扱われます。
p
はp.*
で配列全体を表し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
一方pe
やpec
にはこれらが存在しません。そのかわりpe + 1
やpe += 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.len
はa.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 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
で、配列全体を示すポインタ変数です。sp1
はs1
を代入する時に[*:0]const u8
を設定することで要素を示すポインタを取得しています。番兵付き(:0
)のため(sp1 + s1.len)[0]
の値は0
となります。最後にssl1
はs1
を代入する際にスライスを設定しています。
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
はポインタ演算ができます。
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
がない型の場合です。
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
の値を変更できます。ただしsvpe1
やsvsl1.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
としています。
fn fill(comptime T: type, a: []T, value: T) void {
for (a) |*p| { // *pは配列の各要素のポインタ、aはスライスなので&aにしなくてよい
p.* = value; // 配列の要素をvalueに変更
}
}
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
としました。
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];
}
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
関数を実行するとき、引数となる関数のポインタは&関数名
とします。
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
型のポインタpe
はpe[0], pe[1], ...
がa[0], a[1], ...
を示すが、ポインタ演算によって(pe + 1)[0]
はa[1]
を、(pe + 2)[0]
はa[2]
を示すというようにpe
とa
の添え字が同じでも示す値がずれる
- ポインタ演算は
スライス
-
スライス(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 一覧
Discussion