第5章: 制御構造
学習目標
この章を終えると、以下ができるようになります:
- if文とif式を使い分けられる
- whileループとforループを適切に使用できる
- switchによるパターンマッチングを活用できる
- ブロックラベルと制御フローを理解できる
if文とif式
基本的なif文
const std = @import("std");
pub fn example() void {
const x = 10;
if (x > 5) {
std.debug.print("x is greater than 5\n", .{});
}
if (x > 5) {
std.debug.print("x is greater than 5\n", .{});
} else {
std.debug.print("x is 5 or less\n", .{});
}
}
if式
Zigでは、ifは式として使用でき、値を返すことができます。
const std = @import("std");
pub fn example() void {
const x = 10;
// if式で値を返す
const result = if (x > 5) "greater" else "smaller";
std.debug.print("Result: {s}\n", .{result});
// 複雑な式
const value = if (x > 10)
100
else if (x > 5)
50
else
0;
std.debug.print("Value: {}\n", .{value});
}
オプショナル型のアンラップ
const std = @import("std");
pub fn example() void {
const maybe_number: ?i32 = 42;
// オプショナル型のアンラップ
if (maybe_number) |value| {
std.debug.print("Value exists: {}\n", .{value});
} else {
std.debug.print("Value is null\n", .{});
}
// ポインタのアンラップ
var x: i32 = 100;
var maybe_ptr: ?*i32 = &x;
if (maybe_ptr) |ptr| {
std.debug.print("Pointer value: {}\n", .{ptr.*});
}
}
エラーユニオンのアンラップ
const std = @import("std");
fn riskyOperation() !i32 {
return 42;
}
pub fn example() void {
// エラーユニオンのアンラップ
if (riskyOperation()) |value| {
std.debug.print("Success: {}\n", .{value});
} else |err| {
std.debug.print("Error: {}\n", .{err});
}
}
whileループ
基本的なwhileループ
const std = @import("std");
pub fn example() void {
var i: usize = 0;
while (i < 5) {
std.debug.print("i = {}\n", .{i});
i += 1;
}
}
continueとbreak
const std = @import("std");
pub fn example() void {
var i: usize = 0;
while (i < 10) : (i += 1) {
if (i == 3) {
continue; // 3をスキップ
}
if (i == 7) {
break; // 7で終了
}
std.debug.print("{} ", .{i});
}
std.debug.print("\n", .{});
}
while式
const std = @import("std");
pub fn example() void {
var i: usize = 0;
// whileは式として使える
const result = while (i < 10) : (i += 1) {
if (i == 5) {
break i; // 5を返す
}
} else 0; // breakしなかった場合
std.debug.print("Result: {}\n", .{result});
}
オプショナルとwhile
const std = @import("std");
fn getNext() ?i32 {
// シミュレーション
return null;
}
pub fn example() void {
// オプショナル型の値がnullになるまでループ
var value: ?i32 = 10;
while (value) |v| : (value = getNext()) {
std.debug.print("Value: {}\n", .{v});
}
}
forループ
配列のイテレーション
const std = @import("std");
pub fn example() void {
const numbers = [_]i32{ 1, 2, 3, 4, 5 };
// 値のイテレーション
for (numbers) |num| {
std.debug.print("{} ", .{num});
}
std.debug.print("\n", .{});
// インデックス付きイテレーション
for (numbers, 0..) |num, i| {
std.debug.print("[{}]={} ", .{i, num});
}
std.debug.print("\n", .{});
}
スライスのイテレーション
const std = @import("std");
pub fn example() void {
const items = [_]i32{ 10, 20, 30, 40, 50 };
const slice = items[1..4]; // [20, 30, 40]
for (slice) |item| {
std.debug.print("{} ", .{item});
}
std.debug.print("\n", .{});
}
範囲のイテレーション
const std = @import("std");
pub fn example() void {
// 0から9まで
for (0..10) |i| {
std.debug.print("{} ", .{i});
}
std.debug.print("\n", .{});
}
複数の配列の同時イテレーション
const std = @import("std");
pub fn example() void {
const a = [_]i32{ 1, 2, 3 };
const b = [_]i32{ 4, 5, 6 };
// 2つの配列を同時にイテレート
for (a, b) |x, y| {
std.debug.print("{} + {} = {}\n", .{x, y, x + y});
}
}
for式
const std = @import("std");
pub fn example() void {
const numbers = [_]i32{ 1, 2, 3, 4, 5 };
// forは式として使える
const sum = blk: {
var total: i32 = 0;
for (numbers) |num| {
total += num;
}
break :blk total;
};
std.debug.print("Sum: {}\n", .{sum});
}
switch文
基本的なswitch
const std = @import("std");
pub fn example() void {
const x = 2;
switch (x) {
1 => std.debug.print("One\n", .{}),
2 => std.debug.print("Two\n", .{}),
3 => std.debug.print("Three\n", .{}),
else => std.debug.print("Other\n", .{}),
}
}
複数のケース
const std = @import("std");
pub fn example() void {
const x = 5;
switch (x) {
1, 2, 3 => std.debug.print("Small\n", .{}),
4, 5, 6 => std.debug.print("Medium\n", .{}),
7, 8, 9 => std.debug.print("Large\n", .{}),
else => std.debug.print("Other\n", .{}),
}
}
範囲のマッチング
const std = @import("std");
pub fn example() void {
const x = 42;
switch (x) {
0...10 => std.debug.print("0-10\n", .{}),
11...50 => std.debug.print("11-50\n", .{}),
51...100 => std.debug.print("51-100\n", .{}),
else => std.debug.print("Other\n", .{}),
}
}
switch式
const std = @import("std");
pub fn example() void {
const day = 3;
const day_name = switch (day) {
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
6 => "Saturday",
7 => "Sunday",
else => "Invalid",
};
std.debug.print("Day: {s}\n", .{day_name});
}
タグ付きユニオンとswitch
const std = @import("std");
const Shape = union(enum) {
circle: f64, // 半径
rectangle: struct {
width: f64,
height: f64,
},
triangle: struct {
base: f64,
height: f64,
},
};
fn area(shape: Shape) f64 {
return switch (shape) {
.circle => |radius| 3.14159 * radius * radius,
.rectangle => |rect| rect.width * rect.height,
.triangle => |tri| 0.5 * tri.base * tri.height,
};
}
pub fn example() void {
const circle = Shape{ .circle = 5.0 };
const rect = Shape{ .rectangle = .{ .width = 4.0, .height = 6.0 } };
std.debug.print("Circle area: {d:.2}\n", .{area(circle)});
std.debug.print("Rectangle area: {d:.2}\n", .{area(rect)});
}
列挙型とswitch
const std = @import("std");
const Color = enum {
red,
green,
blue,
yellow,
};
pub fn example() void {
const color = Color.green;
const color_name = switch (color) {
.red => "Red",
.green => "Green",
.blue => "Blue",
.yellow => "Yellow",
};
std.debug.print("Color: {s}\n", .{color_name});
}
ブロックラベル
名前付きブロック
const std = @import("std");
pub fn example() void {
const result = blk: {
const x = 10;
const y = 20;
break :blk x + y;
};
std.debug.print("Result: {}\n", .{result});
}
ネストしたループからの脱出
const std = @import("std");
pub fn example() void {
outer: for (0..5) |i| {
for (0..5) |j| {
if (i * j > 6) {
std.debug.print("Breaking at i={}, j={}\n", .{i, j});
break :outer; // 外側のループから脱出
}
std.debug.print("({}, {}) ", .{i, j});
}
std.debug.print("\n", .{});
}
}
ラベル付きcontinue
const std = @import("std");
pub fn example() void {
outer: for (0..3) |i| {
for (0..3) |j| {
if (j == 1) {
continue :outer; // 外側のループの次のイテレーション
}
std.debug.print("({}, {}) ", .{i, j});
}
std.debug.print("\n", .{});
}
}
defer文
基本的なdefer
const std = @import("std");
pub fn example() !void {
const allocator = std.heap.page_allocator;
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer); // スコープ終了時に自動解放
// bufferを使用...
std.debug.print("Buffer size: {}\n", .{buffer.len});
}
複数のdefer
const std = @import("std");
pub fn example() void {
defer std.debug.print("First defer\n", .{});
defer std.debug.print("Second defer\n", .{});
defer std.debug.print("Third defer\n", .{});
std.debug.print("Main code\n", .{});
}
// 出力:
// Main code
// Third defer
// Second defer
// First defer
errdefer
const std = @import("std");
fn allocateResources(allocator: std.mem.Allocator) !void {
const buffer1 = try allocator.alloc(u8, 1024);
errdefer allocator.free(buffer1); // エラー時のみ解放
const buffer2 = try allocator.alloc(u8, 2048);
errdefer allocator.free(buffer2);
// エラーが発生したら、errdefer が実行される
return error.SimulatedError;
}
pub fn example() !void {
const allocator = std.heap.page_allocator;
allocateResources(allocator) catch |err| {
std.debug.print("Error occurred: {}\n", .{err});
};
}
実践例
例1: 配列の検索
const std = @import("std");
fn findMax(numbers: []const i32) ?i32 {
if (numbers.len == 0) return null;
var max = numbers[0];
for (numbers[1..]) |num| {
if (num > max) {
max = num;
}
}
return max;
}
pub fn main() void {
const numbers = [_]i32{ 3, 7, 2, 9, 1, 5 };
if (findMax(&numbers)) |max| {
std.debug.print("Max: {}\n", .{max});
} else {
std.debug.print("Empty array\n", .{});
}
}
例2: フィボナッチ数列
const std = @import("std");
fn fibonacci(n: u32) u64 {
if (n <= 1) return n;
var a: u64 = 0;
var b: u64 = 1;
var i: u32 = 2;
while (i <= n) : (i += 1) {
const tmp = a + b;
a = b;
b = tmp;
}
return b;
}
pub fn main() void {
for (0..10) |i| {
const fib = fibonacci(@intCast(i));
std.debug.print("fib({}) = {}\n", .{i, fib});
}
}
例3: 文字列のパース
const std = @import("std");
const TokenType = enum {
number,
operator,
unknown,
};
fn getTokenType(c: u8) TokenType {
return switch (c) {
'0'...'9' => .number,
'+', '-', '*', '/' => .operator,
else => .unknown,
};
}
pub fn main() void {
const input = "123+456";
for (input) |c| {
const token_type = getTokenType(c);
std.debug.print("'{c}' -> {}\n", .{c, token_type});
}
}
例4: エラーハンドリング
const std = @import("std");
const ParseError = error{
InvalidDigit,
Overflow,
};
fn parseDigit(c: u8) ParseError!u8 {
return switch (c) {
'0'...'9' => c - '0',
else => error.InvalidDigit,
};
}
pub fn main() !void {
const chars = "0123456789X";
for (chars) |c| {
if (parseDigit(c)) |digit| {
std.debug.print("'{c}' = {}\n", .{c, digit});
} else |err| {
std.debug.print("'{c}' -> Error: {}\n", .{c, err});
}
}
}
まとめ
この章では、Zigの制御構造について学びました:
次の章では、関数について学びます。