関数はメモリ上に配置される命令データであるため、アドレスを取得して、ポインタから利用することができます。
次の例では、add
関数のポインタをfunc
変数に格納して、func(1, 2)
により間接的にadd
関数を呼び出しています。
examples/ch02-primitives/src/function_pointers.zen:4:12
fn add(a: u32, b: u32) u32 {
return a + b;
}
test "use a function pointer" {
const func = add;
ok(func(1, 2) == 3);
ok(@TypeOf(func) == fn(u32, u32) u32);
}
関数ポインタの型は、fn(パラメータ型, ... ) 戻り値型
です。関数のパラメータ数や型、戻り値型の組み合わせを、関数シグネチャと呼びます。関数シグネチャが同一の関数は、同じ関数ポインタ型に格納できます。
examples/ch02-primitives/src/function_pointers.zen:4:24
fn add(a: u32, b: u32) u32 {
return a + b;
}
fn sub(a: u32, b: u32) u32 {
return a - b;
}
test "function pointer type" {
var func = add;
ok(func(1, 2) == 3);
func = sub;
ok(func(2, 1) == 1);
}
関数シグネチャが異なると、同じ関数ポインタ変数に格納することができません。
fn signedAdd(a: i32, b: i32) i32 {
return a + b;
}
test "different function pointer type" {
var func = add;
func = signedAdd;
}
上のコードは、次のコンパイルエラーになります。
error[E02046]: expected 'fn(u32, u32) u32', found 'fn(i32, i32) i32'
func = signedAdd;
~
コンパイラが推論可能な場合、関数ポインタ変数の型は省略可能です。
examples/ch02-primitives/src/function_pointers.zen:26:30
test "initialize function pointer" {
// 下の2行は同じです
const explicited: fn(u32, u32) u32 = add;
const inferred = add;
}
関数ポインタが何の関数も参照していないことを示すには、通常のポインタ型同様、オプション型を使用します。最初にnull
で初期化する場合、コンパイラに格納するポインタ型を教えてあげる必要があります。
examples/ch02-primitives/src/function_pointers.zen:32:41
test "null function pointer" {
var func: ?fn(u32, u32) u32 = null;
ok(func == null);
func = add;
if (func) | f | {
ok(@TypeOf(f) == fn(u32, u32) u32);
ok(f(1, 2) == 3);
}
}
関数ポインタを関数の引数として渡すことができます。
examples/ch02-primitives/src/function_pointers.zen:43:50
fn calculate(a: u32, b: u32, func: fn(u32, u32) u32) u32 {
return func(a, b);
}
test "function pointer as an argument" {
ok(calculate(1, 2, add) == 3);
ok(calculate(2, 1, sub) == 1);
}
上の例では、calculate
関数の第3引数func
は関数ポインタです。関数の引数として関数ポインタを渡すことで、関数呼び出し時に処理内容を変更したり、コールバック関数として利用することができます。
コンパイル時計算可能な値により、呼び出す関数を切り替えることができます。例えば、1つのコードで、プログラムを実行するOSごとに異なるメッセージを出力する場合には、次のようにします。
examples/ch02-primitives/src/function_pointers.zen:52:79
fn printLinux() void {
std.debug.warn("I'm running on Linux!\n", .{});
}
fn printWindows() void {
std.debug.warn("I'm running on Windows!\n", .{});
}
fn printMacosx() void {
std.debug.warn("I'm running on macOS!\n", .{});
}
fn printDefault() void {
std.debug.warn("I'm running on Unknown OS!\n", .{});
}
const builtin = @import("builtin");
const printOS = switch (builtin.os.tag) {
.linux => printLinux,
.windows => printWindows,
.macosx => printMacosx,
else => printDefault,
};
test "compile time function pointer selection" {
ok(@TypeOf(printOS) == fn () void);
printOS();
}
関数ポインタ変数printOS
は、コンパイル時の実行環境情報に合わせて、printLinux
, printWindows
, printMacosx
, printDefault
のいずれかの関数ポインタが格納されます。利用側は、関数ポインタが参照している関数を意識せずに、printOS
を使用するだけです。
例えば、Linuxでプログラムを実行した場合、次のメッセージが出力されます。
I'm running on Linux!
関数の戻り値として、関数ポインタを返すことができます。
examples/ch02-primitives/src/function_pointers.zen:81:88
fn getAddFunctionPointer() fn(u32, u32) u32 {
return add;
}
test "function pointer as a return value" {
var func = getAddFunctionPointer();
ok(func(1, 2) == 3);
}
構造体に関数ポインタのフィールドを定義することができます。
examples/ch02-primitives/src/function_pointers.zen:90:100
const Calculator = struct {
calculate: fn(u32, u32) u32,
};
test "function pointer as a struct field" {
const adder = Calculator{ .calculate = add };
const subtractor = Calculator{ .calculate = sub };
ok(adder.calculate(1, 2) == 3);
ok(subtractor.calculate(2, 1) == 1);
}
関数ポインタの型は長くなりがちですし、型名から意図を読み取ることが困難です。そこで、エイリアスを作成することで、型の可読性を向上することができます。
examples/ch02-primitives/src/function_pointers.zen:102:109
const BinOpU32 = fn(u32, u32) u32;
test "type name alias" {
const func: BinOpU32 = add;
ok(add(1, 2) == 3);
ok(@TypeOf(add) == BinOpU32);
ok(@TypeOf(add) == fn(u32, u32) u32);
}
この例では、BinOpU32
はfn(u32, u32) u32
のエイリアスで、両者は全く同じものとして扱うことが可能です。
☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.