第8章: 配列と文字列
学習目標
この章を終えると、以下ができるようになります:
- 配列の基本操作を理解する
- 文字列とUTF-8エンコーディングを扱える
- 文字列処理ユーティリティを実装できる
- 効率的な配列操作ができる
配列の基本
配列の宣言と初期化
const std = @import("std");
pub fn example() void {
// サイズ指定の配列
const arr1: [5]i32 = [_]i32{ 1, 2, 3, 4, 5 };
// 型推論
const arr2 = [_]i32{ 10, 20, 30 };
// すべて同じ値で初期化
const arr3 = [_]i32{42} ** 5; // [42, 42, 42, 42, 42]
// 未初期化配列
var arr4: [10]i32 = undefined;
arr4[0] = 1;
std.debug.print("arr1: ", .{});
for (arr1) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
std.debug.print("arr3: ", .{});
for (arr3) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
}
配列のサイズ
const std = @import("std");
pub fn example() void {
const numbers = [_]i32{ 1, 2, 3, 4, 5 };
// 配列のサイズを取得
std.debug.print("Length: {}\n", .{numbers.len});
std.debug.print("Size in bytes: {}\n", .{@sizeOf(@TypeOf(numbers))});
}
多次元配列
const std = @import("std");
pub fn example() void {
// 2次元配列
const matrix = [_][3]i32{
[_]i32{ 1, 2, 3 },
[_]i32{ 4, 5, 6 },
[_]i32{ 7, 8, 9 },
};
std.debug.print("Matrix:\n", .{});
for (matrix) |row| {
for (row) |value| {
std.debug.print("{} ", .{value});
}
std.debug.print("\n", .{});
}
}
配列操作
配列の反復
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 src = [_]i32{ 1, 2, 3, 4, 5 };
var dest: [5]i32 = undefined;
// 要素ごとにコピー
for (src, 0..) |value, i| {
dest[i] = value;
}
// または std.mem.copy を使用
@memcpy(&dest, &src);
std.debug.print("Dest: ", .{});
for (dest) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
}
配列の比較
const std = @import("std");
pub fn example() void {
const arr1 = [_]i32{ 1, 2, 3 };
const arr2 = [_]i32{ 1, 2, 3 };
const arr3 = [_]i32{ 1, 2, 4 };
// 配列の等価性チェック
const eq1 = std.mem.eql(i32, &arr1, &arr2);
const eq2 = std.mem.eql(i32, &arr1, &arr3);
std.debug.print("arr1 == arr2: {}\n", .{eq1});
std.debug.print("arr1 == arr3: {}\n", .{eq2});
}
配列のソート
const std = @import("std");
pub fn example() void {
var numbers = [_]i32{ 5, 2, 8, 1, 9, 3 };
// 昇順ソート
std.mem.sort(i32, &numbers, {}, comptime std.sort.asc(i32));
std.debug.print("Sorted: ", .{});
for (numbers) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
}
文字列の基本
文字列リテラル
const std = @import("std");
pub fn example() void {
// 文字列は []const u8 または [*:0]const u8
const str1: []const u8 = "Hello, Zig!";
const str2: [*:0]const u8 = "Null-terminated";
std.debug.print("str1: {s}\n", .{str1});
std.debug.print("str2: {s}\n", .{str2});
// 文字列の長さ
std.debug.print("Length: {}\n", .{str1.len});
}
文字列の連結
const std = @import("std");
pub fn example() !void {
const allocator = std.heap.page_allocator;
const str1 = "Hello";
const str2 = " World";
// std.fmt.allocPrint を使用
const result = try std.fmt.allocPrint(allocator, "{s}{s}", .{str1, str2});
defer allocator.free(result);
std.debug.print("Concatenated: {s}\n", .{result});
}
文字列の比較
const std = @import("std");
pub fn example() void {
const str1 = "abc";
const str2 = "abc";
const str3 = "xyz";
// 等価性チェック
const eq1 = std.mem.eql(u8, str1, str2);
const eq2 = std.mem.eql(u8, str1, str3);
std.debug.print("str1 == str2: {}\n", .{eq1});
std.debug.print("str1 == str3: {}\n", .{eq2});
// 辞書順比較
const cmp = std.mem.order(u8, str1, str3);
std.debug.print("str1 vs str3: {}\n", .{cmp});
}
UTF-8エンコーディング
UTF-8の基本
const std = @import("std");
pub fn example() void {
const str = "こんにちは世界";
// バイト長
std.debug.print("Byte length: {}\n", .{str.len});
// 文字数を数える
var count: usize = 0;
var i: usize = 0;
while (i < str.len) {
const byte_len = std.unicode.utf8ByteSequenceLength(str[i]) catch break;
i += byte_len;
count += 1;
}
std.debug.print("Character count: {}\n", .{count});
}
UTF-8の反復
const std = @import("std");
pub fn example() !void {
const str = "Hello 世界 🌍";
var iter = (try std.unicode.Utf8View.init(str)).iterator();
std.debug.print("Characters:\n", .{});
while (iter.nextCodepoint()) |codepoint| {
std.debug.print("U+{X:0>4} ", .{codepoint});
}
std.debug.print("\n", .{});
}
文字の種類判定
const std = @import("std");
pub fn example() void {
const chars = "Aa1 @";
for (chars) |ch| {
std.debug.print("'{c}': ", .{ch});
std.debug.print("alpha={}, ", .{std.ascii.isAlphabetic(ch)});
std.debug.print("digit={}, ", .{std.ascii.isDigit(ch)});
std.debug.print("space={}\n", .{std.ascii.isWhitespace(ch)});
}
}
文字列操作
部分文字列
const std = @import("std");
pub fn example() void {
const str = "Hello, World!";
// スライスで部分文字列を取得
const substr1 = str[0..5]; // "Hello"
const substr2 = str[7..12]; // "World"
std.debug.print("substr1: {s}\n", .{substr1});
std.debug.print("substr2: {s}\n", .{substr2});
}
文字列の検索
const std = @import("std");
pub fn example() void {
const str = "Hello, World!";
// 文字の検索
if (std.mem.indexOf(u8, str, "World")) |index| {
std.debug.print("Found 'World' at index {}\n", .{index});
}
// 最後から検索
if (std.mem.lastIndexOf(u8, str, "o")) |index| {
std.debug.print("Last 'o' at index {}\n", .{index});
}
}
文字列の分割
const std = @import("std");
pub fn example() void {
const str = "apple,banana,cherry";
var iter = std.mem.split(u8, str, ",");
std.debug.print("Split:\n", .{});
while (iter.next()) |token| {
std.debug.print(" {s}\n", .{token});
}
}
文字列のトリム
const std = @import("std");
pub fn example() void {
const str = " Hello, World! ";
// 空白を削除
const trimmed = std.mem.trim(u8, str, " ");
std.debug.print("Original: '{s}'\n", .{str});
std.debug.print("Trimmed: '{s}'\n", .{trimmed});
}
文字列フォーマット
基本的なフォーマット
const std = @import("std");
pub fn example() !void {
const allocator = std.heap.page_allocator;
// 整数のフォーマット
const str1 = try std.fmt.allocPrint(allocator, "Number: {}", .{42});
defer allocator.free(str1);
// 浮動小数点のフォーマット
const str2 = try std.fmt.allocPrint(allocator, "Pi: {d:.2}", .{3.14159});
defer allocator.free(str2);
// 複数の値
const str3 = try std.fmt.allocPrint(allocator, "x={}, y={}", .{10, 20});
defer allocator.free(str3);
std.debug.print("{s}\n", .{str1});
std.debug.print("{s}\n", .{str2});
std.debug.print("{s}\n", .{str3});
}
カスタムフォーマット
const std = @import("std");
const Point = struct {
x: i32,
y: i32,
pub fn format(
self: Point,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
try writer.print("({}, {})", .{self.x, self.y});
}
};
pub fn example() void {
const point = Point{ .x = 10, .y = 20 };
std.debug.print("Point: {}\n", .{point});
}
実践例
例1: 文字列ユーティリティ
const std = @import("std");
const StringUtils = struct {
pub fn toUpper(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
const result = try allocator.alloc(u8, str.len);
for (str, 0..) |ch, i| {
result[i] = std.ascii.toUpper(ch);
}
return result;
}
pub fn toLower(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
const result = try allocator.alloc(u8, str.len);
for (str, 0..) |ch, i| {
result[i] = std.ascii.toLower(ch);
}
return result;
}
pub fn reverse(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
const result = try allocator.alloc(u8, str.len);
for (str, 0..) |ch, i| {
result[str.len - 1 - i] = ch;
}
return result;
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
const str = "Hello, World!";
const upper = try StringUtils.toUpper(allocator, str);
defer allocator.free(upper);
const lower = try StringUtils.toLower(allocator, str);
defer allocator.free(lower);
const reversed = try StringUtils.reverse(allocator, str);
defer allocator.free(reversed);
std.debug.print("Original: {s}\n", .{str});
std.debug.print("Upper: {s}\n", .{upper});
std.debug.print("Lower: {s}\n", .{lower});
std.debug.print("Reversed: {s}\n", .{reversed});
}
例2: CSV パーサー
const std = @import("std");
const CsvParser = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) CsvParser {
return CsvParser{ .allocator = allocator };
}
pub fn parse(self: CsvParser, line: []const u8) ![][]const u8 {
var fields = std.ArrayList([]const u8).init(self.allocator);
defer fields.deinit();
var iter = std.mem.split(u8, line, ",");
while (iter.next()) |field| {
const trimmed = std.mem.trim(u8, field, " ");
try fields.append(trimmed);
}
return fields.toOwnedSlice();
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
const parser = CsvParser.init(allocator);
const line = "Alice, 25, Tokyo";
const fields = try parser.parse(line);
defer allocator.free(fields);
std.debug.print("Fields:\n", .{});
for (fields, 0..) |field, i| {
std.debug.print(" [{}]: {s}\n", .{i, field});
}
}
例3: テンプレートエンジン(簡易版)
const std = @import("std");
fn replaceAll(
allocator: std.mem.Allocator,
haystack: []const u8,
needle: []const u8,
replacement: []const u8,
) ![]u8 {
var result = std.ArrayList(u8).init(allocator);
defer result.deinit();
var i: usize = 0;
while (i < haystack.len) {
if (std.mem.startsWith(u8, haystack[i..], needle)) {
try result.appendSlice(replacement);
i += needle.len;
} else {
try result.append(haystack[i]);
i += 1;
}
}
return result.toOwnedSlice();
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
const template = "Hello, {{name}}! You are {{age}} years old.";
var result = try replaceAll(allocator, template, "{{name}}", "Alice");
defer allocator.free(result);
const final = try replaceAll(allocator, result, "{{age}}", "25");
defer allocator.free(final);
std.debug.print("{s}\n", .{final});
}
まとめ
この章では、Zigの配列と文字列について学びました:
これでZig Foundationsの基礎的な内容を学び終えました。次のステップでは、より高度なトピック(構造体、列挙型、エラーハンドリングなど)に進みます。